PCAP Analysis with Scapy: From Packet Inspection to Automated Port Scan Hunting

Python tutorial - IT technology blog
Python tutorial - IT technology blog

Why use Scapy instead of Wireshark?

After 6 months of building a network monitoring system for an ISP, I realized a harsh reality: Wireshark is great for investigating individual bugs. However, if you have to process 500 PCAP files a day or need instant alerts, Wireshark will exhaust you. That’s where Python + Scapy shines.

Scapy isn’t just a simple file-reading library. It allows you to manipulate every bit of data from L2 to L4. My project initially only took 200 lines of code to filter IPs. After adding behavior detection modules, that number grew to 2,000. Here is a roadmap to help you master this tool without spending weeks self-teaching.

Quick Start: Reading PCAP files in 5 minutes

First, install Scapy in a virtual environment (venv) to keep your system clean.

pip install scapy

Try it with any capture.pcap file. Here is the shortest snippet to explore its contents:

from scapy.all import rdpcap

# Read pcap file
packets = rdpcap('capture.pcap')

# View summary of the first 10 packets
for packet in packets[:10]:
    print(packet.summary())

Important note: The rdpcap function loads all data into RAM. If the PCAP file is around 1GB, an 8GB RAM laptop might freeze immediately. I will show you how to handle large files at the end of this post.

Packet Dissection: Diving into Protocol Layers

Scapy organizes packets into layers similar to the TCP/IP model. You can access data extremely quickly using the packet[Layer] syntax.

Extracting IP and TCP/UDP

Most analysis work revolves around source IP, destination IP, and Port. The code below helps you quickly filter this information:

from scapy.all import rdpcap, IP, TCP

pkts = rdpcap('capture.pcap')
for pkt in pkts:
    if pkt.haslayer(IP):
        src_ip = pkt[IP].src
        dst_ip = pkt[IP].dst
        
        if pkt.haslayer(TCP):
            src_port = pkt[TCP].sport
            dst_port = pkt[TCP].dport
            print(f"[{src_ip}:{src_port}] -> [{dst_ip}:{dst_port}]")

Hunting for DNS and HTTP Data

To retrieve DNS information, you need to check the DNSQR layer. For HTTP, leverage the scapy.layers.http module to save time on manual parsing.

from scapy.all import *
from scapy.layers.http import HTTPRequest

def process_packet(pkt):
    if pkt.haslayer(DNSQR):
        query = pkt[DNSQR].qname.decode()
        print(f"[DNS] Query: {query}")

    if pkt.haslayer(HTTPRequest):
        url = pkt[HTTPRequest].Host.decode() + pkt[HTTPRequest].Path.decode()
        print(f"[HTTP] Request: {url}")

# Use sniff to process each packet, maximizing RAM efficiency
sniff(offline='capture.pcap', prn=process_packet, store=0)

Practical Application: Writing a Port Scanning Detection Script

Port Scanning is like a thief knocking on every door to find a vulnerability. Attackers typically send a barrage of TCP SYN packets to many different ports.

Implementation logic: If a source IP sends SYN packets to more than 100 ports of a destination within 10 seconds, we will flag it.

from scapy.all import rdpcap, IP, TCP
from collections import defaultdict

def detect_port_scan(pcap_file, threshold=100):
    packets = rdpcap(pcap_file)
    scan_attempts = defaultdict(set)

    for pkt in packets:
        # Check for TCP packet with SYN flag (0x02)
        if pkt.haslayer(IP) and pkt.haslayer(TCP) and pkt[TCP].flags == 0x02:
            src = pkt[IP].src
            dst = pkt[IP].dst
            dport = pkt[TCP].dport
            scan_attempts[(src, dst)].add(dport)

    for (src, dst), ports in scan_attempts.items():
        if len(ports) > threshold:
            print(f"[!] Warning: {src} is scanning {len(ports)} ports on {dst}")

detect_port_scan('capture.pcap')

Practical Tips for Handling Large Data

Working with real-world PCAP files is much tougher than lab examples. Here are 3 tips I’ve learned:

  • Forget rdpcap: For large files, use sniff(offline='file.pcap', store=0). The store=0 parameter prevents Scapy from keeping processed packets in memory, keeping RAM usage stable.
  • Leverage BPF Filters: Don’t read the whole packet if you only need web traffic. Use filter="tcp port 80 or 443" directly in the sniff function. This speeds up processing by up to 60% because Scapy filters data at the kernel level.
  • Defend against junk data: Real network data often has decoding errors. Always wrap .decode() commands in try-except blocks so your script doesn’t crash in the middle of processing millions of log lines.

Mastering Scapy opens up unlimited automation possibilities. Instead of manually clicking through lines on a screen, you can let your script run and send reports directly to Telegram. Good luck with your implementation!

Share: