forked from arti/vt1200
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:
1 parent
041c82cb73
commit
1e5c095b72
22 files changed
+1351
No files matched your search
@@ -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.
|
||||
Reference in new issue
Block a user