Skip to main content

Building a WiFi/BT sniffer - part 1 (construction)

(tl;dr version - the sniffer project is at this Github repo.  Project is well documented there)

For various reasons, I wanted to build a portable device to sniff WiFi and Bluetooth signals.  My goals for the project were for the device to be able to:
  • "Survey" all devices in the area, and log the results for later analysis
  • Continuously monitor for target broadcasters
    •  based on:
      • WiFi SSID
      • WiFi MAC prefix (Manufacturer's OUI)
      • Bluetooth device name
      • Bluetooth MAC prefix (Manufacturer's OUI) 
      • Bluetooth service UUID value
    • Log detection for analysis
    • Signal detection in an obvious way
  • Have GPS to keep track of geographic location (as well as timekeeping)
I found a couple of projects on Github that looked kind of promising, but not exactly what I was looking for.  One seemed a bit more promising than the others at first glance.  The project developer was happy to sell pre-built hardware (with a buzzer for alerts), but could run on just an ESP32 dev board.  I happened to have some ESP32-S3s around, so I downloaded the code and gave it a try.  

It didn't work as advertised - it didn't detect what it was designed to detect, but picked up other things.  A quick look at the code showed that the WiFi detection was a bit of a dog's breakfast - no differentiation between 'data' and 'management' frames, incorrect offsets into the packets, etc.  I considered starting with that project, forking it and sending PRs to the upstream to help contribute.  Then I noticed that project had no license.  Sigh.  

Kids - if you put something on Github, you really need to select a license.  If your project is "just for yourself", mark it private!  The Github T&C's are very clear about this - no license declaration means the entire repo is "all rights reserved".  It's unclear if it's even legal to clone and install; changes, mods, etc. are right out.

I opened an issue asking the dev to add a license, but crickets.  Given that, I decided to just start from scratch.

First Prototype

I started a new project with a Seeed XIAO ESP32-S3.  Why that particular dev board?  Small size, cheap, fast, WiFi/BT built in, and I had some in my bin.  Capabilities that were of big interest to me were the 8MB PSRAM and 8MB Flash.  Lots of resources for an embedded device.  (Who else remembers 4K of RAM being extravagant!!)

My first decision was development platform.  I chose Arduino.  WTF - why Arduino?  Aren't I a grown up?  It was because I can't stand the way Copilot (and the AI mentality in general) has been shoved into VS Code.  It's unfortunate; I had been a huge fan of VS Code for a very long time, but can't even bring myself to open it anymore.

(I discovered Zed after I was well into this project.  Too bad I was so far along - I would've happily used it.  Can't recommend it enough over VS Code.)

The first prototype was made on perfboard and consisted of:
  • an EPS32-S3
  • a cheap GPS module (with serial NMEA interface)
  • 2 RGB LEDs
  • 6 N-channel FETs (to drive the LEDs), current limiting resistors
Rough, yes; but it allowed for development.

Using this platform, the first pass of the code was developed.  The main focus was the "survey" function and GPS integration.  I took it wardriving a few times and found it to be a pretty good start.

Prototype to PCB

I felt the concept was good enough and ready for a real PCB with one change: I liked the way the two LEDs worked for signaling conditions, but didn't like needing 6 FETs to drive them.  I remembered I had some through-hole WS2812 addressable LEDs, and using them would really simplify the hardware.  It was important to me for the entire design to use through-hole components to simplify construction for anyone with basic soldering skills.

With the current US trade situation, I decided to go with OSHPark to make the boards.  I used KiCAD for the simple schematic and layout and sent the gerbers out for fab.  Three weeks and $19 later I had 3 boards.  They looked great!  It took just a few minutes to put one together for testing and continued development.  More wardriving!

It's not practical to use a loose PCB and clear tape for a finished product :)

Enclosure

OpenSCAD was used to design a case to hold the EPS32, GPS module, and their two antennae.  I had originally thought of adding a battery, but decided against it in the end.  The first 3D version was very utilitarian and had some minor dimensional issues:

A second pass was made, correcting the sizing and adding some design features.  Printed in two colors, it looks more like a "real product".  The OpenSCAD file is in the repo.


Notes about the code

The Github repo is linked above at the top of this post for reference.  

The code is pretty straight forward with a few interesting features:
  • The 8MB EPS32 Flash is custom partitioned: 4MB for application, 4MB for a filesystem.
  • Most dynamic memory allocations are made in PSRAM (a bit more below)
  • Logs, survey results, config, etc. are stored in the Flash filesystem.  Most files are JSON with the logs being plain text.
  • There is a fairly rich command line interface that can be used to start/stop scans and surveys, manage the filesystem, and get basic diagnostic status information.
The repo also contains a companion Python script that is used to pull survey results and populate a Sqlite database (examples and demo in the next post).

About that PSRAM

By default, if a developer allocates memory using `new`, that memory is allocated from the internal SRAM - a limited resource.  The `ps_malloc()` statement will allocate from the external PSRAM chip.  It's just a bit slower, but with 8MB to play with it's worth it.

The code makes much use of STL containers, again allocated from SRAM by default.  A bit of extra code is needed to get them to allocate from PSRAM.  This is from `alloc.h`:

namespace flk
{
    template <class T>
    class psramAlloc
    {
        public:
            using value_type = T;
            using size_type = std::size_t;
            using difference_type = std::ptrdiff_t ;
            using propagate_on_container_move_assignment  = std::true_type;

            // use ps_malloc() to force allocation from external PSRAM chip
            T* allocate(size_type n, const void* hint = 0)
            {
                return static_cast<T*>(ps_malloc(n*sizeof(T)));
            }

            // matching heap release to ps_malloc is free()
            void deallocate(T* p, size_type n)
            {
                free (p);
            }

            // The full external PSRAM chip is only 8Meg, and there's no virtual memory, so don't think about
            // setting max size to MAX_UINT
            size_type max_size() const
            {
                return (size_type(0x7fffff / sizeof(T)));
            }

            // be sure to call constructor/destructors
            void construct(T* p, const T& value) { _construct(p, value); }
            void destroy(T* p) { _destroy(p); }
    };
    
    // to allocate std::string in external RAM instead of onboard SRAM.  Slower, but SRAM is a limited resource
    
    typedef std::basic_string<char, std::char_traits<char>, psramAlloc<std::string::value_type>> string;
};

The big thing to note here is that there's a custom allocator template class that calls `ps_malloc()` and `free()` instead of `new` and `delete`.  Declaring an STL container to use the PSRAM allocator is just a little more complicated:

static std::map<flk::string, found_wifi_t, std::less<>, flk::psramAlloc<std::map<flk::string, found_wifi_t>::value_type>> wifiDevices;

This is for an STL map with a std::string as the key and found_wifi_t struct as the member.  Also note that flk::string is just a std::string allocated from PSRAM (see the typedef above).

Next post

In the next post, I'll show examples of using the device.












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!