scapy is a Python framework for crafting and transmitting arbitrary packets. I've used scapy in labs for my articles a few times before, but today we'll be looking at scapy itself and why it's an invaluable component of every networker's virtual toolbox.

To run scapy, you'll need to have Python installed. The latest version of scapy is available for download here. The most recent version of scapy at the time of writing is 2.1.0, and that's what the examples provided in this article use. scapy is installed per the typical Python package installation mechanism on Linux:

$ sudo ./setup.py install

scapy is typically used as an interactive Python interpreter, but its libraries can also be imported for use in your own code. The examples here use scapy's shell, which is essentially a pre-configured Python environment. For a quick introduction to Python you can check out the official tutorial, but building packets with scapy requires only minimal Python knowledge.

Launch the scapy executable to get started. Note that you'll need to invoke scapy with root or administrative rights in order to send or sniff packets.

$ sudo scapy INFO: Can't import python gnuplot wrapper . Won't be able to plot. INFO: Can't import PyX. Won't be able to use psdump() or pdfdump(). Welcome to Scapy (2.1.0) >>>

You may see some warnings like the two above regarding absent optional dependencies or missing routes; don't worry about them.

The two most important commands to remember for scapy are ls() and lsc() . These commands are actually Python functions, which is why they must be called with the ending parentheses. ls() displays all of the available protocols and lsc() lists all of the scapy command functions.

>>> ls() ARP : ARP ASN1_Packet : None BOOTP : BOOTP CookedLinux : cooked linux DHCP : DHCP options DHCP6 : DHCPv6 Generic Message) ... >>> lsc() arpcachepoison : Poison target's cache with (your MAC,victim's IP) couple arping : Send ARP who-has requests to determine which hosts are up bind_layers : Bind 2 layers on some specific fields' values corrupt_bits : Flip a given percentage or number of bits from a string ...

scapy's primary purpose is to build and transmit arbitrary packets, so let's get started!

Packets are constructed as layers of protocols, loosely analogous to the OSI model, which can be manipulated independently or glued together. For example, the IP() object represents an IPv4 header. Use the show() method of an object to display all of its fields.

>>> IP().show() ###[ IP ]### version= 4 ihl= None tos= 0x0 len= None id= 1 flags= frag= 0 ttl= 64 proto= ip chksum= None src= 127.0.0.1 dst= 127.0.0.1 \options\

This should look familiar. We can see that all of the fields have default values set. We can modify these fields by passing them as arguments when the IP() object is created, or after saving it as a variable.

>>> ip=IP(src="192.168.0.1") >>> ip.dst="192.168.0.2" >>> ip <IP src=192.168.0.1 dst=192.168.0.2 |>

Of course, an IP packet by itself isn't very useful. We can add a layer four protocol like TCP or UDP by using the division operator to attach it to our IP packet.

>>> ip/TCP() <IP frag=0 proto=tcp src=192.168.0.1 dst=192.168.0.2 |<TCP |>>

Notice that when we did this, the protocol ("proto") field in the IPv4 header was automatically set to TCP. scapy conveniently takes care of such housekeeping so that we can focus on the fun stuff (these automatic configurations can always be overridden manually if need be).

We can manipulate the TCP header fields just as we did with our IP header.

>>> tcp=TCP(sport=1025, dport=80) >>> (tcp/ip).show() ###[ TCP ]### sport= 1025 dport= www seq= 0 ack= 0 dataofs= None reserved= 0 flags= S window= 8192 chksum= None urgptr= 0 options= {} ###[ IP ]### version= 4 ihl= None tos= 0x0 len= None id= 1 flags= frag= 0 ttl= 64 proto= ip chksum= None src= 192.168.0.1 dst= 192.168.0.2 \options\

Remember to enclose combined headers in a pair of parentheses when using methods like show() on the entire packet.

scapy also supports Ethernet and IEEE 802.11 at layer two:

>>> Ether()/Dot1Q()/IP() <Ether type=0x8100 |<Dot1Q type=0x800 |<IP |>>> >>> Dot11()/IP() <Dot11 |<IP |>>

Try combining different protocols to form a variety of packets. To send packets onto the wire, use the send() function if transmitting at layer three (i.e. without a layer two header) or the sendp() function if transmitting at layer two.

>>> send(ip/tcp) . Sent 1 packets. >>> sendp(Ether()/ip/tcp) . Sent 1 packets.

Values for blank fields, such as the source and destination addresses in the Ethernet header, are populated automatically by scapy where possible.

scapy also has the ability to listen for responses to packets it sends, such as ICMP echo requests (pings). We can build an IP packet carrying an ICMP header, which has a default type of echo request, and use the sr() (send/receive) function to transmit the packet and record any response.

>>> sr(IP(dst="packetlife.net")/ICMP()) Begin emission: Finished to send 1 packets. * Received 1 packets, got 1 answers, remaining 0 packets (<Results: TCP:0 UDP:0 ICMP:1 Other:0>, <Unanswered: TCP:0 UDP:0 ICMP:0 Other:0>)

What if we want to send and listen for responses to multiple copies of the same packet? We can use the srloop() function and specify a count of packets to send.

>>> srloop(IP(dst="packetlife.net")/ICMP(), count=3) RECV 1: IP / ICMP 174.143.213.184 > 192.168.1.140 echo-reply 0 / Padding RECV 1: IP / ICMP 174.143.213.184 > 192.168.1.140 echo-reply 0 / Padding RECV 1: IP / ICMP 174.143.213.184 > 192.168.1.140 echo-reply 0 / Padding Sent 3 packets, received 3 packets. 100.0% hits. (<Results: TCP:0 UDP:0 ICMP:3 Other:0>, <PacketList: TCP:0 UDP:0 ICMP:0 Other:0>)

There's plenty more to scapy that we haven't touched on yet, but hopefully this article will encourage you to play with it a bit on your own and see what kind of crazy packets you can come up with!