mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-31 12:05:05 +02:00
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.
198 lines
5.4 KiB
Lua
198 lines
5.4 KiB
Lua
-- oracle_call.lua -- drive the firmware's own dwt_prf transform from the
|
|
-- debugger, with no TCP connection.
|
|
--
|
|
-- At frame 1000:
|
|
-- 0x3F5A9620(1) dwt_prf's allocator/init (Ghidra 0x07EB52C4)
|
|
-- 0x3F5AA2C0(0x1BE810) key schedule from the boot-time block
|
|
-- then, for each vector in VECTORS:
|
|
-- 0x3F5A09C0(BUF, 16) the transform dwt_prf applies to the reply
|
|
-- and prints the 16 output bytes.
|
|
--
|
|
-- Optionally (INVERSE=1) it calls 0x3F5AADA0 (the per-record companion that
|
|
-- looks like the inverse) on the vectors instead.
|
|
--
|
|
-- Output lines: ORACLE <inhex> <outhex>
|
|
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 ARG = 0x01800000
|
|
local STACK = 0x01840000
|
|
local RET = 0x018c0000
|
|
local BUF = 0x01810000
|
|
local IE = 0x200000
|
|
local INIT = 0x3F5A9620
|
|
local SCHED = 0x3F5AA2C0
|
|
local XFORM = 0x3F5A09C0
|
|
local XFORM1 = 0x3F5AADA0
|
|
|
|
local INVERSE = (os.getenv("ORACLE_INVERSE") or "") ~= ""
|
|
local COUNT = tonumber(os.getenv("ORACLE_COUNT") or "0")
|
|
|
|
local f = 0
|
|
local started = false
|
|
local phase = "init" -- init | sched | vectors | dump | done
|
|
local saved = {}
|
|
local idx = 0
|
|
local vectors = {}
|
|
local cur = nil
|
|
|
|
-- deterministic pseudo-random vectors plus the all-zero and ff cases
|
|
local function make_vectors()
|
|
vectors[1] = string.rep("\0", 16)
|
|
vectors[2] = string.rep("\255", 16)
|
|
local x = 0x12345678
|
|
for i = 3, COUNT do
|
|
local t = {}
|
|
for j = 1, 16 do
|
|
x = (x * 1103515245 + 12345) & 0x7fffffff
|
|
t[j] = string.char((x >> 16) & 0xff)
|
|
end
|
|
vectors[i] = table.concat(t)
|
|
end
|
|
end
|
|
|
|
local function state(name)
|
|
local r = cpu.state[name]
|
|
return r and r.value or nil
|
|
end
|
|
|
|
local function push(v)
|
|
local sp = state('SP') - 0x20
|
|
space:write_u32(sp, v)
|
|
cpu.state['SP'].value = sp
|
|
end
|
|
|
|
local function setup_call(target, args)
|
|
saved.pc = state('PC')
|
|
saved.sp = state('SP')
|
|
saved.a14 = state('A14')
|
|
saved.st = state('ST')
|
|
cpu.state['SP'].value = STACK
|
|
if #args > 0 then
|
|
local a = ARG
|
|
for i = 1, #args do
|
|
space:write_u32(a, args[i])
|
|
a = a + 0x20
|
|
end
|
|
push(saved.a14)
|
|
cpu.state['A14'].value = ARG + #args * 0x20
|
|
else
|
|
push(saved.a14)
|
|
cpu.state['A14'].value = ARG
|
|
end
|
|
push(RET)
|
|
cpu.state['PC'].value = target
|
|
cpu.state['ST'].value = saved.st & ~IE
|
|
dbg:command('bclear')
|
|
dbg:command(string.format('bpset %X', RET))
|
|
dbg.execution_state = 'run'
|
|
end
|
|
|
|
local function restore()
|
|
dbg:command('bclear')
|
|
cpu.state['PC'].value = saved.pc
|
|
cpu.state['SP'].value = saved.sp
|
|
cpu.state['A14'].value = saved.a14
|
|
cpu.state['ST'].value = saved.st
|
|
end
|
|
|
|
local function hexbytes(addr, n)
|
|
local t = {}
|
|
for i = 0, n - 1 do
|
|
t[#t + 1] = string.format("%02X", space:read_u8(addr + i * 8))
|
|
end
|
|
return table.concat(t)
|
|
end
|
|
|
|
local function writebytes(addr, s)
|
|
for i = 1, #s do
|
|
space:write_u8(addr + (i - 1) * 8, s:byte(i))
|
|
end
|
|
end
|
|
|
|
local function next_vector()
|
|
idx = idx + 1
|
|
if idx > #vectors then
|
|
phase = "done"
|
|
restore()
|
|
machine:exit()
|
|
return
|
|
end
|
|
cur = vectors[idx]
|
|
writebytes(BUF, cur)
|
|
if INVERSE then
|
|
-- per-record inverse companion; call it twice, one 8-byte record each
|
|
phase = "vec_inv_lo"
|
|
setup_call(XFORM1, { BUF })
|
|
else
|
|
phase = "vec_fwd"
|
|
setup_call(XFORM, { 16, BUF })
|
|
end
|
|
end
|
|
|
|
emu.register_frame_done(function()
|
|
f = f + 1
|
|
if f == 1000 and not started then
|
|
started = true
|
|
if COUNT < 2 then COUNT = 2 end
|
|
make_vectors()
|
|
print(string.format("ORACLE start frame=%d p200=%08X p400=%08X",
|
|
f, space:read_u32(0x1c0200), space:read_u32(0x1c0400)))
|
|
phase = "init"
|
|
setup_call(INIT, { 1 })
|
|
return
|
|
end
|
|
if not started then return end
|
|
if f % 100 == 0 then
|
|
print(string.format("ORACLE trace frame=%d phase=%s state=%s pc=%08X sp=%08X",
|
|
f, phase, tostring(dbg.execution_state),
|
|
state('PC') or 0, state('SP') or 0))
|
|
end
|
|
if f > 1600 and phase ~= "done" then
|
|
print("ORACLE watchdog: exiting")
|
|
phase = "done"
|
|
restore()
|
|
machine:exit()
|
|
return
|
|
end
|
|
if dbg.execution_state ~= 'stop' then return end
|
|
|
|
if phase == "init" then
|
|
print(string.format("ORACLE init done @1C0200=%08X @1C0220=%08X @1C0240=%08X @1C0260=%08X @1C0400=%08X",
|
|
space:read_u32(0x1c0200), space:read_u32(0x1c0220),
|
|
space:read_u32(0x1c0240), space:read_u32(0x1c0260), space:read_u32(0x1c0400)))
|
|
phase = "sched"
|
|
setup_call(SCHED, { 0x1be810 })
|
|
elseif phase == "sched" then
|
|
local kp = space:read_u32(0x1c0260)
|
|
local fp = space:read_u32(0x1c0200)
|
|
print("ORACLE KP=" .. string.format("%08X", kp) .. " FP=" .. string.format("%08X", fp))
|
|
if kp ~= 0 then print("ORACLE KEY " .. hexbytes(kp, 128)) end
|
|
if fp ~= 0 then print("ORACLE FTAB " .. hexbytes(fp, 2048)) end
|
|
-- boot-time inputs the schedule reads
|
|
print("ORACLE SEED " .. hexbytes(0x1be810, 128))
|
|
print("ORACLE R1 " .. hexbytes(0x1bed40, 0x38))
|
|
print("ORACLE R2 " .. hexbytes(0x1bef00, 0x80))
|
|
print("ORACLE R3 " .. hexbytes(0x1bef80, 0x80))
|
|
print("ORACLE R4 " .. hexbytes(0x1c0280, 0xE0))
|
|
next_vector()
|
|
elseif phase == "vec_fwd" then
|
|
print("ORACLE " .. cur:gsub(".", function(c) return string.format("%02X", c:byte()) end)
|
|
.. " " .. hexbytes(BUF, 16))
|
|
next_vector()
|
|
elseif phase == "vec_inv_lo" then
|
|
phase = "vec_inv_hi"
|
|
setup_call(XFORM1, { BUF + 0x40 })
|
|
elseif phase == "vec_inv_hi" then
|
|
print("ORACLE_INV " .. cur:gsub(".", function(c) return string.format("%02X", c:byte()) end)
|
|
.. " " .. hexbytes(BUF, 16))
|
|
next_vector()
|
|
end
|
|
end, "vt1200-oracle-call")
|
|
|
|
dbg.execution_state = 'run'
|