1. Work
  2. ▲ Ethical hacking
  3. Recon, Scanning & Initial Access

Recon, scanning & initial access

Enumerating two very different targets — a defended, real Windows 10 laptop and a deliberately vulnerable Metasploitable 2 VM — with nmap, a threaded Python port scanner and banner grabber built from scratch, and a from-scratch netcat replacement. Ends in a cross-machine PowerShell reverse shell and an unauthenticated route straight to root. Every target is my own hardware or a VM I run — an authorised, self-contained lab.

Platform
Kali Linux · VMware
Difficulty
Foundational
Targets
Windows 10 · Metasploitable 2
Known good — verified against live output
  • nmap and scanner.py agreed exactly — 5 open ports on Windows (135, 139, 445, 902, 912)
  • SMB signing enabled but not required — a concrete NTLM-relay precondition
  • Metasploitable enumerated top to bottom — 14 ports, 6 named vulnerabilities
  • vsftpd 2.3.4 and anonymous FTP on port 21 — a known backdoored version
  • Cross-machine reverse shell on Windows — PowerShell called back to a Kali listener
  • Unauthenticated root on Metasploitable — bind shell on port 1524, no exploit
Obstacles — four faults, all resolved
  • iWindows reported down: Wi-Fi NIC power-saving resolved
  • iiEvery port filtered: Defender drops silently resolved
  • iiiReverse-shell payload quarantined on sight resolved
  • ivListener won't rebind (Errno 98): stale process resolved
Four chapters — the figure follows your readingHover a device to inspect it
01Environment

One platform, two targets.

One attacker platform, two deliberately different targets — one defended, one left vulnerable to validate technique.

One attacker platform, two deliberately different targets: a physical Windows 10 laptop with its own defences live, and a Metasploitable 2 VM left unhardened to validate technique. Kali is dual-homed — bridged onto the home LAN to reach the Windows box, and on a VMware host-only segment for the vulnerable VM. Every target is my own hardware or a VM I run.

Evidence — host roles
Kali            recon & exploitation platform (dual-homed)
Windows 10      defended target — Defender + firewall live
Metasploitable  deliberately vulnerable target (VM)
02Enumerate

Enumerate, then verify every finding.

nmap and a from-scratch scanner run against both targets; every finding cross-checked between the two before it counts.

nmap for mature, proven recon; hand-rolled Python for the case that eventually matters — a compromised host with no tools installed except Python. Each finding was cross-checked between nmap and the from-scratch scanner before being treated as real. The Windows target gave up five open ports once its own firewall was accounted for; Metasploitable enumerated wide open, six of its fourteen ports carrying a named, specific vulnerability rather than a generic service label.

Evidence — nmap -sV -sC (Metasploitable excerpt)
21/tcp   open  ftp     vsftpd 2.3.4
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
22/tcp   open  ssh     OpenSSH 4.7p1 Debian 8ubuntu1
139/tcp  open  netbios-ssn  Samba smbd 3.X - 4.X
445/tcp  open  netbios-ssn  Samba smbd 3.0.20-Debian
1524/tcp open  ingreslock   Metasploitable root shell
03Initial access

Two targets, two routes in.

A PowerShell reverse shell dials back from Windows; a direct connection to an exposed bind shell gives unauthenticated root on Metasploitable.

On the Windows target, a PowerShell one-liner called back to a Kali nc listener and returned an interactive prompt — a reverse shell riding out through the firewall asymmetry that trusts outbound traffic far more than inbound. On Metasploitable, connecting directly to an exposed bind shell on port 1524 gave a root shell with no exploit and no credentials required.

Evidence — root shell, direct connection, no exploit
$ nc 172.20.10.6 1524
root@metasploitable:/# whoami
root
root@metasploitable:/# id
uid=0(root) gid=0(root) groups=0(root)
04Troubleshoot

The one that got me.

The Windows target: alive on ARP, silent on TCP. Telling “filtered” apart from “down” is the whole problem.

With the host confirmed alive via ARP, a full port sweep still came back all-filtered — and neither -Pn, a higher --min-rate, nor packet fragmentation (-f) changed the result. The cause was Windows Defender Firewall dropping unsolicited SYNs and ICMP silently rather than rejecting them, which looks identical to a dead host from a single scan. Distinguishing “alive but silent” from “actually down” — ARP reachable, zero TCP replies — is what resolved it, not re-running the same scan with more flags.

