Scapy is a powerful interactive packet manipulation program. 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.

Scapy Tool

scapy is built using Python, it can run in interactive mode or write in python script to automate tasks.

Let’s go through some basic tasks to get started:

Installation

You can go to scapy home page to download the package to install. In my case, I run scapy in ubuntu 16.04, it is an easy step as below:

weng@weng-u1604:~$ sudo apt-get install python-scapy
[sudo] password for weng: 
Reading package lists... Done
Building dependency tree       
Reading state information... Done
Suggested packages:
  tcpreplay wireshark graphviz imagemagick python-gnuplot python-crypto
  python-pyx ebtables python-visual sox gv hexer librsvg2-bin python-pcapy
The following NEW packages will be installed:
  python-scapy
0 upgraded, 1 newly installed, 0 to remove and 1143 not upgraded.
Need to get 236 kB of archives.
After this operation, 1,122 kB of additional disk space will be used.
Get:1 http://us.archive.ubuntu.com/ubuntu xenial/universe amd64 python-scapy all 2.2.0-1 [236 kB]
Fetched 236 kB in 5s (41.4 kB/s)     
Selecting previously unselected package python-scapy.
(Reading database ... 222945 files and directories currently installed.)
Preparing to unpack .../python-scapy_2.2.0-1_all.deb ...
Unpacking python-scapy (2.2.0-1) ...
Processing triggers for man-db (2.7.5-1) ...
Setting up python-scapy (2.2.0-1) ...
weng@weng-u1604:~$ which scapy
/usr/bin/scapy
weng@weng-u1604:~$ 

Start scapy in interactive mode

Simply type “sudo scapy”, it will bring you to interactive mode:

weng@weng-u1604:~$ sudo scapy
INFO: Can't import python gnuplot wrapper . Won't be able to plot.
INFO: Can't import PyX. Won't be able to use psdump() or pdfdump().
WARNING: No route found for IPv6 destination :: (no default route?)
INFO: Can't import python Crypto lib. Won't be able to decrypt WEP.
INFO: Can't import python Crypto lib. Disabled certificate manipulation tools
Welcome to Scapy (2.2.0)
>>> 

To see what you can do under interactive mode, type “lsc()” tells what are available commands:

