Scapy p.04

Looking at Packets

May - 2019 (~7 minutes read time)

Packets, Layers, and Fields. Oh My!

Scapy uses Python dictionaries as the data structure for packets. Each packet is a collection of nested dictionaries with each layer being a child dictionary of the previous layer, built from the lowest layer up. Visualizing the nested packet layers would look something like this:

Each field (such as the Ethernet dst value or ICMP type value) is a key:value pair in the appropriate layer. These fields (and nested layers) are all mutable so we can reassign them in place using the assignment operator. Scapy has packet methods for viewing the layers and fields that I will introduce next.

Packet summary() and show() Methods

Now let's go back to our pkt and have some fun with it using Scapy's Interactive mode. We already know that using the summary() method will give us a quick look at the packet's layers:

>>> pkt[0].summary()
'Ether / IP / ICMP 172.16.20.10 > 4.2.2.1 echo-request 0 / Raw'

But what if we want to see more of the packet contents? That's what the

show() method is for:

>>> pkt[0].show()
###[ Ethernet ]###
  dst= 00:24:97:2e:d6:c0
  src= 00:00:16:aa:bb:cc
  type= 0x800
###[ IP ]###
     version= 4L
     ihl= 5L
     tos= 0x0
     len= 84
     id= 57299
     flags= 
     frag= 0L
     ttl= 64
     proto= icmp
     chksum= 0x0
     src= 172.16.20.10
     dst= 4.2.2.1
     \options\
###[ ICMP ]###
        type= echo-request
        code= 0
        chksum= 0xd8af
        id= 0x9057
        seq= 0x0
###[ Raw ]###

Very cool, that's some good info. If you're familiar with Python you have probably noticed the list index, [0], after the pkt variable name. Remember that our sniff only returned a single packet, but if we increase the count argument value, we will get back an list with multiple packets:

>>> pkts = sniff(count=10)
>>> pkts
<Sniffed: TCP:0 UDP:0 ICMP:10 Other:0>

Getting the value of the list returns a quick glance at what type of packets were sniffed.

>>> pkts.summary()
Ether / IP / ICMP 172.16.20.10 > 4.2.2.1 echo-request 0 / Raw
Ether / IP / ICMP 4.2.2.1 > 172.16.20.10 echo-reply 0 / Raw
Ether / IP / ICMP 172.16.20.10 > 4.2.2.1 echo-request 0 / Raw
Ether / IP / ICMP 4.2.2.1 > 172.16.20.10 echo-reply 0 / Raw
Ether / IP / ICMP 172.16.20.10 > 4.2.2.1 echo-request 0 / Raw
Ether / IP / ICMP 4.2.2.1 > 172.16.20.10 echo-reply 0 / Raw
Ether / IP / ICMP 172.16.20.10 > 4.2.2.1 echo-request 0 / Raw
Ether / IP / ICMP 4.2.2.1 > 172.16.20.10 echo-reply 0 / Raw
Ether / IP / ICMP 172.16.20.10 > 4.2.2.1 echo-request 0 / Raw
Ether / IP / ICMP 4.2.2.1 > 172.16.20.10 echo-reply 0 / Raw

And we can show the summary or packet contents of any single packet by using the list index with that packet value. So, let's look at the contents of the 4th packet (Remember, list indexes start counting at 0):

>>> pkts[3]
<Ether  dst=00:00:16:aa:bb:cc src=00:24:97:2e:d6:c0 type=0x800 |<IP  version=4L ihl=5L tos=0x20 len=84 id=47340 flags= frag=0L ttl=57 proto=icmp chksum=0x3826 src=4.2.2.1 dst=172.16.20.10 options=[] |<ICMP  type=echo-reply code=0 chksum=0xcfbf id=0x3060 seq=0x1 |<Raw |>>>>

Getting the value of a single packet returns a quick glance of the contents of that packet.

The show() method will give us a cleaner print out:

>>> pkts[3].show()
###[ Ethernet ]###
  dst= 00:00:16:aa:bb:cc
  src= 00:24:97:2e:d6:c0
  type= 0x800
###[ IP ]###
     version= 4L
     ihl= 5L
     tos= 0x20
     len= 84
     id= 47340
     flags= 
     frag= 0L
     ttl= 57
     proto= icmp
     chksum= 0x3826
     src= 4.2.2.1
     dst= 172.16.20.10
     \options\
