Scapy – Network Packet Toolkit
We hebben het vaak over “netwerken”, “TCP”, “IP” en bijbehorende pakketten. Informatie welke over het netwerk van punt A naar punt B gestuurd wordt over het TCP / IP protocol wordt namelijk verzend met pakketten. Ik heb diverse posts gemaakt waarin we pakketten kunnen afluisteren en bekijken maar vandaag gaan we zelf aan de slag met deze pakketten. We gaan zelf pakketten maken met “Scapy”. Maar eerst even een korte opfrissing…
In deze “recap” gaan we kort kijken hoe pakketten over het netwerk verstuurd worden. Herinneren jullie het OSI model nog?

Het OSI model beschrijft de 7 lagen die nodig zijn om data bij de gebruiker (layer 8) te krijgen. Alles begin op de fysieke laag (bekabeling / hardware) en eindigt middels een applicatie bij de eindgebruiker. Voor deze post gaan we het voornamelijk hebben over de 2e (Data Link), 3e (Network) laag en 4e (Transport) laag. De 2e laag gaat voornamelijk over het routeren van verkeer tussen direct verbonden apparaten. In deze laag leven b.v. switches en komen we zaken als MAC adressen en LLC (foutcorrectie). De 3e laag is een hele belangrijke laag voor het versturen van data tussen verschillende netwerken. Deze laag forward zogenaamde pakketjes en routeert deze tussen verschillende knooppunten. In deze laag komen routers voor en het IP protocol welke gebruikt wordt voor de routering en adressering van de pakketten. Laag 4 is de transport layer. Deze laag is verantwoordelijk voor de overdracht van de data. In deze laag komen we het TCP protocol tegen welke verantwoordelijk is voor de overeenstemming tussen 2 endpoints over de data die verzonden / ontvangen moet worden. Hoeveel data wordt er verzonden? Met welke snelheid wordt deze data verzonden? In welke pakketten wordt de data verdeeld? Over welk kanaal wordt de data verzonden. Etc.
Gezamenlijk hebben we het vaak over het TCP/IP protocol. Deze protocolstack is dus een verzamelnaam voor de 2 protocollen op laag 3 en 4 welke we meestal gebruiken voor dataoverdracht tussen 2 computers op een netwerk.
Het OSI model wordt van boven naar beneden doorlopen op de verzendende computer. Deze zal de data dus eerst verdelen in TCP segmenten (deze data heten geen “packets” maar “segments”). Deze segmenten zullen ingepakt worden in IP packets welke vervolgens naar over het ethernet protocol (laag 1) naar buiten verzonden worden. De ontvangende computer zal de data ontvangen en doorloopt het OSI model van onder naar boven. De data komt binnen en wordt naar de juiste node gerouteerd. Op de eindbestemming zullen de TCP segmenten uitgepakt, gecontroleerd en aan elkaar gemonteerd worden zodat uiteindelijk de ontvangende applicatie de data kan interpreteren en gebruiken.
Een TCP segment ziet er als volgt uit:

Het segment kent een header van 20 bytes als er geen opties zijn. Er is 40 bytes beschikbaar voor opties dus de header is maximaal 60 bytes groot. Vervolgens bestaat het segment uit de werkelijke data. De header bestaat uit de source en destination poorten. Een 32-bit sequence number wat feitelijk gewoon een volgnummer is. Ook is er een 32 bits acknowledgement number, het nummer welke de receiver vervolgens denkt te ontvangen. Header length geeft aan hoe groot de header van het TCP segment is. Vervolgens zijn er diverse flags welke geactiveerd (1) kunnen zijn of niet (0).
- URG = Urgent
- ACK = Acknowledgement number is valide
- PSH = Push request
- RST = Reset de verbinding
- SYN = Synchroniseer de sequence numbers
- FIN = Klaar, verbreek de verbinding
Dan volgt er een “Window Size” ofwel het maximale tijdsduur dat er over de transmissie mag doen. De checksum wordt gebruikt voor error checking. De Urgent Pointer is alleen van toepassing als de URG flag gebruikt is. Dit veld geeft meer informatie over de data welke als urgent verstuurd wordt. Waar begint en vooral waar stopt deze urgente data. Tenslotte het er ruimte voor opties, als er opties zijn. Een optie bestaat altijd uit 3 onderdelen, namelijk “option-kind” (1 byte), “option-length” (1 byte) en “option-value” (variabel aantal bytes). De padding is extra ruimte (bestaande uit nullen) om de header af te sluiten op een 32-bits grens waarna de data begint.
Dit TCP segment wordt vervolgens in een IP pakket verpakt en gerouteerd. Een IP pakket ziet er als volgt uit:

Het TCP segment wordt verpakt in de “payload” van het IP pakket. De header van het IP pakket bestaat eveneens uit veel opties. De belangrijkste zijn uiteraard het IP adres van de verzender en de ontvanger. We hebben:
- Version = IP versie (in het geval van IPv4 is dit “4″).
- Internet Header Length = Dit veld geeft de maximale lengte van de header aan. Omdat een IP packet net als een TCP segment een variabel aantal opties kan hebben kan het formaat ook varieren tussen 20 en 60 bytes.
- DSCP / ToS = Differentiated Services Code Point / Type of Service. Dit veld specificeert een specifieke service zoals b.v. VOIP.
- ECN = Explicit Congestion Notification. Dit veld is aanwezig sinds RFC 3168 en maakt end-to-end melding van netwerkcongestie mogelijk zonder pakketten te laten vallen. Deze optie wordt alleen gebruikt als beide netwerken het kunnen (en willen) gebruiken.
- Packet/Total Length = Specificeert het totale formaat van het IP packet (data en header). Dit is maximal 65,535 bytes.
- Identification = Dit veld wordt voornamelijk gebruikt voor unieke identificatie van de groep fragmenten van een enkel IP-datagram
- (D/M)F = Dit is het “flags” veld. Er zijn 2 soorten flags mogelijk, namelijk “DF” = Don’t Fragment en “MF” = More Fragements. Als de DF-vlag is ingesteld en fragmentatie is vereist om het pakket te routeren dan wordt het pakket verwijderd. Dit kan b.v. worden gebruikt bij het verzenden van pakketten naar hosts die geen middelen hebben om met fragmentatie om te gaan. Het kan ook worden gebruikt voor pad MTU-detection, hetzij automatisch door de host-IP-software, of handmatig met behulp van diagnostische hulpmiddelen zoals ping of traceroute. Voor niet-gefragmenteerde pakketten wordt de MF-vlag verwijderd. Voor gefragmenteerde pakketten hebben alle fragmenten behalve de laatste de MF-vlag ingesteld. Het laatste fragment heeft een niet-nul Fragment Offset-veld, dat het onderscheidt van een niet-gefragmenteerd pakket.
- Fragment Offset = Het Fragment Offset veld wordt gemeten blokken van acht bytes. Het is 13 bits lang en specificeert de offset van een bepaald fragment ten opzichte van het begin van het oorspronkelijke niet-gefragmenteerde IP-datagram.
- Time to Live = De TTL geeft de lifetime in seconden / hops aan. Wanneer de TTL op 0 komt dan zal het pakket gedropt worden. De TTL voorkomt dat pakketten oneindig lang blijven rondcirkelen op het internet.
- Protocol = Geeft aan welk protocol er wordt gebruikt voor de data in de payload. In het geval van TCP is dat nummer 6. Alle protocolnummers zijn hier te vinden.
- Header Checksum = De waarde van de checksum wordt gebruikt bij error checking. Wanneer een pakket bij een router aankomt, berekent de router de controlesom van de header en vergelijkt deze met het checksum veld. Als de waarden niet overeenkomen, verwijdert de router het pakket. Fouten in het gegevensveld moeten worden afgehandeld door het ingekapselde protocol.
- Source IP Address = Het IP adres van de verzendende host.
- Destination IP Address = Het IP adres van de ontvangende host.
- Options = Opties worden meestal niet gebruikt maar het is wel mogelijk om ze te gebruiken.
- Padding = Eventuele padding om ervoor te zorgen dat de header een geheel getal van 32-bits words bevat
- Payload = De werkelijke data (b.v. TCP segmenten)
Bovenstaande weergave is de weergave van een IPv4 pakket. Een IPv6 pakket ziet er als volgt uit:

