Skip to content

Metasploit Reverse Handler Detector - #21551

Open
h00die wants to merge 7 commits into
rapid7:masterfrom
h00die:handler_finder
Open

Metasploit Reverse Handler Detector#21551
h00die wants to merge 7 commits 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.

@bwatters-r7

Copy link
Copy Markdown
Contributor

h00die commented on Jun 8
I expect this to be done end of the week

I'm in this comment, and I don't like it.
Is this still a WIP?😆

@h00die

h00die commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

oh wow, forgot i said that! Yes, i'll work on it this week and get it all buttoned up

@h00die

h00die commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

all good!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new auxiliary scanner module to fingerprint Metasploit reverse payload handlers (TCP/UDP/HTTP/HTTPS) by observing “talk-first” staging/echo behavior and (optionally) probing silent ports with HTTP/TLS and deeper heuristics, with accompanying documentation.

Changes:

  • Introduces auxiliary/scanner/msf/handler_detect to detect handler families via stage framing, shell echo probes, HTTP “It works!” signatures, and optional deep/UDP probes.
  • Adds optional echo-back verification to capture potential AutoRunScript/operator follow-up commands into loot/reporting.
  • Provides extensive module documentation, including the detection decision tree and testing guidance.

Impact Analysis:

  • Blast radius: low (new auxiliary module + new documentation; no changes to shared framework code evident in diff).
  • Data and contract effects: none (adds new service/note/loot reporting entries; no schema or API contract changes evident in diff).
  • Rollback and test focus: rollback is removing the new module/docs; test focus on thread/concurrency behavior and docs linting/ASCII compliance.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
modules/auxiliary/scanner/msf/handler_detect.rb New scanner module implementing handler fingerprinting, optional deep probing, and optional echo-back capture/loot.
documentation/modules/auxiliary/scanner/msf/handler_detect.md New documentation describing behavior, detection flow, options, and validation/testing notes.
Suppressed comments (1)

documentation/modules/auxiliary/scanner/msf/handler_detect.md:87

  • Important: Problem: this documentation includes non-ASCII arrow characters (), but project guidance asks that module descriptions/docs remain ASCII-only (see AGENTS.md:47). Impact: non-ASCII can break downstream tooling and docs linting. Fix: replace these arrows with ASCII sequences (e.g. ->) and apply consistently throughout the file.
    EB -->|yes| CAP["echo token back →<br/>capture AutoRunScript → loot"]

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread modules/auxiliary/scanner/msf/handler_detect.rb
Comment thread modules/auxiliary/scanner/msf/handler_detect.rb Outdated
Comment thread documentation/modules/auxiliary/scanner/msf/handler_detect.md Outdated
Comment thread modules/auxiliary/scanner/msf/handler_detect.rb Outdated
Comment thread modules/auxiliary/scanner/msf/handler_detect.rb
Comment thread modules/auxiliary/scanner/msf/handler_detect.rb
Comment thread documentation/modules/auxiliary/scanner/msf/handler_detect.md Outdated
@bwatters-r7