###[ ICMP ]###
        type= echo-reply
        code= 0
        chksum= 0xcfbf
        id= 0x3060
        seq= 0x1
###[ Raw ]###

Digging into Packets by Layer

Scapy builds and dissects packets by the layers contained in each packet, and then by the fields in each layer. Each layer is nested inside the parent layer as can be seen with the nesting of the < and > brackets:

>>> pkts[4]
<Ether  dst=00:24:97:2e:d6:c0 src=00:00:16:aa:bb:cc type=0x800 |<IP  version=4L ihl=5L tos=0x0 len=84 id=17811 flags= frag=0L ttl=64 proto=icmp chksum=0x0 src=192.168.201.203 dst=4.2.2.1 options=[] |<ICMP  type=echo-request code=0 chksum=0xc378 id=0x3060 seq=0x2 |<Raw |>>>>

You can also dig into a specific layer using an list index. If we wanted to get to the ICMP layer of pkts[3], we could do that using the layer name or index number:

>>> pkts[3][ICMP].summary()
'ICMP 4.2.2.1 > 192.168.201.203 echo-reply 0 / Raw'
>>> pkts[3][2].summary()
'ICMP 4.2.2.1 > 192.168.201.203 echo-reply 0 / Raw'

Since the first index chooses the packet out of the pkts list, the second index chooses the layer for that specific packet. Looking at the summary of this packet from an earlier example, we know that the ICMP layer is the 3rd layer.

Packet .command() Method

If you're wanting to see a reference of how a packet that's been sniffed or received might look to create, Scapy has a packet method for you! Using the .command() packet method will return a string of the command necessary to recreate that packet, like this:

>>> pkts[2].command()
'Ether(src=\'00:11:22:aa:bb:cc\', dst=\'c0:c1:c0:b7:ce:63\', type=2048)/IP(frag=0L, src=\'172.16.20.10\', proto=1, tos=0, dst=\'4.2.2.1\', chksum=51457, len=84, options=[], version=4L, flags=0L, ihl=5L, ttl=64, id=59755)/ICMP(gw=None, code=0, ts_ori=None, addr_mask=None, seq=3, ptr=None, unused=None, ts_rx=None, chksum=50424, reserved=None, ts_tx=None, type=8, id=59999)/Raw(load=\'Rk\\xe8\\x02\\x00\\x0c#\\\'\\x08\\t\\n\\x0b\\x0c\\r\\x0e\\x0f\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f !"#$%&\\\'()*+,-./01234567\')'

Digging into Layers by Field

Within each layer, Scapy parses out the value of each field if it has support for the layer's protocol. Depending on the type of field, Scapy may replace the value with a more friendly text value for the summary views, but not in the values returned for an individual field. Here are some examples:

>>> pkts[3]
<Ether  dst=00:00:16:aa:bb:cc src=00:24:97:2e:d6:c0 type=0x800 |\
<IP  version=4L ihl=5L tos=0x20 len=84 id=47340 flags= frag=0L ttl=57 proto=icmp chksum=0x3826 src=4.2.2.1 dst=192.168.201.203 options=[] |\
<ICMP  type=echo-reply code=0 chksum=0xcfbf id=0x3060 seq=0x1 |<Raw |>>>>
>>> pkts[3][Ether].src
'00:24:97:2e:d6:c0'
>>> pkts[3][IP].ttl
57
>>> pkts[3][IP].proto
1
>>> pkts[3][ICMP].type
0

Using Python control statements with Scapy

The awesome thing about Scapy being a module of Python is that we can use the power of Python to do stuff with our packets. Here's a tip of the iceberg example using a Python for statement along with some new Scapy packet methods:

>>> for packet in pkts:
...     if (packet.haslayer(ICMP)):
...         print(f"ICMP code: {packet.getlayer(ICMP).code}")
...
ICMP code: 0
ICMP code: 0
ICMP code: 0
ICMP code: 0
ICMP code: 0
ICMP code: 0
ICMP code: 0
ICMP code: 0
ICMP code: 0
ICMP code: 0

As you can guess, the haslayer() and getlayer() methods will test for the existence of a layer and return the layer (and any nested layers) respectively. This is just a very basic use of Python statements with Scapy and we'll see a lot more when it comes to packet generation and custom actions.