Using Scapy to send WLAN frames

Scapy[1] is one mighty python tool to create, receive and manipulate various network packets and it comes with a very handy CLI as well. If you ever had the need to create specific network packets within a program, I suggest you use python together with scapy to do so. Let’s start with a short introduction to the scapy CLI and end with a small example of scapy within python code. With the right kind of WLAN interface it is even possible to create a connection to an unencrypted SSID and I am going to publish the steps to do so in another post.

Pleate note: The following commands were made on a Fedora 23 Linux system with an TP-Link TL-WN727N (v4.1) WLAN USB Stick. The scapy package can be installed as python-scapy (Ubuntu) or just scapy (Fedora), wireshark is also required. Your linux and python skills should be quite solid and include knowledge about the use of ifconfig and iwconfig in linux as well as class definition and inheritance in python.

Scapy CLI

After using a bash to call “scapy”, the CLI should show up and should look like this (Your version might be different):

Welcome to Scapy (2.2.0)
>>>

You can take a look at all the predefined protocols via the “ls()” command…

>>> ls()
ARP : ARP
ASN1_Packet : None
BOOTP : BOOTP
CookedLinux : cooked linux
DHCP : DHCP options [...]

… and get some details about them via “ls(<PROTOCOL>)” e.g. ls(ARP):

>>> ls(ARP)
hwtype : XShortField = (1)
ptype : XShortEnumField = (2048)
hwlen : ByteField = (6)
plen : ByteField = (4)
op : ShortEnumField = (1)
hwsrc : ARPSourceMACField = (None)
psrc : SourceIPField = (None)
hwdst : MACField = ('00:00:00:00:00:00')
pdst : IPField = ('0.0.0.0')

Since we want to send WLAN packets, the following predefined protocols can be used “out of the box”:

Dot11 : 802.11
Dot11ATIM : 802.11 ATIM
Dot11AssoReq : 802.11 Association Request
Dot11AssoResp : 802.11 Association Response
Dot11Auth : 802.11 Authentication
Dot11Beacon : 802.11 Beacon
Dot11Deauth : 802.11 Deauthentication
Dot11Disas : 802.11 Disassociation
Dot11Elt : 802.11 Information Element
Dot11ProbeReq : 802.11 Probe Request
Dot11ProbeResp : 802.11 Probe Response
Dot11QoS : 802.11 QoS
Dot11ReassoReq : 802.11 Reassociation Request
Dot11ReassoResp : 802.11 Reassociation Response
Dot11WEP : 802.11 WEP packet
[...]
RadioTap : RadioTap dummy

A quick example of how to send a Dot11 frame, e.g. a Null Frame, from an AP (e.g. MAC = 00:a0:57:98:76:54) to a Client (STA) (e.g. MAC = 00:a0:57:12:34:56) is:

>>> ls(Dot11)
subtype : BitField = (0)
type : BitEnumField = (0)
proto : BitField = (0)
FCfield : FlagsField = (0)
ID : ShortField = (0)
addr1 : MACField = ('00:00:00:00:00:00')
addr2 : Dot11Addr2MACField = ('00:00:00:00:00:00')
addr3 : Dot11Addr3MACField = ('00:00:00:00:00:00')
SC : Dot11SCField = (0)
addr4 : Dot11Addr4MACField = ('00:00:00:00:00:00')
>>> packet = Dot11(addr1="00:a0:57:12:34:56", addr2="00:a0:57:98:76:54", addr3="00:a0:57:98:76:54", type=2, subtype=4)

The addr1 field defines the destination/receiver, addr2 the source/transmitter and addr3 the BSSID of the frame. The Null frame is defined as type 2 and subtype 4. One of the most powerful possibilities of the scapy CLI is the direct call of wireshark to inspect a packet. This is as handy as:

>>> wireshark(packet)

A wireshark window should open and display the packet as shown in the following screenshot:
Scapy Packet in Wireshark

 

Creating a customized packet

Although there are a lot of predefined protocols/packets, we do need to create certain packets/protocols like e.g. information fields manually. This is where python comes into play and a more elaborate introduction can be found at [2].

We import scapy and create a class that is derived from scapy.Packet. Besides the class member name, the fields_desc is the most important variable. With the help of ByteField from scapy, we can create new information elements like ID and length of the included elements as well as the supported rates themselves by setting up a list of supported rates in hexadecimal notation and insert these values in ByteFields. Since each ByteField is unique, the names of the fields for supported rates need to differ, that’s why we enumerate the values and create them with supported_rate1, supported_rate2 and so on.

from scapy.all import *

class Dot11EltRates(Packet):
    """ Our own definition for the supported rates field """
    name = "802.11 Rates Information Element"
    # Our Test STA supports the rates 6, 9, 12, 18, 24, 36, 48 and 54 Mbps
    supported_rates = [0x0c, 0x12, 0x18, 0x24, 0x30, 0x48, 0x60, 0x6c]
    fields_desc = [ByteField("ID", 1), ByteField("len", len(supported_rates))]
    for index, rate in enumerate(supported_rates):
        fields_desc.append(ByteField("supported_rate{0}".format(index + 1),
                                     rate))

You can either enter the values of the defined fields as hexadecimal ones with “0x” in front or as integers like the value of the “ID” ByteField in the example above. It is important that the byte value of the “len” field matches length of the successive byte elements.

Sending a (customized) packet

If we want to send a packet via python/scapy, we first have to set up our monitoring interface, which will be our USB WLAN Stick. We can use the command “iwconfig” in a bash to find the all the installed WLAN interfaces, use “ifconfig” to identify the right one via MAC address if you got more than one WLAN interface. In my case the TP-Link WLAN USB Stick is named “wlp0s29u1u7”. For the following steps, you need to be root or use sudo to configure the stick to the required monitor mode.

1) Turn the interface down via “ifconfig wlp0s29u1u7 down”
2) Set the device to monitor mode via “iwconfig wlp0s29u1u7 mode monitor”
3) Turn the interface back on via “ifconfig wlp0s29u1u7 up”
4) Set the interface to a certain channel via “iwconfig wlp0s29u1u7 chan 6”
5) If the settings were correctly adapted, the output of “iwconfig wlp0s29u1u7” includes “Mode:Monitor” and “Frequency:2.437 GHz” (for channel 6 in this example).

We can now use this interface to send packets via scapy. The following python code builds an association request packet via the predefined Dot11, Dot11AssoReq and Dot11Elt packets and extends the packet with rate information that is defined in the class given above. Afterwards we use the “sendp()” command to send the packet on the defined monitoring interface.

packet = Dot11(
    addr1="00:a0:57:98:76:54",
    addr2="00:a0:57:12:34:56",
    addr3="00:a0:57:98:76:54") / Dot11AssoReq(
        cap=0x1100, listen_interval=0x00a) / Dot11Elt(
            ID=0, info="MY_BSSID")
packet /= Dot11EltRates()
sendp(packet, iface="wlp0s29u1u7")
packet.show()

The code has to be executed as root or via sudo due to the control over the network interface. The script will output a warning, that no route for IPv6 destination :: could be found (happens during import of scapy and can be ignored) afterwards a “.” and the message “Sent 1 packets” should be visible, which means that the created packet was sent.

WARNING: No route found for IPv6 destination :: (no default route?). This affects only IPv6
.
Sent 1 packets.

Due to the packet.show() command at the end of our code, scapy will also print the packet to the standard output:

###[ 802.11 ]###
 subtype = 0
 type = Management
 proto = 0
 FCfield = 
 ID = 0
 addr1 = 00:a0:57:98:76:54
 addr2 = 00:a0:57:12:34:56
 addr3 = 00:a0:57:98:76:54
 SC = 0
 addr4 = 00:00:00:00:00:00
###[ 802.11 Association Request ]###
 cap = ESS+privacy
 listen_interval= 10
###[ 802.11 Information Element ]###
 ID = SSID
 len = None
 info = 'MY_BSSID'
###[ 802.11 Rates Information Element ]###
 ID = 1
 len = 8
 supported_rate1= 12
 supported_rate2= 18
 supported_rate3= 24
 supported_rate4= 36
 supported_rate5= 48
 supported_rate6= 72
 supported_rate7= 96
 supported_rate8= 108

