Skip to content

Metasploit Reverse Handler Detector#21551

Open
h00die wants to merge 1 commit into
rapid7:masterfrom
h00die:handler_finder
Open

Metasploit Reverse Handler Detector#21551
h00die wants to merge 1 commit into
rapid7:masterfrom
h00die:handler_finder

Conversation

@h00die

@h00die h00die commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Ever see persistence on your box and get the penetration tester's IP address (or port even) and wonder what they're up to? This module will help with that! This module scans open ports on a host to determine if they're a Metasploit Reverse Handler or not. This module can also help determine what kind of shell they were going to land.

Claude assisted in the development of this module.

An example run (since colors help):
image

We're able to do this because Metasploit reverse handlers act in unique ways. Reverse shells on connection send "echo " for instance w/o being probed. The Apache server used in http payloads (and fetch) responds to / with the default 200 OK and It works!. However it also responds to EVERY URI (non-payload bearing) with the same response, which normal (although i'm sure its possible to reconfigure Apache to do it) Apache would respond with 404. There are more, but thats just a quick review.

Mermaid decision tree

flowchart TD
    A["check_port: TCP connect"] -->|refused| R0["Closed / not a listener"]
    A -->|connected| B["drain_stage: read unsolicited bytes<br/>(up to FIRST_BYTE_WAIT)"]
    B --> C{"server talked<br/>first?"}
    C -->|"yes (bytes)"| FP["fingerprint(buf)"]
    C -->|"no (silent)"| SIL["silent-port fallbacks"]

    %% ---- fingerprint() ordered checks ----
    FP --> F1{"starts with<br/>'echo TOKEN'?"}
    F1 -->|yes| RShell["Command shell handler<br/>(echo probe) — high"]
    F1 -->|no| F2{"base64 + 4-byte<br/>big-endian length?"}
    F2 -->|yes| RPy["python meterpreter<br/>(base64/zlib staged)"]
    F2 -->|no| F3{"4-byte little-endian<br/>length delivered?"}
    F3 -->|yes| RWin["Windows native staged<br/>(metsrv/shell) — high"]
    F3 -->|no| F4{"4-byte big-endian<br/>length delivered?"}
    F4 -->|yes| RBE["php / java / android<br/>staged — medium"]
    F4 -->|no| F5{"small buf<br/>with /bin/sh?"}
    F5 -->|yes| RExec["unix execve staged shell"]
    F5 -->|no| F6{"starts with 0xFC?"}
    F6 -->|yes| RStager["Windows raw stager shellcode<br/>(reverse_nonx/ord) — medium"]
    F6 -->|no| F7{">=128 bytes,<br/>mostly non-text?"}
    F7 -->|yes| RNative["linux/osx native stage<br/>or RC4/encrypted — low"]
    F7 -->|no| RBanner["Talks first, not a stage<br/>(unrelated service)"]

    RShell --> EB{"ECHO_BACK?"}
    EB -->|yes| CAP["echo token back →<br/>capture AutoRunScript → loot"]

    %% ---- silent-port fallbacks ----
    SIL --> H1["HTTP probe: GET random URI"]
    H1 --> H1c{"200 + 'It works!'?"}
    H1c -->|yes| RHttp["reverse_http handler"]
    H1c -->|no| H2{"Rex 404 page?"}
    H2 -->|yes| RRex["web_delivery / fetch server"]
    H2 -->|no| HS["HTTPS probe: TLS + GET"]
    HS --> HSc{"reply?"}
    HSc -->|"'It works!'"| RHttps["reverse_https"]
    HSc -->|"echo / stage"| RSsl["ssl shell / reverse_tcp_ssl"]
    HSc -->|nothing| DP{"DEEP_PROBE on?"}
    DP -->|no| SOpen["Open, no data<br/>(stageless / powershell / not MSF)"]
    DP -->|yes| D1["2nd TCP connection"]
    D1 --> D1c{"echo on the pair?"}
    D1c -->|yes| RDouble["ReverseTcpDouble<br/>(cmd/unix/reverse)"]
    D1c -->|no| D2["2 TLS connections"]
    D2 --> D2c{"echo on the pair?"}
    D2c -->|yes| RDoubleSsl["double-SSL handler"]
    D2c -->|no| D3["send 16-byte UUID"]
    D3 --> D3c{"fast reply-less<br/>close?"}
    D3c -->|yes| RPing["pingback — low"]
    D3c -->|no| SOpen

    %% ---- UDP (independent, if SCAN_UDP) ----
    UDP["check_port_udp (if SCAN_UDP):<br/>send datagram → fingerprint()"] --> RUdp["reverse_udp"]

