Skip to main content
  1. Posts/

Advanced network scanning and enumeration

··3230 words·16 mins·
Table of Contents

Scanning is most of the engagement, not the warmup. The interesting findings — exposed shares with weak ACLs, a forgotten dev portal on 8080, a printer that responds to SNMP v1 with a default community string — are almost never the thing that gets the customer to call you. They’re the thing you only see if you actually do the recon work instead of running nmap -sC -sV and moving on.

This post covers what’s behind the flags: the TCP/IP behavior that determines what a port scan can and can’t tell you, NSE used for actual enumeration, timing and packet-level controls for staying under the IDS threshold, the Rust and Go alternatives where they earn their keep, and service-specific enumeration for the protocols you’ll keep running into.


TCP/IP behavior behind a port scan
#

What the scanner reports as “open”, “closed”, or “filtered” is just an interpretation of how the target’s stack reacted to specific packets. Knowing the underlying state machine is the difference between reading scan output and understanding it.

The TCP three-way handshake
#

TCP establishes a connection with SYN, SYN-ACK, ACK. The client sends SYN. If the port is listening, the server replies with SYN-ACK. The client closes the loop with ACK and the connection is up.

If the port is closed, the server sends RST instead of SYN-ACK. If a firewall is filtering, the packet gets dropped silently — you get nothing back, or maybe an ICMP unreachable from somewhere upstream. That ambiguity (“did the host go away, or is the firewall eating my packets?”) is the entire reason scans take longer than they “should.”

UDP: no handshake
#

UDP is connectionless. You send a datagram and hope. An open UDP port may or may not respond — the application decides, not the stack. A closed UDP port typically returns ICMP “Port Unreachable.” Filtered ports return nothing. The result is that UDP scanning is fundamentally slower and noisier than TCP, and you’ll be guessing more often.

ICMP
#

ICMP carries the diagnostic and error messages that everything else relies on. Type 8/0 is the echo request/reply pair you call ping. Type 3 is destination unreachable (which covers “closed port” among other things). Type 11 is the TTL-expired message traceroute uses to walk the path one hop at a time.

Host discovery before port scanning depends on ICMP almost everywhere — which is also why “block all ICMP” stops a lot of tools cold and why you’ll usually want -Pn on internal engagements where someone tried to harden the network this way.


Nmap
#

Fyodor’s been maintaining nmap since 1997 and it still has the best surface-area-to-ergonomics ratio of any scanner. The basic flags get you 80% of what you need; the remaining 20% is where the interesting findings live.

Host discovery (ping scans)
#

Before any port scan, nmap figures out which hosts are alive. The default uses a mix of probes — ICMP echo, TCP SYN to 443, TCP ACK to 80, ICMP timestamp.

# Default host discovery (ICMP echo, TCP SYN to 443, TCP ACK to 80, ICMP timestamp)
nmap -sn 192.168.1.0/24

# Skip host discovery and scan all targets (useful if ICMP is blocked)
nmap -Pn 192.168.1.10

# Use specific discovery probes
nmap -PS22,80,445 192.168.1.0/24   # TCP SYN ping to specific ports
nmap -PA80,443 192.168.1.0/24      # TCP ACK ping
nmap -PU53,161 192.168.1.0/24      # UDP ping
nmap -PE 192.168.1.0/24            # ICMP Echo ping only
Tip

On internal networks where ICMP is often blocked, use -PS with ports likely to be open (22, 80, 445) for more reliable host discovery.

TCP scan types
#

The default -sS (SYN scan) is also called “half-open” or “stealth.” Nmap sends SYN, the target replies with SYN-ACK if open, and nmap tears the connection down with RST before it completes the handshake. Applications don’t log it because they only see completed connections, but every modern firewall and IDS absolutely does. “Stealth” here means stealthy from the perspective of the listening process, not from the network.

sudo nmap -sS 192.168.1.10

-sT (TCP connect) finishes the handshake. Use it when you don’t have root or admin and can’t get raw sockets — the OS does the connect for you.

nmap -sT 192.168.1.10

-sA (ACK scan) doesn’t tell you whether a port is open or closed. It tells you whether a stateful firewall is in the path: an unsolicited ACK to an unrelated connection gets dropped by stateful filters and lets through unfiltered ports. Used right, it’s a firewall map.

sudo nmap -sA 192.168.1.10

FIN, Xmas, and Null scans (-sF, -sX, -sN) send packets with weird flag combinations. RFC 793 says closed ports should RST and open ports should ignore. In practice, modern stacks don’t follow this cleanly, so these scans are noisy and often unreliable. They occasionally get through old stateless filters.

Idle scan (-sI) bounces probes off a third host with a predictable IP ID sequence. Your address never sends a packet directly to the target port — every probe comes from the zombie. Setup is fiddy, finding a usable zombie is harder than it used to be, but in high-monitoring environments it’s the only scan that doesn’t immediately give away the source.

sudo nmap -sI zombie_host target_host

UDP scan (-sU)
#

Skip UDP and you miss DNS (53), SNMP (161), NTP (123), TFTP (69), DHCP (67/68), and a long tail of management services that don’t show up in your TCP results. The catch is that UDP scanning is painfully slow. Open ports often don’t respond at all, so nmap waits for a timeout on every one of them, and most systems rate-limit ICMP “Port Unreachable” responses on top of that.

# Scan top 20 UDP ports (much faster than all 65k)
sudo nmap -sU --top-ports 20 192.168.1.10

# Combine with version detection for better accuracy
sudo nmap -sU -sV --top-ports 100 192.168.1.10
Tip

Never scan all 65,535 UDP ports unless you have hours to spare. Target the top 100 or use a custom list of security-relevant UDP ports.

Version detection (-sV)
#

Once a port is known open, -sV probes it with protocol-specific banners and request templates to figure out the service and version.

nmap -sV 192.168.1.10

# Increase intensity for more thorough (but slower/noisier) probing
nmap -sV --version-intensity 9 192.168.1.10

OS detection (-O)
#

Nmap fingerprints the OS by sending crafted probes and reading the stack’s quirks back — TTL, TCP window size, options ordering, idiosyncrasies in how it responds to weird flag combinations.

sudo nmap -O 192.168.1.10

# Require at least one open and one closed port for more accurate results
sudo nmap -O --osscan-limit 192.168.1.10

Flag combinations worth memorizing
#

# The "default recon" scan (SYN scan + version + default scripts)
sudo nmap -sS -sV -sC 192.168.1.10

# Full port scan with version and OS detection
sudo nmap -sS -sV -O -p- 192.168.1.10

# Aggressive scan (OS, version, scripts, traceroute) - LOUD
sudo nmap -A 192.168.1.10

The Nmap Scripting Engine
#

NSE is what turns nmap into something closer to a vulnerability scanner. Scripts live in /usr/share/nmap/scripts/ (on most Linuxes) and are written in Lua. There are hundreds of them, and most of the high-value findings on a real engagement come from a small handful you’ll learn to reach for.

Categories
#

NSE scripts are tagged with one or more categories:

  • auth: Authentication-related scripts.
  • broadcast: Discover hosts via broadcast.
  • brute: Brute-force credential attacks.
  • default: Safe scripts run with -sC.
  • discovery: Service discovery and enumeration.
  • dos: Denial of service (use with extreme caution).
  • exploit: Attempt to exploit vulnerabilities.
  • external: Scripts that query external services.
  • fuzzer: Fuzzing scripts.
  • intrusive: Scripts likely to crash services or be detected.
  • malware: Detect malware infections.
  • safe: Scripts unlikely to crash services.
  • version: Version detection helpers.
  • vuln: Vulnerability detection.

Running scripts
#

# Run default scripts
nmap -sC 192.168.1.10

# Run a specific script
nmap --script http-title 192.168.1.10

# Run all scripts in a category
nmap --script vuln 192.168.1.10

# Run scripts matching a pattern
nmap --script "smb-*" 192.168.1.10

# Combine categories (run safe AND vuln scripts)
nmap --script "safe and vuln" 192.168.1.10

Targeted enumeration
#

SMB (port 445):

# Enumerate shares, users, and check for EternalBlue (MS17-010)
nmap -p 445 --script smb-enum-shares,smb-enum-users,smb-vuln-ms17-010 192.168.1.10

# Full SMB enumeration
nmap -p 139,445 --script "smb-enum*,smb-vuln*,smb-os-discovery" 192.168.1.10

