Overview
A host and service scanner written for internal CTF prep and lab environments. The goal was a tool that outputs structured data rather than terminal noise, making it trivial to pipe results into other scripts.
Design
Built on Python's asyncio with a semaphore-controlled concurrency model. Scanning 1000 hosts doesn't block on slow responders.
async def scan_range(targets: list[str], concurrency: int = 256) -> list[HostResult]:
sem = asyncio.Semaphore(concurrency)
tasks = [scan_host(t, sem) for t in targets]
return await asyncio.gather(*tasks)Each host result is a typed dataclass: open ports, detected services, banner strings, and reverse DNS if available. Everything serializes to JSON.
Service Fingerprinting
Rather than running full Nmap on every host, a lightweight banner grab runs first. Nmap is invoked only when the banner is ambiguous or a well-known port is closed in an unexpected way.
This two-pass approach cuts total scan time by roughly 60% on typical lab ranges compared to running -sV across all hosts upfront.
Output
{
"host": "10.10.10.45",
"hostname": "bastion.lab",
"open_ports": [22, 80, 443, 8080],
"services": {
"22": { "name": "ssh", "banner": "OpenSSH_8.9p1" },
"80": { "name": "http", "server": "nginx/1.22.1" },
"8080": { "name": "http", "server": "Apache/2.4.54" }
}
}Notes
Archived because active maintenance fell off after the CTF season ended. The core async pattern is worth reusing; the service fingerprint database is stale.