You might have noticed that scapy does not print the length of the 802.11 Information Element containing the SSID info. The length is calculated automatically while sending the packet and not while it is printed. See the following screenshot in the Conclusion for proof.

Conclusion

Within the examples given in this post, we are able to create packets based on predefined ones in scapy and are able modify or create certain elements via python. If you are able to sniff the packet on the air, we can take a look at it in wireshark:

Scapy Assoc Packet in Wireshark

Following up is the complete code.

from scapy.all import *

class Dot11EltRates(Packet):
    """ Our own definition for the supported rates field """
    name = "802.11 Rates Information Element"
    # Our Test STA supports the rates 6, 9, 12, 18, 24, 36, 48 and 54 Mbps
    supported_rates = [0x0c, 0x12, 0x18, 0x24, 0x30, 0x48, 0x60, 0x6c]
    fields_desc = [ByteField("ID", 1), ByteField("len", len(supported_rates))]
    for index, rate in enumerate(supported_rates):
        fields_desc.append(ByteField("supported_rate{0}".format(index + 1),
                                     rate))

packet = Dot11(
    addr1="00:a0:57:98:76:54",
    addr2="00:a0:57:12:34:56",
    addr3="00:a0:57:98:76:54") / Dot11AssoReq(
        cap=0x1100, listen_interval=0x00a) / Dot11Elt(
            ID=0, info="MY_BSSID")
packet /= Dot11EltRates()
sendp(packet, iface="wlp0s29u1u7")
packet.show()

Links

[1] http://www.secdev.org/projects/scapy/
[2] http://www.secdev.org/projects/scapy/doc/build_dissect.html

