Python Quick Guide: Building a Simple Port Scanner

Python Quick Guide: Building a Simple Port Scanner

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 scanning

The 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
Scanner usage example

How It Works

Let's break down the key components:

  1. Socket Connection: We use Python's socket library to attempt TCP connections to each port.
  2. Threading: Multiple threads process the port queue concurrently for faster scanning.
  3. Service Identification: We try to identify common services using `socket.getservbyport()`.
  4. 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"
}

Security Considerations

Remember: - Port scanning without permission can be illegal - Some networks/firewalls may block scanning attempts - Aggressive scanning can trigger security alerts - Always test in authorized environments first

Pro Tips

1. Adjust the timeout value based on network conditions 2. Use fewer threads on less powerful systems 3. Be careful with large port ranges - they take longer to scan 4. Consider adding logging for scan results 5. Think about adding IP range support for network scanning

Wrapping Up

There you have it - a basic but functional port scanner in Python! It's a great starting point for learning about network programming and security tools. Remember to use this knowledge responsibly! Stay safe, and happy hunting! 🕵️‍♂️ P.S. Want to learn more about Python security tools? Let me know in the comments!

0 comments:

Post a Comment