HTTP (ports 80, 443):

# Web server headers, methods, and directory enumeration
nmap -p 80,443 --script http-headers,http-methods,http-enum 192.168.1.10

# Check for common vulnerabilities
nmap -p 80,443 --script http-vuln* 192.168.1.10

SSH (port 22):

# Check supported authentication methods
nmap -p 22 --script ssh-auth-methods 192.168.1.10

# Brute force (intrusive!)
nmap -p 22 --script ssh-brute --script-args userdb=users.txt,passdb=pass.txt 192.168.1.10

LDAP (port 389):

# Enumerate LDAP root DSE (often reveals domain information)
nmap -p 389 --script ldap-rootdse 192.168.1.10

# Search LDAP for sensitive objects
nmap -p 389 --script ldap-search 192.168.1.10

Script arguments
#

A lot of scripts accept arguments to change their behavior. Pass them with --script-args:

# HTTP enum with custom paths
nmap -p 80 --script http-enum --script-args http-enum.basepath=/api/ 192.168.1.10

# SMB brute force with credentials
nmap -p 445 --script smb-brute --script-args smbuser=admin,smbpass=password 192.168.1.10

Updating the script database
#

# Update scripts from the Nmap repository
sudo nmap --script-updatedb

# Download new scripts manually (for example, from GitHub)
sudo wget -O /usr/share/nmap/scripts/my-custom.nse https://example.com/script.nse
sudo nmap --script-updatedb

Writing your own NSE scripts
#

Every engagement eventually has an indicator that nobody’s written a script for — a custom banner string, a specific favicon hash, a non-standard endpoint a particular product exposes. Lua’s verbose but the boilerplate is shallow:

-- /usr/share/nmap/scripts/http-confidential-check.nse
local http = require "http"
local shortport = require "shortport"
local stdnse = require "stdnse"

description = [[
Checks for the presence of "Company Confidential" in HTTP responses.
]]

author = "UncleSp1d3r"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}

portrule = shortport.http

action = function(host, port)
    local response = http.get(host, port, "/")
    if response.body and response.body:find("Company Confidential") then
        return "Found 'Company Confidential' on homepage!"
    end
end

Staying under the IDS threshold
#

Default nmap output looks exactly like default nmap output. Every IDS vendor has signatures for it. Slowing down, spreading source addresses, and breaking up packet shape buys you visibility without immediately tripping alarms.

Timing templates (-T)
#

Timing controls the rate and aggressiveness of probes.

TemplateNameDescription
-T0ParanoidSends one packet every 5 minutes. For extremely high-security targets.
-T1SneakyOne packet every 15 seconds. Still slow but more practical.
-T2PoliteOne packet every 0.4 seconds. Avoids overloading fragile systems.
-T3NormalDefault timing. Balances speed and stealth.
-T4AggressiveFaster, assumes a fast and reliable network.
-T5InsaneMaximum speed, high packet loss expected. For CTF, not real engagements.

For real engagements, -T2 or hand-tuned timing controls; -T4/-T5 are for CTFs and labs where nobody’s watching.

# Custom timing: wait 500ms between probes, limit parallelism
nmap --scan-delay 500ms --max-parallelism 10 192.168.1.10

Packet manipulation
#

Decoys (-D):

Spoof your scan from multiple IP addresses. The target sees traffic from ten IPs; one is real, the rest are noise. Attribution becomes a forensics problem instead of a log lookup.

# Scan from 5 random IPs plus your own (ME)
sudo nmap -sS -D RND:5,ME 192.168.1.10

# Use specific decoy IPs
sudo nmap -sS -D 10.0.0.1,10.0.0.2,10.0.0.3,ME 192.168.1.10
Warning

Decoys must appear to be valid hosts on the network, or they will be obviously fake. Do not use decoys across the internet - they are typically only effective on LANs.

Source port (-g / --source-port):

A lot of firewalls allow traffic from “trusted” source ports — DNS (53), HTTP (80) — outbound, and treat inbound from those ports more permissively than they should.

# Force scan traffic to originate from port 53
sudo nmap -sS -g 53 192.168.1.10

IP fragmentation (-f):