Copy link
Copy Markdown
Contributor
[*] Processing /tmp/msf-21551-testing/scanner.rc for ERB directives.
  resource (/tmp/msf-21551-testing/scanner.rc)> use auxiliary/scanner/msf/handler_detect
  resource (/tmp/msf-21551-testing/scanner.rc)> set RHOSTS 127.0.0.1
  RHOSTS => 127.0.0.1
  resource (/tmp/msf-21551-testing/scanner.rc)> set PORTS 4001-4011
  PORTS => 4001-4011
  resource (/tmp/msf-21551-testing/scanner.rc)> set SCAN_UDP true
  SCAN_UDP => true
  resource (/tmp/msf-21551-testing/scanner.rc)> set VERBOSE true
  VERBOSE => true
  resource (/tmp/msf-21551-testing/scanner.rc)> run
  [*] 127.0.0.1             - Connection refused
  [*] 127.0.0.1             - Metasploit handler detected (tcp): Windows staged payload - raw stager shellcode
  [*] 127.0.0.1             - Metasploit handler detected (tcp): staged payload with big-endian length prefix
  [*] 127.0.0.1             - Metasploit handler detected (tcp): windows/x64/meterpreter/reverse_tcp
  [-] 127.0.0.1             - SSL probe failed: Errno::ECONNRESET Connection reset by peer - SSL_connect
  [*] 127.0.0.1             - Metasploit handler detected (tcp): command shell handler
  [*] 127.0.0.1             - Metasploit handler detected (udp): command shell handler
  [*] 127.0.0.1             - Metasploit handler detected (tcp): reverse_https Meterpreter handler
  [*] 127.0.0.1             - Metasploit handler detected (tcp): staged payload with big-endian length prefix
  [*] 127.0.0.1             - Metasploit handler detected (tcp): reverse_http Meterpreter handler
  [*] 127.0.0.1             - Metasploit handler detected (tcp): likely pingback handler
  [-] 127.0.0.1             - SSL probe failed: OpenSSL::SSL::SSLError SSL_connect returned=1 errno=0 peeraddr=127.0.0.1:4009
  state=error: record layer failure
  [*] 127.0.0.1             - Metasploit handler detected (tcp): command shell handler
  [*] 127.0.0.1             - No follow-up commands after echo-back (no AutoRunScript configured)
  [*] 127.0.0.1             - Metasploit handler detected (tcp): command shell handler
  [+] 127.0.0.1             - Captured follow-up after shell verification (likely AutoRunScript / operator commands):
        whoami
        id
        hostname
        uname -a
  [+] 127.0.0.1             - Saved captured commands to loot: /tmp/msf-21551-test/
  loot/20260831125326_default_127.0.0.1_metasploit.handl_002224.txt
  [+] 127.0.0.1             - Metasploit payload handlers detected on 127.0.0.1 (11 found)
  ============================================================

   Port  Proto  Payload                                        Detail                                       Confidence  Bytes
   Framing                                                                                                          Notes
   ----  -----  -------                                        ------                                       ----------  -----
   -------                                                                                                          -----
   4001  tcp    windows/x64/meterpreter/reverse_tcp            x64 native staged                            high        249293  4-
   byte little-endian length prefix (declared 248902, 249293 bytes received)
   4002  tcp    staged payload with big-endian length prefix   python/php/java family                       medium      23791   4-
   byte big-endian length prefix (declared 23400, 23791 bytes received)
   4003  tcp    command shell handler                                                                       high        16
   unsolicited "echo <token>" shell-verification command
   4004  tcp    reverse_http Meterpreter handler               HTTP transport                               high                200
   OK + default "It works!" body on an unknown URI; Server: Apache
   4005  tcp    reverse_https Meterpreter handler              HTTPS transport                              high                200
   OK + default "It works!" body on an unknown URI; Server: Apache
   4006  tcp    staged payload with big-endian length prefix   over SSL/TLS; python/php/java family         medium      23795   4-
   byte big-endian length prefix (declared 23404, 23795 bytes received)
   4007  udp    command shell handler                                                                       high        30
   unsolicited "echo <token>" shell-verification command
   4008  tcp    Windows staged payload - raw stager shellcode  e.g. reverse_nonx_tcp/reverse_ord_tcp/shell  medium      216     raw
   x86 stager shellcode, no length prefix (216 bytes, 0xFC prelude)
   4009  tcp    command shell handler                          ReverseTcpDouble paired connections          high        23
   unsolicited "echo <token>" shell-verification command
   4010  tcp    likely pingback handler                        pingback_reverse_tcp family                  low
   handler closed the connection 0.18s after a 16-byte write, returning no data (reads a 16-byte UUID then closes)
   4011  tcp    command shell handler                                                                       high        27
   unsolicited "echo <token>" shell-verification command                                                            AutoRunScript/
   operator commands captured to loot

  [*] 127.0.0.1             - Scanned 1 of 1 hosts (100% complete)
  [*] Auxiliary module execution completed

@bwatters-r7 bwatters-r7 moved this from Todo to What about Second Review? in Metasploit Kanban Aug 31, 2026
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: What about Second Review?

Development

Successfully merging this pull request may close these issues.

5 participants