Offensive Security · Home-lab project

Recon, Scanning & Initial Access

Enumerating two very different targets — a hardened, 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.

Nmap Python Sockets Threading Banner Grabbing Netcat Internals Reverse & Bind Shells PowerShell Windows Defender
PlatformKali Linux (VMware) on Kubuntu
TargetsWindows 10 laptop · Metasploitable 2
FormatHome-lab engagement + tool build
DifficultyFoundational

01Lab environment

One attacker platform, two deliberately different targets: a physical Windows laptop with its own defenses live, and a Metasploitable 2 VM left unhardened to validate technique. Every target is my own hardware or a VM I run — an authorised, self-contained lab.

Lab topology: Kali Linux dual-homed attacker VM connects to a physical Windows 10 laptop on the home LAN and a Metasploitable 2 VM on a VMware host-only segment.
Kali sits on both segments. The Windows box keeps Defender and its firewall live throughout; Metasploitable is left at its deliberately vulnerable defaults.

Scanner, banner grabber, and netcat replacement all live in the GitHub repo.

HostRoleNetwork
Kali LinuxRecon and exploitation platformDual-homed — bridged + host-only
Windows 10 (physical)Realistic, defended target — Defender and firewall active192.168.0.0/24 · home LAN
Metasploitable 2 (VM)Deliberately vulnerable target for technique validation172.20.10.0/28 · VMware host-only

02What I built

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 an exception, farmed out 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.check_output-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 writeup 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 target's live 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)
    result = s.connect_ex((target, port))
    if result == 0:
        banner = grab_banner(s)
        print(f"Port {port} is OPEN   {banner}")
    s.close()

# 100 workers waiting on timeouts in parallel — ~500x faster than serial
with ThreadPoolExecutor(max_workers=100) as executor:
    executor.map(scan_port, range(1, 1025))

netcat.py — the command-shell branch (BHP ch. 2)

elif self.args.command:
    cmd_buffer = b''
    while True:
        client_socket.send(b'BHP: #> ')
        while b'\n' not in cmd_buffer:
            cmd_buffer += client_socket.recv(64)
        response = execute(cmd_buffer.decode())
        if response:
            client_socket.send(response.encode())
        cmd_buffer = b''

03Verification & results

Each finding was cross-checked between nmap and the from-scratch scanner before being treated as real.

nmap and scanner.py agreed exactly on the five open ports found on the Windows target (135, 139, 445, 902, 912) once the host's own firewall was accounted for.

SMB signing confirmed enabled but not required on the Windows target — a concrete NTLM-relay precondition, found by nmap's NSE scripts rather than a plain port scan.

Metasploitable enumerated top to bottom — 14 open ports, six carrying a named, specific vulnerability rather than a generic service label.

vsftpd 2.3.4 and anonymous FTP identified on port 21 — one of the most widely known backdoored service versions in the field.

Cross-machine reverse shell obtained — a PowerShell one-liner on the Windows target called back to a Kali nc listener and returned an interactive prompt.

Unauthenticated root shell obtained on Metasploitable by connecting directly to an exposed bind shell on port 1524 — no exploit and no credentials required.

Metasploitable — nmap -sV -sC (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

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.

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)

04Troubleshooting scenario

Four real failures across the two targets — not staged for the writeup. The firewall one is the one that actually made me stop and think.

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 that's 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 (-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.

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 — no execution needed. 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.

05Real-world relevance

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.

Open to junior penetration testing roles

Site-based or remote, in and around Milton Keynes.