Messing around with Scapy

Here’s a really cool tool I’ve meaning to talk about for a while - Scapy!

What Heck is Scapy?

Scapy is a Python library specifically for messing with packet crafting, whether it’s ethernet, WiFi, CAN, Bluetooth, etc. Additionally, it’s a great tool to work with custom protocols that may not necessarily have tools readily available for reading and sending packets.

Hands on Learning

Easiest way to get familiar with Scapy is to just start messing with it.


I’ll be using my current Linux platform, Ubuntu 18.04. Since I like to keep my Python packages separated, I use virtualenvwrapper to install Scapy with. You can install it with pip but I like to have at least the latest tag (2.4.4 as of this post) on mine.

# make virutalenv scapyenv with Python3
mkvirtualenv scapyenv -p $(which python3)
# if you're not dropped in the virtualenv, activate it
workon scapyenv
# clone, checkout tag 2.4.4, and install
git clone
cd scapy
git checkout tags/v2.4.4 
python install

Scapy should now be installed in your virtualenv scapyenv. You can start the Scapy CLI just like this and it should start up:

(scapy) daniel@thinking:~/scapy$ scapy
INFO: Can't import matplotlib. Won't be able to plot.
INFO: Can't import PyX. Won't be able to use psdump() or pdfdump().
INFO: Can't import python-cryptography v1.7+. Disabled WEP decryption/encryption. (Dot11)
INFO: Can't import python-cryptography v1.7+. Disabled IPsec encryption/authentication.
WARNING: IPython not available. Using standard Python shell instead.
AutoCompletion, History are disabled.
             apyyyyCY//////////YCa       |
            sY//////YSpcs  scpCY//Pp     | Welcome to Scapy
 ayp ayyyyyyySCP//Pp           syY//C    | Version 2.4.4
 AYAsAYYYYYYYY///Ps              cY//S   |
         pCCCCY//p          cSSps y//Y   |
         SPPPP///a          pP///AC//Y   |
              A//A            cyP////C   | Have fun!
              p///Ac            sC///a   |
              P////YCpc           A//A   | Craft packets like I craft my beer.
       scccccp///pSP///p          p//Y   |               -- Jean De Clerck
      sY/////////y  caa           S//P   |
       cayCyayP//Ya              pY/Ya
        sY/PsY////YCc          aC//Yp 
         sc  sccaCY//PCypaapyCP//YSs  

Since I didn’t install the other packages into my virtualenv, Scapy complains a little but you can use it as is.

Interface Setup

In this case, I’ll be collecting and crafting WiFi packets, so I’ll need to prep the interface a bit for that. You’ll first need to put the wireless interface into monitor mode in order to both listen to packets over the air and inject packets over the air.

(scapy) daniel@thinking:~$ iw dev
	Interface wlan0
		ifindex 22
		wdev 0x300000003
		addr b4:74:9f:a7:03:51
		type managed
		txpower 20.00 dBm
(scapy) daniel@thinking:~$ sudo ip link set wlan0 down
(scapy) daniel@thinking:~$ sudo iw phy phy1 interface add wlan0mon type monitor
(scapy) daniel@thinking:~$ sudo iw dev wlan0 del
(scapy) daniel@thinking:~$ sudo ip link set wlan0mon up

So what happened here? What we did was essentially what a tool like aircrack-ng does under the hood when you set your interface into monitor mode, except here we used Linux’s built-in iw and ip tools to interact with our wireless interfaces. First, with iw dev, we show a list of all our active wireless interfaces, mine being wlan0. If it’s active, we need to shut it down with ip link set wlan0 down. Next, we create our virtual interface (wlan0) off of the physical interface (phy1)(1), using iw phy phy1 interface add wlan0mon type monitor. Delete our old virtual managed interface iw dev wlan0 del, and we can bring our new virtual monitor interface up with ip link set wlan0mon up. If you didn’t get any errors, the result should look something like this:

(scapy) daniel@thinking:~$ iw dev
	Interface wlan0mon
		ifindex 23
		wdev 0x300000004
		addr b4:74:9f:a7:03:51
		type monitor
		channel 1 (2412 MHz), width: 20 MHz (no HT), center1: 2412 MHz
		txpower 20.00 dBm

Depending on your use case, it may be important to note the channel and width parameters your interface is showing. If you want to send packets to a device on channel 3 that has a bandwidth (width) of 40MHz, you’ll need to set your interface to that channel on that width in order for your computer and that device to have a conversation on, with something like this:

(scapy) daniel@thinking:~$ sudo iw dev wlan0mon set channel 3 HT40+

If you get lost, iw provides helpful hints:

Usage:	iw [options] dev <devname> set channel <channel> [NOHT|HT20|HT40+|HT40-|5MHz|10MHz|80MHz]

Packet Sniffing

Let’s dip our feet into packet sniffing first with Scapy. Using dedicated tools like Wireshark is preferred if you’re trying to dig into what’s in a packet with a GUI, but Scapy can work in a pinch as well. Let’s try running the Scapy CLI.

(wifi) daniel@thinking:~$ scapy

In the terminal, sniff() is how we capture packets. We will need to provide the interface name at iface. The prn argument is an action you do for each packet received. In this case, we use a lambda function to simply show the packet summary.

