flatten: promiscuous WiFi firmware is the branch content, not a subfolder

- Moved promiscuis-flock-you/{main.cpp,partitions.csv,platformio.ini} to root
- Removed BLE firmware (src/main.cpp, src/CMakeLists.txt) — the 'main' branch
  still has it
- Removed the subfolder README (root README has the full walkthrough)
- Updated README paths and build commands for the flat layout
- Retagged the BLE companion section as a pointer to the 'main' branch

The 'promiscious' branch is now purely the WiFi promiscuous firmware plus
the shared Flask app (api/), datasets, and branding. Builds green with
the standard 'pio run' from the repo root.
This commit is contained in:
Colonel Panic
2026-04-20 07:47:14 -04:00
parent 54ef193e6b
commit ec7c04a0df
8 changed files with 11 additions and 1749 deletions
+6 -7
View File
@@ -8,7 +8,7 @@
## Credit
All WiFi promiscuous detection research — the **30-OUI target list**, the **promiscuous-mode strategy**, and the **addr1-receiver detection technique** — is the work of **ØяĐöØцяöЪöяцฐ / @NitekryDPaul**. The firmware in `promiscuis-flock-you/` is a mod of his original firmware with added SPIFFS persistence and Flask-dashboard integration. Full research writeup: [`datasets/NitekryDPaul_wifi_ouis.md`](datasets/NitekryDPaul_wifi_ouis.md).
All WiFi promiscuous detection research — the **30-OUI target list**, the **promiscuous-mode strategy**, and the **addr1-receiver detection technique** — is the work of **ØяĐöØцяöЪöяцฐ / @NitekryDPaul**. The firmware here is a mod of his original firmware with added SPIFFS persistence and Flask-dashboard integration. Full research writeup: [`datasets/NitekryDPaul_wifi_ouis.md`](datasets/NitekryDPaul_wifi_ouis.md).
---
@@ -178,17 +178,16 @@ Boot sound: first 6 notes of Super Mario Bros. World 1-2 (underground).
Requires [PlatformIO](https://platformio.org/).
```bash
cd promiscuis-flock-you
pio run # build
pio run -t upload # flash
pio device monitor # serial output
```
The subdirectory has its own `platformio.ini` and `partitions.csv` (1.9 MB SPIFFS partition, 6 MB app). No extra libraries needed beyond the Arduino-ESP32 core that ships with the espressif32 platform.
`platformio.ini` and `partitions.csv` are at the root (1.9 MB SPIFFS partition, 6 MB app). No extra libraries needed beyond the Arduino-ESP32 core that ships with the espressif32 platform.
---
## Config cheatsheet (top of `promiscuis-flock-you/main.cpp`)
## Config cheatsheet (top of `main.cpp`)
| Define | Default | Notes |
|---|---|---|
@@ -218,15 +217,15 @@ Both modes work simultaneously — the SPIFFS write path doesn't care if a host
---
## BLE companion firmware (on `main` branch)
## BLE companion firmware
The original BLE-only firmware still lives in `src/main.cpp`. It detects Flock and Raven gear via BLE advertisements (OUI prefix, device name, manufacturer ID `0x09C8`, Raven service UUIDs), runs its own WiFi AP with a phone-facing dashboard at `192.168.4.1`, and emits the same Flask JSON schema. Run both firmwares on separate boards for overlapping BLE + WiFi coverage feeding one dashboard. See the `main` branch README for BLE-specific details.
The BLE-only sibling of this firmware lives on the [`main` branch](https://github.com/colonelpanichacks/flock-you/tree/main). It detects Flock and Raven gear via BLE advertisements (OUI prefix, device name, manufacturer ID `0x09C8`, Raven service UUIDs), runs its own WiFi AP with a phone-facing dashboard at `192.168.4.1`, and emits the same Flask JSON schema. Flash both on separate boards for overlapping BLE + WiFi coverage feeding one Flask dashboard.
---
## Acknowledgments
- **ØяĐöØцяöЪöяцฐ (@NitekryDPaul)** — **WiFi promiscuous detection research**: the 30-OUI Flock Safety target list and the addr1-receiver detection technique that form the entirety of the `promiscuis-flock-you` firmware on this branch. The firmware here is a mod of his original work.
- **ØяĐöØцяöЪöяцฐ (@NitekryDPaul)** — **WiFi promiscuous detection research**: the 30-OUI Flock Safety target list and the addr1-receiver detection technique that are the entirety of this firmware. The code here is a mod of his original work.
- **Will Greenberg** ([@wgreenberg](https://github.com/wgreenberg)) — BLE manufacturer company ID detection (`0x09C8` XUNTONG) sourced from his [flock-you](https://github.com/wgreenberg/flock-you) fork (used by the BLE companion on `main`)
- **[DeFlock](https://deflock.me)** ([FoggedLens/deflock](https://github.com/FoggedLens/deflock)) — crowdsourced ALPR location data and detection methodologies. Datasets included in `datasets/`
- **[GainSec](https://github.com/GainSec)** — Raven BLE service UUID dataset (`raven_configurations.json`) used by the BLE companion
+5 -15
View File
@@ -1,3 +1,6 @@
[platformio]
src_dir = .
[env:xiao_esp32s3]
platform = espressif32@^6.3.0
board = seeed_xiao_esp32s3
@@ -5,26 +8,13 @@ framework = arduino
monitor_speed = 115200
upload_speed = 921600
; Build options
build_flags =
build_flags =
-DCORE_DEBUG_LEVEL=0
-DARDUINO_USB_CDC_ON_BOOT=1
-DBOARD_HAS_PSRAM
-mfix-esp32-psram-cache-issue
-DCONFIG_BT_NIMBLE_ENABLED=1
; Libraries
lib_deps =
h2zero/NimBLE-Arduino@^1.4.0
mathieucarbou/ESP Async WebServer@^3.0.6
bblanchon/ArduinoJson@^7.0.4
build_src_filter = +<main.cpp>
; Board configuration
board_build.arduino.memory_type = qio_opi
board_build.partitions = partitions.csv
board_build.filesystem = spiffs
; USB CDC configuration
board_build.f_cpu = 240000000L
board_build.f_flash = 80000000L
board_build.flash_mode = qio
-220
View File
@@ -1,220 +0,0 @@
# promiscuis-flock-you
WiFi-side companion to [flock-you](https://github.com/colonelpanichacks/flock-you). Passively detects Flock Safety cameras, Raven gunshot detectors, and related surveillance infrastructure by sniffing 2.4 GHz management and data frames in promiscuous mode. Emits Flask-compatible JSON over USB for live dashboard ingestion, stores detections on-device in SPIFFS so it works standalone too.
---
## Credits
All OUI research, the promiscuous-mode detection strategy, and the original firmware this is modded from: **ØяĐöØцяöЪöяцฐ @NitekryDPaul**. The core discovery — that Flock stations with randomised transmitter addresses still show up as the *destination* of probe responses and data frames during their burst-sleep duty cycle — is his. The 30-OUI target list below is his research. This fork adds Flask-app integration and on-device persistence on top of his work.
---
## What it does
- Sets the WiFi radio to `WIFI_MODE_NULL` and enables promiscuous sniffing
- Hops channels (default 1 / 6 / 11, 350 ms dwell)
- Inspects every 802.11 management and data frame
- Flags any frame whose `addr2` (transmitter) **or** `addr1` (receiver) matches a known Flock / Raven / SoundThinking OUI
- Skips multicast (`addr1` broadcast frames) and randomised MACs (locally-administered bit set) before matching
- Beeps the buzzer and flashes the onboard LED on every new detection
- Emits one JSON line per detection over USB CDC in the exact schema the Flask dashboard expects
- Stores detections in SPIFFS with an atomic CRC-checked envelope, so nothing is lost across power cycles
Runs with or without USB attached. No AP, no web server — the radio stays dedicated to sniffing, channel hopping is preserved.
---
## Why `addr1` matters
Most WiFi sniffers only check the transmitter address (`addr2`). Flock infrastructure often goes long windows without transmitting — it sleeps, wakes in bursts, uploads, sleeps again. During that silence it may still show up on the air as the **destination** of probe responses or data frames from nearby APs.
Checking `addr1` (receiver) picks those up. It requires a multicast guard (`addr1` is `ff:ff:ff:ff:ff:ff` in beacons and broadcasts) and a randomised-MAC guard, both of which are done at the top of the match function.
This is the key insight from @NitekryDPaul's research.
---
## Hardware
- **Board**: Seeed Studio XIAO ESP32-S3
- **Buzzer**: piezo on GPIO3
- **LED**: onboard user LED on GPIO21 (active low)
- **Serial mirror**: TX-only on GPIO43 at 115200 baud (for attaching a second logger or a secondary device)
---
## OUI target list
All lowercase, colon-separated. From @NitekryDPaul's research:
```
70:c9:4e 3c:91:80 d8:f3:bc 80:30:49 b8:35:32
14:5a:fc 74:4c:a1 08:3a:88 9c:2f:9d c0:35:32
94:08:53 e4:aa:ea f4:6a:dd f8:a2:d6 24:b2:b9
00:f4:8d d0:39:57 e8:d0:fc e0:4f:43 b8:1e:a4
70:08:94 58:8e:81 ec:1b:bd 3c:71:bf 58:00:e3
90:35:ea 5c:93:a2 64:6e:69 48:27:ea a4:cf:12
```
Pre-compiled into a byte table in `setup()` so the matcher stays entirely in IRAM with no flash-resident lookups during callback execution.
---
## Architecture
```
[2.4GHz air]
wifiSniffer() ← IRAM promiscuous callback (WiFi task)
│ fast match, no Serial, no malloc
alertQueue[32] ← lock-free ring buffer (ISR-safe mux)
drainAlertQueue() ← loop() context
├─► fyAddDetection() ← always, every hit
│ │
│ ▼
│ fyDet[200] ← unique-by-MAC table
│ │
│ ▼
│ autosaveTick() ← every 60s when dirty
│ │
│ ▼
│ fySaveSession() ← atomic CRC-envelope write to SPIFFS
├─► shouldSuppressDuplicate() ← 5s per-MAC cooldown
└─► emitDetectionJSON() ← USB CDC line for Flask
buzzerBeep() + ledFlash()
```
The split between callback and loop is deliberate: the WiFi task has hard real-time constraints and can't call `Serial.print` or `malloc` safely. The callback writes only to the ring buffer; `loop()` does all the heavy work.
---
## SPIFFS wire format
File layout on flash (atomic, crash-safe):
```
Line 1: {"v":1,"count":N,"bytes":B,"crc":"0xXXXXXXXX"}
Line 2: [{"mac":"...","method":"...","rssi":...,...},...]
```
Save procedure:
1. Compute CRC32 + byte count over the serialised payload
2. Write envelope header + payload to `/session.tmp`
3. Re-read and re-validate `/session.tmp` (CRC check)
4. Remove `/session.json`
5. Atomic rename `/session.tmp``/session.json` (copy+delete fallback)
Boot recovery:
1. If `/session.json` validates, promote it to `/prev_session.json`
2. Otherwise try `/session.tmp` (interrupted save)
3. Delete both working files, start with an empty live table
4. `/prev_session.json` stays around for inspection
CRC32 uses the standard `0xEDB88320` polynomial so the same file can be verified on a host with any off-the-shelf CRC tool.
---
## Flask dashboard integration
This firmware emits the same JSON schema as the BLE `flock-you` firmware, so the Flask app (`api/flockyou.py` in [colonelpanichacks/flock-you](https://github.com/colonelpanichacks/flock-you)) ingests it with no changes.
Per-detection JSON line:
```json
{"event":"detection","detection_method":"wifi_oui_addr2","protocol":"wifi_2_4ghz","mac_address":"aa:bb:cc:dd:ee:ff","oui":"aa:bb:cc","device_name":"","rssi":-62,"channel":6,"frequency":2437,"ssid":""}
```
`detection_method` values:
- `wifi_oui_addr2` — transmitter-address OUI match
- `wifi_oui_addr1` — receiver-address OUI match (the @NitekryDPaul technique)
- `wifi_oui_addr3` — BSSID-address OUI match (management frames only, disabled by default)
- `wifi_ssid` — SSID keyword match (disabled by default)
### GPS wardriving
No AP, no on-device GPS — GPS is handled Flask-side. Plug a USB NMEA puck into the host running Flask, or open the Flask dashboard in a phone browser and let it post browser-geolocation updates. Flask does a temporal match between the detection timestamp and the GPS timeline, and exports JSON / CSV / KML for Google Earth.
### Running Flask
```bash
cd flock-you/api
pip install -r requirements.txt
python flockyou.py
```
Open `http://localhost:5000`, connect your serial port from the UI, and detections start showing up live.
---
## Build and flash
PlatformIO config for the XIAO ESP32-S3:
```ini
[env:xiao_esp32s3]
platform = espressif32@^6.3.0
board = seeed_xiao_esp32s3
framework = arduino
monitor_speed = 115200
upload_speed = 921600
build_flags =
-DCORE_DEBUG_LEVEL=0
-DARDUINO_USB_CDC_ON_BOOT=1
-DBOARD_HAS_PSRAM
board_build.arduino.memory_type = qio_opi
board_build.partitions = partitions.csv
board_build.filesystem = spiffs
```
`partitions.csv` is included — 1.9 MB SPIFFS partition, 6 MB app.
No extra libraries needed. `SPIFFS.h` ships with Arduino-ESP32 core.
---
## Config cheatsheet (top of `main.cpp`)
| Define | Default | Notes |
|---|---|---|
| `CHANNEL_MODE` | `CHANNEL_MODE_CUSTOM` | `CUSTOM` (1/6/11), `FULL_HOP` (1-11), or `SINGLE` |
| `CHANNEL_DWELL_MS` | 350 | Time on each channel before hop |
| `RSSI_MIN` | -95 | Drop frames weaker than this |
| `ALERT_COOLDOWN_MS` | 5000 | Per-MAC serial-emit rate limit |
| `CHECK_ADDR1` | 1 | The @NitekryDPaul receiver-side technique |
| `CHECK_ADDR3` | 0 | BSSID fallback (mgmt frames only) |
| `ENABLE_SSID_MATCH` | 0 | Substring match against `target_ssid_keywords[]` |
| `PROCESS_MGMT_FRAMES` | 1 | Beacons, probe req/resp, etc. |
| `PROCESS_DATA_FRAMES` | 1 | Data frames (where addr1 catch shines) |
| `MAX_DETECTIONS` | 200 | On-device table cap |
| `AUTOSAVE_INTERVAL_MS` | 60000 | SPIFFS save cadence |
| `LED_PIN` | 21 | Onboard user LED |
| `BUZZER_PIN` | 3 | Piezo |
---
## Standalone vs connected operation
**Without USB:** device boots, beeps, starts scanning, stores every unique detection to SPIFFS, flashes the onboard LED on each hit. Plug in later, the prior session is sitting in `/prev_session.json`.
**With USB + Flask running:** same thing, plus every detection streams live to the dashboard as a JSON line. Flask adds GPS (if configured) and deduplicates across MAC, building the wardriving map as you move.
Both modes work simultaneously — the SPIFFS write path doesn't care if a host is listening.
---
## Legal / intended use
Passive reception of publicly-broadcast 802.11 frames and public BLE advertisements. Privacy research, surveillance auditing, education. The device does not transmit and does not attempt to authenticate to any network.
-5
View File
@@ -1,5 +0,0 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x600000,
spiffs, data, spiffs, 0x610000, 0x1F0000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x600000
5 spiffs data spiffs 0x610000 0x1F0000
-20
View File
@@ -1,20 +0,0 @@
[platformio]
src_dir = .
[env:xiao_esp32s3]
platform = espressif32@^6.3.0
board = seeed_xiao_esp32s3
framework = arduino
monitor_speed = 115200
upload_speed = 921600
build_flags =
-DCORE_DEBUG_LEVEL=0
-DARDUINO_USB_CDC_ON_BOOT=1
-DBOARD_HAS_PSRAM
build_src_filter = +<main.cpp>
board_build.arduino.memory_type = qio_opi
board_build.partitions = partitions.csv
board_build.filesystem = spiffs
-6
View File
@@ -1,6 +0,0 @@
# This file was automatically generated for projects
# without default 'CMakeLists.txt' file.
FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/*.*)
idf_component_register(SRCS ${app_sources})
-1476
View File
File diff suppressed because it is too large Load Diff