>>> 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
corrupt_bits        : Flip a given percentage or number of bits from a string
corrupt_bytes       : Corrupt a given percentage or number of bytes from a string
defrag              : defrag(plist) -> ([not fragmented], [defragmented],
defragment          : defrag(plist) -> plist defragmented as much as possible 
dyndns_add          : Send a DNS add message to a nameserver for "name" to have a new "rdata"
dyndns_del          : Send a DNS delete message to a nameserver for "name"
etherleak           : Exploit Etherleak flaw
fragment            : Fragment a big IP datagram
fuzz                : Transform a layer into a fuzzy layer by replacing some default values by random objects
getmacbyip          : Return MAC address corresponding to a given IP address
hexdiff             : Show differences between 2 binary strings
hexdump             : --
hexedit             : --
is_promisc          : Try to guess if target is in Promisc mode. The target is provided by its ip.
linehexdump         : --
ls                  : List  available layers, or infos on a given layer
promiscping         : Send ARP who-has requests to determine which hosts are in promiscuous mode
rdpcap              : Read a pcap file and return a packet list
send                : Send packets at layer 3
sendp               : Send packets at layer 2
sendpfast           : Send packets at layer 2 using tcpreplay for performance
sniff               : Sniff packets
split_layers        : Split 2 layers previously bound
sr                  : Send and receive packets at layer 3
sr1                 : Send packets at layer 3 and return only the first answer
srbt                : send and receive using a bluetooth socket
srbt1               : send and receive 1 packet using a bluetooth socket
srflood             : Flood and receive packets at layer 3
srloop              : Send a packet at layer 3 in loop and print the answer each time
srp                 : Send and receive packets at layer 2
srp1                : Send and receive packets at layer 2 and return only the first answer
srpflood            : Flood and receive packets at layer 2
srploop             : Send a packet at layer 2 in loop and print the answer each time
traceroute          : Instant TCP traceroute
tshark              : Sniff packets and print them calling pkt.show(), a bit like text wireshark
wireshark           : Run wireshark on a list of packets
wrpcap              : Write a list of packets to a pcap file
>>>

As it is shown, there are many commands, let’s pick a few commands to play around.

sniff packets

The below shows the example to sniff 7 packets from interface “enp0s3”. While it is sniffing, I have web browser pointing to “www.google.com”.

>>> conf.route
Network         Netmask         Gateway         Iface           Output IP
127.0.0.0       255.0.0.0       0.0.0.0         lo              127.0.0.1      
0.0.0.0         0.0.0.0         10.0.2.2        enp0s3          10.0.2.15      
10.0.2.0        255.255.255.0   0.0.0.0         enp0s3          10.0.2.15      
169.254.0.0     255.255.0.0     0.0.0.0         enp0s3          10.0.2.15      

>>> pkts=sniff(iface="enp0s3",count=7)
>>> pkts.summary()
Ether / IP / UDP / DNS Qry "www.google.com." 
Ether / IP / UDP / DNS Qry "www.google.com." 
Ether / IP / TCP 10.0.2.15:57696 > 216.58.195.68:https PA / Raw
Ether / IP / TCP 216.58.195.68:https > 10.0.2.15:57696 A / Padding
Ether / IP / UDP / DNS Ans "216.58.195.68" 
Ether / IP / UDP / DNS Ans "2607:f8b0:4005:807::2004" 
Ether / IP / TCP 216.58.195.68:https > 10.0.2.15:57696 PA / Raw
>>> 

We can see that the first packet is a DNS query packet, we can dig into deeper:

>>> pkts[0].show()
###[ Ethernet ]###
  dst= 52:54:00:12:35:02
  src= 08:00:27:62:e8:1d
  type= 0x800
###[ IP ]###
     version= 4L
     ihl= 5L
     tos= 0x0
     len= 60
     id= 42461
     flags= DF
     frag= 0L
     ttl= 64
     proto= udp
     chksum= 0xc71b
     src= 10.0.2.15
     dst= 192.168.1.1
     \options\
###[ UDP ]###
        sport= 50427
        dport= domain
        len= 40
        chksum= 0xcdf1
###[ DNS ]###
           id= 14338
           qr= 0L
           opcode= QUERY
           aa= 0L
           tc= 0L
           rd= 1L
           ra= 0L
           z= 0L
           rcode= ok
           qdcount= 1
           ancount= 0
           nscount= 0
           arcount= 0
           \qd\
            |###[ DNS Question Record ]###
            |  qname= 'www.google.com.'
            |  qtype= A
            |  qclass= IN
           an= None
           ns= None
           ar= None
>>> 

Create packet and send packet

If we already have a packet, and want to send that the same packet, see below example:

>>> pkts[0].command()
"Ether(src='08:00:27:62:e8:1d', dst='52:54:00:12:35:02', type=2048)/IP(frag=0L, src='10.0.2.15', proto=17, tos=0, dst='192.168.1.1', chksum=50971, len=60, options=[], version=4L, flags=2L, ihl=5L, ttl=64, id=42461)/UDP(dport=53, sport=50427, len=40, chksum=52721)/DNS(aa=0L, qr=0L, an=None, nscount=0, qdcount=1, ns=None, tc=0L, rd=1L, arcount=0, ar=None, opcode=0L, ra=0L, z=0L, rcode=0L, id=14338, ancount=0, qd=DNSQR(qclass=1, qtype=1, qname='www.google.com.'))"
>>> eval(pkts[0].command())
<Ether  dst=52:54:00:12:35:02 src=08:00:27:62:e8:1d type=0x800 |<IP  version=4L ihl=5L tos=0x0 len=60 id=42461 flags=DF frag=0L ttl=64 proto=udp chksum=0xc71b src=10.0.2.15 dst=192.168.1.1 options=[] |<UDP  sport=50427 dport=domain len=40 chksum=0xcdf1 |<DNS  id=14338 qr=0L opcode=QUERY aa=0L tc=0L rd=1L ra=0L z=0L rcode=ok qdcount=1 ancount=0 nscount=0 arcount=0 qd=<DNSQR  qname='www.google.com.' qtype=A qclass=IN |> an=None ns=None ar=None |>>>>
>>> sendp(eval(pkts[0].command()))
.
Sent 1 packets.
>>>

There is send() command to send L3/IP packet, and sendp() command to send L2 frame. E.g. to send a ping command to www.google.com, the following commands will do:

>>> send(IP(dst="www.google.com")/ICMP())
.
Sent 1 packets.
>>> sendp(Ether()/IP(dst="www.google.com")/ICMP())
.
Sent 1 packets.
>>> ls(Ether)
dst        : DestMACField         = (None)
src        : SourceMACField       = (None)
type       : XShortEnumField      = (0)
>>> ls(IP)
version    : BitField             = (4)
ihl        : BitField             = (None)
tos        : XByteField           = (0)
len        : ShortField           = (None)
id         : ShortField           = (1)
flags      : FlagsField           = (0)
frag       : BitField             = (0)
ttl        : ByteField            = (64)
proto      : ByteEnumField        = (0)
chksum     : XShortField          = (None)
src        : Emph                 = (None)
dst        : Emph                 = ('127.0.0.1')
options    : PacketListField      = ([])
>>> ls(ICMP)
type       : ByteEnumField        = (8)
code       : MultiEnumField       = (0)
chksum     : XShortField          = (None)
id         : ConditionalField     = (0)
seq        : ConditionalField     = (0)
ts_ori     : ConditionalField     = (24189224)
ts_rx      : ConditionalField     = (24189224)
ts_tx      : ConditionalField     = (24189224)
gw         : ConditionalField     = ('0.0.0.0')
ptr        : ConditionalField     = (0)
reserved   : ConditionalField     = (0)
addr_mask  : ConditionalField     = ('0.0.0.0')
unused     : ConditionalField     = (0)
>>>

In the above example, “Ether”, “IP”, “ICMP” are protocols/layers’ name supported by scapy. If you type command ls(), it will tell all layers/protocols.

More, scapy has command like below commands to send and receive response packet:

  • sr(): Send and receive packets at layer 3
  • sr1(): Send packets at layer 3 and return only the first answer
  • srp(): Send and receive packets at layer 2
  • srp1(): Send and receive packets at layer 2 and return only the first answer

see example below: it use sr1() and srp1() to send a ping packet to www.cisco.com, and receives back response successfully.

>>> p=sr1(IP(dst="www.cisco.com")/ICMP())
Begin emission:
.Finished to send 1 packets.
*
Received 2 packets, got 1 answers, remaining 0 packets
>>> p.summary()
'IP / ICMP 23.56.123.188 > 10.0.2.15 echo-reply 0 / Padding'
>>> p2=srp1(Ether()/IP(dst="www.cisco.com")/ICMP())
Begin emission:
Finished to send 1 packets.
*
Received 1 packets, got 1 answers, remaining 0 packets
>>> p2.summary()
'Ether / IP / ICMP 23.56.123.188 > 10.0.2.15 echo-reply 0 / Padding'
>>>

running Scapy in a Python Script

see example below to do the exact same as above to ping www.cisco.com

weng@weng-u1604:~$ cat test-scapy.py 
from scapy.all import *
 
print sr1(IP(dst="www.cisco.com")/ICMP()).summary()

weng@weng-u1604:~$ sudo python test-scapy.py 
WARNING: No route found for IPv6 destination :: (no default route?)
Begin emission:
.Finished to send 1 packets.
*
Received 2 packets, got 1 answers, remaining 0 packets
IP / ICMP 23.56.123.188 > 10.0.2.15 echo-reply 0 / Padding
weng@weng-u1604:~$ 

For more details, check http://www.secdev.org/projects/scapy/doc/usage.html.