dwt: runtime oracle and ground-truth vectors for the dwt_prf challenge transform

This commit is contained in:
SDD committed 2026-10-18 02:30:49 +03:00
1 parent 5645bb00b3
commit 041c82cb73
31 files changed
+7367

No files matched your search

+51
View File
@@ -197,3 +197,54 @@ tap, BOOTP address 192.168.99.2):
The tap (`tap-mess-1000-0` / bridge `vt1200`) is free again as of 02:40; the
MAME instance and the BOOTP server are stopped. Full write-up:
`dwt/notes/EXPLOIT_ATTEMPT.md`; transcript and logs under `dwt/evidence/`.
## 2026-10-18 02:42 EEST — oracle agent: dwt_prf transform ground truth is captured
Full method and caveats: `dwt/notes/CHALLENGE_ORACLE.md`.
**Vectors:** `dwt/challenge_vectors.json` — a top-level JSON **array** of 180
instances (177 distinct inputs). Load with
`vectors = json.load(open("dwt/challenge_vectors.json"))`; each entry is
`{"input_hex": <32 hex>, "output_hex": <32 hex>, "meta": {...}}`.
`input_hex` is the 16-byte buffer **after** dwt_prf's per-word `ntohl` (the
transform's actual input); `output_hex` is `f(input)` in place. The full
128-byte key schedule and seed-block dumps are in
`dwt/evidence/oracle/runA/stdout.log`.
**Direction (confirmed):** the firmware transforms the reply, not the
challenge. With host-order challenge words `W`, wire challenge
`C = ntohl(W)` (per-32-bit-word byte swap), wire reply `R`:
`X = ntohl(R)`, `Y = f(X)`, and dwt_prf memcmp's `Y` against `W`.
Pass condition: `f(ntohl(R)) == W`, i.e.
`R = ntohl(f^-1(W)) = ntohl(f^-1(ntohl(C)))`. The vectors give `f`, so the
implementation needs the inverse (and the outer word swap).
**Determinism:** yes. 177 distinct inputs give identical outputs on two
fresh-NVRAM boots, and on a third boot that hijacks at a different frame.
Seed block `0x1BE810` and the key schedule `@0x1C0260` are byte-identical
across boots — the key is genuinely fixed for this image.
**Per-8-byte independence:** yes. Records are processed separately: changing
only bytes 0–7 never changes output bytes 8–15 (0/64 single-bit trials), and
32 independence-grid pairs agree. Output for a doubled record is the doubled
output. Permutation on samples: 152 distinct inputs → 152 distinct outputs, no
fixed points. Avalanche: one input bit flips **32.66 of 64** first-record
output bits on average (23–42).
**Two reversing shortcuts found while building the oracle:**
1. **Argument order for a direct call.** `0x3F5A09C0` expects `(len, buf)` in
source order, i.e. `buf` must be the **last** argument written (the callee
reads `*a13(0xffe0)` as the pointer and `*a13(0xffc0)` as the length).
Writing `(buf, 16)` gives a pointer of 16 and will not work.
2. **The two "font render" calls are identity copies on this path.** dwt_prf
calls INIT with mode **1** (`0x3F5A9620(1)`); that mode leaves
`@0x1C0220`/`@0x1C0240` NULL, so `0x3F5ABA40`'s two render calls take their
memcpy branch. Captured per-record intermediates
(`dwt/evidence/oracle/stages.txt`) show `feistel_in == input` and
`pre_final_render == output` for all six samples. The effective transform is
the 16-round Feistel over the key schedule expanded from `0x1BE810` (+ the
`0x1BED40/0x1BEF00/0x1BEF80` auxiliary blocks).
Reproduce: `python3 dwt/oracle.py` (~35 s, two boots + stage capture). No ROM,
driver or `dwt_prf` code was modified; no socket was used for the vectors.