Investigate network traffic with Scapy
In previous sections, we sniffed and injected packets with raw sockets, where we have to do parsing, decoding, creating, and injecting packets all by ourselves. Also, raw sockets are not compatible with all operating systems. There are many third-party libraries that will help us to work with packets. Scapy is a very powerful interactive packet manipulation library and tool that stands out from all these libraries. Scapy provides us different commands, from basic level to advanced level, for investigating a network. We can use Scapy in two different modes: interactively within a terminal window, and programmatically from a Python script by importing it as a library.
Let's start Scapy using the interactive mode. Interactive mode is like Python shell; to activate this, just run Scapy with root privileges in a terminal:
$ sudo scapy
This will return an interactive terminal in Scapy:
These are some basic commands for interactive usage:
ls()
: Displays all the protocols supported by Scapylsc()
: Displays the list of commands supported by Scapyconf
: Displays all configurations optionshelp()
: Display help on a specific command, for example,help(sniff)
show()
: Display the details about a specific packet, for example,Newpacket.show()
Scapy helps to create custom packets based on the huge set of protocols it supports. Now we can create simple packets with Scapy in an interactive Scapy shell:
>>> packet=IP(dst='google.com') >>> packet.ttl=10
This will create a packet; now we can see the packet using the following method:
>>> packet.show()
This use of the packet is shown in the following screenshot:
Scapy creates and parses packets by the layers in each packet and by the fields in every layer. Each layer is encapsulated inside the parent layer. Packets in Scapy are Python dictionaries, so each packet is a set of nested dictionaries with each layer being a child dictionary of the parent layer. The summary()
method will provide the details of the packet's layers:
>>> packet[0].summary() 'Ether / IP / UDP 192.168.1.35:20084 > 117.206.55.151:43108 / Raw'
The layer structure of a packet can be better seen with the nesting of brackets (<
and >
):
>>> packet[0] <Ether dst=6c:19:8f:e1:4a:8c src=b8:76:3f:8b:f5:fe type=0x800 |<IP version=4L ihl=5L tos=0x0 len=140 id=30417 flags=DF frag=0L ttl=64 proto=udp chksum=0x545f src=192.168.1.35 dst=117.206.55.151 options=[] |<UDP sport=20084 dport=43108 len=120 chksum=0xd750 |<Raw load='\x90\x87]{\xa1\x9c\xe7$4\x07\r\x7f\x10\x83\x84\xb5\x1d\xae\xa1\x9eWgX@\xf1\xab~?\x7f\x84x3\xee\x98\xca\xf1\xbdtu\x93P\x8f\xc9\xdf\xb70-D\x82\xf6I\xe0\x84\x0e\xcaH\xd0\xbd\xf7\xed\xf3y\x8e>\x11}\x84T\x05\x98\x02h|\xed\t\xb1\x85\x9f\x8a\xbc\xdd\x98\x07\x14\x10\no\x00\xda\xbf9\xd9\x8d\xecZ\x9a2\x93\x04CyG\x0c\xbd\xf2V\xc6<"\x82\x1e\xeb' |>>>>
We can dig into a specific layer by its name or its index number in the list index. For example, we can get the UDP layer of the preceding packets with the following:
>>> packet[0] .[UDP].summary()
Or you can get the UDP layer using the following method:
>>> packet[0] .[2].summary()
With Scapy, we can parse the value of fields within each layer. For example, we can get the source field in the Ethernet layer with the following:
>>> packet[0] [Ether].src
Packet sniffing with Scapy
With Scapy, it is very simple to sniff
packets with the sniff
method. We can run the following command in a Scapy shell to sniff
in interface eth0
:
>>>packet = sniff(iface="eth0", count=3)
This will get three packets from the eth0
interface. With hexdump()
, we can dump the packet in hex
:
The arguments for the sniff()
method are as follows:
count
: Number of packets to capture, but 0 means infinityiface
: Interface to sniff; sniff for packets only on this interfaceprn
: Function to run on each packetstore
: Whether to store or discard the sniffed packets; set to 0 when we only need to monitortimeout
: Stops sniffing after a given time; the default value is nonefilter
: Takes BPF syntax filters to filter sniffing
If we want to see more of the packet contents, the show()
method is good. It will display the packet in a cleaner and produce a formatted print out, as follows:
>>>packet[1].show()
This command will give the following output:
To see the sniffed packets in realtime, we have to use the lambda function, along with the summary()
or show()
method:
>>> packet=sniff(filter="icmp", iface="eth0″, count=3, prn=lambda x:x.summary())
Also, it is possible to write the packets to a pcap
file with Scapy. To write the packets to a pcap
file, we can use the wrpcap()
method:
>>>wrpcap("pkt-output.cap" packets)
This will write the packets to a pkt-output.cap
file. We can read from the pcap
file with rdpcap()
:
>>> packets = rdpcap("pkt-output.cap")
Packet injection with Scapy
Before injecting, we have to create a spoofed packet. With Scapy, it is very simple to create a packet if we know the packet's layered structure. To create an IP packet, we use the following syntax:
>>> packet = IP (dst="packtpub.com")
To add more child layers to this packet, we can simply add the following:
>>> packet = IP (dst="packtpub.com")/ICMP()/"Hello Packt"
This will create a packet with an IP layer, ICMP
layer, and raw payload, as "Hello Packt"
. The show()
method will display this packet as follows:
>>> packet.show() ###[ IP ]### version= 4 ihl= None tos= 0x0 len= None id= 1 flags= frag= 0 ttl= 64 proto= icmp chksum= None src= 192.168.1.35 dst= Net('packtpub.com') \options\ ###[ ICMP ]### type= echo-request code= 0 chksum= None id= 0x0 seq= 0x0 ###[ Raw ]### load= 'Hello world'
To send the packet, we have two methods:
sendp()
: Layer-2 send; sends layer-2 packetssend()
: Layer-3 send; only sends layer-3 packets like IPv4 and Ipv6
The main arguments for send commands are as follows:
iface
: The interface to send packetsinter
: The time in between two packets (in seconds)loop
: To keep sending packets endlessly, set this to1
packet
: Packet or a list of packets
If we are using a layer-2 send, we have to add an Ethernet layer and provide the correct interface to send the packet. But with layer-3, sending all this routing stuff will be handled by Scapy itself. So let's send the previously created packet with a layer-3 send:
>>> send(packet)
The packet we send can be sniffed using another Scapy interactive terminal. The output will be like this, the second packet is the response we received from packtpub.com
:
Similarly, to send a layer-2 packet, we have to add the Ethernet header and interface as follows:
>>> sendp(Ether()/IP(dst="packtpub.com")/ICMP()/"Layer 2 packet", iface="eth0")
Scapy send and receive methods
These methods are used to send a packet or group of packets when we expect a response back. There are four different types of send and receive methods. They are as follows:
sr()
: Layer-3 send and receive, returns both answers and unanswered packetssr1()
: Layer-3 send and receive, returns only answers or sent packetssrp()
: Layer-2 send and receive, returns both answers and unanswered packetssrp1()
: Layer-2 send and receive, returns only answers or sent packets
These methods are almost similar to the send()
method. To send a packet and receive its response, use the following:
>>> packet = IP (dst="packtpub.com")/ICMP()/"Hello Packt" >>> sr(packet) Begin emission: .Finished to send 1 packets. .* Received 3 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>)
Here, while waiting for the response, Scapy got three packets and exited when the response received. If we used sr1()
, this will wait only for one response and print the response packet. Similarly, we can send layer-2 packets with the srp()
and srp1()
methods.
Programming with Scapy
Earlier, we were using Scapy in interactive mode. But in some cases, we may need to use Scapy in scripts. Scapy can be used as a library if it is imported in our programs. We can import all Scapy functions as follows:
from scapy.all import*
Or we can import specific packages if we only need a few of the functions, as follows:
from scapy.all Ether, IP, TCP, sr1
For example we can create a DNS request. With sr1()
method, we can create and get the response for a DNS request. As DNS packets are built from IP and UDP packets, we can create a DNS packet with IP and UDP layers in it:
from scapy.all import * #Import Scapy # Create a DNS request Packet to 8.8.8.8 dns_packet = IP(dst="8.8.8.8")/UDP(dport=53)/DNS(rd=1,qd=DNSQR(qname="packtpub.com")) # Send packet and get the response dns_request = sr1(dns_packet,verbose=1) # Print the response print dns_request[DNS].summary()
We have to run this script with root privileges. If the verbose option is 1
, the output will be as follows:
$ sudo python dns_scapy.py WARNING: No route found for IPv6 destination :: (no default route?) Begin emission: Finished to send 1 packets Received 18 packets, got 1 answers, remaining 0 packets DNS Ans "83.166.169.231"
To parse DNS packets, we can use the sniff()
method. The prn
argument in sniff()
can be used to change the output by Scapy for each packet. It helps to replace the default Scapy printout with our own function, thus we can decide how Scapy will print the output for each packet. Here, in the following example, we are using the select_DNS()
function each time a matched packet is identified by the filter and sniffed with Scapy:
from scapy.all import * #Import Scapy from datetime import datetime interface = 'eth0' #Interface to sniff filter_bpf = 'udp and port 53' #BPF filter to filter udp packets in port 53 #Runs this for each packet def select_DNS(packet): packet_time = packet.sprintf('%sent.time%') try: if DNSQR in packet and packet.dport == 53: #Print queries print 'DNS queries Message from '+ packet[IP].src + ' to ' + packet[IP].dst +' at ' + packet_time elif DNSRR in packet and packet.sport == 53: #Print responses print 'DNS responses Message from '+ packet[IP].src + ' to ' + packet[IP].dst +' at ' + packet_time except: pass #Sniff the packets sniff(iface=interface, filter=filter_bpf, store=0, prn=select_DNS)
As usual, we imported the necessary modules, Scapy and datetime, in the first two lines; later, we declared the interface to sniff and the filter to get the udp
packet from port 53
with the Berkeley Packet Filter (BPF) syntax:
from scapy.all import * #Import Scapy from datetime import datetime interface = 'eth0' #Interface to sniff filter_bpf = 'udp and port 53' #BPF filter to filter udp packets in port 53
Then we declared the function to be called when each packet is sniffed with the sniff()
method. This will modify the default printout summary in sniff()
and provide custom output. Here, it will check the DNS packet and output its source destination and time. The prn
argument is used to bind this function to the sniff()
method:
def select_DNS(packet): packet_time = packet.sprintf('%sent.time%') try: if DNSQR in packet and packet.dport == 53: #Print queries print 'DNS queries Message from '+ packet[IP].src + ' to ' + packet[IP].dst +' at ' + packet_time elif DNSRR in packet and packet.sport == 53: #Print responses print 'DNS responses Message from '+ packet[IP].src + ' to ' + packet[IP].dst +' at ' + packet_time except: pass
Finally we will call the sniff()
method with a select_DNS()
function as a prn
argument.
sniff(iface=interface, filter=filter_bpf, store=0, prn=select_DNS)
Tip
For more details on Berkeley Packet Filter (BPF) syntax, read http://biot.com/capstats/bpf.html.
Let's check another example in OS fingerprinting; we can do this by two methods:
- Nmap fingerprinting
- p0f
If Nmap is installed on your system, we can utilize its active OS fingerprinting database with Scapy. Make sure the signature database is located in the path specified in conf.nmap_base
. If you are using the default installation directory, Scapy will automatically detect the fingerprints file.
We can load nmap
module with the following:
load_module("nmap")
Then we can use nmap_fp()
function to start fingerprinting the OS.
nmap_fp("192.168.1.1",oport=443,cport=1)
If we have p0f
installed, we can use this to identify the OS. Make sure the configuration conf.p0f_base
is correct. We can guess the OS from a single captured packet with the following:
sniff(prn=prnp0f)
Tip
For more details on Scapy, read http://www.secdev.org/projects/scapy/doc/usage.html.