The idea for this post came about from just wanting to play around with Scapy. Not having spent any time on it before, it was high time to see what this excellent packet crafting tool was all about. There was no well-defined end goal here, only a desire to dissect some packets. In the end (about 20 hours later), what came out of it is essentially a Wi-Fi network reconnaissance tool. As such, it almost feels obligatory to have a disclaimer stating that the content provided here is for educational purposes only, if it wasn’t for the fact that the tool is harmless (besides exposing networks with weak security that is…).
What does the Wi-Fi scanner tool do? It uses the Scapy sniff module to capture wireless frames, and analyses their contents to display a list of wireless networks and their properties. It displays BSSID, SSID, RSSI, frequency on which the frame was received, as well as the cipher & AKM suites which give us the configured security level. The last parameter is the age of the beacon, the entries are aged out after 30 seconds, but this is configurable. Each entry is updated in real time, then the results are sorted by RSSI and displayed using varying colours from strongest to weakest.
A few words on the lab environment. Here we have a Debian Buster VM (Raspberry Pi Desktop) running on a Windows 10 host using VMware Workstation 15 Player. The wireless adapter is a tp-link Archer T9UH using the freely available aircrack-ng drivers. Getting a combination of hardware and software suitable for this task (i.e. wireless sniffing) can be a hassle, as can getting it running even after you find a combination that works. For a bit more on this see an earlier post titled Wireless Capture Analysis. As Python (3) comes pre-installed on Debian the only additional software is Scapy itself, easily added using pip3 install scapy.
All of the required information is available by looking into the various frame headers of the beacon frame, or layers, as they are referred to in Scapy. The link below shows a Scapy dump of all available layers from a captured beacon frame.
Now its just a case of extracting the information we are interested in.
RSSI and frequency are in the RadioTap header/layer, and give us the parameters of the frame as it was received. The RadioTap header is added by the driver of the interface performing the capture.
The BSSID can be found in the 802.11-FCS layer (or Frame Control Field in 802.11 parlance), and corresponds to the transmitting station’s MAC address. Recall that 802.11 communication uses up to four MAC addresses for each frame, depending on the type of transmission. Address 2 (addr2) represents the TA or Transmitter Address. For a Beacon frame this is the radio MAC address of the transmitting AP, exactly what we are looking for.
For the SSID value we have to look at the Dot11Beacon layer, or specifically, the Information Element immediately following the Dot11Beacon layer. This also doesn’t quite correspond to the way that (say) Wireshark would decode the information, the IE order is different, but it doesn’t really affect anything from a scripting point of view.
Last but not least, the network security information can be found in the RSN Information Element. Scapy presents this as two layers, RSNCipherSuite and AKMSuite, and also decodes the value for each as a decimal number. The tables below show the possible values for Cipher and AKM suites, representing the configured network encryption and authentication types respectively.
Raw | Encryption | Scapy |
00-0F-AC-01 | WEP40 | 1 |
00-0F-AC-02 | TKIP | 2 |
00-0F-AC-04 | CCMP | 4 |
00-0F-AC-05 | WEP104 | 5 |
Raw | Authentication | Scapy |
00-0F-AC-01 | 802.1X | 1 |
00-0F-AC-02 | PSK | 2 |
00-0F-AC-03 | 802.1X (FT) | 3 |
To capture wireless frames the network interface needs to be in monitor mode. This can be achieved with the following commands:
sudo ip link set wlan0 down sudo iw wlan0 set type monitor sudo ip link set wlan0 up sudo iw wlan0 set channel 36
There are some caveats however, as there are a number of system services that can interfere with the scanning/sniffing process and should be disabled. The airmon-ng tool is very useful here and will provide a list of services that may need to be disabled.
After some testing though, it seems that the dhcpcd and wpa_supplicant services are the real culprits here (at least in this case). Particularly the dhcpcd service, as it appears to initiate periodic channel scans (!?) about once every second, which means you may miss traffic if trying to capture only a single channel. In this case we are not capturing all traffic, only beacons, so it’s not as much of a problem. However, if you do want to scan a single channel, it is best to disable the dhcpcd service, and possibly the wpa_supplicant service too.
Scanning can also be performed while cycling through all channels, allowing for a wider view of available networks. For this, instead of writing code to change channels periodically, we can just use the channel scanning ‘feature’ of dhcpcd, and simply leave the dhcpcd service enabled.
Pseudocode
Function IF packet has Dot11Beacon layer Set Timestamp from current time Load RadioTap layer Set Signal variable from RadioTap field dBm_AntSignal Set Frequency variable from RadioTap field ChannelFrequency Load Dot11FCS layer Set Bssid variable from Dot11FCS field addr2 Load Dot11Beacon layer Load next Information Element Set Ssid variable from Information Element field info IF packet has RSNCipherSuite layer Load RSNCipherSuite layer Set Cipher variable from RSNCipherSUite field cipher (map against list of known values) ELSE Set Cipher variable to Open IF packet has AKMSuite layer Load AKMSuite layer Set Akm variable from AKMSuite field suite (map against list of known values) ELSE Set Akm variable to Open Assign Bssid, Signal, Ssid, Timestamp, Frequency, Cipher, Akm to nested AP dictionary Clear screen Print headings LOOP through AP dictionary, sorted by Signal value Set Beacon_age = Timestamp – Saved AP value IF Beacon_age less than Max_beacon_age IF Signal below -65 Print Ssid, Signal, Frequency, Cipher, Akm in green IF Signal between -65 and -50 Print Ssid, Signal, Frequency, Cipher, Akm in yellow IF Signal between -49 and -40 Print Ssid, Signal, Frequency, Cipher, Akm in red IF Signal above -40 Print Ssid, Signal, Frequency, Cipher, Akm in magenta ELSE delete AP record Main code Set Max_beacon_age Define empty AP dictionary Run Scapy sniff module, pass each packet to Function, print output from Function
Real code