- Version = IP versie (in het geval van IPv6 is dit “6″).
- Traffic Class = De bits van dit veld bevatten twee waarden. De zes meest significante bits bevatten het DS (Differentiated Services) veld welke wordt gebruikt om pakketten te classificeren. Momenteel eindigen alle standaard DS-velden met een ‘0’-bit. Elk DS-veld dat eindigt met twee ‘1’-bits is bedoeld voor lokaal of experimenteel gebruik. De resterende twee bits worden gebruikt voor ECN (Explicit Congestion Notification).
- Flow Label = Het Flow Label veld is oorspronkelijk gemaakt voor het verlenen van speciale service aan realtimetoepassingen. Wanneer deze is ingesteld op een waarde die niet gelijk is aan nul dient deze als een aanwijzing voor routers en switches met meerdere uitgaande paden dat deze pakketten op hetzelfde pad moeten blijven zodat ze niet opnieuw worden gerangschikt.
- Payload Length = Het totale formaat van de payload in octetten, inclusief extensie headers. De lengte wordt op nul gezet wanneer een Hop-by-Hop uitbreidingskop een “Jumbo Payload” optie draagt.
- Next Header = Specificeert het type van de volgende header. Dit veld geeft meestal het transportlaagprotocol aan dat wordt gebruikt door de payload van een pakket. Wanneer er uitbreidingsheaders in het pakket aanwezig zijn geeft dit veld aan welke extensieheader volgt. De waarden worden gedeeld met de waarden die worden gebruikt voor het IPv4-protocolveld omdat beide velden dezelfde functie hebben (zie Lijst met IP-protocolnummers).
- Hop Limit = Hop Limit vervangt het time-to-live veld van IPv4. De Hop Limit waarde wordt met één verlaagd op elk doorstuurknooppunt. Het pakket wordt verwijderd als de waarde 0 wordt. Het bestemmingsknooppunt moet het pakket echter normaal verwerken zelfs als de hoplimiet 0 wordt.
- Source IP Address = Het IP adres van de verzendende host.
- Destination IP Address = Het IP adres van de ontvangende host.
Zoals je ziet is een IPv6 header vele malen simpeler opgebouwd dan een IPv4 header. De standaard IPv6 header heeft een vaste lengte van 40 bytes om de verwerking van IPv6-pakketten te vereenvoudigen en de doorstuurefficiëntie te verbeteren.
In deze post zullen we ons voornamelijk bezig houden met IPv4 packets in combinatie met Scapy. Scapy kan overigens prima IPv6 pakketten maken, of zoals Scapy zelf zegt “Craft the packets before they craft you”.
Scapy
Zoals al tijdens de intro verklapt, Scapy is een packet manipulation tool. Scapy is een Python based applicatie welke het mogelijk maakt om pakketten te maken, verzenden, omleiden en te sniffen. Scapy kan vele taken uitvoeren zoals scannen, tracerouting, probing, unit tests, aanvallen en netwerkdetectie. Gezien het groot aantal mogelijkheden kan Scapy tools als hping, arpspoof, arp-sk, arping, p0f en zelfs sommige delen van Nmap, tcpdump en tshark vervangen).
Scapy kan echter ook veel taken uitvoeren die vele tools niet kunnen zoals het verzenden van ongeldige frames, het injecteren van je eigen 802.11-frames, het combineren van technieken zoals VLAN-hopping + ARP cache poisoning, VOIP-decodering op een WEP gecodeerd kanaal enz.
Scapy doet hoofdzakelijk twee dingen: pakketten verzenden en antwoorden ontvangen. Je definieert een set pakketten, verzendt ze, ontvangt antwoorden, vergelijkt verzoeken met antwoorden en retourneert een lijst met pakketkoppelingen (verzoek, antwoord) en een lijst met niet-overeenkomende pakketten. Dit heeft een groot voordeel ten opzichte van tools zoals Nmap of hping. Het antwoord wordt niet beperkt tot “open / gesloten / gefilterd” maar het hele pakket is.
Daarbovenop kunnen meer functies op hoog niveau worden gebouwd Denk b.v. aan een traceroute welke als resultaat alleen de start-TTL van de aanvraag en het bron-IP van het antwoord geeft. Een ping welke een heel netwerk pingt en de lijst met antwoordende machines retourneerd. Een portscan welke een LaTeX-rapport retourneert. Etc. De mogelijkheden met Scapy lijken eindeloos omdat je met Scapy niet gebonden bent aan standaarden van de applicatie of het protocol. Scapy stelt je in staat om volledig zelf te experimenteren.
Om Scapy te installeren gebruik je de Python PIP applicatie en voer je onderstaande commando uit:
pip install --pre scapy[complete] |
Je kunt Scapy ook als ZIP downloaden van de installatie pagina https://scapy.readthedocs.io/en/latest/installation.html. De bekende hacker distributies zoals Kali en Parrot hebben Scapy al native geïnstalleerd.
Je start Scapy heel simpel met het “scapy” commando. Dit start Scapy in de zogenaamde “interactive mode” ofwel, de “type & run” mode. Je kunt Scapy ook in scripts gebruiken. Dit noemen we de “script mode”.

