1. Introduction
In cybersecurity, network reconnaissance is the initial phase of any security audit. Security teams cannot protect assets they do not know exist. A port scanner serves as the foundational scanning engine to identify active hosts, open ports, and vulnerable services listening across network nodes.
I built this project to move beyond simply executing black-box network tools like Nmap. My goal was to understand the precise mechanics of socket handshakes, manage concurrent execution threads, and write a script to inspect and secure target hosts.
Cybersecurity Problem Addressed: Attack Surface Management. Rogue services, shadow IT, or forgotten open ports (such as database listeners or remote desktop ports) are major vectors for initial compromise. By mapping out open ports and banner versions, teams can identify vulnerabilities and enforce defensive firewalls.
2. Objectives
The objective of this project was to develop a professional-grade command-line tool that accomplishes the following:
- Perform Concurrent Scans: Utilize thread pool executors to scan large ranges of ports in seconds.
- Implement Banner Grabbing: Establish TCP socket streams post-discovery to grab software version banners.
- JSON Export Capabilities: Save active results into structured formats for ingestion by SOC dashboards or downstream automation systems.
3. Technologies Used
The scanner is written purely in Python to avoid third-party dependencies:
- Python 3: Core programming language.
- Sockets Library: For managing low-level TCP/IP stream connections.
- ThreadPoolExecutor (concurrent.futures): For asynchronous thread execution.
- Argparse: For parsing command-line parameters and arguments.
- JSON: For structuring and saving audit results.
- Git/GitHub: Version control and public hosting.
4. How It Works
The architecture follows a pipeline designed for optimal speeds and reliable banner harvesting:
Step-by-step workflow:
- Argument Parsing: The tool takes target IP/domains, ports range, timeout settings, and output options from the terminal.
- Hostname Resolution: Resolves target domains into IPv4 addresses using DNS lookups.
- Multi-threaded Scanning Loop: Submits scanning jobs across a ThreadPool. Each worker attempts to initialize a TCP socket connection (
socket.AF_INET,socket.SOCK_STREAM). - Banner Grabbing: If
connect_ex()returns0, the socket sends an introductory handshake bytes string (e.g.,b"Hello\r\n") and reads the responsive banner payload. - Output & Export: Output tables are printed in colored ANSI terminal layouts and written to a JSON file if configured.
5. Implementation
The script is split into two modules: scanner.py (the scanning engine class) and main.py (the command-line interface helper).
import socket
import concurrent.futures
from typing import List, Dict, Any, Optional
COMMON_PORTS: Dict[int, str] = {
21: "FTP", 22: "SSH", 23: "Telnet", 25: "SMTP", 53: "DNS",
80: "HTTP", 110: "POP3", 143: "IMAP", 443: "HTTPS", 445: "SMB",
3306: "MySQL", 3389: "RDP", 8080: "HTTP-Proxy"
}
class PortScanner:
def __init__(self, target: str, timeout: float = 1.0, grab_banner: bool = True):
self.target = target
self.timeout = timeout
self.grab_banner = grab_banner
self.target_ip = self._resolve_target(target)
def _resolve_target(self, target: str) -> str:
try:
return socket.gethostbyname(target)
except socket.gaierror:
raise ValueError(f"Could not resolve hostname: {target}")
def scan_port(self, port: int) -> Optional[Dict[str, Any]]:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(self.timeout)
result = s.connect_ex((self.target_ip, port))
if result == 0:
service = COMMON_PORTS.get(port, "Unknown")
banner = ""
if self.grab_banner:
try:
s.sendall(b"Hello\r\n")
banner = s.recv(1024).decode('utf-8', errors='ignore').strip()
except Exception:
pass
return {
"port": port,
"status": "open",
"service": service,
"banner": banner
}
except Exception:
pass
return None
def scan_range(self, start_port: int, end_port: int, threads: int = 100) -> List[Dict[str, Any]]:
results = []
with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as executor:
futures = {executor.submit(self.scan_port, port): port for port in range(start_port, end_port + 1)}
for future in concurrent.futures.as_completed(futures):
res = future.result()
if res:
results.append(res)
return sorted(results, key=lambda x: x["port"])
import argparse
import sys
import json
import time
from scanner import PortScanner
def main():
parser = argparse.ArgumentParser(description="Multi-threaded Port Scanner")
parser.add_argument("target", help="Target host")
parser.add_argument("-sp", "--start-port", type=int, default=1)
parser.add_argument("-ep", "--end-port", type=int, default=1024)
parser.add_argument("-t", "--threads", type=int, default=100)
parser.add_argument("--timeout", type=float, default=1.0)
parser.add_argument("--no-banner", action="store_true")
parser.add_argument("-o", "--output")
args = parser.parse_args()
try:
scanner = PortScanner(args.target, timeout=args.timeout, grab_banner=not args.no_banner)
print(f"[*] Target Host: {args.target} ({scanner.target_ip})")
start_time = time.time()
results = scanner.scan_range(args.start_port, args.end_port, threads=args.threads)
elapsed = time.time() - start_time
print("\nPORT STATE SERVICE BANNER")
print("-" * 50)
for res in results:
print(f"{res['port']:<10}open {res['service']:<10} {res['banner']}")
print(f"\n[*] Scan completed in {elapsed:.2f} seconds. Found {len(results)} open ports.")
if args.output:
with open(args.output, "w") as f:
json.dump(results, f, indent=4)
except Exception as e:
print(f"[-] Error: {e}")
if __name__ == "__main__":
main()
6. Testing
The tool was verified against an active testing virtual machine in a lab sandbox:
- Test Host Target: Local loopback interface (
127.0.0.1) and remote staging machine. - Commands Executed:
python main.py 127.0.0.1 -sp 1 -ep 1024 -t 100 -o scan_results.json
Terminal Scan Logs
[*] Target Host: 127.0.0.1 (127.0.0.1)
[*] Port Range : 1 - 1024
[*] Threads : 100
[*] Scanning in progress...
PORT STATE SERVICE BANNER
--------------------------------------------------
22/tcp open SSH SSH-2.0-OpenSSH_8.9p1 Ubuntu-3
80/tcp open HTTP Apache/2.4.52 (Ubuntu)
443/tcp open HTTPS
--------------------------------------------------
[*] Scan completed in 1.45 seconds.
[*] Total open ports found: 3
[+] Results saved to scan_results.json
7. Security Use Case
How does building a custom scanner align with industry operations?
- Network Security: Auditing network firewalls by scanning hosts from outside the firewall perimeter to verify that only explicitly allowed ports are reachable.
- Vulnerability Management: Programmatically calling the port scanning script to identify open ports, extract software banners, and compare software strings against CVE databases to flag critical patches.
- SOC & Incident Response: Conducting routine internal asset scans to detect unauthorized servers, helping secure environments against shadow IT hazards.
8. Challenges
The Problem: When I initially integrated banner grabbing, threads started hanging. If a port was open but the service did not respond to the probe payload (e.g., firewall drop or non-interactive service), the thread block lasted until the timeout expired, which delayed the entire scan pipeline.
The Solution: I configured separate timeout boundaries. I set socket timeouts strictly to 0.5s for connections, and encapsulated the socket read process in a robust try-except wrapper. By limiting the bytes buffer size and applying strict read deadlines, I ensured that slow/silent services did not interrupt the execution flow of the main script.
9. Improvements
Future additions to make the utility more robust:
- Asyncio Execution: Migrate from threading module to Python's
asynciofor better handling of larger networks. - UDP Handshakes: Add ICMP socket sniffing to handle UDP packets.
- CVE Correlation: Auto-query public vulnerability APIs with service banner strings to report active server risks.
10. Conclusion
Building this tool manually enhanced my knowledge of standard socket connections and the low-level TCP handshake sequence. It demonstrated how easy it is to spin up custom network probes, and highlighted the importance of securing open services and maintaining strong firewalls.
11. GitHub
The complete project repository containing source codes, documentation, and configuration files is hosted publicly on GitHub: