Wireless Sniffing: How to Build a simple WiFi Sniffer in Python

by vault . 21 July 2018

Wireless Sniffing: How to Build a simple WiFi Sniffer in Python

In this tutorial, we are going to build a wireless sniffer using Python by manipulating fields from captured packets. You might have seen airodump working before and had observed that how it excellently sniff and manipulate packets over the air while hopping through random channels. We will try to extract network ESSIDs and will take utmost care of various possibilities of how the packet is sequenced.

But before, let's dig into the beacon frames. The beacon frame is one of the management frames in 802.11 specifications by IEEE and is sent from Access Point to let other stations know of its presence. This helps stations recognizing various characteristics like Cipher, Channel and encryption the AP is using. Tough we will not study any other packet but such an identical frame is Probe response frame which is also sent from APs.

Prerequisities

A bit knowledge of networking and previous experience with scapy and Python. Scapy is what we are going to use throughout the rest of this tutorial.

STEP 1

Scapy and Interface

Install scapy using pip:

$ sudo pip install scapy

This would install the latest version of scapy yet available. As of we are going to do wireless sniffing, we will need to card to be operating in Monitor mode. Put your wireless interface in monitor mode with airmon-ng:

$ airmon-ng start wlan1
airmon-ng

Note that the interface was renamed from wlan1 to wlan1mon.

STEP 2

Channel Hopping

We will need to listen on all channels to almost grab every dispatched frame and also we will have to do this in the background. So, the sniffing environment would not get polluted. Well, if you feel like skipping this part, you can use airodump in place of coding this.

iwconfig will do the work for us. We will constantly change channels and define it as a thread:

import threading
import os, time
import random

def hopper(iface):
    n = 1
    stop_hopper = False
    while not stop_hopper:
        time.sleep(0.50)
        os.system('iwconfig %s channel %d' % (iface, n))
        print "Current Channel %d" % (n)
        dig = int(random.random() * 14)
        if dig != 0 and dig != n:
            n = dig

if __name__ == "__main__":
    thread = threading.Thread(target=hopper, args=('wlan1mon', ), name="hopper")
    thread.daemon = True
    thread.start()

    while True:
        pass

Go on save the file and test the program:

hopper

STEP 3

Sniffing

Now, we need to sniff wireless frames. For this we have sniff function from scapy. It takes a few arguments as input and accordingly perform operations on each packet if provided. The syntax of sniff function is:

>>> sniff(count, store, offline, prn, lfilter, L2socket, \
    timeout, opened_socket, stop_filter, iface, *args, **kwargs)

Note the arguments:

  • count: Number of packets to capture
  • store: Whether to store the frames or discard them.
  • offline: Read packets from a file
  • prn: Function to apply on each packet
  • lfilter: Function to further work on the captured packet.
  • L2socket: Layer 2 socket provided.
  • timeout: Number of seconds after which to stop
  • stop_filter: Function to determine when to stop.
  • iface: Interface to use. In our case wlan1mon

STEP 4

Filtering Frames

As of now, we have the sniffing function which will capture the wireless frames. We will use prn function to filter Beacon frames and perform further actions on each of it. Beacon frame is comprised of many fields like timestamp, interval, ssid, rates and TIM etc. If you feel like exploring more about Beacon frames, navigate to this link

...

from scapy.all import *

F_bssids = []    # Found BSSIDs
def findSSID(pkt):
    if pkt.haslayer(Dot11Beacon):
       if pkt.getlayer(Dot11).addr2 not in F_bssids:
           F_bssids.append(pkt.getlayer(Dot11).addr2)
           ssid = pkt.getlayer(Dot11Elt).info
           print "Network Detected: %s" % (ssid)

if __name__ == "__main__":
    interface = "wlan1mon"
    thread = threading.Thread(target=hopper, args=('wlan1mon', ), name="hopper")
    thread.daemon = True
    thread.start()

    sniff(iface=interface, prn=findSSID)

When any of packet will get detected by the card, it will be send to function findSSID as an argument. Further, in the findSSID function, we verified if the packets have the Beacon layer to make sure that the packet was sent from an AP. Next, to this, we need to verify if we've already detected the network for which we need the MAC of AP which is stored in the addr2 field of the Dot11 layer.

The F_bssids will help us with this by maintaining the list of already found networks. There are various Element layers in a beacon frame. Fortunately, the first one stores the name of the network which is all we need right now. Otherwise, you will have to loop through each layer and find related information.

STEP 5

Beating Hidden Wireless Networks

For hidden networks, there could be two possibilities, either there is no element layer for ESSID or the ESSID layer is empty. To detect hidden networks, we further have to filter both of these possibilties:

...

from scapy.all import *

F_bssids = []    # Found BSSIDs
def findSSID(pkt):
    if pkt.haslayer(Dot11Beacon):
       if pkt.getlayer(Dot11).addr2 not in F_bssids:
           F_bssids.append(pkt.getlayer(Dot11).addr2)
           ssid = pkt.getlayer(Dot11Elt).info
           if ssid == '' or pkt.getlayer(Dot11Elt).ID != 0:
               print "Hidden Network Detected"
           print "Network Detected: %s" % (ssid)

if __name__ == "__main__":
    interface = "wlan1mon"
    thread = threading.Thread(target=hopper, args=('wlan1mon', ), name="hopper")
    thread.daemon = True
    thread.start()

    sniff(iface=interface, prn=findSSID)

Tough, its not bulletproof, but would be helpful. This could be easily bypassed by an attacker by swapping Element Layers of a Beacon Frame. To ensure the detection, you have a to loop through each element layer and extract the required data.

STEP 6

Put it together

Finally, put all the code in a sequence:

import threading, os, time, random
from scapy.all import *

def hopper(iface):
    n = 1
    stop_hopper = False
    while not stop_hopper:
        time.sleep(0.50)
        os.system('iwconfig %s channel %d' % (iface, n))
        dig = int(random.random() * 14)
        if dig != 0 and dig != n:
            n = dig

F_bssids = []    # Found BSSIDs
def findSSID(pkt):
    if pkt.haslayer(Dot11Beacon):
       if pkt.getlayer(Dot11).addr2 not in F_bssids:
           F_bssids.append(pkt.getlayer(Dot11).addr2)
           ssid = pkt.getlayer(Dot11Elt).info
           if ssid == '' or pkt.getlayer(Dot11Elt).ID != 0:
               print "Hidden Network Detected"
           print "Network Detected: %s" % (ssid)

if __name__ == "__main__":
    interface = "wlan1mon"
    thread = threading.Thread(target=hopper, args=(interface, ), name="hopper")
    thread.daemon = True
    thread.start()

    sniff(iface=interface, prn=findSSID)

STEP 7

Wireless Sniffer

Lets try the code. Copy the code and save your file with the name essidCall.py

$ sudo python essidCall.py
essidcall

Conclusion

So, we saw to code a simple wireless sniffer in Python within a few lines. Beacon frames help us identify the networks within the area. Besides the beacon frames, probe response frames can also do the work for us. They are also sent from an Access Point as a reponse to probe request frames sent from clients.

Stations search for connected networks by transmitting the probe request packets and if the network is in the range of station, it will transmit a probe response frame towards the requested station.

Hidden networks do also transmit the beacon frames but with striped ESSID layers which results in showing no network.