diff --git a/README.md b/README.md index 0d8f971..288b459 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,21 @@ -# Flock-You: Promiscuous WiFi Edition (`promiscious` branch) +# Flock-You: Promiscuous WiFi Edition (`promiscious-dev` branch) Flock You **Passive 2.4 GHz promiscuous-mode detector for Flock Safety surveillance infrastructure. Runs standalone or feeds the Flask dashboard over USB for live GPS-tagged wardriving.** +> **Dev note:** This is the `promiscious-dev` branch — adds the +> DeFlockJoplin wildcard-probe tightening and a 31st OUI on top of the +> `promiscious` baseline. See "Further research" below. + --- ## 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 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). +Additional research credit to **Michael / DeFlockJoplin** for the **wildcard-probe-request signature** and the 31st OUI (`82:6b:f2`). Field-tested to 11/12 cameras caught with only 2 false positives in Joplin. Source: [DeflockJoplin/flock-you](https://github.com/DeflockJoplin/flock-you). + --- ## What this branch does @@ -41,6 +47,26 @@ Both are applied before the OUI match. This whole approach, including the 30-OUI --- +## Further research — the wildcard-probe signature (DeFlockJoplin) + +Michael / DeFlockJoplin used the OUI + addr1/addr2/addr3 work above as a starting point and characterised what Flock cameras actually do on the air. His finding: + +> The cameras are hopping channels and sending out a wildcard WiFi probe request on every channel. This specific type of request combined with OUI matching has created what seems to be a fairly unique signature. + +His drive-test in Joplin caught **11 of 12 cameras** with only **2 false positives**. The 12th camera was doing the same wildcard-probe behaviour but with an OUI (`82:6b:f2`) that wasn't in @NitekryDPaul's original 30 — it's now the 31st entry in our list, credited to him. + +The tightened signature that's active on this branch: + +1. Frame is 802.11 Management, type=0 subtype=4 (**Probe Request**) +2. SSID Information Element (tag 0) is present with **length 0** (wildcard) +3. `addr2` (transmitter) matches the known-OUI list + +When all three hit, we emit `detection_method: wifi_wildcard_probe` — the high-precision class. Non-probe frames from the same OUIs still emit `wifi_oui_addr2`, and the `addr1` receiver-side sleeper-catch still runs independently. + +His proof-of-concept firmware (different enough we're not just pulling it in wholesale, but the core idea carried over cleanly): [DeflockJoplin/flock-you](https://github.com/DeflockJoplin/flock-you). The wildcard-probe analysis is his; we ported the detection into this firmware and kept our SPIFFS persistence, Flask JSON emission, and audio/LED feedback on top. + +--- + ## Detection pipeline ``` @@ -78,7 +104,7 @@ The split between callback and loop is deliberate: the WiFi task has hard real-t ## OUI target list (@NitekryDPaul research) -All lowercase, colon-separated. 30 Flock Safety infrastructure prefixes: +All lowercase, colon-separated. 31 Flock Safety infrastructure prefixes: ``` 70:c9:4e 3c:91:80 d8:f3:bc 80:30:49 b8:35:32 @@ -87,6 +113,7 @@ All lowercase, colon-separated. 30 Flock Safety infrastructure prefixes: 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 +82:6b:f2 ← contributed by Michael / DeFlockJoplin ``` Pre-compiled into a byte table in `setup()` so the matcher stays entirely in IRAM with no flash-resident lookups during callback execution. @@ -133,7 +160,8 @@ The firmware emits one JSON line per detection in the same schema the BLE detect `detection_method` values: -- `wifi_oui_addr2` — transmitter-side OUI match +- `wifi_wildcard_probe` — **Probe Request + wildcard SSID from a known OUI** (the DeFlockJoplin high-precision signature). When this fires, the `addr2` broad alert is suppressed for the same frame to avoid double-counting. +- `wifi_oui_addr2` — transmitter-side OUI match on any non-probe frame - `wifi_oui_addr1` — **receiver-side OUI match** (the @NitekryDPaul technique) - `wifi_oui_addr3` — BSSID OUI match (mgmt frames only; disabled by default) - `wifi_ssid` — SSID keyword match (disabled by default) @@ -225,7 +253,8 @@ The BLE-only sibling of this firmware lives on the [`main` branch](https://githu ## Acknowledgments -- **ØяĐöØцяöЪöяцฐ (@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. +- **ØяĐöØцяöЪöяцฐ (@NitekryDPaul)** — **WiFi promiscuous detection research**: the 30-OUI Flock Safety target list and the addr1-receiver detection technique that are the baseline of this firmware. The code here is a mod of his original work. +- **Michael / DeFlockJoplin** ([DeflockJoplin/flock-you](https://github.com/DeflockJoplin/flock-you), [deflockjoplin.today](https://deflockjoplin.today)) — **wildcard-probe-request signature** + the 31st OUI (`82:6b:f2`). Drive-tested in Joplin to 11/12 cameras caught with only 2 false positives. - **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 diff --git a/datasets/NitekryDPaul_wifi_ouis.md b/datasets/NitekryDPaul_wifi_ouis.md index c5be892..fc4261d 100644 --- a/datasets/NitekryDPaul_wifi_ouis.md +++ b/datasets/NitekryDPaul_wifi_ouis.md @@ -10,7 +10,12 @@ Flock stations spend most of their duty cycle asleep, waking briefly to upload a This addr1 technique is @NitekryDPaul's discovery and is the basis of the `promiscuis-flock-you` firmware. -## OUI list (30 prefixes, lowercase, colon-separated) +## OUI list (31 prefixes, lowercase, colon-separated) + +@NitekryDPaul contributed the first 30. The 31st (`82:6b:f2`) was contributed +by **Michael / DeFlockJoplin** during follow-up drive-testing in Joplin — it's +the OUI of the 12th camera in his field test, which the original list didn't +catch. See [DeflockJoplin/flock-you](https://github.com/DeflockJoplin/flock-you). ``` 70:c9:4e @@ -43,6 +48,7 @@ ec:1b:bd 64:6e:69 48:27:ea a4:cf:12 +82:6b:f2 ``` ## CSV form @@ -79,6 +85,7 @@ a4:cf:12 | 64:6e:69 | Flock Safety infrastructure | WiFi 2.4 GHz | @NitekryDPaul | | 48:27:ea | Flock Safety infrastructure | WiFi 2.4 GHz | @NitekryDPaul | | a4:cf:12 | Flock Safety infrastructure | WiFi 2.4 GHz | @NitekryDPaul | +| 82:6b:f2 | Flock Safety infrastructure | WiFi 2.4 GHz (wildcard probe) | Michael / DeFlockJoplin | ## Detection strategy @@ -90,6 +97,20 @@ For each observed 802.11 management or data frame: 4. Match `addr1` (receiver) against the OUI list — **the addr1 insight** 5. Optional: match `addr3` (BSSID) on mgmt frames when addr2 is randomised +### Wildcard-probe tightening (DeFlockJoplin) + +Michael / DeFlockJoplin observed that Flock cameras channel-hop and spam +wildcard 802.11 Probe Requests on every channel. Combining that with the +OUI match yields a very tight signature: + +1. Frame is Management, type=0 subtype=4 (Probe Request) +2. SSID Information Element (tag 0) is present with length 0 +3. `addr2` (transmitter) matches the OUI list + +Field-tested in Joplin: **11 of 12 cameras caught with only 2 false +positives**. The 12th camera used OUI `82:6b:f2`, which is now in the +list above. Source: [DeflockJoplin/flock-you](https://github.com/DeflockJoplin/flock-you). + ## Firmware The `promiscuis-flock-you` firmware implementing this research is a mod of @NitekryDPaul's promiscuous-mode firmware. It emits Flask-compatible JSON over USB for ingestion by the `flock-you` dashboard and persists detections to on-device SPIFFS. diff --git a/main.cpp b/main.cpp index 0267886..4621777 100644 --- a/main.cpp +++ b/main.cpp @@ -87,7 +87,11 @@ static const char* target_ouis[] = { "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" + "90:35:ea", "5c:93:a2", "64:6e:69", "48:27:ea", "a4:cf:12", + // Contributed by Michael / DeFlockJoplin — discovered via wildcard-probe + // + OUI signature during field testing. The 12th camera in his drive-test + // used this prefix and wasn't in @NitekryDPaul's original 30. + "82:6b:f2" }; static const size_t OUI_COUNT = sizeof(target_ouis) / sizeof(target_ouis[0]); @@ -103,10 +107,14 @@ static uint8_t oui_bytes[OUI_COUNT][3]; #define ALERT_QUEUE_SIZE 32 typedef enum : uint8_t { - ALERT_OUI_ADDR2 = 0, - ALERT_OUI_ADDR1 = 1, - ALERT_OUI_ADDR3 = 2, - ALERT_SSID = 3, + ALERT_OUI_ADDR2 = 0, + ALERT_OUI_ADDR1 = 1, + ALERT_OUI_ADDR3 = 2, + ALERT_SSID = 3, + // Probe Request + wildcard SSID (tag 0, length 0) from a known-OUI addr2. + // Tight signature from Michael / DeFlockJoplin field research: + // https://github.com/DeflockJoplin/flock-you + ALERT_WILDCARD_PROBE = 4, } AlertType; typedef struct { @@ -446,11 +454,12 @@ static void printHeartbeat() { static const char* alertTypeToMethod(AlertType t) { switch (t) { - case ALERT_OUI_ADDR2: return "oui_addr2"; - case ALERT_OUI_ADDR1: return "oui_addr1"; - case ALERT_OUI_ADDR3: return "oui_addr3"; - case ALERT_SSID: return "ssid"; - default: return "unknown"; + case ALERT_OUI_ADDR2: return "oui_addr2"; + case ALERT_OUI_ADDR1: return "oui_addr1"; + case ALERT_OUI_ADDR3: return "oui_addr3"; + case ALERT_SSID: return "ssid"; + case ALERT_WILDCARD_PROBE: return "wildcard_probe"; + default: return "unknown"; } } @@ -797,6 +806,24 @@ static bool IRAM_ATTR extractSsidFromMgmtBody(const uint8_t* body, int len, return false; } +// Returns: +// 1 = wildcard SSID IE found (tag 0, length 0) → Flock-style probe +// 0 = SSID IE found, non-zero length → directed probe, not ours +// -1 = no SSID IE found at all → caller should retry with +// FCS-stripped length, then bail +static int IRAM_ATTR isWildcardProbeIE(const uint8_t* body, int len) { + if (!body || len < 2) return -1; + while (len >= 2) { + uint8_t id = body[0]; + uint8_t elen = body[1]; + if ((int)elen + 2 > len) break; + if (id == 0) return (elen == 0) ? 1 : 0; + body += elen + 2; + len -= elen + 2; + } + return -1; +} + static void IRAM_ATTR wifiSniffer(void* buf, wifi_promiscuous_pkt_type_t type) { if (!buf || sniffingStopped) return; @@ -820,8 +847,39 @@ static void IRAM_ATTR wifiSniffer(void* buf, wifi_promiscuous_pkt_type_t type) { uint8_t ch = (uint8_t)pkt->rx_ctrl.channel; // actual rx channel from driver // --- OUI check: addr2 (transmitter/source) --- + // + // For mgmt Probe Requests (type=0 subtype=4) from a matched OUI, tighten + // to the DeFlockJoplin wildcard-probe signature: SSID IE (tag 0) length + // must be zero. This reduces false positives dramatically (Michael's field + // test: 11/12 true-positive with only 2 false-positives in Joplin). + // + // Non-probe frames from the same OUI still emit the broad ADDR2 alert. + // See: https://github.com/DeflockJoplin/flock-you if (matchOuiRaw(hdr->addr2)) { - enqueueAlert(ALERT_OUI_ADDR2, hdr->addr2, rssi, ch, nullptr, "addr2"); + bool emitted = false; + if (type == WIFI_PKT_MGMT) { + uint8_t fc0 = hdr->frame_ctrl & 0xFF; + uint8_t ftype = (fc0 >> 2) & 0x03; + uint8_t subtype = (fc0 >> 4) & 0x0F; + if (ftype == 0 && subtype == 4) { // Probe Request + int sigLen = (int)pkt->rx_ctrl.sig_len; + int bodyLen = sigLen - (int)sizeof(wifi_ieee80211_mac_hdr_t); + const uint8_t* body = pkt->payload + sizeof(wifi_ieee80211_mac_hdr_t); + int r = (bodyLen > 0) ? isWildcardProbeIE(body, bodyLen) : -1; + // FCS-trailer retry: only when the first parse found no SSID IE AT + // ALL (-1). A found-but-nonzero (0) means legit directed probe; do + // not retry — it would mis-classify. + if (r == -1 && bodyLen > 4) r = isWildcardProbeIE(body, bodyLen - 4); + if (r == 1) { + enqueueAlert(ALERT_WILDCARD_PROBE, hdr->addr2, rssi, ch, + nullptr, "probe_req"); + emitted = true; + } + } + } + if (!emitted) { + enqueueAlert(ALERT_OUI_ADDR2, hdr->addr2, rssi, ch, nullptr, "addr2"); + } } #if CHECK_ADDR1