dwt: reverse the dwt_prf challenge; remote poke/jump now works

The firmware transforms the reply (16-round Feistel per 8-byte record) and
compares it with the host-order copy of the challenge, so the answer is
swap4(inverse_transform(swap4(challenge))).  The font stages are memcpy in
this path because dwt_prf initialises only mode 1.

challenge.py implements the transform and its inverse using the firmware's
fixed key/SP table; solve.py answers the challenge over TCP using the
unmodified exploit client.  320 forward vectors, the firmware's own inverse
and three live sessions validate it.  A live run with no breakpoints poked
the 82-byte hello-world payload and ran it: MAGIC DEAD/BEEF/0400, VRAM
1024/1024.  Notes and evidence under dwt/notes and dwt/evidence.
This commit is contained in:
SDD committed 2026-10-18 02:36:23 +03:00
1 parent 041c82cb73
commit 1e5c095b72
22 files changed
+1351

No files matched your search

+37
View File
@@ -248,3 +248,40 @@ output bits on average (23–42).
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.
## 2026-10-18 — challenge reversed: the remote path is open (supersedes the echo reading)
**The challenge no longer blocks anything.** `dwt/challenge.py` answers it from
the challenge bytes alone; `dwt/solve.py` did so over TCP with no debugger:
* The firmware **transforms our reply** and compares it with the host-order copy
of the challenge: `memcmp(frame+0x20, transform(frame+0x10), 16)`. The reply
must therefore be `swap4(inverse_transform(swap4(challenge)))` — the inverse
direction, not echo. Live bytes at the comparison are in
`dwt/evidence/live-run/debug-live.log` (`expecting == got`, `a8=0`).
* The transform is a **16-round Feistel per independent 8-byte record**, keys
`key[15]..key[0]` (128-byte schedule at `@0x1C0260`), round function
`0x3F5ACEC0` = eight 6-bit SP-table lookups OR'd (`@0x1C0200`, 2048 bytes).
* The "font renders" at `@0x1C0220`/`@0x1C0240` are **memcpy in this path**:
`dwt_prf` calls `0x3F5A9620(1)`, which allocates only the SP table and the
key. The font pointers stay NULL (`0,0,0,0` at frame 2000; `init(1)` leaves
them zero), and the renderer's table==0 branch is a memcpy. Earlier attempts
tried to invert a font stage that never runs here.
* Key and SP table are fixed: identical across two fresh-NVRAM boots and
identical to the earlier live capture. They are embedded in
`dwt/challenge.py`; provenance and disassembly are in
`dwt/notes/CHALLENGE_REVERSE.md`.
* Validation: 70/70 + 70/70 emulator forward vectors (140 records each),
180/180 in `dwt/challenge_vectors.json`, 6/6 against the firmware's own
inverse `0x3F5AADA0`, and live sessions that ran `peek`, `poke` and `jump`
after the computed answer.
* **Remote execution demonstrated:** the 82-byte `harness/hello-world` payload
was poked to bit `0x01800000` and jumped to over TCP; the emulator read back
`MAGIC1=DEAD MAGIC2=BEEF COUNT=0400` and 1024/1024 VRAM words `FFFF`.
`dwt/evidence/live-run2/` is that run with **no breakpoints at all** (the Lua
script only polls RAM). Only `VT1200_SKIP_LEVEL1=1` is needed for TCP.
Files: `dwt/challenge.py`, `dwt/solve.py`, `dwt/notes/CHALLENGE_REVERSE.md`,
`dwt/evidence/oracle-*.log`, `dwt/evidence/VALIDATION.txt`,
`dwt/evidence/live-run{,2}/`, `dwt/evidence/scripts/{oracle_call,live_verify,live_check}.lua`.
`dwt/exploit.py` and `dwt/mop_console.py` were not modified.