- ▸ nmap installed:
pacman -S nmap - ▸ A target IP or range you own or have permission to scan
- ▸ Run as root for SYN scanning:
sudo ./vanta
set target 127.0.0.1 # your own machine set operation scan run
- • Easy: scan, host_discover, port_scan
- • Medium: service_detect, vuln_scan, iot_probe
- • Advanced: cve_chain, scada_probe, full_enum
Overview
What netrecon does
netrecon is a concurrent multi-engine network reconnaissance module inside VANTA. It runs nmap, masscan, rustscan, arp-scan, Shodan, gobuster, ffuf, whatweb, and searchsploit all in a single scan pass, building a full profile for every host discovered. Every engine runs in parallel — scan time scales with host count, not tool count.
On top of standard port scanning, the module adds automatic device category classification for IoT hardware, IP cameras, routers, NAS devices, ICS/SCADA systems, and Flock Safety LPR surveillance cameras. SSL certificate domains (CN + all SANs) are extracted from every TLS port. Country and ASN-based targeting lets you scope scans to entire countries or BGP autonomous systems. GeoIP2 local DB support provides offline, rate-limit-free ASN and country lookups.
Quick Start
Running a scan
netrecon reads JSON from stdin and writes JSON to stdout. The input structure takes a target and a params object.
# Quick scan of a single host
echo '{"target": "192.168.1.1", "params": {"mode": "normal"}}' \
| python3 netrecon.py | jq .
# Full network sweep - deep mode with output directory
echo '{
"target": "192.168.1.0/24",
"params": {
"mode": "deep",
"threads": 30,
"output_dir": "/tmp/scan",
"web_enum": true,
"searchsploit": true
}
}' | python3 netrecon.py
# IoT sweep - MQTT, RTSP, camera, ICS protocols
echo '{"target": "10.0.0.0/24", "params": {"ports": "device"}}' \
| python3 netrecon.py | jq '.data.hosts[] | {ip, device_category}'
# Country-wide scan — all German IP space (ipdeny.com CIDRs, 1024 sample IPs)
echo '{"target": "country:de", "params": {"max_hosts": 1024}}' \
| python3 netrecon.py
# ASN scan — all Google-announced prefixes (RIPE stat API)
echo '{"target": "asn:AS15169", "params": {"max_hosts": 200}}' \
| python3 netrecon.py
# Evasion mode through proxychains
echo '{
"target": "10.10.10.5",
"params": {
"mode": "evasion",
"proxychains": true,
"decoys": 10,
"source_port": 53
}
}' | python3 netrecon.py
Reference
Scan modes
The mode parameter controls the overall behaviour - timeouts, rate limits, which scripts run, and whether evasion flags are active.
Parameters
All parameters go inside the params object of the JSON input.
Core
| Parameter | Values | Default | Description |
|---|---|---|---|
| mode | quick | normal | deep | stealth | evasion | normal | Scan profile - controls speed, depth, and evasion posture |
| ports | preset name or custom | top-1000 | Ports to scan. See Port Presets section. |
| threads | integer | 20 | Max concurrent host threads |
| rate | integer | 1000 | Masscan packets/sec |
| timeout | integer (seconds) | 5 | Per-host base timeout |
| os_detection | true | false | false | Enable OS fingerprinting via nmap -O |
| vuln_scripts | true | false | false | Run nmap default,vuln NSE scripts |
| nse_profile | string (e.g. vuln,exploit,auth) | "" | Custom NSE script list |
| passive_only | true | false | false | DNS/WHOIS/Shodan only - no active scanning |
| interface | string (e.g. eth0) | "" | NIC for ARP scan |
| exclude | comma-separated IPs | "" | IPs to skip entirely |
| shodan_key | string | "" | Shodan API key for external intel |
| nvd_api_key | string | env: NVD_API_KEY | NVD API key for live CVE enrichment |
| max_hosts | integer | 1024 | Max representative IPs sampled from country: / asn: targets |
Evasion / Anonymity
| Parameter | Values | Default | Description |
|---|---|---|---|
| evasion | true | false | false | Enable all IDS/FW bypass flags on any mode |
| decoys | integer | 5 | Number of random decoy IPs in nmap -D RND:N |
| mtu | integer (8, 16, 24…) | 8 | Packet fragment MTU - smaller = harder to reassemble |
| source_port | integer | 53 | Spoof source port (53=DNS, 80=HTTP, 443=HTTPS) |
| data_length | integer (bytes) | 32 | Append random bytes to packets to confuse signatures |
| proxychains | true | false | false | Wrap nmap/gobuster through proxychains4 |
Web Enumeration & Output
| Parameter | Values | Default | Description |
|---|---|---|---|
| web_enum | true | false | true | Run gobuster/ffuf + nikto + whatweb on discovered web ports |
| web_wordlist | path string | auto-detect | Custom wordlist path for gobuster/ffuf |
| searchsploit | true | false | false | Run searchsploit against each discovered service |
| output_dir | path string | "" | Save nmap XML, HTML report, MSF RC, gobuster results here |
Port presets
Pass any preset name as the ports parameter, or provide a custom comma-separated list or range like 80,443,8080-8090.
| Preset | Ports covered | Use case |
|---|---|---|
| quick | 21,22,23,25,53,80,443,445,3389,62078 | Fast triage - critical ports only |
| top-20 | Top 20 common service ports | Rapid sweep of standard services |
| top-100 | –top-ports 100 | Broader quick sweep |
| top-1000 | –top-ports 1000 (default) | Standard full scan |
| web | 80,443,8000–8091,8443,8888,9000,9090,9200 | Web application focused |
| database | 1433,1521,3306,5432,6379,9200,11211,27017 | Database exposure audit |
| common | All standard service ports including auth, mail, SMB | Broad enterprise sweep |
| ios | 62078,5000,7000,548,3689,49152,88,5353 | Apple iOS/macOS device discovery |
| iot | 80,443,1883,8883,5683,5353,8080,8443,5000 | MQTT, MQTT-TLS, CoAP, mDNS IoT sweep |
| camera | 80,443,554,8000,8080,8443,37777,34567,3702 | IP cameras - RTSP, Hikvision, Dahua, ONVIF |
| router | 80,443,8080,8443,22,23,7547,8291 | Routers - TR-069, MikroTik Winbox, management |
| nas | 80,443,5000,5001,9000,445,139,22 | NAS - Synology DSM, SMB, management |
| ics | 80,443,502,20000,102,47808,1911,161,22 | ICS/SCADA - Modbus, DNP3, S7, BACnet, Niagara Fox |
| device | All IoT + camera + router + NAS + ICS ports combined | Full embedded device sweep |
| all | 1–65535 | Complete port space - use with care on large nets |
Target formats
Pass any of these formats as the target field in the JSON input. Multiple targets can be comma-separated.
| Format | Example | How it works |
|---|---|---|
| Single IP | 192.168.1.1 | Direct host scan |
| CIDR | 192.168.1.0/24 | All hosts in subnet (capped at 1024 unless deep mode) |
| Range | 192.168.1.1-50 | Expanded to individual IPs |
| Hostname | example.com | Resolved via DNS then scanned |
| Multi | 10.0.0.1,10.0.0.5 | Each target parsed independently |
country:XX | country:de | Fetches all CIDRs for the country from ipdeny.com, samples one IP per CIDR up to max_hosts. Passes full CIDR list to masscan natively. |
asn:AS#### | asn:AS15169 | Fetches all announced prefixes for the ASN via RIPE stat API. Samples one IP per prefix up to max_hosts. |
country: and asn: targets require requests to be installed for the CIDR/prefix fetch. The sampled IPs represent the range for per-host profiling; masscan receives the full CIDR list for port discovery.
SSL cert domain extraction
After port scanning, netrecon automatically probes every TLS port on each host (443, 8443, 9443, 465, 636, 993, 995) and extracts the certificate's Common Name and all Subject Alternative Names. The result is stored in host.ssl_domains[] and shown inline in the terminal output.
Extraction method
With cryptography installed, the full DER certificate is parsed to extract every SAN. Without it, the stdlib ssl module's decoded cert dict is used as a fallback — both CN and DNS SANs are captured.
Noise filtering
Raw domains from the cert are passed through _clean_domains() before being stored. This strips:
- Wildcard prefixes (
*.example.com→example.com) - CDN-assigned default hostnames (cloudfront.net, akamaiedge.net, fastly.net, cloudflare.net, amazonaws.com, azure*.net, trafficmanager.net)
- Hash hostnames — long hex strings before the first dot
- Bare labels with no dot (internal-only names)
# Example: host with TLS on 443
{
"ip": "93.184.216.34",
"ssl_domains": ["example.com", "www.example.com"]
}
pip3 install cryptography for full SAN extraction. Without it, only the CN and any SANs exposed via the stdlib decoded dict are captured.
GeoIP2 offline lookup
By default, ASN and country lookups use the ipinfo.io API (requires requests). If MaxMind GeoLite2 databases are present locally, the module uses them instead — no rate limits, no API key, fully offline.
DB search paths
The module checks these locations in order for each DB type:
# GeoLite2-ASN.mmdb — ASN number + organisation
~/.vanta/GeoLite2-ASN.mmdb
/var/lib/vanta/GeoLite2-ASN.mmdb
/usr/share/GeoIP/GeoLite2-ASN.mmdb
# GeoLite2-City.mmdb — country ISO code + city name
~/.vanta/GeoLite2-City.mmdb
/var/lib/vanta/GeoLite2-City.mmdb
/usr/share/GeoIP/GeoLite2-City.mmdb
Setup
pip3 install geoip2 --break-system-packages
# Download free DBs (account required) from:
# https://dev.maxmind.com/geoip/geolite2-free-geolocation-data
mkdir -p ~/.vanta
mv GeoLite2-ASN.mmdb GeoLite2-City.mmdb ~/.vanta/
requests is available, the module falls back to ipinfo.io automatically. If neither is available, ASN/country fields are left empty.
Device detection & IoT profiling
netrecon automatically classifies each host into a device category based on open ports, HTTP server headers, page content, favicon hashes, and active protocol probes. No manual configuration required - it runs on every scan.
Camera fingerprinting
When a host is identified as a camera, netrecon fetches /favicon.ico from each web port and computes an mmh3 hash against 17 known camera vendor hashes. Identified models include: Axis, Hikvision, D-Link, Panasonic, Foscam, Vivotek, TP-Link, Logitech Circle, NETGEAR Arlo, Samsung SmartCam, Sony, Dahua, Amcrest, Toshiba, Sricam, Reolink, and Zmodo.
Server header signatures (boa, goahead, uc-httpd, dahua, hikvision-webs) and page title keywords are also checked as a fallback when mmh3 is not installed.
MQTT probe
On port 1883, netrecon sends a real MQTT v0.0.1 CONNECT packet and reads the CONNACK. A return code of 0x00 in the CONNACK means the broker accepted the connection without credentials - flagged as MQTT-NO-AUTH CRITICAL severity.
RTSP probe
On port 554, an RTSP OPTIONS request is sent. A 200 OK response without a 401 challenge indicates the stream is unauthenticated - flagged as RTSP-NO-AUTH HIGH severity.
ICS/SCADA
ICS ports have the highest detection priority. Any host with an open ICS port immediately gets device_category: "ics" and a CRITICAL-severity vulnerability finding for each exposed protocol. The known absence of authentication in Modbus, DNP3, and BACnet is flagged directly without any further probing needed.
Firewall & IDS evasion
Use mode: "evasion" or set evasion: true on any mode to activate all bypass flags. The following nmap flags are applied:
-f # Fragment packets into 8-byte chunks
--mtu 8 # Force small MTU - stateless FW can't reassemble
-n # Skip DNS resolution (no PTR lookups that alert SOC)
-Pn # Treat all hosts as up - skip ICMP ping
-D RND:5 # Inject 5 random decoy IPs in each probe
-g 53 # Spoof source port as 53 (DNS) - passes some FW rules
--data-length 32 # Pad each packet with 32 random bytes
When proxychains: true, every nmap and gobuster call is prefixed with proxychains4 -q. Make sure your /etc/proxychains4.conf is configured before enabling this.
evasion timeout profile allows 480s for port scans and 720s for full host profiling. Combine with a low rate (rate: 50) for maximum stealth against deep packet inspection.
Recommended evasion configs
| Scenario | Config |
|---|---|
| Basic IDS bypass | mode: "evasion" |
| Full anonymity | mode: "evasion", proxychains: true, decoys: 15, source_port: 443 |
| Rate-limit aware | mode: "stealth", rate: 50, timeout: 20 |
| Custom fragments | evasion: true, mtu: 16, data_length: 64 |
Web enumeration
Web enumeration runs automatically after host profiling when web_enum: true (the default). It runs on all discovered HTTP/HTTPS ports.
Tools used
gobuster is tried first for directory brute-force. If not available, ffuf is used as fallback. Found paths are stored in svc.web_paths[] in the JSON output.
nikto runs a full vulnerability scan on each web port after directory brute-force completes. Findings are stored in svc.nikto_findings[] and the nikto source tag is added to the service entry. Nikto detects outdated server versions, dangerous methods, default files, misconfigurations, and known CVEs.
whatweb identifies CMS, frameworks, server software, and JavaScript libraries per-host. Results are merged into svc.http_technologies[].
Evasion in web tools
When evasion: true, all web scanning tools receive anti-detection measures automatically:
| Tool | Evasion applied |
|---|---|
| gobuster | Thread cap 5, random browser User-Agent, 500ms inter-request delay |
| ffuf | Thread cap 5, random browser User-Agent, 0.5s delay between probes |
| nikto | Techniques 1,2,5,7 (URI encoding, self-reference, fake param, random case), random browser UA, 1s pause between probes |
| whatweb | Random browser User-Agent, 500ms throttle |
| RTSP probe | Random browser User-Agent instead of vanta-netrecon |
HTTP analysis
For each web port, http_probe captures:
- HTTP status code, page title, server header
- Technology fingerprints (20+ patterns including WordPress, Joomla, Django, Flask, Spring)
- Security header audit - HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, CORP
- Missing headers list (actionable finding)
- Cookie security flags - Secure, HttpOnly, SameSite per cookie
- Full redirect chain
- TLS: subject, issuer, expiry, not-before, signature algorithm, Subject Alternative Names (SANs)
Wordlist discovery
If no web_wordlist is specified, the module searches these paths in order:
/usr/share/wordlists/dirb/common.txt
/usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
/usr/share/seclists/Discovery/Web-Content/common.txt
/usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
Output format
All output is JSON to stdout. The top-level structure is:
{
"success": true,
"data": {
"target": "192.168.1.0/24",
"scan_start": "2025-01-01T00:00:00",
"scan_duration": 42.3,
"hosts_discovered": 12,
"engines_used": ["nmap", "masscan", "arp"],
"capabilities": { ... },
"hosts": [ ... ], // per-host profiles (includes ssl_domains[])
"summary": { ... }, // aggregate stats
"outputs": { ... } // html_report, nmap_xml, msf_rc paths (if output_dir set)
},
"errors": []
}
Host profile
{
"ip": "192.168.1.100",
"hostname": "camera-01.local",
"mac": "ec:1b:bd:aa:bb:cc",
"mac_vendor": "Flock Safety",
"os_family": "Linux",
"device_type": "Hikvision", // specific model if identified
"device_category": "camera", // camera|router|nas|iot|ics|surveillance
"risk_score": 85,
"risk_level": "CRITICAL",
"ssl_domains": ["camera-01.example.com", "www.example.com"], // CN + SANs, noise-filtered
"services": [ ... ],
"vulnerabilities": [
{
"id": "RTSP-NO-AUTH",
"severity": "HIGH",
"desc": "RTSP stream accessible without authentication"
},
{
"id": "ICS-EXPOSED-MODBUS",
"severity": "CRITICAL",
"desc": "MODBUS port 502 is exposed - unauthenticated ICS/SCADA access possible"
}
],
"asn": "AS12345",
"country": "US",
"shodan": { ... }
}
Service entry
{
"port": 1883,
"service": "mqtt",
"version": "Mosquitto 2.0.15",
"banner": "MQTT CONNACK received",
"mqtt_no_auth": true, // present if broker has no auth
"snmp_community": "public", // present if SNMP probed
"smb_shares": ["ADMIN$", "C$"], // present if SMB enumerated
"http_title": "",
"http_server": "",
"http_technologies": [],
"http_security_headers": {},
"http_missing_headers": ["Strict-Transport-Security", "CSP"],
"tls_subject": "",
"tls_expiry": "",
"tls_sans": [],
"web_paths": ["/admin", "/config"],
"cves": [
{
"id": "MQTT-NO-AUTH",
"cvss": 9.1,
"desc": "MQTT broker allows unauthenticated connections"
}
],
"sources": ["nmap", "banner_grab"]
}
Summary block
{
"hosts_alive": 12,
"total_open_ports": 87,
"top_services": [["http", 8], ["ssh", 6], ["mqtt", 3]],
"os_distribution": {"Linux": 9, "Windows": 2, "Unknown": 1},
"risk_distribution": {"CRITICAL": 2, "HIGH": 3, "MEDIUM": 5, "LOW": 2},
"vulnerabilities": {
"critical": 4, "high": 11, "medium": 8, "total": 23,
"top_cves": ["CVE-2021-36260", "CVE-2019-0708", "MQTT-NO-AUTH"]
},
"high_risk_hosts": ["192.168.1.100", "192.168.1.142"],
"engines_used": ["nmap", "masscan", "arp", "tcp_connect"]
}
Examples
Standard network recon
echo '{"target": "192.168.1.0/24", "params": {}}' | python3 netrecon.py
Deep single host with Shodan + output
echo '{
"target": "10.0.0.1",
"params": {
"mode": "deep",
"shodan_key": "YOUR_KEY",
"searchsploit": true,
"output_dir": "/tmp/scan"
}
}' | python3 netrecon.py
IoT sweep - MQTT, CoAP, cameras
echo '{"target": "192.168.1.0/24", "params": {"ports": "device"}}' \
| python3 netrecon.py \
| jq '.data.hosts[] | select(.device_category != "")
| {ip, device_category, device_type, risk_level}'
ICS/SCADA exposure audit
echo '{"target": "10.0.0.0/16", "params": {"ports": "ics", "threads": 50}}' \
| python3 netrecon.py \
| jq '.data.hosts[] | select(.device_category == "ics") | {ip, vulnerabilities}'
Evasion - fragmented packets through proxy
echo '{
"target": "10.10.10.5",
"params": {
"mode": "evasion",
"proxychains": true,
"decoys": 15,
"source_port": 443,
"mtu": 16,
"data_length": 64
}
}' | python3 netrecon.py
Country-wide scan
# Sample 500 hosts from German IP space (fetches all .de CIDRs from ipdeny.com)
echo '{"target": "country:de", "params": {"max_hosts": 500, "ports": "top-100"}}' \
| python3 netrecon.py | jq '.data.hosts[] | {ip, country, asn, ssl_domains}'
ASN recon
# Probe 200 hosts across Google's announced prefixes (RIPE stat API)
echo '{"target": "asn:AS15169", "params": {"max_hosts": 200}}' \
| python3 netrecon.py | jq '.data.hosts[] | {ip, asn, ssl_domains, risk_level}'
Passive intel only
echo '{"target": "example.com", "params": {"passive_only": true, "shodan_key": "KEY"}}' \
| python3 netrecon.py
Camera sweep with HTML report
echo '{
"target": "192.168.0.0/24",
"params": { "ports": "camera", "output_dir": "/tmp/cam_scan" }
}' | python3 netrecon.py && open /tmp/cam_scan/netrecon_*.html
Requirements
| Tier | What to install | Adds |
|---|---|---|
| Minimum | Python 3.8+ | TCP connect fallback - always works |
| Standard | nmap | Service/version/OS detection, NSE scripts |
| Full | masscan nmap arp-scan whatweb gobuster nikto + root | SYN scans, rapid discovery, web tech, directory brute, vuln scan |
| Python libs | pip3 install requests scapy dnspython shodan | HTTP probing, ARP, DNS, Shodan |
| IoT/Camera | pip3 install mmh3 | Favicon hash fingerprinting (17 camera models) |
| SSL extraction | pip3 install cryptography | Full DER cert parsing — CN + all SANs from TLS certs |
| GeoIP2 offline | pip3 install geoip2 + GeoLite2 .mmdb files | Offline ASN + country lookup, no rate limits, no API key |
| SMB/SNMP | apt install nmblookup smbclient snmpwalk | NetBIOS names, share enum, SNMP community probe |
| Evasion | proxychains4 + configured proxy | Route scans through SOCKS proxy |
| Full output | apt install ffuf xsltproc exploitdb | ffuf fallback, searchsploit (HTML report built-in, no xsltproc needed) |
| NVD enrichment | NVD API key in NVD_API_KEY env var | Live CVSS scores and full CVE descriptions |
Internals
Architecture Deep Dive
This chapter dissects every layer of netrecon — from nmap's raw TCP probes to how XML output is parsed and enriched, how service fingerprinting works at the byte level, and how to extend the module with custom detection logic.
Internals
nmap XML Output — Parsing Internals
netrecon does not parse nmap's human-readable terminal output (which changes between versions).
It invokes nmap with -oX - to receive structured XML on stdout, then uses Python's
xml.etree.ElementTree to extract port states, service versions, and OS fingerprints.
## nmap invocation by netrecon (simplified)
nmap -sV -sC -O --open -T4 -oX - <targets>
↑ ↑ ↑ ↑ ↑ ↑
│ │ │ │ │ └─ output XML to stdout
│ │ │ │ └───── timing template 4 (aggressive)
│ │ │ └────────── only show open ports
│ │ └───────────── OS detection
│ └───────────────── run default NSE scripts (-sC)
└─────────────────────── service/version detection
## nmap XML structure (abbreviated)
<nmaprun scanner="nmap" args="..." start="1748000000">
<host starttime="..." endtime="...">
<status state="up" reason="echo-reply"/>
<address addr="192.168.1.10" addrtype="ipv4"/>
<address addr="aa:bb:cc:dd:ee:ff" addrtype="mac" vendor="Apple"/>
<hostnames>
<hostname name="target.local" type="PTR"/>
</hostnames>
<os>
<osmatch name="Linux 4.15 - 5.6" accuracy="96">
<osclass type="general purpose" vendor="Linux" osfamily="Linux" osgen="4.X|5.X"/>
</osmatch>
</os>
<ports>
<port protocol="tcp" portid="22">
<state state="open" reason="syn-ack"/>
<service name="ssh" product="OpenSSH" version="8.9" extrainfo="Ubuntu Linux" method="probed" conf="10"/>
<script id="ssh-hostkey" output="...">...</script>
</port>
<port protocol="tcp" portid="80">
<state state="open" reason="syn-ack"/>
<service name="http" product="nginx" version="1.24.0"/>
</port>
</ports>
</host>
</nmaprun>
How netrecon Parses the XML
# Simplified parser matching what netrecon does internally
import xml.etree.ElementTree as ET
def parse_nmap_xml(xml_str: str) -> list[dict]:
root = ET.fromstring(xml_str)
hosts = []
for host in root.findall("host"):
if host.find("status").get("state") != "up":
continue
addr = host.find("address[@addrtype='ipv4']").get("addr")
mac_e = host.find("address[@addrtype='mac']")
mac = mac_e.get("addr") if mac_e is not None else None
vendor = mac_e.get("vendor", "") if mac_e is not None else ""
hostname_e = host.find("hostnames/hostname[@type='PTR']")
hostname = hostname_e.get("name") if hostname_e is not None else ""
ports = []
for port in host.findall("ports/port"):
state_e = port.find("state")
if state_e.get("state") != "open":
continue
svc = port.find("service")
scripts = {s.get("id"): s.get("output","") for s in port.findall("script")}
ports.append({
"port": int(port.get("portid")),
"proto": port.get("protocol"),
"service": svc.get("name","?") if svc is not None else "?",
"product": svc.get("product","") if svc is not None else "",
"version": svc.get("version","") if svc is not None else "",
"scripts": scripts,
})
hosts.append({"ip": addr, "mac": mac, "vendor": vendor,
"hostname": hostname, "ports": ports})
return hosts
Internals
Service Fingerprinting — Byte-Level Probes
nmap's -sV flag performs service version detection by sending
carefully crafted probe strings to open ports and matching the responses against the
nmap-service-probes database (usually at /usr/share/nmap/nmap-service-probes).
## nmap-service-probes format (excerpt — HTTP probe)
Probe TCP GetRequest q|GET / HTTP/1.0\r\n\r\n|
# nmap sends these exact bytes to TCP port:
47 45 54 20 2F 20 48 54 54 50 2F 31 2E 30 0D 0A 0D 0A
G E T / H T T P / 1 . 0 \r \n \r \n
match http m|^HTTP/1\.[01] \d\d\d| p/HTTP title server/ v/$1/ cpe:/a:...../
# Pattern: look for "HTTP/1.x NNN" at start of response
# p/ = product name, v/ = version capture group, cpe/ = CVE lookup key
## SSH fingerprint probe — nmap reads the SSH banner first
Probe TCP NULL q||
match ssh m|^SSH-([.\d]+)-(.+)$|s p/OpenSSH/ v/$2/ i/protocol $1/
# SSH servers send their banner without any probe:
# "SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.6\r\n"
# nmap reads it, matches the regex, extracts version "8.9p1"
ADB TCP Fingerprint (Custom — netrecon)
netrecon's Android preset includes a custom ADB fingerprinting probe that's not in stock nmap. It sends the ADB CNXN handshake and reads the device banner:
## ADB CNXN packet structure (sent by netrecon to port 5555)
struct adb_message {
uint32_t command; // 0x4e584e43 = 'CNXN' (little-endian)
uint32_t arg0; // ADB protocol version: 0x01000001
uint32_t arg1; // max payload size: 0x00100000 (1MB)
uint32_t data_length;
uint32_t data_check; // CRC32 of data field
uint32_t magic; // command ^ 0xFFFFFFFF
char data[]; // "host::features=..." string
};
## Hex dump of ADB CNXN probe (24 header bytes + data):
43 4E 58 4E 01 00 00 01 00 00 10 00 <len> <crc> <magic>
C N X N version max_sz
data: "host::features=shell_v2,cmd,stat_v2,ls_v2,fixed_push_mkdir\0"
## If port 5555 is ADB-open, device responds with CNXN containing:
data: "device::ro.product.name=Pixel7;ro.product.model=Pixel 7;..."
## netrecon parses this to extract model, Android version, serial
Internals
Scan Evasion — How netrecon Bypasses Firewalls
The mode parameter controls how netrecon evades IDS/IPS and firewall filtering.
Each technique manipulates different layers of the TCP/IP stack.
Fragmented Packets (mode: fragment)
## Normal TCP SYN to port 80:
[ IP Header ][ TCP Header (SYN) ]
20 bytes 20 bytes min
## Fragmented (nmap -f flag — 8-byte IP fragments):
Fragment 1: [ IP Header (MF=1, offset=0) ][ first 8 bytes of TCP header ]
Fragment 2: [ IP Header (MF=0, offset=1) ][ remaining TCP header bytes ]
## Effect: IDS that reassembles TCP before inspecting sees the full SYN
## IDS that inspects individual fragments misses the TCP header structure
## Most modern IDS reassemble — use decoy or timing for better evasion
nmap flag: -f (8-byte frags), -ff (16-byte frags), --mtu <N> (custom)
Decoy Scan (mode: decoy)
## Decoy scan: SYN packets appear to come from multiple sources
nmap -D RND:10 <target>
# nmap sends SYN from 10 random IPs + your real IP, interspersed
# Target's firewall log shows 11 sources — analyst can't tell which is real
## Packet level: each "decoy" SYN has a spoofed source IP in the IP header
## nmap crafts raw IP packets (requires root) with the spoofed src fields
## The actual SYN-ACK replies go to the decoy IPs — real IP's SYN is buried
Idle Scan (mode: idle)
## Idle scan: your IP never appears in target's logs at all
nmap -sI <zombie_ip> <target>
## Mechanism (3 steps per port):
# 1. Query zombie's IP ID (IPID) sequence number:
attacker → zombie: SYN/ACK → zombie responds RST with IPID=X
# 2. Send spoofed SYN to target, pretending to be the zombie:
attacker (spoofed=zombie) → target: SYN
# 3. If port open: target sends SYN-ACK to zombie → zombie sends RST → zombie IPID increments
attacker → zombie: SYN/ACK → zombie RST with IPID=X+2 ← port OPEN
# If port closed: target sends RST to zombie → zombie ignores → IPID=X+1 ← port CLOSED
## Works only if zombie has predictable IPID (older Linux/Windows hosts)
## Modern hosts use random IPID — useless as zombies
Extend
Customization & Extension
Adding a Custom Service Detector
# In netrecon.py — SERVICE_DETECTORS dict
# Each entry: port → function(banner: bytes) → dict | None
def detect_mqtt(banner: bytes) -> dict | None:
# MQTT CONNECT packet response: starts with 0x20 (CONNACK)
if len(banner) >= 4 and banner[0] == 0x20 and banner[1] == 0x02:
rc = banner[3] # 0=accepted, 1-5=errors
return {
"service": "mqtt",
"auth_required": rc == 4, # 0x04 = bad credentials
"anonymous": rc == 0, # 0x00 = accepted without auth
"severity": "CRITICAL" if rc == 0 else "INFO",
"note": "Anonymous MQTT broker — subscribe to all topics: mosquitto_sub -h HOST -t '#'"
}
return None
SERVICE_DETECTORS = {
1883: detect_mqtt,
8883: detect_mqtt, # MQTT over TLS
# ... existing detectors ...
}
Adding a Custom Report Format
# netrecon.py — REPORT_FORMATS dict
def format_splunk(findings: list[dict]) -> str:
"""Output findings as Splunk-compatible key=value events."""
lines = []
for f in findings:
kv = " ".join(f'{k}="{v}"' for k, v in f.items() if isinstance(v, (str, int)))
lines.append(f"source=vanta_netrecon {kv}")
return "\n".join(lines)
REPORT_FORMATS = {
"json": format_json, # existing
"html": format_html, # existing
"splunk": format_splunk, # your addition
}
# Usage: set output_format splunk
Adding an NSE Script
netrecon passes --script arguments to nmap. You can bundle a custom NSE script
with the VANTA repo and reference it directly:
-- tools/netrecon/scripts/vanta-banner-grab.nse
description = [[Grab raw banner from any open port for VANTA enrichment.]]
author = "0xb0rn3"
categories = {"discovery", "safe"}
local nmap = require "nmap"
local shortport = require "shortport"
portrule = shortport.port_or_service({1-65535}, nil, "tcp")
action = function(host, port)
local sock = nmap.new_socket()
sock:set_timeout(3000)
local status, err = sock:connect(host, port)
if not status then return nil end
local banner
status, banner = sock:receive_lines(3)
sock:close()
if status then
return banner:sub(1, 256) -- trim to 256 chars
end
end
-- Reference in netrecon:
-- set extra_scripts /path/to/vanta-banner-grab.nse
-- netrecon appends: --script /path/to/vanta-banner-grab.nse to nmap args
Study
Learning Path & Resources
Network reconnaissance is the first phase of every engagement. Build your knowledge from TCP/IP basics through advanced evasion techniques.
Step 1: Networking Fundamentals
| Resource | Type | Why |
|---|---|---|
| Professor Messer's CompTIA Network+ (free on YouTube) | Free video course | Covers TCP/IP, subnets, ports, protocols — everything netrecon operates on. Watch before touching nmap. |
| TCP/IP Illustrated Vol 1 (Stevens) | Book | The authoritative TCP/IP reference. Chapter 1-4 explain packets, routing, ports — the foundation for understanding nmap output. |
| Wireshark for beginners (free YouTube) | Free | Watch actual packets as nmap scans. Run Wireshark alongside netrecon to see every probe at the byte level. |
Step 2: nmap Mastery
| Resource | Focus |
|---|---|
| Nmap Network Scanning (Fyodor, nmap.org/book) — free online | The official nmap book by its creator. Chapter 5 (port scanning) and Chapter 8 (OS detection) are essential. netrecon is a wrapper around nmap — knowing nmap deeply means you know netrecon deeply. |
| nmap NSE scripting guide (nmap.org/book/nse.html) | Writing custom NSE scripts. Directly applicable to the netrecon customization guide above. |
| nmap cheat sheet (StationX) | Quick reference for flags. netrecon sets most flags automatically — this helps you understand which ones and why. |
Step 3: Practice Labs
| Platform | Cost | Focus |
|---|---|---|
| TryHackMe — "Nmap" room (free) | Free | Guided nmap exercises. Run netrecon alongside to compare outputs and understand what VANTA automates. |
| TryHackMe — "Passive Recon" and "Active Recon" rooms | Free | Cover DNS enumeration, WHOIS, Shodan — all integrated into netrecon's web enumeration mode. |
| HackTheBox — Any machine (scan phase) | Free tier | Every HackTheBox machine starts with a reconnaissance phase. Use netrecon instead of running nmap manually — compare findings. |
| Vulnhub machines | Free | Download VMs and scan them with netrecon. The "android" preset is great for local Android lab machines. |
Step 4: Deep Reference
| Resource | Use For |
|---|---|
| Shodan (shodan.io) | Internet-wide passive reconnaissance — search for devices by banner, port, OS. Complements netrecon's active scanning. |
| Censys (censys.io) | Similar to Shodan. Excellent for finding SSL certs, open ports across IP ranges before active scanning. |
| HackTricks — Network Services Pentesting (book.hacktricks.xyz) | Port-by-port exploitation guide. Use it alongside netrecon's findings — when netrecon finds port 21 (FTP), HackTricks tells you how to test it. |
| PTES Technical Guidelines (pentest-standard.org) | Penetration Testing Execution Standard — professional recon methodology that netrecon implements. |
Practice Path
## Progression: from first scan to professional recon
Week 1: Your First Scan
Setup: lab-setup.html — Kali + Metasploitable on host-only network
Run: netrecon with default settings against 192.168.56.0/24
Learn: read every finding — look up every open port in HackTricks
Week 2: Service Fingerprinting
Target: Metasploitable 2
Run: netrecon with service_scan preset
Manual: replicate what netrecon does with nmap -sV -sC -O directly
Compare: outputs — understand what each nmap flag adds
Week 3: Evasion Modes
Setup: a pfSense firewall VM between Kali and targets
Test: each evasion mode (fragment, decoy, idle)
Monitor: pfSense logs — which techniques get logged vs evade
Week 4: Android Recon
Run: netrecon with android preset against your lab network
Connect: an Android device with ADB-WiFi enabled (port 5555)
Observe: how netrecon detects and fingerprints the ADB service