Skip to main content

Building a WiFI/BT Sniffer - part 2 (Operation)

Command line interface

Connect to the device with a USB cable, and open it in your favorite serial terminal.  Your terminal needs to be VT102 compatible for the colors to work.  

Start with the 'help' command:

Most of these should be pretty self-explanatory - commands that work with the filesystem or give information.  The `scan`, `survey`, and `config` commands will be detailed below.  For now, let's see what the status of the device shows:

Status shows a few useful pieces of information:
  • Hardware - not much to interesting; just shows the ESP32-S3 information
  • GPS - shows current lock status, long/lat coordinates, GPS time, and number of satellites.  If the GPS is not receiving good data, the display will be "NO GPS LOCK"
  • Filesystem - shows used/free space
  • Memories - shows free heap space for both internal and external RAM
  • Wall clock - synced to GPS, shows current date/time and offset as well as timezone.

Configuration

Device configuration is performed using the `config` command:

There are 9 config variables:
  • Device name - saved in the `survey` data
  • Timezone - local timezone.  GPS time is GMT, this is used to translate to local time
  • MAX LED brightness - a value from 0 to 255.  255 is unnecessarily bright - if the device is used in a vehicle after dark, a value of 255 will definitely cause issues with night vision.  Values below 10 are barely visible
  • Debug logging - enabling this will cause the device to log most actions to a plain text file in the filesystem
  • Scan logging - enabling this will cause the device to save real-time scan information to a file in the filesystem
  • Debug file count - if debug logging is enabled, a new file is created each time the device is powered up or resets.  The previous files will be "rolled".  The "current" file is named `system.log`.  On reset or power up, the existing system.log file will be renamed system.1.log; existing system.1.log will be renamed system.2.log, etc., up to this count.
  • Scan file count - works the same as debug file rolling.  Scan files are named `autoscan.log`, `autoscan.1.log`, etc.
  • Minimum RSSI - minimum signal level (both WiFi and Bluetooth) for a target to be detected.
  • Minimum alert time - when in scan mode (either manually triggered or autoscan), this is the minimum amount of time that the LEDs will flash when a target was detected.
The configuration data is stored in `config.json`.  If any changes are made, the "old" file will be saved as `config.bak`.  Changes can be reverted by copying the backup over the current and restarting ("mv config.bak config.json", and then "reset")

If the `reset` command is issued with the `--factory`, the entire filesystem is formatted (all files deleted), and default config file built.

Scan

In `scan` mode, the device will continually monitor WiFi and Bluetooth signals, "looking" for packets that match preset criteria.  When scanning, the RF LED on the device will glow a steady blue; if a matching device is nearby, the RF LED will alternate blue/purple flashing.  The LED will flash as long as the matching device is present.  (Minimum flash time is defined in the configuration, for a case when the matching device moves by quickly).

A scan can be started in one of two ways:

Automatic scan

When the device is powered up or reset, if no communication is seen on the serial interface in the first 5 seconds, a scan will automatically start.  If scan logging is enabled, the files will roll according to the file count, and a new `autoscan.log` will be created.  

Manual scan

A manual scan can be started with the `scan` command on the CLI.  If the `-l <FILENAME>` parameter is passed, the scan will be saved to that filename; otherwise it will use `autoscan.log`

Stopping a scan

A manual or automatic scan is stopped when any key is pressed while connected to the serial interface.

Scan data


Example scan data.  3 devices (2 WiFi, 1 Bluetooth) matched scan criteria.  One of them was seen only for about 29 seconds, then disappeared.

Currently, there are some default matching criteria hardcoded into the device; this will be more configurable in a future release.

Survey

A survey will do a single scan of all WiFi channels and Bluetooth to get an idea of the devices in the area.  The command takes a few parameters:

In this example, a survey was started with each WiFi channel being scanned for 100ms.  The scan found 10 WiFi devices and 8 BT.  Since no file was specified, the raw JSON is output in the console:


Passing a filename with the `-f` parameter will save this (minified) JSON data in the filesystem.  Currently, this device has a few saved surveys:

Post-processing survey data

Survey data can be exported by the device and used to populate a Sqlite database using the included Python script:

To load data, first make sure the serial console is closed, then call the script:

The file `survey.sm11.json` was pulled form the device, parsed, then loaded into the sqlite3 file `example.db`.  The database file did not exist, so extra data was loaded to initialize it - this data comes from yaml and json files in the project folder: Bluetooth serviceUUID, serviceDataUUID, and a recent MAC OUI file.  This helps in later analysis.

If the database files existed, the status of the UUID and OUI data is checked and not reloaded if up to date.  More survey files can be loaded into the database to generate more data to work with.

SQL Queries

The database can be queried using the `sqlite3` CLI command or a GUI.  

The survey table contains the header information for all surveys in the database.:
select * from surveys;

surveyInxversiondevicenotesdateTimelongitudelattitudesatCount
12Super Secret SnifferSouth Milwaukee 10th Marquette2026-02-09 13:14:2242.90744-87.8614511
22Super Secret SnifferSouth MKE Marquette Chicago2026-02-09 13:23:3442.90746-87.8647511
32Super Secret SnifferHwy 32 near Elm2026-02-09 13:36:2142.84986-87.8532812
42Super Secret Sniffercarwash2026-02-09 13:48:0642.8873-87.9138611

There are two tables for WiFi results, depending on the type of WiFi packet: wifi_data and wifi_management.  They are similar, but the packet data is parsed a bit differently:
select * from wifi_data limit 10;


wifiInxsurveyInxsubtypesourceAddrdestAddrrssichannel
11QoS Data24:0a:c4:50:5d:5800:30:44:48:b8:14-8211
21Data24:81:3b:6c:4f:c024:81:3b:6c:4f:c0-901
31Dataac:8f:a9:37:a2:b401:80:c2:00:00:00-796
42ND CF-ACK + CF-Poll00:a0:84:8b:d6:9cc5:3c:b0:e2:69:2d-84157
52ND CF-ACK + CF-Poll10:a0:84:8b:d6:9cc5:3c:b0:e2:69:2d-84157
62ND CF-ACK + CF-Poll20:a0:84:8b:d6:9cc5:3c:b0:e2:69:2d-89153
72ND CF-ACK + CF-Poll30:a0:84:8b:d6:9cc5:3c:b0:e2:69:2d-86157
82ND CF-ACK + CF-Poll40:a0:84:8b:d6:9cc5:3c:b0:e2:69:2d-81157
92ND CF-ACK + CF-Poll50:a0:84:8b:d6:9cc5:3c:b0:e2:69:2d-88153
102ND CF-ACK + CF-Poll60:a0:84:8b:d6:9cc5:3c:b0:e2:69:2d-83157

select * from wifi_management limit 10;

wifiInxsurveyInxsubtypessidbssidrssichannel
11beaconHPEE063300:25:b3:ee:06:33-7310
21probe requestHPEF468B00:25:b3:ef:46:8b-798
31beaconTT-CRD206300:30:44:48:b8:14-7511
41probe responseahc0100:42:68:be:1f:80-841
51beaconahc0200:42:68:be:1f:81-831
61probe responseAHC_Guest00:42:68:be:1f:82-821
71probe requestSpectrumSetup-8300:e0:4c:19:52:b0-887
81beaconMySpectrumWiFib2-2G08:3e:5d:b7:da:b8-901
91beaconSMC-31C01120091610:98:c3:5d:07:93-795
101beaconSpectrumSetup-D914:7d:05:4b:02:db-7511

For both of those tables, the surveyInx is a foreign key into the surveys table.

Bluetooth data is stored in several tables.  The main bluetooth table contains devices, and there are separate tables for UUID data (using a foreign key back into the bluetooth table).

select * from btle limit 10:

btInxsurveyInxnamemacrssi
1103:62:20:af:fd:41-42
2103:de:d7:aa:24:60-60
310a:e1:7b:b0:34:72-80
4142:5d:b3:10:0c:a4-89
515d:85:0d:d1:48:50-90
615f:b0:bc:46:75:30-65
716e:46:30:d9:cb:b4-86
8173:d7:24:05:98:11-87
91Employee Enc.b4:e3:f9:1f:72:f7-89
101Bank Managerb4:e3:f9:1f:84:8a-88

select * from uuid16 limit 10;

uuidInxbtInxuuid16
1765,267
22265,214
3246,159
43265,267

Joining tables for the bigger picture

The tables can be joined to get a better picture of what was seen in the survey.  An example of this would be something like:

select distinct sta.subtype "station data subtype",
ap.ssid "AP SSID", 
ap.bssid "AP BSSID", 
sta.sourceAddr "station MAC", 
sta.channel "CH",
s.notes  "survey",
mv.vendorName "station vendor" 
from wifi_data sta
join surveys s on s.surveyInx = sta.surveyInx 
join wifi_management ap on ap.bssid = sta.destAddr and ap.surveyInx = sta.surveyInx
left join mac_vendor mv on mv.prefix like substr(sta.sourceAddr, 1, 8) 
order by ap.bssid, sta.sourceAddr
limit 20;


station data subtypeAP SSIDAP BSSIDstation MACCHsurveystation vendor
ND (null no data)TT-CRD206300:30:44:48:b8:1424:0a:c4:50:5d:5811day2 posn8Espressif Inc.
ND QoSNETGEAR2514:59:c0:c9:45:7400:f6:20:79:f8:d28day2 posn 1Google, Inc.
ND (null no data)SpectrumSetup-ED14:7d:05:67:6e:ef34:5f:45:cf:50:b811day2 posn 1Espressif Inc.
QoS DataTMOBILE-6C3818:60:41:49:6c:3a40:2a:34:84:70:563day2 posn5 
ND (null no data)SpectrumSetup-D01C2c:67:be:31:d0:20ba:3e:54:15:25:b310day2 posn4 
QoS Datasticky-fingers34:19:4d:bf:fc:9438:83:9a:9f:ee:968day2 posn 1SHENZHEN RF-LINK TECHNOLOGY CO.,LTD.
QoS DataVerizon_M73QVW4c:ab:f8:7c:cb:a82c:aa:8e:6b:30:d81day2 posn4Wyze Labs Inc
ND QoSLotus60:32:b1:a7:4f:0264:9a:63:eb:e3:6f10day2 posn8Ring LLC
ND QoSLotus60:32:b1:a7:4f:0290:48:6c:e1:c2:c710day2 posn8Ring LLC
ND (null no data)pussy palace74:37:5f:56:84:ab3c:31:74:2a:56:1511day2 posn5Google, Inc.
ND QoSpussy palace74:37:5f:56:84:ab50:14:79:a3:74:9512day2 posn 1iRobot Corporation
QoS Datapussy palace74:37:5f:56:84:ab50:14:79:a3:74:9511day2 posn5iRobot Corporation
QoS DataTonrol778:6a:1f:6c:c0:0408:12:a5:bc:30:2a6day2 posn 1Amazon Technologies Inc.
QoS DataSpectrumSetup-913Ba0:55:1f:09:91:4118:ef:3a:c7:a7:ab7day2 posn4Sichuan AI-Link Technology Co., Ltd.
QoS DataSpectrumSetup-913Ba0:55:1f:09:91:4118:ef:3a:c7:a7:ab5day2 posn5Sichuan AI-Link Technology Co., Ltd.
QoS DataSpectrumSetup-913Ba0:55:1f:09:91:4118:ef:3a:c7:a7:ab6day2 posn 7Sichuan AI-Link Technology Co., Ltd.
ND (null no data)YKLhomec4:41:1e:ea:74:1e44:87:63:b3:ff:d010day2 posn 7FN-LINK TECHNOLOGY Ltd.
More information about the schema and data can be found the repo.

Comments

Popular posts from this blog

Successful Fault Injection (glitching) with the Bus Pirate

Introduction In this post, I'll discuss using power fault injection (glitching) to bypass UART password authentication in an application running on a simple Arduino dev board using the Bus Pirate.  (Spoiler - it works!) The Bus Pirate  is an open source hardware/firmware debugging and test tool that is capable of many, many things useful to an embedded engineer and/or hacker.  In this case, we'll be using the UART functionality to communicate with and time, generate, and inject a power fault into our target (an Arduino Uno). Background For a recent project, I was connected to a consumer IoT device's UART port and found that I could break into the U-Boot bootloader at which time I prompted to enter a password.  I noticed that once the password prompt mode was entered, it would allow infinite retries of password until the correct password was entered (not a good idea from a product security standpoint).  I later pulled the entire firmware from the flash of the dev...

Bus Pirate 5!

I've been a happy user of the Bus Pirate for several years now - it's perfect for probing circuits to get at TTL level UARTs, sniffing SPI and I2C, generic JTAG, and other shenanigans.  From a hardware/firmware developer use case, it's great to be able to sniff bus traffic during debug.  From a red team standpoint, I've used it to extract and modify the contents of serial EEPROMs and serial flash chips - good clean fun :) I noticed that there's a new version of the hardware finally available, so of course I immediately ordered one.  It arrived this morning and I spent a couple of hours playing with it. Ordering and Shipping It can be ordered from Dirty PCBs  (link at end of blog).  I already had a Dirty PCB account from some projects a few years back - they do a pretty good job of cheaply producing PCBs if you don't mind waiting a bit to get them.  Ordering was straight forward; the only thing I noted was that it would be shipped after February 19th due to ...

I'll be speaking at CypherCon!

 Just a short note here - I'll be speaking at CypherCon in early April.  My topic is about hacking a cheap IP camera, and what the vendor could have done to make it less hackable. Hope to see you there!