WiFi Sniff: Finding Connected Clients of an Access Point with Python

by hash3liZer . 27 November 2018

WiFi Sniff: Finding Connected Clients of an Access Point with Python

Stations (Cients) of an Access Point (WiFi) are of it's important assets and can easily be detected as of data frames traveling between both devices. We have seen various attacks on the clients in some of the previous tutorials like while capturing 4-way handshake, deauthentication and while generations IV's. Altough, there are tools available for the purpose at the moment like airodump, we are going to dig with this in Python.

802.11 is the standard implemented throughout the world for wireless communication. The communication takes place over packets in small bytes in the shape of chunks. There are various frames defined each for some purpose and then there comes the data frames. Data frames are sent for the purpose of transmission of actual data. And these frames are being sent when both of the devices are connected.

However, there's another case as well. Data frames are also sent when a client tries to authenticate with Access Point but fails. So, we have to look on both scenarios while capturing these packets.

Monitor Mode...

Put your wireless Card in Monitor Mode:

$ airmon-ng start wlan1

Capture the Station.

In few past tutorials, i've widely used the function sniff from scapy library to sniff nearby packets. We'll do the same here as well. But first, we need to define a function to which a packet will be passed and will print the result.

from scapy.all import *

def pkt_callback(pkt):
    if pkt.haslayer(Dot11) and pkt.getlayer(Dot11).type == 2L:
        # This means it's data frame.
        sn = pkt.getlayer(Dot11).addr2.upper()
        rc = pkt.getlayer(Dot11).addr1.upper()

        print "Frame Sent %s > %s" % (sn, rc)

The above function begins with a condition which verifies that the received frame is a data frame and returns true. Remember that Data frames are of type 2. The next two statements assignes two variables with the addresses of sender and recevier. Note that we don't know yet which one is the AP and which is STA. And then print statement come into the scene.

Let's figure out the first mistake we had done here. If you run the script with this function, it would show every client even those who tries to authenticate with access point which fails. To prevent it, we need to filter frames with EAPOL layer. In simple:

from scapy.all import *

def pkt_callback(pkt):
    if pkt.haslayer(Dot11) and pkt.getlayer(Dot11).type == 2L and not pkt.haslayer(EAPOL):
        # This means it's data frame.
        sn = pkt.getlayer(Dot11).addr2.upper()
        rc = pkt.getlayer(Dot11).addr1.upper()

        print "Frame Sent %s > %s" % (sn, rc)

The condition has become a bit obsecure. Let's break into it as three independent conditions:

  1. Return true if the packet contains 802.11 layer. (Make sure it's a wireless frame)
  2. Return true if the 802.11 packet type is 2 which identifies that it's a data frame.
  3. Return true if the packet doesn't contain EAPOL layer.

Identifying Clients and Access Points:

In an earlier tutorial, I've shown to find access points using Beacon frame. What we will do is keep a record of BSSID of access points in range and then match extracted addresses from data frames with the BSSID. If the address matches with the BSSID, this explains that matched address is the Access Point. More lively:

from scapy.all import *

APs = []

def pkt_callback(pkt):
    if pkt.haslayer(Dot11Beacon):
        bss = pkt.getlayer(Dot11).addr2.upper()
        if bss not in APs:
            APs.append(bss)

    elif pkt.haslayer(Dot11) and pkt.getlayer(Dot11).type == 2L and not pkt.haslayer(EAPOL):
        # This means it's data frame.
        sn = pkt.getlayer(Dot11).addr2.upper()
        rc = pkt.getlayer(Dot11).addr1.upper()

        if sn in APs:
            print "AP (%s) > STA (%s)" % (sn, rc)
        elif rc in APs:
            print "AP (%s) < STA (%s)" % (rc, sn)

The above function is almost simple. The control will keep managing the APs list with the bssid of networks in range. When a data frame will be encountered, if the sender is in list then AP is the sender and vice virsa.

Sniffing...

Now, put the sniff function at the end of the program:

def pkt_callback(pkt):
    if pkt.haslayer(Dot11Beacon):
        bss = pkt.getlayer(Dot11).addr2.upper()
        if bss not in APs:
            APs.append(bss)

    elif pkt.haslayer(Dot11) and pkt.getlayer(Dot11).type == 2L and not pkt.haslayer(EAPOL):
        # This means it's data frame.
        sn = pkt.getlayer(Dot11).addr2.upper()
        rc = pkt.getlayer(Dot11).addr1.upper()

        if sn in APs:
            print "AP (%s) > STA (%s)" % (sn, rc)
        elif rc in APs:
            print "AP (%s) < STA (%s)" % (rc, sn)

if __name__ == "__main__":
    sniff(iface="wlan1mon", prn=pkt_callback)

Manufacturers of Devices (Optional)

The first three octets of a BSSID are alotted to a manufacturer just like TP-Link and Fiberhome etc. While the last three octets are uniquely assigned to a device. Well, to find who manufactured the device, we need to loop through a list containing all the available record. Download the list from github:

$ wget https://raw.githubusercontent.com/hash3liZer/WiFiBroot/master/utils/macers.txt -O bssid.txt

Add a few statements in your code and you are good to go:

from scapy.all import *

BSSID_FILE = "/root/bssid.txt"
APs = []

def manf(bss):
    file = open(BSSID_FILE, 'r')
    for line in file.read().splitlines():
        if line.split("~")[0][7] == bss[7]:
            return line.split("~")[1].rstrip("\n")
    return "unknown"

def pkt_callback(pkt):
    if pkt.haslayer(Dot11Beacon):
        bss = pkt.getlayer(Dot11).addr2.upper()
        if bss not in APs:
            APs.append(bss)

    elif pkt.haslayer(Dot11) and pkt.getlayer(Dot11).type == 2L and not pkt.haslayer(EAPOL):
        # This means it's data frame.
        sn = pkt.getlayer(Dot11).addr2.upper()
        rc = pkt.getlayer(Dot11).addr1.upper()

        if sn in APs:
            print "AP (%s) [%s] > STA (%s) [%s]" % (sn, manf(sn), rc, manf(rc))
        elif rc in APs:
            print "AP (%s) [%s] < STA (%s) [%s]" % (rc, manf(rc), sn, manf(sn))

if __name__ == "__main__":
    sniff(iface="wlan1mon", prn=pkt_callback)

Pack it up!

Now, put everything in sequence and execute it:

$ python scanner.py

Now, we know how easy it is to locate connected clients of an access point with the help of this extensive library of scapy. So, stay tuned and keep learning.