Als Scapy gestart is zie je dat aan de “>>>” prompt. Om de verschillende commando’s op te vragen gebruik je:
lsc() |

Omdat Scapy Python gebruikt als command board is het zinvol om enige Python https://jarnobaselier.nl/python-leren-programmeren-deel-1/ kennis te hebben. Je kunt namelijk direct Python gebruiken binnen Scappy. Gebruik Python bijvoorbeeld voor het toewijzen van variabelen, gebruiken van loops, definiëren van functies etc.
Mits anders geconfigureerd gebruikt Scapy verschillende default waardes. Belangrijk om in de gaten te houden zijn:
- IP Source adres wordt gekozen op basis van het destination adres en de routing tabel
- Standaard wordt een checksum gegenereerd
- Source MAC adres wordt gekozen op basis van de output interface
- Ethernet type en IP protocol worden gekozen op basis van bovenstaande layer
- UDP Source & Destination poort is 53
- ICMP type is “echo request”
IP packets bestaan standaard uit de volgende waardes:
- Version = 4
- IHL = [None]
- TOS = 0
- LEN = [None]
- ID = 1
- Flags = 0
- Frag = 0
- TTL = 64
- Proto = 0
- Checksum = [None]
- Source = [None]
- Destination = 127.0.0.1
- Options = [None]
TCP Segments bestaan standaard uit de volgende waardes:
- Source Port = 20
- Destination Port = 80
- Sequence = 0
- Ack = 0
- Data Offset = [None]
- Reserved = 0
- Flags = 2
- Windows = 8192
- Checksum = [None]
- URG = 0
- Options = [None]
In totaal kent Scapy ontzettend veel commando’s. Belangrijke commando’s zijn:
- ls() = Laat beschikbare layers zien
- send() = Zend het packet over layer 3
- sendp() = Zend het packet over layer 2
- sniff() = Sniff pakketjes
- sr*() = Zend en ontvang packets op layer 3
- srp() = Zend en ontvang packets op layer 2
- wireshark = Gebruik Wireshark tegen een lijst van packets
- wrpcap = Schrijf een lijst van packets in een PCAP bestand
Zoals je ziet in bovenstaande screenshots ziet Scapy er nog een beetje basic uit zo met zwarte achtergrond en witte letters. Om Scapy wat duidelijker te maken kent Scapy diverse kleurenthema’s zoals:
- DefaultTheme
- BrightTheme
- RastaTheme
- ColorOnBlackTheme
- BlackAndWhite
- HTMLTheme
- LatexTheme
Het gewenste kleurenschema selecteer je met het commando:
conf.color_theme = BrightTheme()

Belangrijk om te weten is dat Scapy werkt met protocol layers. Op die manier kan b.v. het IP protocol (layer 1) een TCP segment bevatten (layer 2). Elk protocol kent weer sublayers om het protocol te specificeren. Zo zal het DNS protocol verschillende layers kennen zoals een DNS Question Record (DNSQR), DNS Resource Record (DNSRR) en nog veel meer. Scapy kent een grafische weergave om door de protocol layers te lopen. Dit doe je met het volgende commando:
explore()

Je kunt het “explore()” commando ook gebruiken om direct de sublayers van een protocol te bekijken:
explore(dns)
Je kunt commando’s ook afmaken met de TAB toets welke een grafisch menu toont zodat je beter tussen de verschillende commando’s kunt navigeren

Met het “ls” (list) commando kun je inzien uit welke onderdelen een bepaald pakket en wat de default values zijn als je deze zelf niet definieert. Bijvoorbeeld:
ls(IP)

Stel voor dat je wilt zien op welke interface Scapy werkt, dan gebruik je:
conf.iface
En als je dat wilt veranderen naar een andere interface dan kan dat natuurlijk ook:
conf.iface="en2"
Wil je meerdere interfaces definiëren dan kun je de volgende waardes gebruiken:
sniff(count=10, iface="en1")
send(pkt, iface="en2")
Zoals al eerder uitgelegd werkt Scapy met layers. Dit principe werkt als volgt:

