Inspecting packets

Get detailed description of the packet along with datatypes

>>> packet = IP()/TCP()
>>> ls(packet)
version    : BitField             = 4               (4)
ihl        : BitField             = None            (None)
tos        : XByteField           = 0               (0)
len        : ShortField           = None            (None)
id         : ShortField           = 1               (1)
flags      : FlagsField           = 0               (0)
frag       : BitField             = 0               (0)
ttl        : ByteField            = 64              (64)
proto      : ByteEnumField        = 6               (0)
chksum     : XShortField          = None            (None)
src        : Emph                 = '127.0.0.1'     (None)
dst        : Emph                 = '127.0.0.1'     ('127.0.0.1')
options    : PacketListField      = []              ([])
[-- snipped --]


show()

Displays detailed headers but does not assemble the packet

>>> packet.show()
###[ IP ]###
  version= 4
  ihl= None
  len= None
  [...]
  proto= hopopt
  chksum= None
  src= 192.168.1.100
  dst= Net('8.8.8.8/30')


show2

Similar to show() but also assembles the packet and calculates the checksums and IHL.

>>> packet.show2()
###[ IP ]###
  version= 4L
  ihl= 5L
  [...]
  ttl= 64
  proto= hopopt
  chksum= 0xa8cd
  src= 192.168.1.100
  dst= 8.8.8.8


Get only user supplied values

>>> b.hide_defaults( )

summary

  • Display short & interesting summary of a packet.
>>> packet.summary()
'Ether / IP / TCP 192.168.1.100:ftp_data > 8.8.8.8:domain S'


nsummary

  • Display short & interesting summary of a packet with numbering.
>>> pkts[0].nsummary()
0000 IP / TCP 192.168.1.103:ftp_data > 198.58.109.32:tcpmux S ==> IP / TCP 198.58.109.32:tcpmux > 192.168.1.103:ftp_data SA
0001 IP / TCP 192.168.1.103:ftp_data > 198.58.109.32:3128 S ==> IP / TCP 198.58.109.32:3128 > 192.168.1.103:ftp_data SA
0002 IP / TCP 192.168.1.103:ftp_data > 198.58.109.32:http_alt S ==> IP / TCP 198.58.109.32:http_alt > 192.168.1.103:ftp_data SA


summary() and nsummary() supports advanced features such as:

  • Filtering packets by individual header field values using lfilter argument.
  • Printing only necessary parts of packet using prn argument.
>>> egadz[0].nsummary(lfilter= lambda (s,r): r[TCP].sport == 3128 or r[TCP].sport==1)
0000 IP / TCP 192.168.1.103:ftp_data > 198.58.109.32:tcpmux S ==> IP / TCP 198.58.109.32:tcpmux > 192.168.1.103:ftp_data SA
0001 IP / TCP 192.168.1.103:ftp_data > 198.58.109.32:3128 S ==> IP / TCP 198.58.109.32:3128 > 192.168.1.103:ftp_data SA
>>> egadz[0].nsummary(lfilter= lambda (s,r): r[TCP].sport == 3128, prn = lambda (s,r): s.dst)
0001 198.58.109.32


Interacting with fields inside packet

To access a specific field: [packet_name].[field]

>>> packet.dst
'd8:55:a3:fe:80:78'


For fields that are not unique [packet_name][proto].[field]

>>> packet[Ether].dst
'd8:55:a3:fe:80:78'
>>> packet[IP].dst
'8.8.8.8'


.payload ignores the lowest layer and parses the next layer.

>>> packet.payload.flags
0
>>> packet.payload.payload.flags
2


Checking for presence of layer in packet

haslayer method

checks for presence of a layer in a packet

>>> if packet.haslayer(TCP):
...     print packet[TCP].flags
... 
2
>>>

Using an in construct

>>> pkt = IP()/TCP()/DNS()
>>>
>>> DNS in pkt
True


Scapy’s sprintf

  • sprintf() method is one of the very powerful features of Scapy.sprintf comes very handy while writing custom tools.
  • sprintf fills a format string with values from the packet , much like it sprintf from C Library, except here it fills the format string with field values from packets.

sprintf format     -    % [ [ fmt ] [ r ] , ] [ layer [ :nb ] . ] field %

Example     -    %-5sr, TCP.flags%

>>> packet.sprintf("Ethernet source is %Ether.src% and IP proto is %IP.proto%")
'Ethernet source is 00:00:00:00:00:00 and IP proto is icmp'

>>> a=Ether( )/Dot1Q(vlan=42)/IP(dst="192.168.0.1")/TCP(flags="RA")
>>>
>>> a.sprintf("%dst% %IP.dst% vlan=%Dot1Q.vlan%")
'00:00:d4:ae:3f:71 192.168.0.1 vlan=42'
>>>
>>>a.sprintf(" %TCP.flags% | %5s,TCP.flags% | %#05xr,TCP.flags%")
' RA | RA    | 0x014'
>>> res.nsummary(lfilter = lambda (s,r): r[TCP].flags & 2)
0008 IP / TCP 192.168.5.20:ftp-data > 192.168.5.22:discard S ==>
IP / TCP 192.168.5.22:discard > 192.168.5.20:ftp-data SA / Padding
>>> res.nsummary(lfilter = lambda (s,r): r[TCP].flags & 2, prn = lambda (s,r):s.dport)
0008 9
0012 13
0021 22
0024 25

Packet handlers

In the below example, we used lambda function to write a packet handler that can handle TCP packets but this function does not work with anything other than TCP packets.

>>> f=lambda x:x.sprintf("%IP.dst%:%TCP.dport%")
>>> f(IP(dst='8.8.8.8')/TCP())
'8.8.8.8:www'
>>> f(IP('8.8.8.8')/UDP())
'8.8.8.8:??'

Having a function that can work with various packets can be helpful in practical senarios, we can achieve this using conditional substrings in sprintf(). A conditional substring is only triggered when a layer is present in the packet or else it is ignored. You can also use ! for checking the absence of a layer.

Conditional substring format     -    { [ ! ] layer : substring }

>>> f=lambda x: x.sprintf("=> {IP:ip=%IP.dst% {UDP:dport=%UDP.dport%}\
... {TCP:%TCP.dport%/%TCP.flags%}{ICMP:type=%r,ICMP.type%}}\
... {!IP:not an IP packet}")
>>> 
>>> f(IP()/TCP())
'=> ip=127.0.0.1 http/S'
>>> 
>>> f(IP()/UDP())
'=> ip=127.0.0.1 dport=domain'
>>> 
>>> f(IP()/ICMP())
'=> ip=127.0.0.1 type=8'
>>>
>>> f(Ether()/ARP())
'=> not an IP packet'


Python’s format method

  • Python string format method generates beautiful output but unlike sprintf it prints literal values.
>>> "Ether source is: {} & IP proto is: {}".format(packet.src, packet.proto)
'Ether source is: 00:00:00:00:00:00 & IP proto is: 1'