OUI-SPY · Volume 4
OUI-SPY Volume 4 — Detection Methodology I: Wi-Fi
4.1 About this Volume
This is the first of two volumes on how the OUI-SPY ecosystem actually decides that a
radio in earshot belongs to a surveillance device. It covers the Wi-Fi side — the half of
the problem that runs in 802.11 promiscuous mode. Volume 5 covers the Bluetooth Low Energy
side, and Volume 6 takes the flagship flock-you firmware apart define-by-define,
including the companion dashboard.
The reason these three volumes are the heart of the dive is that detection methodology is the one axis on which OUI-SPY genuinely differs from every other “surveillance scanner” on the bench. A tool such as SquachWatch (covered in its own Hack Tools dive) matches a broad catalogue of named signatures and surfaces them on a colour touchscreen. OUI-SPY does something narrower and, in the Wi-Fi case, more forensic: it grades every hit on a five-tier confidence scale, with the top tier reserved for a frame that passes an information-element fingerprint, not merely a MAC-prefix match. The firmware is headless — a passive piezo buzzer on one GPIO and nothing else is required — so almost everything of interest is in the logic, not the hardware. That is why these volumes lean on tables and ASCII rather than photographs.
The material in this volume is drawn from the flock-you README (the methodology gold
source) as fetched 2026-10-16. Where the README summarises rather than lists a value — most
importantly the exact byte fields of the tier-4 IE fingerprint — the gap is flagged rather
than guessed.
Scope note. Everything in this volume is passive receive. The Wi-Fi detection path never transmits: it puts the radio in monitor mode and listens. The asterisk on the word “passive” belongs to the BLE active-scan phase (Volume 5) and to the offensive Red-edition tools (Volume 9), not here.
4.2 The OUI as a detection key
The brand name is a pun with a real technical anchor. OUI is the Organizationally Unique Identifier — the top 24 bits of a 48-bit MAC address, assigned by the IEEE Registration Authority to the organisation that makes the radio. Every Ethernet or Wi-Fi interface a vendor ships carries that vendor’s OUI in the high half of its burned-in address:
48-bit MAC address
┌───────────────────────┬───────────────────────┐
│ OUI (24 bits) │ NIC-specific (24 b) │
│ b4: 1e: 52 │ xx: xx: xx │
└───────────────────────┴───────────────────────┘
^ ^
│ └── bit 0 of first octet = I/G (individual/group)
└────────────── bit 1 of first octet = U/L (universal/local)
Two bits of the very first octet matter to this methodology:
- I/G bit (bit 0, least-significant of the first octet): 0 = unicast, 1 = group/multicast. Detection targets are individual devices, so this is 0 for the hits that matter.
- U/L bit (bit 1): 0 = a globally unique, IEEE-assigned address; 1 = a locally-administered address (LAA) that software chose. Modern phones randomise their MACs by setting this bit, which is why a naïve scanner is tempted to skip LAA frames as noise.
That temptation is a trap here, and the README says so explicitly: one of the 34 target
prefixes — 82:6b:f2 — has the locally-administered bit set, and the firmware must not
install an “LAA skip filter.” A camera that advertises from a locally-administered-looking
prefix would otherwise be filtered out as a randomising phone.
The detection premise, then: surveillance hardware is built from a small, slowly-changing set of radio modules and vendor assignments. Enumerate the OUIs those modules use, watch the air for any frame whose transmitter sits in that set, and you have a cheap, deterministic first-pass filter that a $7.50 microcontroller can run at line rate.
4.3 The 34-prefix OUI union
flock-you compiles a union of 34 MAC prefixes into a byte table during setup(), so
the matcher runs out of IRAM and can be evaluated inside the Wi-Fi receive callback without
touching flash. The union has two very different halves by provenance.
Community set — 32 prefixes. Thirty-one come from @NitekryDPaul’s nite-oui-collection
(the 2026-07-16 revision), the crowd-sourced list of prefixes observed on Flock Safety
automated-licence-plate-reader (ALPR) cameras. The thirty-second, 82:6b:f2, comes from the
DeFlockJoplin contribution and is the locally-administered one discussed above.
Table 1 — 3. The 34-prefix OUI union
| # | Prefix | # | Prefix | # | Prefix | # | Prefix | |||
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 70:c9:4e | 9 | 9c:2f:9d | 17 | d0:39:57 | 25 | 58:00:e3 | |||
| 2 | 3c:91:80 | 10 | c0:35:32 | 18 | e8:d0:fc | 26 | 90:35:ea | |||
| 3 | d8:f3:bc | 11 | 94:08:53 | 19 | e0:4f:43 | 27 | 5c:93:a2 | |||
| 4 | 80:30:49 | 12 | e4:aa:ea | 20 | b8:1e:a4 | 28 | 64:6e:69 | |||
| 5 | b8:35:32 | 13 | f4:6a:dd | 21 | 70:08:94 | 29 | 48:27:ea | |||
| 6 | 14:5a:fc | 14 | e0:0a:f6 | 22 | 58:8e:81 | 30 | a4:cf:12 | |||
| 7 | 74:4c:a1 | 15 | 24:b2:b9 | 23 | ec:1b:bd | 31 | 14:b5:cd | |||
| 8 | 08:3a:88 | 16 | 00:f4:8d | 24 | 3c:71:bf | 32 | 82:6b:f2 |
Firmware-extracted set — 2 prefixes. These two are the standout provenance of the whole project, and they get their own section below.
Table 2 — 3. The 34-prefix OUI union
| # | Prefix | Owner / meaning |
|---|---|---|
| 33 | b4:1e:52 | Flock Safety’s own IEEE MA-L block |
| 34 | 00:03:7f | Qualcomm Atheros QCA9377 default radio MACs |
The two sets do not overlap. The union is a flat lookup: a prefix is either in the table or it is not, and membership alone never decides the tier — that depends on where in the frame the match landed and whether the probe-request fingerprint also fired (§8).
4.4 Firmware-dump provenance: the two extracted prefixes
The community set is empirical — prefixes people saw on cameras in the wild. The two
firmware-extracted prefixes are different in kind: they come from a static analysis of a
Flock Safety ALPR firmware image (README codename hpnotiq, dated 2026-09-16), and they
explain why the cameras radiate what they radiate.
Flock ALPR firmware image ("hpnotiq", 2026-09-16)
│
▼ static extraction
┌──────────────────────────────┐
│ b4:1e:52 ← Flock Safety │ the vendor's own MA-L assignment:
│ MA-L (IEEE) │ the "front-door" identity of the device
├──────────────────────────────┤
│ 00:03:7f ← Qualcomm Atheros │ the Wi-Fi SoC inside the camera:
│ QCA9377 defaults │ the "back-door" identity of the chip
└──────────────────────────────┘
b4:1e:52is the block IEEE assigned to Flock Safety as a hardware manufacturer. A frame transmitted from this prefix is, by construction, a Flock-branded interface announcing itself with its registered identity.00:03:7fis the default MAC range of the Qualcomm Atheros QCA9377 — the 802.11 Wi-Fi/Bluetooth combo SoC inside the cameras. Devices that never had a vendor MAC written over the chip default, or that fall back to it, broadcast from this prefix. Detecting the silicon is more durable than detecting the brand: Flock can change its MA-L assignment far more easily than it can change the Wi-Fi chip it designed the board around.
Why this matters for the dive’s thesis. The firmware-dump prefixes are the single clearest reason OUI-SPY is categorised as a forensic detector rather than a signature scraper. The community list answers “what have people seen”; the firmware list answers “what is the device physically made of.” Pairing the two is what lets the tool catch a camera whose observed prefix has rotated but whose Qualcomm SoC default is unchanged.
4.5 Promiscuous mode and the capture path
On Wi-Fi, the ESP32-S3 radio is put into promiscuous (monitor) mode: it hands every 802.11 frame it demodulates to a receive callback, regardless of destination. That callback is the hot path, and its design is the difference between a scanner that keeps up with the air and one that drops frames.
RF in ──▶ ESP32-S3 Wi-Fi PHY ──▶ promiscuous RX callback (runs in Wi-Fi task)
│
│ 1. cheap filters: frame type, RSSI_MIN gate
│ 2. prefix match against the 34-OUI IRAM table
│ 3. wildcard-probe / IE fingerprint checks
│ 4. push compact record ──┐
▼ │
alertQueue[32] ◀────────────────────┘ (lock-free ring)
│
▼
loop() ──▶ drain queue ──▶ update detection table ──▶ cooldown ──▶ JSON + tone
The receive callback does only fast matching. It never allocates, never logs, never
touches the buzzer. When a frame qualifies, it writes a compact record into a 32-slot
lock-free ring buffer (alertQueue[32]) and returns immediately. The main loop() drains
that queue at its own pace: it updates the live detection table, applies the per-MAC
cooldown, emits the JSON detection line over USB-CDC, and drives the tone. This split keeps
the time-critical PHY callback bounded and means a burst of hits — a camera spraying probes
every ~125 ms — cannot stall the radio.
A frame is discarded before it ever reaches the prefix table if its RSSI is below
RSSI_MIN (default −95 dBm). That floor is deliberately generous: it exists to drop frames
so weak they can’t be reliably fingerprinted, not to range-gate the detector.
4.6 Channel hopping and dwell
2.4 GHz Wi-Fi has only three non-overlapping channels in the common plan — 1, 6, and 11 — and a monitor-mode radio can sit on exactly one at a time. The scanner therefore hops, and the dwell time is tuned against the cameras’ own probe cadence.
Table 3 — 6. Channel hopping and dwell
| Mode | Channel sequence | Notes |
|---|---|---|
CHANNEL_MODE_CUSTOM (default) | 11 → 6 → 1 (descending) | the three non-overlapping channels |
FULL_HOP | 11 → 1 (descending, all) | every channel, higher miss risk per channel |
SINGLE | one fixed channel | park on a known camera channel |
Dwell is CHANNEL_DWELL_MS = 250 ms per channel. The cameras emit wildcard probe
requests at roughly 125 ms intervals, so a 250 ms dwell is two probe-intervals — long
enough that a transmitting camera on the current channel will, with high probability, emit at
least one probe inside the window.
time ─────────────────────────────────────────────────────────▶
ch11 [■■■■■] ch6 [■■■■■] ch1 [■■■■■] ch11...
250 ms 250 ms 250 ms
camera probes: · · · · · · · · · (~every 125 ms)
└── two probe-intervals land inside each 250 ms dwell ──┘
There is a documented inconsistency: older README prose describes ascending channel order, while the code hops descending (11 → 6 → 1). The behaviour of the shipped firmware is the descending order; the ascending note is stale text.
4.7 The wildcard probe-request IE fingerprint
This is the highest-confidence Wi-Fi signature and the reason a hit can reach tier 4. A plain OUI match tells you a frame came from a targeted prefix; the fingerprint tells you the frame is shaped like a Flock camera’s probe. All four conditions must hold:
┌─ TIER-4 GATE ───────────────────────────────────────────────┐
│ │
│ (1) 802.11 management frame ── type = 0, subtype = 4 │
│ (Probe Request) │
│ AND │
│ (2) SSID IE (tag 0) present, length = 0 (wildcard probe) │
│ AND │
│ (3) addr2 (transmitter) OUI ∈ 34-prefix union │
│ AND │
│ (4) remaining IE fields match the DeFlockJoplin/ │
│ LiteON signature │
│ │
│ all four true ─────────────▶ wifi_wildcard_probe_ie_sig │
│ (1)(2)(3) true, (4) false ─▶ wifi_wildcard_probe (tier 3) │
└──────────────────────────────────────────────────────────────┘
Each condition earns its place:
- Management type 0 / subtype 4 = Probe Request. A client looking for networks. Cameras that act as Wi-Fi stations probe for their backhaul; this is the frame they leak most often and most predictably.
- Wildcard SSID. The SSID information element (tag number 0) is present but has length zero — a “broadcast” probe asking every AP to respond, rather than naming one network. This is characteristic behaviour, not proof on its own.
- Transmitter in the OUI union.
addr2is the actual source radio. Requiring the OUI match on the transmitter address (not a receiver or BSSID field) is what distinguishes a tier-4/tier-3 wildcard-probe hit from the weaker addr1/addr3 tiers (§8). - The IE fingerprint. Beyond the wildcard SSID, a probe request carries a sequence of
further information elements — supported rates, extended capabilities, vendor-specific
tags — whose exact contents and ordering form a per-firmware signature.
flock-youmatches these against a reference signature contributed as “DeFlockJoplin / LiteON.” The technique follows the Pintor & Atzori (2022) line of research on probe-request IE fingerprinting.
Gap, stated honestly. The
flock-youREADME does not publish the exact IE field values that constitute condition (4); it names the signature and cites the research but keeps the byte table in the source. The precise tier-4 fingerprint bytes are therefore unverified from the README alone.If conditions (1)–(3) hold but the IE fingerprint (4) does not match, the hit is still a wildcard probe from a targeted OUI — it drops one tier to tier 3 (wifi_wildcard_probe) rather than being discarded. The IE fingerprint raises confidence; it is not a gate on detection itself.
4.8 The five-tier confidence model
Every Wi-Fi (and BLE) detection is graded. The grade is the product’s signature idea, so it is worth stating the full model as a table even though two of its rows are BLE methods covered in Volume 5.
Table 4 — 8. The five-tier confidence model
| Tier | detection_method | Gate | Buzzer |
|---|---|---|---|
| 4 | wifi_wildcard_probe_ie_sig | OUI + wildcard SSID + IE fingerprint match | two-note chirp 2000 → 2800 Hz |
| 3 | wifi_wildcard_probe | OUI + wildcard SSID, IE did not match | two-note chirp 1400 → 1800 Hz |
| 2 | wifi_oui_addr2, ble_name, ble_mfg, ble_gatt_svc | transmitter-OUI match, or any BLE firmware-derived hit | single blip 1200 Hz |
| 1 | wifi_oui_addr1, wifi_oui_addr3 | OUI seen in addr1 (receiver) or addr3 (BSSID) | single blip 800 Hz |
| 0 | wifi_ssid | SSID keyword match (OFF by default) | single blip 600 Hz |
Three rules govern how a tier sticks to a device:
- Highest tier wins and never drops. Once a given MAC has been seen at, say, tier 4, its label stays tier 4 even if later frames from it only qualify at tier 2. Confidence ratchets up, never down.
- A higher tier can pre-empt the cooldown. The per-MAC
ALERT_COOLDOWN_MS(default 5000 ms) suppresses repeat alerts — but if a device that was blipping at tier 2 suddenly produces a tier-4 frame, the stronger hit is allowed through immediately rather than waiting out the cooldown. - The audio encodes the tier. The buzzer tone is the readout on a headless device: a rising two-note chirp means “high confidence, probably a real camera”; a single low blip means “a weak positional match, treat with suspicion.” An operator learns to read the pitch without a screen.
The tier is monotonic in confidence from top to bottom. Tier 4 requires the most to be true (the full IE fingerprint); tier 0 is a bare SSID keyword hit and is disabled by default precisely because it is the easiest to spoof or trip accidentally.
confidence
▲
T4 │ ████████████ IE fingerprint — "this is Flock-shaped"
T3 │ █████████ wildcard probe from a target OUI
T2 │ ██████ transmitter-OUI / BLE firmware signature
T1 │ ███ OUI only in addr1 / addr3 (can misfire)
T0 │ █ SSID keyword (off by default)
└────────────────────────────────────────────▶ what must be true
4.9 Frame-to-buzzer: the full decision flow
Putting §5–§8 together, this is the life of a single 802.11 frame from antenna to audio:
┌──────────────────────────┐
802.11 frame ──────▶│ RSSI ≥ RSSI_MIN (−95)? │── no ─▶ drop
└────────────┬─────────────┘
│ yes
▼
┌──────────────────────────┐
│ type/subtype of frame? │
└───┬───────────────┬───────┘
mgmt probe req │ │ data / other mgmt
(type0 sub4) │ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────────┐
│ wildcard SSID (len0) │ │ OUI in addr2? ─▶ TIER 2 │
│ AND addr2 ∈ OUI? │ │ OUI in addr1/addr3? ─▶ T1 │
└──────┬────────────────┘ │ SSID keyword (if on)? ─▶ T0│
│ yes └────────────┬─────────────┘
▼ │
┌───────────────────┐ │
│ IE fingerprint OK? │── no ─▶ TIER 3 │
└────────┬──────────┘ │
│ yes │
▼ │
TIER 4 │
│ │
└───────────────┬─────────────────────┘
▼
┌──────────────────────────────────────┐
│ highest-tier-wins table update │
│ per-MAC ALERT_COOLDOWN_MS (5000 ms) │
│ (higher tier pre-empts cooldown) │
└───────────────┬────────────────────────┘
▼
tone for tier + JSON line over USB-CDC + LED
The important structural point: tiering happens before the cooldown, and the cooldown is per-MAC, so a crowded street full of targeted devices still produces one readable alert per device per five seconds rather than a continuous scream — while a device that upgrades its tier still cuts through immediately.
4.10 Wi-Fi configuration knobs
The Wi-Fi behaviour is set by a block of #defines at the top of main.cpp. The full list
lives in Volume 6; the ones that shape the Wi-Fi methodology specifically are:
Table 5 — lives in Volume 6; the ones that shape the Wi-Fi methodology specifically are
| Define | Default | Effect on Wi-Fi detection |
|---|---|---|
CHANNEL_MODE | CHANNEL_MODE_CUSTOM | 11 → 6 → 1 hop pattern |
CHANNEL_DWELL_MS | 250 | dwell per channel; 2× camera probe interval |
RSSI_MIN | −95 | pre-match reject floor (dBm) |
ALERT_COOLDOWN_MS | 5000 | per-MAC repeat-alert suppression |
CHECK_ADDR1 | 1 | enable the tier-1 receiver-address check |
CHECK_ADDR3 | 1 | enable the tier-1 BSSID check |
ENABLE_SSID_MATCH | 0 | tier-0 SSID keyword match — off by default |
PROCESS_MGMT_FRAMES | 1 | evaluate management frames (probes live here) |
PROCESS_DATA_FRAMES | 1 | evaluate data frames (addr2/addr1/addr3 OUI) |
MAX_DETECTIONS | 200 | live-table capacity |
Two patterns to keep straight. ENABLE_SSID_MATCH defaults to 0 because the raw SSID
keyword match (Flock, Flock-XXXXXX) is the weakest and most spoofable signal — turning it
on gives you tier-0 hits you probably don’t want. CHECK_ADDR1/CHECK_ADDR3 default on
but feed only tier 1, the tier the README itself admits “do misfire” — see §11.
The Flock SSID patterns themselves, for reference: cameras bring up a SoftAP named
Flock-XXXXXX protected with WPA2 and the literal passphrase security, plus a bare Flock
SSID. Even with SSID matching disabled, these are a useful human sanity-check when you dump
the raw capture — but the firmware does not alert on them unless you flip ENABLE_SSID_MATCH.
4.11 Limitations and honest caveats
A methodology volume that only lists strengths is marketing. The Wi-Fi path has real edges,
and the flock-you README is candid about them.
- Tier-1 addr1/addr3 matches “do misfire.”
addr1is the receiver andaddr3is the BSSID — neither is the transmitting radio. An OUI appearing in those fields means a targeted device is being addressed or is the access point of record, which is weaker evidence than the device transmitting with its own OUI inaddr2. The README explicitly warns these tier-1 hits produce false positives; that is exactly why they are tier 1 and not tier 2, and why the buzzer gives them the lowest, least-alarming tone. - The IE fingerprint bytes are not in the README. As noted in §7, tier-4’s condition (4) is named and cited but not published in the README; the exact fingerprint is in source.
- Stale channel-order prose. README text says ascending; code hops descending. The shipped behaviour is descending (11 → 6 → 1).
- Prefix lists drift. The 32 community prefixes track observed hardware; vendors can and
do rotate MA-L assignments, retire modules, and change defaults. The firmware-extracted
pair (§4) is more durable precisely because it keys on the silicon, but the community list
needs periodic refresh from the upstream
nite-oui-collection. - “Passive” is clean here, with an asterisk elsewhere. The Wi-Fi path is pure monitor-mode listening. The asterisk on “passive” is earned by the BLE active-scan phase (Volume 5) and the Red-edition transmit tools (Volume 9), not by anything in this volume.
Operator takeaway. Trust the pitch. A rising two-note chirp (tier 3/4) from a wildcard probe is strong evidence of a nearby Flock-class camera. A single 800 Hz blip (tier 1) is a prompt to look closer, not a confirmation — it may be a device merely addressed by a targeted radio, or a genuine coincidence in addr1/addr3.
4.12 Where this connects
The BLE half of the detection story — NimBLE passive scanning, the Penguin/Flock-GATT/
Nordic-DFU/Raven signatures, duty cycling, and the host-side Classic-Bluetooth corroboration
that the ESP32 cannot do on its own — is Volume 5. The tier-2 BLE methods named in the
five-tier table (ble_name, ble_mfg, ble_gatt_svc) are defined there.
The config defines touched on in §10, the per-MAC session storage and CRC envelope, the USB-CDC JSON protocol, and the Flask + GPS companion dashboard that turns this stream into a map are all taken apart in Volume 6, “flock-you in depth.”
The hardware the whole methodology runs on — the Seeed XIAO ESP32-S3, its GPIO3 buzzer, the U.FL antenna option for direction-finding — is Volume 3, which also carries the canonical “Where this firmware runs” supported-hardware table. The threat model (what Flock ALPR cameras, Penguin battery packs, Raven acoustic sensors, and Axon gear actually are and why they radiate) is Volume 2.
Comments (0)