According to the introduction page on the scapy documentation website:
Scapy is a Python program that enables the user to send, sniff and dissect and forge network packets. This capability allows construction of tools that can probe, scan or attack networks.
What that doesn’t tell you is that scapy can be used interactively or imported into your python programs. It can do a lot more than they claim above too like craft custom packets for all major network protocols including weird ones like TFTP, read and write packet capture (pcap) files, establish socket connections, deal with encryption, send ethernet or wireless frames (even invalid ones), and much more.
I’m going to break up my intro to scapy into three parts: crafting packets, common commands and programming with scapy. I’ve barely begun to delve into what scapy can really do and we won’t get past the basics but should give provide some insight into what scapy can do. In this post we’ll cover how to create custom packets using scapy.
We can create packets for pretty much any common protocol, like IP, TCP, UDP, NTP, DHCP, SNMP, DNS, ICMP and many more. We can create X509 certificates, talk to Radius servers, do IPSEC (AH and ESP), do WEP decrypting and much more – as you’ll see if you enter the command:>>> ls()
When you create packets, you can inspect the default values or the ones you set by using the display()
command as shown below:
Let’s do a simple ping to get started, since it’s an easy place to start. Start scapy in interactive mode, and we’re going to need root priviledges to send our packets, so we start scapy with:$ sudo scapy
>>>
First we’ll create an IP packet and set some fields in the IP header – in this case the destination must be set since the default is probably localhost. The screencap is a little small, but hopefully you can see we created an IP packet and set the destination as described above.
We used the send and receive command sr1()
which lets us receive one packet, as opposed to the sr()
command which is send and receive multiple. Next we’re creating an ICMP packet that just has the default values set, and then we’re adding a custom payload, “layer 7 data”. We got back an ICMP packet of type echo-reply
as you can hopefully see.
OK, let’s move on to do a UDP example. In the screencap below, you’ll notice I simply needed to set the destination port, since it is different from the default. I’m not exactly sure why, but the 2 commands setting up sending L3 packets through a raw socket connection need to be written this way. I’m pretty sure you’ll need to use something different if you’re on Windows.
It’s very difficult to see since both windows are black, but in the top right corner another window is overlapping the main one. You might be able to see that I’m running netcat to listen on that destination port (UDP 5553) that I set earlier with u.dport=5553
. Netcat receives the extra data that’s encapsulated into this packet that we so easily constructed with scapy.
Next up – let’s create a HTTP request. Not much new here, we set the destination for the IP packet to a hostname, then on layer 4 the TCP port is 80 by default, so nothing to do there. On the application layer we send a basic HTTP request. We can dump our custom packetwith hexdump()
but we also see a new command, command() which tells scapy to construct a command that will create this packet.
If it seems funny to just create the packet and not show the whole HTTP Request and Response sequence using scapy it’s because we would need a more involved sequence of steps – specifically completing the TCP handshake, getting a rediect to 443 from the server then fetching the contents of the web page. For a series of interactions like this it’s just easier to use a python script than to type in commands at the interactive prompt as we’ll see in a later post.
We can’t run through examples of every packet type and payload for all protocols obviously, but let’s look at one of my favorites – DNS. To do a simple DNS lookup requires sending a request for a specific domain name, and receiving a response with an IP address. We can use sr1()
command which we’ll see more of in the next post, but basically we’re going to receive only one packet. Similarly the summary()
command lets us quickly look at the return packet’s contents.
So first of all we create a IP packet with the destination set to Google’s 8.8.8.8 nameserver. We need to do this since the default value for IP()
is usually localhost. Alternately we go with the default values for our UDP()
datagram, and as you can probably guess the default destination port number is fine, so no need to set it with UDP(dport=53)
.
Now for the DNS part, we’re using two parameters: rd and qd. rd
specifies if recursion is desired (1=yes) and the qd
identifies our query domain. We asked for DNS Query Record matching the name “mikeward.net” and as you can see at the bottom, we were able to successfully resolve our domain name to 208.113.152.237.
As mentioned there is good support for pretty much any type of packet you care to create, send or analyze but you probably get the idea by now. Next, we’re going to look at commands in scapy. Many builtin functions give us easy ways to do common tasks and it deserves it’s own post.