Loading

Testing

Testing this PR can be difficult. In theory we'd want to launch every payload and run this module against them all, however that is A LOT, it takes about 4hrs on my system to launch all those, and then since ruby/msf seems to be single threaded it pegs the CPU to 100% for that core. Then the sessions are unreliable because the CPU is trying to do too much. So the best way is to remove sessions that use a duplicate setup handshake or method. If we can find one, we can find them all. So while I have tested this in 10 batches to test every payload, it took a half day to do. Don't do that unless you hate your life. Instead use the following

Also, on shell_reverse_* type sessions, we may be able to steal commands sent (although it may just be echo .... in reality from modules doing checks).

To test

First, create this msfconsole.rc with this content, this will start up the multi-handler with a bunch of payload types (tcp, udp, meterpreter, shell, pingback

msfconsole.rc

setg ExitOnSession false
setg LHOST 127.0.0.1
setg PayloadUUIDTracking false
use exploit/multi/handler
set payload windows/x64/meterpreter/reverse_tcp
set LPORT 4001
run -j -z
set payload python/meterpreter/reverse_tcp
set LPORT 4002
run -j -z
set payload linux/x64/shell_reverse_tcp
set LPORT 4003
run -j -z
set payload windows/meterpreter/reverse_http
set LPORT 4004
run -j -z
set payload windows/meterpreter/reverse_https
set LPORT 4005
run -j -z
set payload python/meterpreter/reverse_tcp_ssl
set LPORT 4006
run -j -z
set payload python/shell_reverse_udp
set LPORT 4007
run -j -z
set payload windows/meterpreter/reverse_nonx_tcp
set LPORT 4008
run -j -z
set payload cmd/unix/reverse
set LPORT 4009
run -j -z
set payload windows/x64/pingback_reverse_tcp
set LPORT 4010
run -j -z
# 4011: a reverse shell with an operator AutoRunScript - the scanner answers its
# echo probe, the AutoRunScript fires, and the commands it runs are captured + looted.
set payload linux/x64/shell_reverse_tcp
set LPORT 4011
set AutoRunScript post/test/autorun_demo
run -j -z

autorun_demo.rb

If you want to test sending fake commands to verify they get capture, drop this in ~/.msf4/modules/post/test/autorun_demo.rb

class MetasploitModule < Msf::Post
  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'AutoRunScript capture demo',
        'Description' => %q{
          Writes a short sequence of recon commands to a freshly opened shell. Used to
          demonstrate auxiliary/scanner/msf/handler_detect ECHO_BACK follow-up capture:
          when the scanner answers the shell's "echo" verification probe, the session is
          treated as live, this AutoRunScript fires, and the scanner captures the commands
          it sends - i.e. the operator's AutoRunScript content.
        },
        'License' => MSF_LICENSE,
        'Author' => ['h00die'],
        'Platform' => %w[win linux unix osx bsd],
        'SessionTypes' => %w[shell]
      )
    )
  end

  def run
    %W[whoami id hostname uname\ -a].each do |cmd|
      session.shell_write("#{cmd}\n")
    end
  end
end

Final test

Now run this module against the ports

use auxiliary/scanner/msf/handler_detect
set RHOSTS 127.0.0.1
set PORTS 4001-4011
set SCAN_UDP true
run

Output

Verify you get similar results:

resource (/home/h00die/test_scan.rc)> run
[+] 127.0.0.1             - 127.0.0.1:4008 - Metasploit handler detected (tcp): Windows staged payload - raw stager shellcode (e.g. reverse_nonx_tcp/reverse_ord_tcp/shell) (medium confidence, 216 bytes) [raw x86 stager shellcode, no length prefix (216 bytes, 0xFC prelude)]
[+] 127.0.0.1             - 127.0.0.1:4002 - Metasploit handler detected (tcp): staged payload with big-endian length prefix (python/php/java family) (medium confidence, 23795 bytes) [4-byte big-endian length prefix (declared 23404, 23795 bytes received)]
[+] 127.0.0.1             - 127.0.0.1:4001 - Metasploit handler detected (tcp): windows/x64/meterpreter/reverse_tcp (native staged) (high confidence, 249293 bytes) [4-byte little-endian length prefix (declared 248902, 249293 bytes received)]
[+] 127.0.0.1             - 127.0.0.1:4003 - Metasploit handler detected (tcp): Metasploit command shell handler (reverse shell - "echo" verification probe) (high confidence, 28 bytes) [unsolicited "echo <token>" shell-verification command]
[+] 127.0.0.1             - 127.0.0.1:4007 - Metasploit handler detected (udp): Metasploit command shell handler (reverse shell - "echo" verification probe) (high confidence, 16 bytes) [unsolicited "echo <token>" shell-verification command]
[+] 127.0.0.1             - 127.0.0.1:4005 - Metasploit handler detected (tcp): Metasploit reverse_https Meterpreter handler (HTTP transport) (high confidence) [200 OK + default "It works!" body on an unknown URI; Server: Apache]
[+] 127.0.0.1             - 127.0.0.1:4006 - Metasploit handler detected (tcp): python/meterpreter/reverse_tcp (base64/zlib staged) over SSL/TLS (high confidence, 23408 bytes) [4-byte big-endian length prefix (declared 23404); base64/zlib stage]
[+] 127.0.0.1             - 127.0.0.1:4004 - Metasploit handler detected (tcp): Metasploit reverse_http Meterpreter handler (HTTP transport) (high confidence) [200 OK + default "It works!" body on an unknown URI; Server: Apache]
[+] 127.0.0.1             - 127.0.0.1:4010 - Metasploit handler detected (tcp): likely pingback handler (pingback_reverse_tcp family) (low confidence) [handler closed the connection 0.39s after a 16-byte write, returning no data (reads a 16-byte UUID then closes)]
[+] 127.0.0.1             - 127.0.0.1:4009 - Metasploit handler detected (tcp): Metasploit command shell handler (reverse shell - "echo" verification probe) (ReverseTcpDouble - paired connections) (high confidence, 23 bytes) [unsolicited "echo <token>" shell-verification command]
[+] 127.0.0.1             - 127.0.0.1:4011 - Metasploit handler detected (tcp): Metasploit command shell handler (reverse shell - "echo" verification probe) (high confidence, 27 bytes) [unsolicited "echo <token>" shell-verification command]
[+] 127.0.0.1             - 127.0.0.1:4011 - Captured follow-up after shell verification (likely AutoRunScript / operator commands):
      whoami
      id
      hostname
      uname -a
[+] 127.0.0.1             - 127.0.0.1:4011 - Saved captured commands to loot: /home/h00die/.msf4/loot/20260607173229_default_127.0.0.1_metasploit.handl_397197.txt
[*] 127.0.0.1             - Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed

@dwelch-r7 dwelch-r7 added module rn-modules release notes for new or majorly enhanced modules labels Jun 8, 2026
@h00die h00die changed the title handler detection WIP Metasploit Reverse Handler Detector Jun 8, 2026
@h00die

h00die commented Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

Just a note, this is still a WIP. while the code seems to work fine, I'm still adding comments, cleaning it up, and trying to figure out a decent method to capture all the info. I expect this to be done end of the week as long as other things don't distract me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

module rn-modules release notes for new or majorly enhanced modules

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

3 participants