Scapy – Decode and forge your own packet

Posted on January 24, 2012

3


I started to write articles on the wiki of Sec IT’s related to the posts on this blog. If you want to modify or improve the articles (I’m pretty sure there are plenty of typos and materials to describe), feel free to edit it on the wiki.

Scapy is an application for packet manipulation written in Python by Philippe Biondi. It is able to forge or decode packets of a wide number of protocols, send them on the wire, capture them, match requests and replies, and much more. It can easily handle most classical tasks like scanning, tracerouting, probing, unit tests, attacks or network discovery.

Installation

Scapy is an application written in Python, therefore, before install it, you need to get Python first. Python is a cross-platform programming language. You should find all information for the install on the official download page. Once Python installed, download and install Scapy. For non-Linux users, you will need to install libpcap (or WinPcap for Windows users) and libnet and their Python wrappers.

For further information about the installation, here is the Scapy portability page.

Usage

Scapy need to be run with root privileges:

sudo scapy

Forge a packet

Once in the interpreter, the first thing you might need to know is all available protocols and all functions. You can list the protocols with the function ls() and list all the functions with lsc():

 >>>  ls()
 ARP        : ARP
 ASN1_Packet : None
 BOOTP      : BOOTP
 CookedLinux : cooked linux
 DHCP       : DHCP options
 ...
 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
 ...

To create a packet, you just need to assign a protocol to a variable:

myPacket = IP()

Here I created an IP packet. An IP packet contains different options and values. To list them use the function show():

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

You can edit the parameters’ value in different ways. Either by attributing the value when you assign a protocol to a variable, either by selecting the parameter and assign it a new value:

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

To print only one parameter’s value, you just need to select it:

 >>> myPacket.src
 '192.168.0.7'

The packets transmit on the network are a stack of multiple protocols with different purposes. For instance, regarding the TCP/IP model, the packet to download a web page is structured like this:

Internet Protocol
Application layer
HTTP
Transport layer
TCP
Internet layer
IP
Data link layer
Ethernet

To stack packets, you just need to use the operator /:

 >>> packet = myPacket/TCP()
 >>> packet.show()
 ###[ IP ]###
   version= 4
   ihl= None
   tos= 0x0
   len= None
   id= 1
   flags=
   frag= 0
   ttl= 64
   proto= tcp
   chksum= 0x0
   src= 192.168.0.7
   dst= 192.168.0.1
   options= ''
 ###[ TCP ]###
      sport= ftp_data
      dport= http
      seq= 0
      ack= 0
      dataofs= None
      reserved= 0
      flags= S
      window= 8192
      chksum= 0x0
      urgptr= 0
      options= {}

Since the variable packet is a stack of an IP and TCP packet, if you want to manipulate TCP parameters you will have select the layer first:

 >>> packet.ttl = 10
 >>> packet[TCP].sport = 1025
 >>> packet.show()
 ###[ IP ]###
  version= 4
  ihl= None
  tos= 0x0
  len= None
  id= 1
  flags=
  frag= 0
  ttl= 10
  ...
 ###[ TCP ]###
     sport= blackjack
     dport= http
     ...

To add a payload to this packet, once again use the operator /

 >>> packet = packet/"GET HTTP/1.1\r\n\r\n"
 >>> packet.show()
 ###[ IP ]###
   version= 4
   ihl= None
   tos= 0x0
   len= None
   id= 1
   flags= 
   frag= 0
   ttl= 10
   proto= tcp
   chksum= 0x0
   src= 192.168.0.7
   dst= 192.168.0.1
   options= ''
 ###[ TCP ]###
      sport= blackjack
      dport= http
      seq= 0
      ack= 0
      dataofs= None
      reserved= 0
      flags= S
      window= 8192
      chksum= 0x0
      urgptr= 0
      options= {}
 ###[ Raw ]###
         load= 'GET HTTP/1.1\r\n\r\n'

Send packets

