Metasploit Reverse Handler Detector - #21551
Conversation
|
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. |
I'm in this comment, and I don't like it. |
|
oh wow, forgot i said that! Yes, i'll work on it this week and get it all buttoned up |
|
all good! |
There was a problem hiding this comment.
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_detectto 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.
|
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):

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 default200 OKandIt 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"]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 beecho ....in reality from modules doing checks).To test
First, create this
msfconsole.rcwith this content, this will start up the multi-handler with a bunch of payload types (tcp, udp, meterpreter, shell, pingbackmsfconsole.rcautorun_demo.rbIf you want to test sending fake commands to verify they get capture, drop this in
~/.msf4/modules/post/test/autorun_demo.rbFinal test
Now run this module against the ports
Output
Verify you get similar results: