Skip to content

🧰 03-02: Sniffing Tools — tcpdump, Wireshark & Scapy


📌 Overview

Now that we know why sniffing works (03-01: Packet Sniffing Basics), let's take a practical tour of the tools people actually use to do it. Three tools dominate this space, each suited to a different job:

Tool Best for
tcpdump Fast, scriptable, command-line capture — great for servers with no GUI
Wireshark Deep, visual, point-and-click packet inspection
Scapy Programmatic packet crafting and sniffing — a building block for custom tools

💡 All three ultimately sit on top of the same underlying capture library on Linux/macOS: libpcap (on Windows, Npcap, the modern successor to WinPcap). That's why capture filter syntax is shared between tcpdump and Wireshark.


🖥️ tcpdump: The Command-Line Classic

tcpdump is a lightweight, text-based sniffer available on virtually every Unix-like system by default. It's the tool you reach for when you're SSH'd into a remote server with no graphical interface.

Basic Usage

# Capture on a specific interface, printing to the terminal
sudo tcpdump -i eth0

# Show more detail (verbose) and don't resolve hostnames/ports (faster, cleaner output)
sudo tcpdump -i eth0 -v -n

# Capture only 20 packets, then stop
sudo tcpdump -i eth0 -c 20

# Write the capture to a file for later analysis in Wireshark
sudo tcpdump -i eth0 -w capture.pcap

# Read a previously saved capture file
tcpdump -r capture.pcap

Filtering with BPF Syntax

tcpdump uses BPF (Berkeley Packet Filter) syntax to narrow down what gets captured:

# Only ICMP (ping) traffic
sudo tcpdump -i eth0 icmp

# Only traffic to/from a specific host
sudo tcpdump -i eth0 host 192.168.1.10

# Only TCP traffic on port 23 (telnet) from a given source
sudo tcpdump -i eth0 'tcp and src host 192.168.1.10 and dst port 23'

# Only traffic to/from an entire subnet
sudo tcpdump -i eth0 net 128.230.0.0/16

💡 Tip: Combine conditions with and/or/not — this same BPF syntax works identically inside Wireshark's capture filter box and inside Scapy's sniff(filter=...) argument.


🦈 Wireshark: Capture Filters vs. Display Filters

Wireshark is the most popular graphical sniffer, and it's worth understanding that it actually has two separate filtering systems that beginners often confuse:

Capture Filter Display Filter
When it applies Before a packet is captured/saved After capture, only affects what's shown
Syntax BPF syntax (same as tcpdump) Wireshark's own rich syntax
Can you change it later? No — packets not matching are gone forever Yes — change it anytime, nothing is lost
Example tcp port 80 http.request.method == "POST"

Why the Distinction Matters

👉 A capture filter is a permanent decision made before packets ever hit disk/memory — useful when you know exactly what you want and need to keep file sizes small on a busy link.

👉 A display filter is applied to packets already captured — the safer default, since you can capture broadly (or everything) and then slice-and-dice the view as many times as you like without re-capturing.

Common Display Filter Examples

ip.addr == 192.168.1.10        # traffic to or from this IP
tcp.port == 443                # any TCP traffic on port 443
http                           # only HTTP traffic
dns.qry.name contains "bank"   # DNS queries mentioning "bank"
tcp.flags.syn == 1 and tcp.flags.ack == 0   # SYN packets only (handshake starts)

💡 A very common workflow: capture everything with a broad or empty capture filter, then use display filters to investigate — this avoids the risk of missing something you didn't think to filter for up front.


🐍 Scapy: Sniffing and Crafting Packets in Python

Scapy is a Python library that goes beyond fixed-function tools like tcpdump — it lets you sniff, dissect, and construct arbitrary packets programmatically. This makes it the tool of choice for building custom sniffing/spoofing programs rather than just observing traffic. We'll use it heavily in the ARP spoofing and TCP attack lessons ahead.

Installing and Entering Interactive Mode

pip install scapy
sudo python3        # root/admin privileges are required for sniffing & sending raw packets

A Minimal Sniffer

from scapy.all import sniff

def print_pkt(pkt):
    pkt.show()   # pretty-print all the layers of the packet

# Sniff on a given interface, filtering only ICMP traffic
sniff(iface="eth0", filter="icmp", prn=print_pkt)

👉 What's happening here:

  • iface — which network interface to listen on (use a list, e.g. ["eth0", "wlan0"], to sniff multiple interfaces at once)
  • filter — a BPF filter string, exactly like tcpdump's
  • prn — a callback function that Scapy invokes once for every packet it captures
  • store=0 can be added to avoid keeping packets in memory (useful for long-running sniffers)

Crafting a Custom Packet

This is where Scapy really shines — building packets layer by layer using the / operator to stack protocol headers:

from scapy.all import IP, TCP, send

# Build an IP layer
ip = IP(dst="10.0.0.5")

# Build a TCP layer — a SYN packet aimed at port 80
tcp = TCP(dport=80, flags="S")

# Stack the layers: IP header, then TCP header
pkt = ip / tcp

pkt.show()    # inspect before sending
send(pkt)     # transmit it onto the network

👉 Every field you don't explicitly set gets a sensible default (e.g., IP() defaults src to the machine's own address, ttl to 64). You can override any field, including ones that are normally auto-generated — which is exactly how spoofing tools set an arbitrary source IP address:

ip = IP(src="1.2.3.4", dst="10.0.0.5")   # forged source address

💡 This single line is the technical seed of IP spoofing — nothing stops a program from lying about the source address field, because IP has no built-in authentication of who is allowed to claim a given source address. We'll build on this directly in 03-04: ARP Spoofing Attacks and all of Module 04.

Sniff-Then-Respond Pattern

Scapy also supports combining sniffing and spoofing in one program — sniff for a trigger packet, then immediately craft and send a forged response. This pattern reappears throughout later attack lessons:

from scapy.all import sniff, IP, ICMP, send

def respond(pkt):
    if ICMP in pkt and pkt[ICMP].type == 8:   # type 8 = echo request
        print(f"Got a ping from {pkt[IP].src}, spoofing a reply...")
        # (attack logic would go here)

sniff(filter="icmp", prn=respond)

🧭 Choosing the Right Tool

Scenario Tool
Quick check on a headless server tcpdump
Deep-dive investigation with lots of protocol context Wireshark
Automating an attack, building a custom tool, or repeatable testing Scapy
Need to both observe and forge traffic Scapy

📌 Key Takeaways

  • tcpdump is a fast, ubiquitous command-line sniffer using BPF filter syntax.
  • Wireshark has two distinct filtering systems: capture filters (BPF syntax, applied before capture, irreversible) and display filters (Wireshark syntax, applied after capture, freely changeable).
  • A safe default workflow is to capture broadly and refine with display filters afterward.
  • Scapy is a Python library that can both sniff traffic and construct arbitrary packets field-by-field.
  • Scapy's sniff(iface=..., filter=..., prn=...) mirrors tcpdump's filtering but lets you run custom Python logic per packet.
  • Building packets with IP()/TCP() and the / operator lets you override any field — including the source IP address, which is the root mechanism behind IP spoofing.
  • Sniffing and spoofing can be combined in a single Scapy program: sniff for a trigger, then craft and send a forged response — a pattern used throughout later ARP and TCP attack lessons.