Layer 0 is de buitenste “Ether” layer (OSI model layer 2 pakket) en layer 3 is het “ICMP” frame (OSI model layer 3 pakket, net als het IP pakket het ICMP frame “draagt”). Deze opmaak moet je goed onthouden tijdens het maken en werken met pakketten. Mocht je van een pakket de lagen op willen vragen kun je dit altijd doen door van een pakket de summary op te vragen. Bijvoorbeeld:
pkt[0].summary() |
Wil je ook weten welke eigenschappen het pakket heeft dan kun je het volgende commando gebruiken:
pkt[0].show() |
Nu gaan we eens echt aan de slag met pakketjes!
Werken met Scapy Pakketten
Voor we zelf pakketten gaan maken en versturen gaan we eerst een paar pakketten sniffen.
Let erop dat wanneer je pakketten wilt “sniffen” Scapy als “sudo” gestart moet zijn. Al het ontvangen verkeer wordt opgeslagen in een variabele en het sniffen wordt gestopt met de CTRL+C sneltoets. Om het sniffen te starten kun je onderstaande commando gebruiken:
capture=sniff(iface="wlan0") |
We definieren hier de variabele (lijst) genaamd “capture”. Deze wordt gevolgd door het “sniff” ommando welke ervoor zorgt dat Scapy start met sniffen. Belangrijk is om de juiste interface te selecteren voor het sniffen en dat is in ons geval “wlan0″.
Wanneer we het sniffen stoppen kunnen we zien hoeveel pakketten de sniffer ontvangen heeft. Dit doe je door de variabele op te roepen:
capture |
Je kunt het sniffen oof verder finetunen zoals je dat ook gewend bent van TCPDump en Wireshark. Stel je voor dat we alleen TCP verkeer willen sniffen, dan gebruik je het volgende filter:
capture=sniff(iface="wlan0", filter="tcp") |
Als we slechts TCP pakketten willen filteren doen we dat als volgt. Uiteraard breekt het sniffing proces dan vanzelf af en is het niet noodzakelijk om CTRL+C te gebruiken:
capture=sniff(iface="wlan0", filter="tcp", count=1) |
Zoals je ziet splitsen we elk filter met een comma en is het niet nodig om integers tussen quotes te plaatsen. Willen we dus slechts het verkeer van 1 host zien dan is het volgende commando een uitkomst:
capture=sniff(iface="wlan0", filter="host 192.168.200.100" and "tcp", count=1, 13.107.6.254) |
Zoals je ziet gebruiken we hierboven het AND commando om ervoor te zorgen dat we alleen TCP pakketten ontvangen vanaf 192.168.200.100. We kunnen hier ook het OR commando gebruiken. In dat geval sniffen we elke verkeer van host 192.168.200.100 OF elk ander TCP pakket. Laten we deze sting nog uitbreiden. Nu sniffen we al het verkeer behalve ICMP requests & replies.
capture=sniff(iface="wlan0", filter="host 192.168.200.100" and "icmp[icmptype] != icmp-echo" and "icmp[icmptype] != icmp-echoreply", count=1, timeout=5) |
Zoals je hierboven ziet gebruiken we de “!=“ (is niet) operator om bepaalde icmp types te negeren tijdens het sniffen. Ook gebruiken we een timeout van 5 wat betekend dat de scan na 5 seconde automatisch afbreekt.
Scapy gebruikt de Berkeley Packet Filter (BPF) syntax welke identiek is aan de syntax van “tcpdump”. Meer mogelijkheden om je eigen sniffer filter te bouwen vindt je hier: http://biot.com/capstats/bpf.html.
Nu we kunnen sniffen en pakketten hebben is het goed om te weten dat deze in een array worden bewaard. Een array begint altijd met 0 en dus is het eerste pakket. Laten we eens bekijken hoe dit eruit ziet.
Om te kijken wat voor pakketten je zoal in de capture hebt zitten roep je de array op:
capture |

Om een wat beter overzicht van je capture te krijgen gebruik je de “summary()” optie:
capture.summary() |

Voor een nog gedetailleerder overzicht gebruik je de “show()” optie:
capture.show() |

Deze commando’s kunnen we ook op een individueel packet toepassen. Om de raw content van een pakket te bekijken definieer je het pakketnummer. Laten we het eerste pakketje in de array (0) eens bekijken:
capture[0] |

Globale informatie van het pakketje krijgen we weer met de “summary()” optie”:
capture[0].summary() |

Je ziet hier dat het packet 3 lagen bevat, namelijk een Ether frame, een IP packet en een TCP frame.
Om de volledige inhoud van het pakket en alle 3 de lagen te bekijken gebruiken we weer het “show” commando:
capture[0].show |

Zoals je ziet laat Scapy duidelijk zien dat het TCP segment ingepakt in een IP packet en het IP packet wordt weer gedragen door een Ether frame.
Om alleen een specifieke layer nader te bekijken kunnen we deze specificeren achter het pakketnummer, bijvoorbeeld:
capture[0][TCP].show |
Bovenstaande commando laat alleen de details zien van de TCP layer.

We kunnen zelf nog gedetailleerder de informatie retourneren. Dit doen we door de eigenschap de definiëren. Stel je voor dat we alleen de source port (sport eigenschap) willen weten van het TCP frame:
capture[0][TCP].sport |

Dat was het voor deze post. In de volgende post gaan we nog meer Scapy magie bekijken. Hopelijk vond je deze post interessant genoeg om een leuke reactie of like te geven of deel hem op je eigen website of social media. Super bedankt. Tot Scapy deel 2!