Evidence — what carried through
Down, filtered and closed are three different facts.
ARP reachable, zero TCP replies = alive but silent,
not offline. Re-running the same scan with more
flags won't tell them apart — the method does.
Environment 1 / 4
Fig. 1 — 01 Environment
up / verifiedfaultinspecting
05 — What I built

nmap where it's proven, Python where it counts.

nmap for mature, proven recon; hand-rolled Python for the case that eventually matters — a compromised host with no tools installed except Python.

Threaded TCP port scanner

A raw-socket scanner using connect_ex() so closed ports return a status code instead of raising, farmed across a ThreadPoolExecutor pool so the scan waits on hundreds of timeouts in parallel instead of one at a time.

Active/passive banner grabber

Reads whatever a chatty service (FTP, SMTP) volunteers on connect, distinguishing that from silent binary protocols like SMB that need a protocol-specific handshake rather than a plaintext nudge.

Netcat replacement

An argparse-driven listener/client pair (Black Hat Python, ch. 2) that pipes a command shell over a raw socket — a working subprocess-backed shell built from nothing but the standard library.

Manual reverse & bind shells

Established by hand with plain netcat first (two terminals, localhost) to isolate the mechanic, then repeated cross-machine against the Windows target with a PowerShell one-liner in place of a native nc binary.

Git-backed documentation

Every tool and target write-up lives in Obsidian, version-controlled and pushed to a private GitHub repo — the same habit this page itself comes out of.

Evasion flags, tested honestly

Packet fragmentation (-f) and decoy source addresses (-D) tried against the Windows firewall — and reported as unsuccessful, because a stateful modern firewall reassembles fragments before filtering.

scanner.py — threaded scan with banner grab
# fresh socket per port; connect_ex returns a code instead of raising
def scan_port(port):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(1)
    if s.connect_ex((target, port)) == 0:
        print(f"Port {port} is OPEN   {grab_banner(s)}")
    s.close()

# 100 workers waiting on timeouts in parallel
with ThreadPoolExecutor(max_workers=100) as executor:
    executor.map(scan_port, range(1, 1025))
scanner.py — matching result, own code
Port 21 is open  220 (vsFTPd 2.3.4)
Port 22 is open  SSH-2.0-OpenSSH_4.7p1 Debian-8ubuntu1
Port 25 is open  220 metasploitable.localdomain ESMTP Postfix (Ubuntu)
Port 139 is open
Port 445 is open
1.19 seconds — scan complete.
06 — Real-world relevance

Why it matters outside the lab.

Down, filtered and closed are three different facts

A live host behind a silent firewall reads identically to an offline machine on a single scan. Treating “no response” as “nothing here” wastes hours on targets that were never actually unreachable.

A version banner is a lookup key

vsftpd 2.3.4 and Samba 3.0.20 aren't generic labels — they're a bundled backdoor and CVE-2007-2447 respectively, the moment the exact version is on record.

Reverse shells exist because of firewall asymmetry

Outbound traffic is trusted far more than inbound by default. A payload that dials home rides through the gap defenders leave open, rather than trying to punch through the wall they actually built.

Appendix A — Faults & fixes

Windows target: reported down mid-scan

Ping showed 100% loss and nmap returned 0 hosts up, despite the same MAC answering an ARP scan seconds earlier. Cause was the laptop's Wi-Fi adapter powering itself down to save battery — a host genuinely asleep, not blocked. ARP told the truth; ICMP couldn't, because there was no host awake to answer it. Fixed by disabling adapter power management on the target NIC.

Windows target: every port filtered the one that got me

With the host confirmed alive via ARP, a full port sweep still came back all-filtered — and neither -Pn, a higher --min-rate, nor packet fragmentation changed the result. Windows Defender Firewall was dropping unsolicited SYNs and ICMP silently rather than rejecting them, which looks identical to a dead host. Distinguishing “alive but silent” from “actually down” is what resolved it, not more flags.

Windows target: reverse-shell payload blocked outright

A canonical PowerShell TCP reverse-shell one-liner was quarantined before it ever ran — Defender pattern-matched the exact, widely published payload text on sight. I confirmed the payload itself was sound by disabling real-time protection on the owned lab target only for the test; genuine AV/EDR evasion is scoped to a later phase, not attempted here.

Kali: listener refused to rebind (Errno 98)

Restarting the custom netcat listener on the same port repeatedly failed with “Address already in use.” SO_REUSEADDR alone didn't help because a previous run was still live, not just lingering in TIME_WAIT. lsof -i :<port> to find the PID, kill -9 to free it.

Let's build something that stays up.

Open to network engineering roles and interesting infrastructure work, in and around Milton Keynes.

bernard@bernardeshun.co.uk