Depending on which layer you want to send the packet, you can use either send() (for layer 3) or sendp() (for layer 2). send() will handle routing and layer 2 for you, regarding the content of the packet. sendp() let you configure the packet at layer 2. This means you have to encapsulate your packet in a frame (Ethernet, 802.11, etc.).

 >>> send(IP(dst="1.2.3.4")/ICMP())
 .
 Sent 1 packets.

 >>> sendp(Ether()/IP(dst="1.2.3.4", iface="en1")
 ....
 Sent 1 packets.

It is possible to send more than one packet with one command. Each parameters of the whole packet can be set with different values. For each value, scapy generates a complete new packet.

 >>> tcp=TCP()
 >>> tcp.dport=(20,30)
 >>> [p for p in tcp]
 [<TCP  dport=ftp_data |>,
  <TCP  dport=ftp |>, 
  <TCP  dport=ssh |>, 
  <TCP  dport=telnet |>, 
  <TCP  dport=24 |>, 
  <TCP  dport=smtp |>, 
  <TCP  dport=26 |>, 
  <TCP  dport=nsw_fe |>, 
  <TCP  dport=28 |>, 
  <TCP  dport=msg_icp |>, 
  <TCP  dport=30 |>]

Sending a packet is one thing, but it is even more interesting to get the answer. There are 2 functions by layer for sending packets and receiving answers: sr() and sr1() on layer 3 and srp() and srp1() on layer 2. sr() sends packets like send() and returns a couple of packets and answers, and the unanswered packets. sr1() is a variant that return only the first answer of the packet sent. srp() and srp1() are the equivalent but for the layer 2.

 >>> a=sr1(IP(dst="www.secits.be")/ICMP())
 Begin emission:
 ..Finished to send 1 packets.
 .*
 Received 4 packets, got 1 answers, remaining 0 packets
 >>> a.show()
 ###[ IP ]###
   version= 4L
   ihl= 5L
   tos= 0x0
   len= 28
   id= 54790
   flags=
   frag= 0L
   ttl= 52
   proto= icmp
   chksum= 0x216f
   src= 195.144.11.44
   dst= 192.168.0.7
   options= ''
 ###[ ICMP ]###
      type= echo-reply
      code= 0
      chksum= 0xffff
      id= 0x0
      seq= 0x0
 ###[ Padding ]###
         load= '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

As decribed previously, sr() returns a couple of packet and answers, and the unanswered packets. To read the return value, you need to store it in a variable:

 >>> ip=IP(dst="192.168.0.1")
 >>> tcp=TCP(dport=(20,30))
 >>> packet=ip/tcp
 >>> packet[TCP].flag="S"
 >>> answers,unanswered=sr(packet)
 Begin emission:
 .....*.*Finished to send 11 packets.
 ............................^C
 Received 36 packets, got 2 answers, remaining 9 packets
 >>> answers.show()
 0000 IP / TCP 192.168.0.7:ftp_data > 192.168.0.1:ssh S ==> IP / TCP 192.168.0.1:ssh > 192.168.0.7:ftp_data RA
 0001 IP / TCP 192.168.0.7:ftp_data > 192.168.0.1:telnet S ==> IP / TCP 192.168.0.1:telnet > 192.168.0.7:ftp_data RA
 >>> unanswered.summary()
 IP / TCP 192.168.0.7:ftp_data > 192.168.0.1:30 S
 IP / TCP 192.168.0.7:ftp_data > 192.168.0.1:ftp S
 IP / TCP 192.168.0.7:ftp_data > 192.168.0.1:26 S
 IP / TCP 192.168.0.7:ftp_data > 192.168.0.1:ftp_data S
 IP / TCP 192.168.0.7:ftp_data > 192.168.0.1:smtp S
 IP / TCP 192.168.0.7:ftp_data > 192.168.0.1:24 S
 IP / TCP 192.168.0.7:ftp_data > 192.168.0.1:msg_icp S
 IP / TCP 192.168.0.7:ftp_data > 192.168.0.1:28 S
 IP / TCP 192.168.0.7:ftp_data > 192.168.0.1:nsw_fe S

As you can see, show() function can be replaced by the more appropriate function summary(). This request is a SYN scan that checks for open ports between 20 and 30 on the host 192.168.0.1.

Another interesting feature in scapy is its ability to handle libcap files. For isntance, it is possible to sniff packets with Wireshark, save the capture and send it via scapy using the function sendp() (or srp()) and rdpcap(). This is called tcpreplay:

 >>> sendp(rdpcap("/tmp/pcapfile"))
 ...........
 Sent 11 packets.

Useful links

Advertisements
Posted in: Tutorials, Wiki