feat(privshield): E2E hardware program — validated C core + multi-provider firmware scaffolds

Take VEIL from the synthetic Rust reference model toward real WiFi silicon
across multiple hardware providers, around one shared, host-validated core.
Answers the questions "can OpenWRT / open WiFi software implement this?" and
"can ESP32 help scramble signals?" with an honest per-platform feasibility map.

Portable C shield core (firmware/privshield/core/) — VALIDATED (host test):
- veil_shield.{h,c}: keyed Givens-rotation obfuscation of the identity-bearing
  "fine" subspace, C99, no malloc / no libc I/O, only <math.h>. SplitMix64 key
  schedule byte-identical to the Rust crate, so on-air behavior is consistent
  everywhere and every adapter links the same math.
- make test passes: energy conservation (orthogonal => "not jamming"),
  reversibility (recover inverts apply), wrong-key-fails, and PRNG stream parity
  with the Rust crate. This is build/host evidence, NOT silicon.

Per-provider adapters (all SYNTHETIC / L0, build-only, TODO(hw) markers):
- openwifi/  grade B (ceiling A, effort D): only open PHY/MAC (FPGA) that can
  host the full keyed rotation + inverse; needs new HDL + 2nd TX chain. Carries
  the P5 measurement protocol (MEASUREMENT.md) for the first MEASURED result.
- openwrt/   grade C: per-packet keyed unitary is blob-blocked on commodity APs;
  coarse compliant knobs (TX antenna map, sounding-cadence jitter) reachable
  from userspace/hostapd; ath9k is the one credible driver-patch route.
- nexmon/    grade C: reading the compressed-BF angles is solved (nexmon_csi /
  Wi-BFI); shaping the transmitted report is research-grade (D11 ucode-adjacent).
- esp32/     grade F (self) / B (supporting): cannot shape its own BF feedback
  (closed esp-phy-lib blob); legitimate as a sensing detector and external-RIS
  controller — the honest way ESP32 "helps scramble", via an external surface.

Docs:
- firmware/privshield/README.md: architecture, layout, and the feasibility matrix.
- ADR-290: the E2E hardware program, PROOF discipline, and per-provider decision;
  added to docs/adr/README.md index.

Compliant waveform controls only, never jamming. No adapter has run on silicon;
no MEASURED claim is made (that is roadmap P5, gated on a captured log).

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01WEXNqzs7UsfNFBcP5yW21p
This commit is contained in:
Claude
2026-08-09 16:34:11 +00:00
parent 192ed2a236
commit b827dc40b1
27 changed files with 2910 additions and 0 deletions

View File

