mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-30 12:03:54 +02:00
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
@@ -0,0 +1,72 @@
|
||||
-- live_verify.lua -- passive verification of the remote challenge bypass.
|
||||
--
|
||||
-- No breakpoint here writes a register or memory: it only prints. In
|
||||
-- particular the memcmp at 3F5A0920 is *observed*, never forced.
|
||||
local machine = manager.machine
|
||||
local cpu = machine.devices[':maincpu']
|
||||
local dbg = machine.debugger
|
||||
local space = cpu.spaces['program']
|
||||
if not dbg then machine:exit(); return end
|
||||
dbg.visible_cpu = cpu
|
||||
|
||||
local D = "/home/arti/code/k-space/vt1200/dwt/evidence/live-run"
|
||||
local MAGIC1 = 0x01810000
|
||||
local MAGIC2 = 0x01810010
|
||||
local COUNT = 0x01810020
|
||||
local VRAM = 0x04019040
|
||||
local f = 0
|
||||
local armed = false
|
||||
local saw_challenge = false
|
||||
local saw_memcmp = false
|
||||
local done_at = nil
|
||||
|
||||
local function report_magic(tag)
|
||||
print(string.format("LIVE2 %s: MAGIC1=%04X MAGIC2=%04X COUNT=%04X",
|
||||
tag, space:read_u16(MAGIC1), space:read_u16(MAGIC2), space:read_u16(COUNT)))
|
||||
local filled, holes = 0, 0
|
||||
for r = 0, 63 do
|
||||
for c = 0, 15 do
|
||||
if space:read_u16(VRAM + r * 1024 + c * 16) == 0xFFFF then
|
||||
filled = filled + 1
|
||||
else
|
||||
holes = holes + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
print(string.format("LIVE2 %s: VRAM block %d/1024 words FFFF, %d holes", tag, filled, holes))
|
||||
end
|
||||
|
||||
emu.register_frame_done(function()
|
||||
f = f + 1
|
||||
if f == 1 and not armed then
|
||||
armed = true
|
||||
dbg:command('bpset 3F58AD60,,{ printf "LIVE2 accept_debug_conn entry pc=%08X\\n", pc ; g }')
|
||||
dbg:command('bpset 3F59F320,,{ printf "LIVE2 dispatcher entry pc=%08X\\n", pc ; g }')
|
||||
dbg:command('bpset 3F59FDC0,,{ printf "LIVE2 dwt_prf challenge entry pc=%08X\\n", pc ; g }')
|
||||
dbg:command('bpset 3F5A0200,,{ printf "LIVE2 challenge sent a13=%08X wire=%08X.%08X.%08X.%08X host=%08X.%08X.%08X.%08X\\n", a13, d@(a13), d@(a13+0x20), d@(a13+0x40), d@(a13+0x60), d@(a13+0x100), d@(a13+0x120), d@(a13+0x140), d@(a13+0x160) ; g }')
|
||||
dbg:command('bpset 3F5A0740,,{ printf "LIVE2 pre-transform reply=%08X.%08X.%08X.%08X\\n", d@(a13+0x80), d@(a13+0xa0), d@(a13+0xc0), d@(a13+0xe0) ; g }')
|
||||
dbg:command('bpset 3F5A08E0,,{ printf "LIVE2 memcmp-args expecting=%08X.%08X.%08X.%08X got=%08X.%08X.%08X.%08X\\n", d@(a9), d@(a9+0x20), d@(a9+0x40), d@(a9+0x60), d@(a11), d@(a11+0x20), d@(a11+0x40), d@(a11+0x60) ; g }')
|
||||
-- OBSERVE ONLY: print the comparison result; never assign to a8.
|
||||
dbg:command('bpset 3F5A0920,,{ printf "LIVE2 challenge memcmp a8=%08X (0=equal)\\n", a8 ; g }')
|
||||
dbg:command('bpset 3F5A1B50,,{ printf "LIVE2 rdebug_poke entry pc=%08X\\n", pc ; g }')
|
||||
dbg:command('bpset 3F5A3890,,{ printf "LIVE2 rdebug_jump entry pc=%08X\\n", pc ; g }')
|
||||
print("LIVE2 armed read-only breakpoints")
|
||||
end
|
||||
if f % 500 == 0 then
|
||||
print(string.format("LIVE2 frame %5d word@0xAECD0=%04X field2..3=%d",
|
||||
f, space:read_u16(0xaecd0), (space:read_u16(0xaecd0) >> 2) & 3))
|
||||
end
|
||||
if done_at == nil and space:read_u16(MAGIC1) == 0xDEAD then
|
||||
done_at = f
|
||||
report_magic("payload-ran")
|
||||
end
|
||||
if done_at and f >= done_at + 120 then
|
||||
print(string.format("LIVE2 exiting at frame %d (payload seen at %d)", f, done_at))
|
||||
machine:exit()
|
||||
end
|
||||
if f >= 9000 then
|
||||
print("LIVE2 timeout: no payload markers")
|
||||
machine:exit()
|
||||
end
|
||||
end, "vt1200-live-verify")
|
||||
dbg.execution_state = 'run'
|
||||
Reference in new issue
Block a user