31 thoughts on “Using Scapy to send WLAN frames

  1. Hi, thank you for your detailed post.
    I also created a script to send 80211 frames using Scapy some
    time ago, but I’m facing a problem:

    You’re defining the adapters capabilities and supported rates in the variable “supported_rates”.
    But what if I don’t know which wifi adapter I will use or if I want to develop a script that is compatible to a large number of wifi adapters?

    Do you know a way how to read the current wifi adapters capabilities with python in order to automatically use those for sending Association frames?

    Regards,
    Bastian

    Like

    • Hello Bastian,
      sorry for this quite late response. I don’t think that there should be a problem with the wifi adapters since they usually support all 11a/g rates. Nonetheless, there might be some WLAN APs with configurations that exclude certain 11a/g rates within the basic rate field for various reasons.

      You could sniff the beacons from the WLAN AP you want to attack and adjust the supported rates field accordingly, which can be done either manually or via script. However, I found it rather complicated to modify this field dynamically and did not succeed with my initial attempt to implement that. But I guess it’s still possible to do.

      Like

  2. hello ! i find how to send packet due to your post, Thank.
    and I’d like to receive packet by using srp function. Do you know how to receive Probe response, Authtification, Association response packet?
    I capture packet on wireshark, Probe responses, Auth packetes are sended to LAN card that i forge packet’s MAC address

    Like

    • Hi there,
      since I’ve been on vacation the last two weeks, I didn’t have access to my code. Is this still bothering you?

      kind regards,
      mtroi

      Like

  3. Hey there,
    thank you for this quick-start – works like a charm and helps a lot in reverse engineering and understanding some protocols.

    Btw… is there any good documentation around? I am in search for all available cap flag e.g.

    Like

  4. I am unable to send packets using the script above. System says it sent the packet, but when I capture on Wireshark on MacBook Pro, I can’t see the packet I sent but I can see all other packets. What else could be wrong ? Sending the packets on 2019 Kali linux.

    Like

    • Hello,
      scary hands the packet over to the adapter and tells you that it sent the packet, but it’s no prove that it actually did. What kind of adapter are you using? Do you operate the adapter on a channel/band that it does not support?

      Regards,
      mtroi

      Like

  5. Hello mtroi,

    Thanks for the post. I wonder if scapy would be able to send the WiFi DATA packet? At the receiver end, are we able to capture the ACK packet? I have read several posts and examples but haven’t seen any posts regarding wifi data and ACK packets. Many of the posts talked about the Beacon, probe request, etc.

    Thanks.

    Justin

    Like

      • Thanks mtroi. Glad to know it is doable.

        I want to ask a follow-on question. Say I have two devices, one configured as AP and another one configured as STA. When I use scapy to send DATA packet from AP to STA, after the STA receives the DATA packet successfully, it will reply an ACK packet to the AP. Will the AP be able to capture the ACK packet in Scapy?

        The reason that I am asking this is that the STA will reply with the ACK packet after the SIFS, which is in the order of 10 microseconds. Will there be sufficient time for the AP to switch between the transmission mode and the receiving mode?

        Like

      • Why shouldn’t it? Since this is the norm, I would assume you can also see that frame in scapy. Go ahead and try it and give feedback here. 🙂

        Like

  6. good
    good
    good
    But I would like to know that I have established a beacon hotspot through scapy. When I try to connect to this hotspot with a device, the device will not actually initiate a connection request. I cannot capture any probe response or Auth/Assoc etc. Response packet. It seems useless. If I want to use python scapy to fully implement a real hotspot similar to airbase or hostapd, can I refer to those examples?

    Like

  7. When I run, it will report a process error. Is the scapy version mismatched? Or other errors? My scapy version is: Version 2.4.4.dev189
    ———————————————————————-BUG——————————
    Scanning max 5 seconds for Authentication from BSSID 00:13:ef:f2:07:d7
    Process Process-2:
    Traceback (most recent call last):
    File “/usr/lib/python3.6/multiprocessing/process.py”, line 258, in _bootstrap
    self.run()
    File “/usr/lib/python3.6/multiprocessing/process.py”, line 93, in run
    self._target(*self._args, **self._kwargs)
    File “/home/helen/Desktop/state/monitor_ifc.py”, line 47, in send_packet
    send(packet)
    File “/usr/local/lib/python3.6/dist-packages/scapy-2.4.4.dev189-py3.6.egg/scapy/sendrecv.py”, line 366, in send
    *args, **kargs
    File “/usr/local/lib/python3.6/dist-packages/scapy-2.4.4.dev189-py3.6.egg/scapy/sendrecv.py”, line 339, in _send
    realtime=realtime, return_packets=return_packets)
    File “/usr/local/lib/python3.6/dist-packages/scapy-2.4.4.dev189-py3.6.egg/scapy/sendrecv.py”, line 313, in __gen_send
    s.send(p)
    File “/usr/local/lib/python3.6/dist-packages/scapy-2.4.4.dev189-py3.6.egg/scapy/arch/linux.py”, line 558, in send
    self.outs.bind(sdto)
    TypeError: argument 1 must be str, not NetworkInterface
    STA is NOT authenticated to the AP!
    Wrong connection state for Association Request: Not Connected – should be Authenticated
    STA is NOT connected to the AP!

    Like

    • Sorry, I haven’t run this code in a long time. I can’t guarantee you, that it runs on latest scapy versions. Maybe you can debug it and send an update?

      Like

  8. Unfortunately, I tried your code and it is different from what I thought.
    What I want to ask you is whether you can use scapy to create a real AP hotspot, just like the hotspot Hostapd that can interact with STA devices. It’s not simply sending a Beacon Frame

    Like

    • Good question, I never tried that as I usually require a client or client functunality to test our Access Points. Therefore, we use scapy for client connections. Sorry, I can’t help you with that.

      Like

  9. Thanks for the tutorial. To validate if the packet is successfully transmitted, I tried to use sniffer tools to capture the generated packets. However, for example, I tried the very simple data packet, as mentioned in the article, “packet = Dot11(addr1=”00:a0:57:12:34:56″, addr2=”00:a0:57:98:76:54″, addr3=”00:a0:57:98:76:54″, type=2, subtype=4)”. I do change the MAC address, established the connection, but still cannot capture this packet. Do you have any suggestion upon this issue? Thanks.

    Like

    • Hi Terry,
      does your sniffer matches the capabilities of the client regarding number of spatial streams (antennas) and Wi-Fi generation? If you can at least capture the management packets on the air, but just not the data packets, then a mismatch in capabilities is usually the reason.

      Like

  10. Hi, it’s a good tutorial but when I use sendp to send the packet It is not sent, on wireshark I see that the mac address source is changed to randomized source address by Windows and I ‘ve an warning: source mac must not be a group address . I’m on windows 10

    Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.