@@ -0,0 +1,116 @@
# Building the VEIL Nexmon patch — **UNTESTED**
> **This procedure has never been run.** It has not been built with the Nexmon
> toolchain, not flashed, and not captured on air. Addresses/symbols in
> `patch/veil_patch.c` are placeholders (one is intentionally invalid,
> `0xDEAD0000`) so it will **not** produce a flashable image as-is. This file
> documents *how it would build* so a hardware operator with real silicon can
> take it forward. `SYNTHETIC / L0`, per CLAUDE.md.
## Prerequisites (host, not in this repo)
- A Linux host (Nexmon expects an x86_64 Ubuntu-like build host) with the
Broadcom-flavored ARM toolchain Nexmon downloads/uses, plus `git`, `make`,
`gcc-arm-none-eabi`, `flex`, `bison`, `libisl`, `automake`.
- Nexmon checked out **outside** this repo (do not vendor it here):
```bash
git clone https://github.com/seemoo-lab/nexmon.git
cd nexmon
source setup_env.sh # sets NEXMON_ROOT, toolchain paths
make # builds libISL / firmwares tooling
```
- The target firmware blob present on the device: BCM43455c0
(`brcmfmac43455-sdio.bin`), version **7_45_189** (Cypress) or 7_45_154
(Raspbian). Do **not** commit the blob or any extracted symbols/ROM to RuView.
## Where this patch would live in the Nexmon tree
Nexmon builds per chip/firmware under `patches/<chip>/<fwver>/<name>/`. This
adapter would be a Nexmon project, e.g.:
```
$NEXMON_ROOT/patches/bcm43455c0/7_45_189/veil/
├── Makefile # copy of an existing nexmon patch Makefile (e.g. nexmon_csi's)
├── src/
│ ├── veil_patch.c # <- symlink/copy of firmware/privshield/nexmon/patch/veil_patch.c
│ ├── veil_shield.c # <- from firmware/privshield/core/ (compiled into the patch)
│ └── veil_shield.h # <- from firmware/privshield/core/
└── ...
```
Keep the RuView copies canonical; the Nexmon tree gets copies/symlinks so the
core stays byte-identical to `../core/`.
## Linking the portable core (MCU-friendly)
The core is `no_std`-style C99: no malloc, no libc I/O, only `<math.h>`
(`sinf`/`cosf`/`sqrtf`/`sqrt`). To build it into the patch:
1. Add `veil_shield.c` to the patch `Makefile`'s object list (alongside
`patch.o`/`wrapper.o`), so it compiles with the same ARM flags.
2. Ensure the firmware provides `sinf`/`cosf`/`sqrtf`. **TODO(hw):** Broadcom
firmware may not export libm. Options, in order of preference:
- link a small `libm`/`compiler-rt` for `arm-none-eabi`;
- or replace the trig with a fixed-point / CORDIC Givens rotation
(`TODO(reverse-engineer)`), which also avoids float on parts without an FPU.
3. All VEIL working storage is stack-bounded (`VEIL_MAX_FINE`, `CACHE` in the
core) — no heap is introduced on-chip.
## Build
```bash
cd $NEXMON_ROOT/patches/bcm43455c0/7_45_189/veil
make # produces the patched brcmfmac43455-sdio.bin
```
Before `make` can succeed you must first resolve every `TODO(reverse-engineer)`
in `veil_patch.c`:
- replace `0xDEAD0000` and the `wlc_sendmgmt_veil_target` symbol with the real,
disassembled target address/symbol for 7_45_189;
- implement `veil_bfr_unpack_fine` / `veil_bfr_pack_fine` (the angle bit-field
codec) and the report-body offset/length;
- confirm the compressed-beamforming report is assembled in ARM on this chip
(else move to hook candidate #2/#3 — see README).
## Flash (Raspberry Pi, on-device)
**TODO(hw) — untested.** Typical Nexmon flow on the Pi:
```bash
# back up stock firmware first!
sudo cp /lib/firmware/brcm/brcmfmac43455-sdio.bin ~/brcmfmac43455-sdio.bin.orig
sudo cp brcmfmac43455-sdio.bin /lib/firmware/brcm/brcmfmac43455-sdio.bin
# (some setups also need the matching *.clm_blob / nexmon's own copy path)
sudo rmmod brcmfmac && sudo modprobe brcmfmac # reload driver with new firmware
dmesg | tail # confirm firmware loaded
```
Push the session key at runtime (matches the IOCTL stub in `veil_patch.c`):
```bash
# TODO(hw): nexutil vendor-IOCTL id and payload format are placeholders
nexutil -s<VEIL_IOCTL_SET_KEY> -b -l8 -v<base64-8-byte-key>
```
**Recovery:** if WiFi breaks, restore the backup blob and reload the driver.
A bad flashpatch offset can knock out WiFi until you reflash stock firmware.
## Validation you can honestly do (still not `MEASURED` firmware)
1. **Host unit test of the math** (already green in this repo):
`cd ../../core && make test`.
2. **Read-back on hardware** with `nexmon_csi`/Wi-BFI: capture the report with
and without the patch and check the fine subspace changed while SNR/norm is
preserved. This validates the transform end-to-end but is a *receiver*
observation, not proof the TX hook is robust.
3. Only a captured device runtime log showing the shaped report leaving *this*
node, plus receiver-side recovery with the shared key, would move any claim
from `SYNTHETIC`/`CLAIMED` toward `MEASURED` (roadmap P5).
## References
See `README.md` for sources (Nexmon, nexmon_csi, Wi-BFI, D11 reverse
engineering).

View File

@@ -0,0 +1,124 @@
# VEIL protector — Nexmon (Broadcom/Cypress) path
C-firmware-patch adapter that would call the portable VEIL core
(`../core/veil_shield.{h,c}`) on the compressed-beamforming-feedback **angles
before transmission**, using the [Nexmon](https://github.com/seemoo-lab/nexmon)
patching framework on a Broadcom/Cypress WiFi chip.
> **Evidence discipline.** Everything here is **`SYNTHETIC` / L0 / build-only**.
> Nothing in this directory has been built with the Nexmon toolchain, flashed to
> a chip, or captured on air. There are **no** `MEASURED` claims and **no**
> hardware logs. The patch is an honest **skeleton** with `TODO(hw)` and
> `TODO(reverse-engineer)` markers, not working firmware. Per CLAUDE.md, no
> defense claim becomes `MEASURED` without a captured runtime log from real
> silicon (roadmap P5).
>
> **Compliant waveform only — never jamming.** The core applies an *orthogonal*
> (energy-preserving) keyed rotation to the node's *own* standards-conformant
> feedback report. It does not add power, transmit out of turn, or interfere
> with any other station.
## Feasibility grade: **C** (research-grade, partial, unproven)
| Sub-path | Grade | Why |
|---|---|---|
| **Read** the compressed BF feedback | **A** (proven by others) | `nexmon_csi` extracts CSI, and Wi-BFI parses the compressed-beamforming *angles* straight from captured action frames — no firmware change at all. The report content is observable today. |
| **Write / shape** the transmitted report | **C / C-** | The report is generated by the proprietary **D11** real-time core, not the ARM firmware Nexmon comfortably patches. The hook point is deep, chip- and firmware-version-specific, and unverified here. Plausible, not demonstrated. |
Grade **C** reflects *this* deliverable's goal — shaping the **TX** report. The
read side is a solved problem and is graded only to contrast honestly.
### Why the write path is hard (the core honesty point)
Broadcom/Cypress chips put all time-critical 802.11 MAC/PHY work on the **D11
core**, a proprietary microcontroller running a programmable state machine
("ucode"). Published reverse-engineering of these chips reports that the D11
generates the **VHT/HE compressed beamforming report ~10 µs after the NDP**, with
its contents fetched from an **internal memory updated directly by the hardware**
on NDP reception. In other words, the angles VEIL wants to touch are staged and
emitted inside the ucode/PHY path on a microsecond deadline — *below* the ARM
"wl" driver firmware where Nexmon's C hooks (`__attribute__((at(addr, ...)))`
flashpatches / branch hooks) live most reliably. Reaching them means either a
D11-ucode patch (needs the D11 assembler and SHM/template-RAM layout) or catching
the report while the ARM path still assembles the action-frame body — if it does
so on this chip at all. Both are `TODO(reverse-engineer)`.
## Target chip(s)
Primary: **BCM43455c0** (Raspberry Pi 3B+/4B; also RPi Zero 2 W), firmware
**7_45_154** (Raspbian) or **7_45_189** (Cypress) — the best-documented,
most-reproducible Nexmon target, and one of the four chips `nexmon_csi` already
supports. Secondary candidates that `nexmon_csi` also supports: **BCM4339**
(Nexus 5), **BCM4358** (Nexus 6P), **BCM4366c0** (Asus RT-AC86U). We scope the
skeleton to BCM43455c0 / 7_45_189 and leave the others as build-matrix `TODO`s.
Caveat: the RPi BCM43455c0 is an **802.11ac (VHT)** single-stream part; its own
*transmit* beamforming/sounding activity as a beamformee is limited. The
skeleton targets the **VHT compressed beamforming report** action-frame path;
whether this chip emits enough to shape in practice is itself a `TODO(hw)`
question.
## Hook-point candidates (all `TODO(reverse-engineer)`)
Ordered most-tractable → deepest. Addresses are **placeholders** — real offsets
come from disassembling the specific firmware blob and cross-checking the Nexmon
symbol tables (`wl_ram.elf` / IDA); none are known-good here.
1. **ARM action-frame TX assembly (best first target).** If the "wl" driver
assembles the VHT Compressed Beamforming Report action-frame *body* in ARM
firmware before handing it to the D11 (function family around
`wlc_txbf_*` / a `wlc_send*mgmt`/action path), a branch hook there could
locate the report's fine-angle block and call `veil_shield_apply` in place.
Cheapest if it exists on this chip.
2. **ARM → D11 TX descriptor / template handoff.** Hook where the driver stages
a frame into the D11 TX FIFO / template RAM (`wlc_d11hdrs` / `wlc_txfifo`
region) and rewrite the angle bytes there. Requires knowing the exact
template-RAM offset of the report body.
3. **D11 ucode patch (deepest).** Patch the ucode routine that copies angles
from the hardware-updated internal memory into the outgoing report, applying
the rotation in D11 SHM. Needs the D11 assembler and PHY/SHM map; highest
fidelity, highest effort, most fragile across firmware versions.
The skeleton wires candidate **#1** and leaves #2/#3 documented but unimplemented.
## What is realistic
- **Realistic now:** verify VEIL's *effect* by reading — capture the shaped vs.
unshaped report with `nexmon_csi`/Wi-BFI and confirm the fine subspace changed
while energy (SNR/norm) is preserved. This validates the math, not the TX hook.
- **Realistic with serious RE effort:** candidate #1, on one pinned firmware, as
a demo — partial, brittle, chip-specific.
- **Not realistic as a portable product:** a clean, firmware-version-stable TX
report-shaping patch across Broadcom parts. Treat as research.
## Risk / honesty
- Wrong flashpatch offsets can **brick the WiFi blob** (recoverable by
reflashing stock firmware, but real).
- Regulatory: the transform is energy-preserving and rides standards-marked
spatial-mapping freedom, but any TX-path firmware patch on a certified radio is
**outside the device's certification** — bench/anechoic use only.
- Firmware blobs are proprietary; do **not** commit extracted firmware, symbols,
or ROM dumps to this repo.
## Sources
- Nexmon framework — <https://github.com/seemoo-lab/nexmon>
- `nexmon_csi` (chips: bcm4339, bcm43455c0, bcm4358, bcm4366c0) —
<https://github.com/seemoo-lab/nexmon_csi>
- Wi-BFI (reads BFAs/BFI from captured compressed-beamforming action frames) —
<https://github.com/kfoysalhaque/Wi-BFI>, paper arXiv:2309.04408
<https://arxiv.org/abs/2309.04408>
- BCM43455c0 patches / D11 headers (`d11.h`) —
<https://github.com/seemoo-lab/nexmon/tree/master/patches/bcm43455c0>
- D11 real-time core / ucode reverse engineering (SEEMOO, Quarkslab) —
<https://www.seemoo.tu-darmstadt.de/> ,
<https://blog.quarkslab.com/reverse-engineering-broadcom-wireless-chipsets.html>
- 802.11ac VHT NDP sounding & compressed beamforming report structure (context) —
<https://community.cisco.com/t5/wireless-mobility-knowledge-base/802-11ac-transmit-beamforming-and-vht-ndp-sounding-procedure/ta-p/3155879>
> The "~10 µs / hardware-updated internal memory" characterization above is drawn
> from published Broadcom D11 reverse-engineering (reported for BCM4365-class
> parts) and is used here as design guidance; it is **not** independently
> verified on BCM43455c0 in this repo. `TODO(reverse-engineer)`: confirm on the
> target blob.

View File

@@ -0,0 +1,176 @@
/* SPDX-License-Identifier: MIT OR Apache-2.0
*
* veil_patch.c — VEIL protector, Nexmon (Broadcom/Cypress) path.
*
* ============================ HONESTY BANNER ============================
* SYNTHETIC / L0 / BUILD-ONLY. This file is an HONEST SKELETON in Nexmon
* style. It has NOT been built with the Nexmon toolchain, NOT flashed to a
* chip, and NOT captured on air. Every __attribute__((at(...))) address and
* every firmware symbol below is a PLACEHOLDER. Do not treat this as working
* firmware. See ../README.md for the feasibility grade (C, research-grade).
*
* Goal: call the portable VEIL core (../../core/veil_shield.c)
* `veil_shield_apply()` on the compressed-beamforming-feedback FINE ANGLES in
* the transmitted VHT/HE compressed beamforming report, so the identity-bearing
* fine subspace is obfuscated by a keyed, ENERGY-PRESERVING (orthogonal)
* Givens rotation before the frame leaves the radio. Compliant only, never
* jamming: the transform preserves the report's L2 norm.
*
* Target: BCM43455c0 (Raspberry Pi 3B+/4B), firmware 7_45_189. Others TODO.
* =======================================================================
*/
#pragma NEXMON targetregion "patch"
#include <firmware_version.h> /* FW_VER_7_45_189, CHIP_VER_BCM43455c0 (Nexmon) */
#include <patcher.h> /* BPatch / GPatch / __attribute__((at(...))) */
#include <structs.h> /* struct sk_buff, struct wlc_info, etc. */
#include <wrapper.h> /* Nexmon wrappers for ROM/firmware functions */
/* --- Portable VEIL core, linked/inlined for the MCU -------------------------
* The core is pure C99: no malloc, no libc I/O, only <math.h> (sinf/cosf/sqrtf).
* On the Nexmon ARM target we compile ../../core/veil_shield.c into this patch
* object (see ../BUILD.md) and pull in only the declarations here. Everything
* operates on a caller-provided fixed buffer — no dynamic allocation on-chip. */
#include "veil_shield.h"
/* ------------------------------------------------------------------------- */
/* Configuration (compile-time; no on-chip allocation) */
/* ------------------------------------------------------------------------- */
/* Max fine-angle count we will touch in one report. Sized for a VHT SU report
* fine block; bound it so all working storage is on the stack, malloc-free. */
#define VEIL_MAX_FINE 64u
/* Rotation passes — MUST match the associated receiver and the Rust reference
* crate default so recover() inverts exactly. TODO(hw): confirm against the
* receiver config actually deployed. */
#define VEIL_PASSES 96u
/* Session key. TODO(hw): DO NOT hardcode a real key in flashed firmware. Inject
* via nexutil IOCTL (see veil_ioctl_set_key stub) or a provisioning step; this
* placeholder exists only so the skeleton type-checks. */
static uint64_t g_veil_key = 0x0000000000000000ULL;
/* ------------------------------------------------------------------------- */
/* Bridge: decode angles -> rotate -> re-encode, in place */
/* ------------------------------------------------------------------------- */
/*
* TODO(reverse-engineer): The compressed beamforming report packs the phi/psi
* angles as bit-fields whose widths depend on the codebook (VHT: (7,5) or (9,7);
* HE differs) and on Nc/Nr. The bytes handed to us are NOT plain floats. This
* bridge must:
* (1) parse the fine-angle bit-fields from `report` into `fine[]` as floats
* in the same units/order the receiver + Rust reference expect,
* (2) call veil_shield_apply() on that flat vector,
* (3) re-quantize and repack the rotated angles back into `report`,
* preserving all coarse/header fields and the frame length.
* Steps (1)/(3) are the real work and are UNIMPLEMENTED here.
*/
static void veil_shape_report_inplace(uint8_t *report, uint32_t report_len)
{
if (report == 0 || report_len == 0)
return;
float fine[VEIL_MAX_FINE];
uint32_t n = 0;
/* TODO(reverse-engineer): unpack fine-angle bit-fields -> fine[0..n) */
/* n = veil_bfr_unpack_fine(report, report_len, fine, VEIL_MAX_FINE); */
if (n < 2 || n > VEIL_MAX_FINE)
return; /* nothing safely shapeable; leave frame untouched (fail-open) */
/* Orthogonal, energy-preserving, keyed. This is the ONLY validated step. */
veil_shield_apply(fine, (size_t)n, g_veil_key, VEIL_PASSES);
/* TODO(reverse-engineer): repack fine[0..n) back into `report` bit-fields,
* keeping report_len and all non-fine fields byte-identical. */
/* veil_bfr_pack_fine(report, report_len, fine, n); */
(void)report_len;
}
/* ------------------------------------------------------------------------- */
/* Hook candidate #1 (see README): ARM action-frame TX assembly */
/* ------------------------------------------------------------------------- */
/*
* We hook the point where the "wl" driver has assembled the VHT Compressed
* Beamforming Report action frame in an sk_buff, just before it is queued to
* the D11 for transmission, locate the report body, and shape it.
*
* TODO(reverse-engineer): the symbol/address below is a PLACEHOLDER. The real
* target must be found by disassembling 7_45_189 (IDA + Nexmon's wl_ram.elf
* symbol map) and confirming: (a) the report body is assembled in ARM (not
* only in D11 ucode), (b) `p` really carries a compressed-beamforming action
* frame, and (c) the offset of the report body within the frame.
*
* If (a) is false on this chip, candidate #1 is dead and we fall to #2/#3
* (TX template-RAM rewrite / D11 ucode patch) — both documented in README,
* neither implemented here.
*/
/* Original firmware function prototype (PLACEHOLDER signature). */
extern int wlc_sendmgmt_veil_target(struct wlc_info *wlc, void *p, void *scb);
/* Our replacement. GPatch/BPatch below redirects the target to this. */
int wlc_sendmgmt_veil_hook(struct wlc_info *wlc, void *p, void *scb)
{
/* TODO(reverse-engineer): confirm `p` is a struct sk_buff* and that this
* frame is a VHT/HE compressed beamforming action frame (category 21
* VHT / 30 HE, action = Compressed Beamforming). Guard hard so we never
* mangle unrelated management frames. */
struct sk_buff *skb = (struct sk_buff *)p;
if (skb != 0 /* && veil_is_bf_report_action(skb) */) {
/* TODO(reverse-engineer): compute report body pointer + length from the
* action-frame layout. PLACEHOLDER offsets: */
uint8_t *report = 0; /* skb->data + VEIL_BFR_BODY_OFFSET; */
uint32_t report_len = 0; /* skb->len - VEIL_BFR_BODY_OFFSET; */
veil_shape_report_inplace(report, report_len);
}
/* Always fall through to the real firmware routine so normal TX proceeds. */
return wlc_sendmgmt_veil_target(wlc, p, scb);
}
/*
* Redirect the firmware's mgmt/action TX routine to our hook.
* PLACEHOLDER ADDRESS — 0xDEAD0000 is intentionally invalid so nobody mistakes
* this for a real, flashable patch. TODO(reverse-engineer): replace with the
* verified address for CHIP_VER_BCM43455c0 / FW_VER_7_45_189.
*
* Nexmon idiom: a branch patch that overwrites the target's prologue with a
* branch to our replacement (which tail-calls the saved original).
*/
__attribute__((at(0xDEAD0000, "flashpatch", CHIP_VER_BCM43455c0, FW_VER_7_45_189)))
BPatch(veil_sendmgmt_hook, wlc_sendmgmt_veil_hook);
/* ------------------------------------------------------------------------- */
/* Key provisioning via nexutil IOCTL (stub) */
/* ------------------------------------------------------------------------- */
/*
* TODO(hw): register a custom IOCTL so `nexutil` can push the 64-bit session
* key at runtime instead of baking it into flash. Hook the driver's ioctl
* dispatch (wlc_ioctl) the same way nexmon_csi installs its config IOCTLs.
* Left as a stub: the dispatch address and the nexmon_ioctl plumbing are
* PLACEHOLDERS.
*/
#define VEIL_IOCTL_SET_KEY 0x7EIL /* TODO(hw): pick a free vendor IOCTL id */
int veil_ioctl_set_key(struct wlc_info *wlc, const uint8_t *buf, uint32_t len)
{
(void)wlc;
if (buf == 0 || len < sizeof(uint64_t))
return -1;
uint64_t k = 0;
for (uint32_t i = 0; i < sizeof(uint64_t); i++)
k |= ((uint64_t)buf[i]) << (8u * i);
g_veil_key = k;
return 0;
}
/*
* ---------------------------------------------------------------------------
* Candidate #2 (TX template-RAM rewrite) and #3 (D11 ucode patch) are NOT
* implemented. See ../README.md "Hook-point candidates". #3 would require the
* D11 assembler and the PHY/SHM angle-staging map — deepest and most fragile.
* ---------------------------------------------------------------------------
*/