>>> sniff(iface="wlan0mon", prn=lambda x: x.summary())
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/daniel/.virtualenvs/scapy/lib/python3.6/site-packages/scapy-2.4.4-py3.6.egg/scapy/", line 1054, in sniff
    sniffer._run(*args, **kwargs)
  File "/home/daniel/.virtualenvs/scapy/lib/python3.6/site-packages/scapy-2.4.4-py3.6.egg/scapy/", line 925, in _run
    *arg, **karg)] = iface
  File "/home/daniel/.virtualenvs/scapy/lib/python3.6/site-packages/scapy-2.4.4-py3.6.egg/scapy/arch/", line 446, in __init__
    self.ins = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(type))  # noqa: E501
  File "/usr/lib/python3.6/", line 144, in __init__
    _socket.socket.__init__(self, family, type, proto, fileno)
PermissionError: [Errno 1] Operation not permitted

If you run into this issue, the reason is because you’re not running Scapy as root, which is necessary because access to our wireless interface requires root priviledges. There are some (messy) ways around this such as with setcap but that is not a great way to do things. For now, easiest way is to close the session and restart with sudo $(which scapy), which will allow your virtualenv’s Python binary to run Scapy as root.

>>> sniff(iface="wlan0mon", prn=lambda x: x.summary())
RadioTap / Dot11FCS / Dot11Beacon / SSID='NeighborSSID' / Dot11EltRates / Dot11EltDSSSet / Dot11Elt / Dot11EltERP / Dot11EltRates / Dot11Elt / Dot11EltHTCapabilities / Dot11EltRSN / Dot11Elt / Dot11Elt / Dot11EltVendorSpecific / Dot11EltVendorSpecific / Dot11EltVendorSpecific / Dot11EltRSN

Packet Crafting

Now that we can sniff packets, let’s try making them. Here, we can craft a beacon frame with an SSID of our choosing.

>>> beacon_pkt = RadioTap()/Dot11(addr1="ff:ff:ff:ff:ff:ff", addr2="aa:bb:cc:dd:ee:ff", addr3="aa:bb:cc:dd:ee:ff")/Dot11Beacon()/Dot11Elt(ID='SSID', info="MySSID")
###[ RadioTap ]### 
  version   = 0
  pad       = 0
  len       = None
  present   = None
  notdecoded= ''
###[ 802.11 ]### 
     subtype   = Beacon
     type      = Management
     proto     = 0
     FCfield   = 
     ID        = 0
     addr1     = ff:ff:ff:ff:ff:ff (RA=DA)
     addr2     = aa:bb:cc:dd:ee:ff (TA=SA)
     addr3     = aa:bb:cc:dd:ee:ff (BSSID/STA)
     SC        = 0
###[ 802.11 Beacon ]### 
        timestamp = 0
        beacon_interval= 100
        cap       = 
###[ 802.11 Information Element ]### 
           ID        = SSID
           len       = None
           info      = 'MySSID'

Here, we created a packet beacon_pkt where we appended each layer necessary to create a valid packet with a backslash. RadioTap() is the header your wireless card will fill out for you regarding physical properties like the channel. Dot11() is where we put information needed about who the reciever is, and who the sender is. In this case(2), for beacon frames, addr1 is the receiver/destination address and is set as the broadcast address, ff:ff:ff:ff:ff:ff. addr2 is the transmitter/source address, which I set as aa:bb:cc:dd:ee:ff, but you can use any other MAC address you’d like. addr3 is the BSSID, which is essentially the network address. Then we append Dot11Beacon() to designate that this is a management frame beacon packet. Finally, we add the Dot11Elt(), or the Dot11 element with an ID of SSID, or 0(3). We are now ready to send the packet!

>>> sendp(beacon_pkt, iface="wlan0mon")
Sent 1 packets.

If we open up Wireshark in another window and start capturing while sending, we’d see our packet being sent out as a valid beacon packet.


Scripting it

So that was pretty easy to do. Let’s script it, as it is Python after all.

from scapy.all import (RadioTap, Dot11, Dot11Beacon, Dot11Elt, sendp)

ssids = ["Never", "Gonna", "Give", "You", "Up"]
while True:
    for s in ssids:
        beacon_pkt = RadioTap()/Dot11(addr1="ff:ff:ff:ff:ff:ff",
            addr3="aa:bb:cc:dd:ee:ff")/Dot11Beacon()/Dot11Elt(ID='SSID', info=s)
        sendp(beacon_pkt, iface="wlan0mon")

Now to run it with sudo $(which python)

Never saw that coming

Well, close enough. The “You” part is somewhere down below but you can’t choose the order your WiFi card sees these packets unless you boost your transmission power levels, which is a no-no with the FCC.

And that’s it! A gentle introduction to Scapy.

  1. There can be multiple virtual interfaces off of a single physical interface, i.e. your built-in WiFi card, that lets you do things like create and route multiple networks. This is how your wireless router can provide a guest network and a personal network on a single wireless interface. 

  2. These addresses will depend on the type and subtype of the packet. Data and management frames for address fields generally are the same. Control frames however can vary widely, and may even only have only one address field. Here’s a quick source for to/from DS fields. 

  3. In beacon packets, everything clients need to know about the network is appended as an element to the beacon packet, such as its encryption, what rates it uses, high throughput capabilities, vendor information, etc. Each element is designated with an ID, the length of the element, and the payload. 

Written on November 11, 2020