Hey there, fellow threat hunters! 👋 Today we're going to build something fun and educational - a basic port scanner in Python. Because sometimes you need to think like the attackers to better defend your systems. (Just remember: only scan systems you own or have explicit permission to test!)
Our Scanner vs. Nmap
While building your own tools is educational, let's be honest - in production environments, you'll probably want to use Nmap. Here's why:
1. Feature Comparison
Our Scanner:
- Basic TCP connect scanning
- Simple service detection
- Multi-threading
- ~100 lines of code
Nmap:
- Multiple scan types (SYN, FIN, NULL, XMAS, etc.)
- OS detection
- NSE (Nmap Scripting Engine)
- Version detection
- Timing templates
- Proven reliability
2. Why Learn Both?
Understanding how to build a basic scanner helps you:
- Better understand what Nmap is doing under the hood
- Debug network issues more effectively
- Customize scanning behavior for specific needs
- Create specialized tools when Nmap isn't available
3. Equivalent Nmap Commands
Our scanner's functionality can be replicated in Nmap like this:
# Basic scan (like our default)
nmap -p1-1024 target.com
# Fast scan with threads (like our threaded version)
nmap -p1-1024 -T4 target.com
# With service detection (better than our service detection)
nmap -p1-1024 -sV target.com
4. When to Use What
Use our scanner when:
- Learning about network programming
- Need a very specific, custom scanning behavior
- Want to integrate scanning into a larger Python application
- Can't install Nmap on the system
Use Nmap when:
- Need reliable, production-ready scanning
- Want advanced features like OS detection
- Need to run complex scanning scripts (NSE)
- Time is critical (Nmap is much faster!)
Remember: Nmap took years of development and community testing to become what it is. Our scanner is a learning tool - think of it as "Nmap 101" rather than a replacement! 😉
What We're Building
We'll create a simple but effective port scanner that can: - Scan a range of ports on a target host - Identify open ports and their potential services - Handle both TCP and UDP scanning - Provide clear, formatted output - Support multiple threads for faster scanningThe Basic Scanner
Let's start with a simple version and then build it up:import socket
import threading
from queue import Queue
import time
import argparse
from typing import List, Tuple
class PortScanner:
def __init__(self, target: str, start_port: int = 1, end_port: int = 1024, threads: int = 50):
self.target = target
self.start_port = start_port
self.end_port = end_port
self.threads = threads
self.queue = Queue()
self.results = []
def _is_port_open(self, port: int) -> Tuple[int, bool, str]:
"""Test if a port is open."""
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(1)
result = s.connect_ex((self.target, port))
if result == 0:
try:
service = socket.getservbyport(port)
except:
service = "unknown"
return port, True, service
return port, False, ""
except:
return port, False, ""
def _worker(self):
"""Worker thread to process the port queue."""
while True:
port = self.queue.get()
if port is None:
break
result = self._is_port_open(port)
if result[1]: # if port is open
self.results.append(result)
self.queue.task_done()
def scan(self) -> List[Tuple[int, bool, str]]:
"""Start the port scanning process."""
start_time = time.time()
# Fill the queue with ports
for port in range(self.start_port, self.end_port + 1):
self.queue.put(port)
# Start worker threads
thread_list = []
for _ in range(self.threads):
t = threading.Thread(target=self._worker)
t.start()
thread_list.append(t)
# Add sentinel values to stop threads
for _ in range(self.threads):
self.queue.put(None)
# Wait for all threads to complete
for t in threading.Thread:
t.join()
end_time = time.time()
self.scan_time = end_time - start_time
return sorted(self.results)
Making It Usable
Now let's add a nice command-line interface:def main():
parser = argparse.ArgumentParser(description="Simple Python Port Scanner")
parser.add_argument("target", help="Target host to scan")
parser.add_argument("-s", "--start", type=int, default=1, help="Start port (default: 1)")
parser.add_argument("-e", "--end", type=int, default=1024, help="End port (default: 1024)")
parser.add_argument("-t", "--threads", type=int, default=50, help="Number of threads (default: 50)")
args = parser.parse_args()
# Create and run scanner
scanner = PortScanner(args.target, args.start, args.end, args.threads)
print(f"\nStarting scan on host {args.target}")
try:
results = scanner.scan()
# Print results
print(f"\nScan completed in {scanner.scan_time:.2f} seconds")
print("\nOpen ports:")
print("PORT\tSTATE\tSERVICE")
print("-" * 30)
for port, is_open, service in results:
if is_open:
print(f"{port}\topen\t{service}")
print(f"\nScanned {args.end - args.start + 1} ports")
print(f"Found {len(results)} open ports")
except KeyboardInterrupt:
print("\nScan interrupted by user")
except socket.gaierror:
print("\nHostname could not be resolved")
except socket.error:
print("\nCouldn't connect to server")
if __name__ == "__main__":
main()
Using the Scanner
Save this code as `port_scanner.py` and run it like this:python port_scanner.py localhost # Scan default ports
python port_scanner.py example.com -s 20 -e 100 # Scan specific port range
python port_scanner.py 192.168.1.1 -t 100 # Use more threads
Scanner usage example |
How It Works
Let's break down the key components:
- Socket Connection: We use Python's socket library to attempt TCP connections to each port.
- Threading: Multiple threads process the port queue concurrently for faster scanning.
- Service Identification: We try to identify common services using `socket.getservbyport()`.
- Queue Management: A queue is used to safely distribute work among threads.
Making It Better
Here are some ways you could enhance this scanner: 1. Add UDP scanning:def _udp_scan(self, port: int) -> Tuple[int, bool, str]:
try:
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.settimeout(1)
s.sendto(bytes(0), (self.target, port))
try:
s.recvfrom(1024)
return port, True, "udp"
except socket.timeout:
return port, False, ""
except:
return port, False, ""
2. Add banner grabbing:
def _grab_banner(self, ip: str, port: int) -> str:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(2)
s.connect((ip, port))
banner = s.recv(1024).decode().strip()
return banner
except:
return ""
3. Add service fingerprinting with common ports dictionary:
COMMON_PORTS = {
20: "FTP-DATA", 21: "FTP", 22: "SSH", 23: "TELNET",
25: "SMTP", 53: "DNS", 80: "HTTP", 443: "HTTPS",
3306: "MYSQL", 3389: "RDP"
}
0 comments:
Post a Comment