Split TCP headers into 8-byte fragments. Simple packet filters that pattern-match on flags without reassembling fragments will miss the scan; anything with stateful inspection won’t.

sudo nmap -sS -f 192.168.1.10

# Double fragmentation (16 bytes per fragment)
sudo nmap -sS -f -f 192.168.1.10

MTU (--mtu):

Pick a specific fragment size instead of nmap’s default of 8 bytes.

sudo nmap -sS --mtu 24 192.168.1.10

Random host order (--randomize-hosts):

Sequential scans across a subnet are obvious in logs. Randomize.

nmap --randomize-hosts 192.168.1.0/24

Spoofed MAC (--spoof-mac):

On a LAN you can choose what your device looks like at L2. Useful when MAC-based ACLs are in play or when you want to look like a printer.

# Use a random vendor MAC
sudo nmap --spoof-mac Apple 192.168.1.10

# Use a specific MAC
sudo nmap --spoof-mac 00:11:22:33:44:55 192.168.1.10

Output formats
#

Nmap can write its results in four formats — pick whichever your downstream tooling needs.

# Normal output (human-readable)
nmap 192.168.1.10 -oN scan.txt

# Greppable output (for scripting)
nmap 192.168.1.10 -oG scan.gnmap

# XML output (for parsing and reporting tools)
nmap 192.168.1.10 -oX scan.xml

# All formats at once
nmap 192.168.1.10 -oA scan_results

Parsing the output
#

The XML output is the one to keep — Python’s python-nmap or xsltproc can render it into something readable later.

# Convert XML to HTML report
xsltproc scan.xml -o scan.html

Faster alternatives when nmap is too slow
#

Nmap scans one host at a time per process and is conservative about parallelism. On a /16 with thousands of live hosts that’s fine for accuracy but unacceptable for time. The newer scanners trade some accuracy for orders of magnitude more speed; the typical workflow is to use them for discovery and then run nmap against the ports they found.

RustScan
#

Rust, async, and a willingness to crank up ulimit for tens of thousands of concurrent connections. The intended workflow is “RustScan finds open ports, pipes them into nmap, nmap does the rest” — and that’s actually the right division of labor.

# Scan all 65535 ports on a target, then run Nmap scripts on open ports
rustscan -a 192.168.1.10 -- -sV -sC

# Scan a subnet with a high connection limit
rustscan -a 192.168.1.0/24 --ulimit 5000

# Scan specific ports
rustscan -a 192.168.1.10 -p 22,80,443,445

On a fast network, a full 65k port scan of a single host takes RustScan under ten seconds.

Masscan
#

Masscan ships its own TCP/IP stack — bypassing the OS kernel — so it can push millions of packets per second. It’s completely stateless, which means it doesn’t actually track connections; you read the responses off the wire and reconstruct what happened yourself.

# Scan a /16 for port 445
sudo masscan 10.10.0.0/16 -p445 --rate 10000

# Scan for common web ports
sudo masscan 10.10.0.0/16 -p80,443,8080,8443 --rate 50000

# Output in Nmap-compatible format
sudo masscan 10.10.0.0/16 -p445 -oG masscan.gnmap
Warning

Masscan at high rates will overwhelm consumer-grade routers and may violate network usage policies. Use on VPSes with dedicated network interfaces or in lab environments, though you may violate the terms of service of your VPS provider.

Naabu
#

ProjectDiscovery’s port scanner. Go, fast, and fits cleanly into their other tools if you’re already using them.

# Fast port scan
naabu -host 192.168.1.10 -p 1-65535

# Scan from a list of hosts
naabu -list targets.txt -p 22,80,443

Service-specific enumeration
#

The port scan is the easy part. What you do once you know SMB is listening on port 445 (or LDAP on 389, or DNS on 53) is what actually produces findings.

SMB (ports 139, 445)
#

SMB is where internal engagements usually start. Shares with weak ACLs, enumerable users, password policies leaked over null sessions, and on neglected boxes, still EternalBlue.

# Nmap scripts
nmap -p 445 --script smb-enum-shares,smb-enum-users,smb-os-discovery 192.168.1.10

# CrackMapExec (CME) for quick enumeration
crackmapexec smb 192.168.1.10

# Enum4linux-ng (updated Python version)
enum4linux-ng -A 192.168.1.10

# List shares with smbclient
smbclient -L //192.168.1.10/ -N

SNMP (port 161 UDP)
#

SNMP is the protocol that keeps giving. A v1 or v2c community string of public (or private) gets you process lists, installed software, network interface tables (which is how you find dual-homed boxes), and sometimes credentials embedded in OID strings. The community string is sent in cleartext, which is why v3 exists and why almost nobody has rolled it out.

# Guess community strings
onesixtyone -c /usr/share/seclists/Discovery/SNMP/common-snmp-community-strings.txt 192.168.1.10

# Deep enumeration with snmpwalk
snmpwalk -v2c -c public 192.168.1.10

# User-friendly enumeration
snmp-check 192.168.1.10 -c public

NFS (port 2049)
#

NFS exports tend to be more permissive than the administrator believed. Specifically look for no_root_squash — it means root on your client maps to root on the server’s filesystem, and that’s usually game over for whatever’s stored there.

# List exports
showmount -e 192.168.1.10

# Mount and explore
sudo mkdir /mnt/nfs
sudo mount -t nfs 192.168.1.10:/share /mnt/nfs

LDAP (port 389)
#

LDAP — usually Active Directory in practice — will hand you the entire directory tree if anonymous bind is enabled or if you happen to have any credentials.

# Anonymous bind and enumerate
ldapsearch -x -H ldap://192.168.1.10 -b "DC=corp,DC=local"

# Nmap scripts
nmap -p 389 --script ldap-rootdse,ldap-search 192.168.1.10

DNS (port 53)
#

A DNS server that allows zone transfers to anyone is rare now but not extinct. When you find one, it hands over a complete map of internal hostnames.

# Zone transfer attempt
dig axfr @192.168.1.10 corp.local

# Reverse DNS sweep
dnsrecon -r 192.168.1.0/24 -n 192.168.1.10

SMTP (port 25)
#

VRFY and EXPN are SMTP commands designed to verify mailbox existence. On servers that haven’t disabled them, they let you enumerate valid usernames one at a time.

# Manual enumeration
nc 192.168.1.10 25
VRFY admin
VRFY root

# Automated with smtp-user-enum
smtp-user-enum -M VRFY -U users.txt -t 192.168.1.10

Organizing recon output
#

A real engagement produces a lot of scan output. The shape that’s worked for me is to keep it as flat files, organized by tool and protocol, so you can grep across it later without standing up a database for one job.

Directory layout
#

project/
  recon/
    nmap/
      initial-tcp.nmap
      full-tcp.nmap
      udp.nmap
      vuln-scan.xml
    masscan/
      full-range.txt
    enum/
      smb/
      snmp/
      ldap/

Aggregation tools
#

Zenmap is the official nmap GUI — useful for comparing scans across time. Nessus and OpenVAS are full vulnerability scanners with their own data models if you need that level of structure. Faraday is a collaborative pentest IDE for teams. Reconftw automates a lot of the bash-glue stage of external recon. None of them replace flat files for ad hoc analysis; pick one when you outgrow grep.


Closing
#

The thing that separates a useful scan from a noisy one isn’t the tool — it’s whether you read the output critically. Anyone can run nmap -A and dump the results into a report. The findings that matter are the ones that come from noticing that a server says it’s running OpenSSH 7.4 (which means RHEL 7, which means the box is probably eight years old and missing every kernel patch since), or that the same printer answers SNMP on three different VLANs (which means the network segmentation isn’t real), or that an internal-only admin panel is bound to 0.0.0.0 instead of 127.0.0.1.

That kind of reading takes time. The high-speed scanners help — RustScan and Masscan exist because nmap’s defaults aren’t fast enough to sweep a /16 before lunch — but speed without attention just produces faster slop. Slow the scan back down for the targets that look interesting, and use the time you saved on the boring ones to actually think about what you’re looking at.


References
#

UncleSp1d3r
Author
UncleSp1d3r
As a computer security professional, I’m passionate about building secure systems and exploring new technologies to enhance threat detection and response capabilities. My experience with Rails development has enabled me to create efficient and scalable web applications. At the same time, my passion for learning Rust has allowed me to develop more secure and high-performance software. I’m also interested in Nim and love creating custom security tools.