mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-30 12:03:54 +02:00
dwt: runtime oracle and ground-truth vectors for the dwt_prf challenge transform
This commit is contained in:
1 parent
5645bb00b3
commit
041c82cb73
31 files changed
+7367
No files matched your search
+248
@@ -0,0 +1,248 @@
|
||||
-- dwt/oracle.lua -- direct-invocation oracle for the VT1200 rdebug `dwt_prf`
|
||||
-- challenge transform.
|
||||
--
|
||||
-- Run as a MAME -autoboot_script. No TCP connection, no driver change, the
|
||||
-- ROM file is untouched. The script reproduces exactly what dwt_prf does
|
||||
-- around the transform:
|
||||
--
|
||||
-- 0x3F5A9620(1) ; alloc/init tables (Ghidra 0x07EB52C4)
|
||||
-- 0x3F5AA2C0(0x1BE810) ; expand key schedule (Ghidra 0x07EB5458)
|
||||
-- 0x3F5A09C0(buf, 16) ; the transform (Ghidra 0x07EB4138)
|
||||
-- 0x3F5A9FA0() ; free tables (Ghidra 0x07EB53F4)
|
||||
--
|
||||
-- by calling those ROM routines with the firmware's own stack convention
|
||||
-- (caller pushes A14, writes arguments upward from A14, CALLA pushes the
|
||||
-- return address; callee restores A14 from the stack and `rets 2` pops it).
|
||||
-- The harness borrows a scratch stack in unused top-of-RAM, pushes a
|
||||
-- synthetic return address, and stops on a breakpoint there.
|
||||
--
|
||||
-- Inputs: VT1200_ORACLE_IN text file, one 32-hex-char (16-byte) input per
|
||||
-- line; '#' comments and blank lines are skipped.
|
||||
-- Output: VT1200_ORACLE_OUT one line per vector:
|
||||
-- "<index> <input hex> <output hex>"
|
||||
-- Env: VT1200_ORACLE_START frame to hijack the CPU (default 3000)
|
||||
-- VT1200_ORACLE_TAG label used in log lines (default "oracle")
|
||||
--
|
||||
-- The input is the buffer as the transform sees it: the 16 received bytes
|
||||
-- after dwt_prf's ntohl (i.e. each 32-bit wire word byte-swapped). The
|
||||
-- output is the in-place transformed buffer that dwt_prf then memcmp's
|
||||
-- against the host-order challenge words.
|
||||
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 IN_PATH = os.getenv("VT1200_ORACLE_IN")
|
||||
local OUT_PATH = os.getenv("VT1200_ORACLE_OUT")
|
||||
local START = tonumber(os.getenv("VT1200_ORACLE_START") or "3000")
|
||||
local TAG = os.getenv("VT1200_ORACLE_TAG") or "oracle"
|
||||
local INTER = os.getenv("VT1200_ORACLE_INTERMEDIATE") == "1"
|
||||
|
||||
-- Scratch RAM: the top 1 MiB of the 4 MiB main RAM (bytes 0x300000..).
|
||||
-- hello-world and the earlier probes also used 0x01800000 without trouble.
|
||||
local ARG = 0x01800000 -- argument block (A14 target), grows upward
|
||||
local STACK = 0x01840000 -- scratch stack top, grows downward
|
||||
local CBUF = 0x01880000 -- 16-byte transform buffer
|
||||
local RET = 0x018C0000 -- synthetic return address (never executed)
|
||||
local IE = 0x200000 -- TMS34010 ST.IE (tms34010.cpp: STBIT_IE = 1<<21)
|
||||
|
||||
local INIT = 0x3F5A9620
|
||||
local EXPAND = 0x3F5AA2C0
|
||||
local TRANSFORM = 0x3F5A09C0
|
||||
local FREE = 0x3F5A9FA0
|
||||
local SRC_BLOCK = 0x1BE810 -- 128-byte seed block expanded by EXPAND
|
||||
|
||||
local f = 0
|
||||
local stage = "boot"
|
||||
local idx = 1
|
||||
local vectors = {}
|
||||
local out = nil
|
||||
local saved = {}
|
||||
local err = nil
|
||||
|
||||
-- ---------------------------------------------------------------- utilities
|
||||
local function state(name)
|
||||
local r = cpu.state[name]
|
||||
return r and r.value or nil
|
||||
end
|
||||
|
||||
local function hexbytes(bitaddr, n)
|
||||
local t = {}
|
||||
for i = 0, n - 1 do
|
||||
t[#t + 1] = string.format("%02X", space:read_u8(bitaddr + i * 8))
|
||||
end
|
||||
return table.concat(t)
|
||||
end
|
||||
|
||||
local function dump_lines(label, bitaddr, n)
|
||||
for off = 0, n - 1, 32 do
|
||||
local m = math.min(32, n - off)
|
||||
print(string.format("%s: %s+%03X %s", TAG, label, off,
|
||||
hexbytes(bitaddr + off * 8, m)))
|
||||
end
|
||||
end
|
||||
|
||||
local function push(v)
|
||||
local sp = state('SP') - 0x20
|
||||
space:write_u32(sp, v)
|
||||
cpu.state['SP'].value = sp
|
||||
end
|
||||
|
||||
-- Call a firmware routine with the firmware's own convention.
|
||||
-- args == {} -> bare CALL (routine does `rets`, no pushed A14)
|
||||
-- args ~= {} -> caller pushes A14, writes args upward, CALLA
|
||||
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
|
||||
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_and_exit(rc)
|
||||
dbg:command('bclear')
|
||||
if saved.pc then
|
||||
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
|
||||
if out then out:close() end
|
||||
print(string.format("%s: done (%s), rc=%s", TAG, stage, tostring(rc)))
|
||||
machine:exit()
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------- start-up
|
||||
if IN_PATH then
|
||||
local fh, e = io.open(IN_PATH, "r")
|
||||
if not fh then
|
||||
print(TAG .. ": cannot open input " .. tostring(IN_PATH) .. ": " .. tostring(e))
|
||||
machine:exit()
|
||||
return
|
||||
end
|
||||
for line in fh:lines() do
|
||||
line = line:gsub("%s+", "")
|
||||
if #line > 0 and line:sub(1, 1) ~= "#" then
|
||||
if #line ~= 32 then
|
||||
print(TAG .. ": bad vector length " .. #line .. ": " .. line)
|
||||
machine:exit()
|
||||
return
|
||||
end
|
||||
vectors[#vectors + 1] = line:upper()
|
||||
end
|
||||
end
|
||||
fh:close()
|
||||
end
|
||||
print(string.format("%s: loaded %d vectors from %s", TAG, #vectors, tostring(IN_PATH)))
|
||||
if OUT_PATH then
|
||||
out = io.open(OUT_PATH, "w")
|
||||
if not out then
|
||||
print(TAG .. ": cannot open output " .. tostring(OUT_PATH))
|
||||
machine:exit()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local function write_out(index, inhex, outhex)
|
||||
if out then
|
||||
out:write(string.format("%d %s %s\n", index, inhex, outhex))
|
||||
out:flush()
|
||||
end
|
||||
end
|
||||
|
||||
local function next_vector()
|
||||
if idx > #vectors then
|
||||
-- mirror dwt_prf's teardown, then leave
|
||||
stage = "free_run"
|
||||
setup_call(FREE, {})
|
||||
return
|
||||
end
|
||||
local v = vectors[idx]
|
||||
for i = 0, 15 do
|
||||
space:write_u8(CBUF + i * 8, tonumber(v:sub(i * 2 + 1, i * 2 + 2), 16))
|
||||
end
|
||||
stage = "transform_run"
|
||||
setup_call(TRANSFORM, { 16, CBUF })
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------- main loop
|
||||
emu.register_frame_done(function()
|
||||
f = f + 1
|
||||
|
||||
if stage == "boot" and f >= START then
|
||||
print(string.format("%s: frame %d hijack pc=%08X sp=%08X a14=%08X",
|
||||
TAG, f, state('PC'), state('SP'), state('A14')))
|
||||
stage = "init_run"
|
||||
setup_call(INIT, { 1 })
|
||||
return
|
||||
end
|
||||
|
||||
if stage == "boot" then return end
|
||||
|
||||
-- While the debugger is halted the OSD keeps pumping frame updates
|
||||
-- (running_machine::run -> video.frame_update when paused), so this
|
||||
-- callback still runs; act once per stop.
|
||||
if dbg.execution_state ~= 'stop' then return end
|
||||
local pc = state('PC')
|
||||
|
||||
if pc ~= RET then
|
||||
print(string.format("%s: unexpected stop at %08X in stage %s", TAG, pc, stage))
|
||||
err = "unexpected stop"
|
||||
restore_and_exit(1)
|
||||
return
|
||||
end
|
||||
|
||||
if stage == "init_run" then
|
||||
print(string.format("%s: init returned; @1C0200=%08X @1C0220=%08X @1C0240=%08X @1C0260=%08X",
|
||||
TAG, space:read_u32(0x1c0200), space:read_u32(0x1c0220),
|
||||
space:read_u32(0x1c0240), space:read_u32(0x1c0260)))
|
||||
stage = "expand_run"
|
||||
setup_call(EXPAND, { SRC_BLOCK })
|
||||
|
||||
elseif stage == "expand_run" then
|
||||
print(string.format("%s: expand returned; keyptr=%08X fontptr=%08X renderptr=%08X",
|
||||
TAG, space:read_u32(0x1c0260), space:read_u32(0x1c0220),
|
||||
space:read_u32(0x1c0240)))
|
||||
-- Evidence: the seed block and the full 128-byte expanded key schedule,
|
||||
-- per run. dwt_prf calls INIT(1), so @1C0220/@1C0240 stay NULL and
|
||||
-- both "font render" steps are memcpy (see notes/CHALLENGE_ORACLE.md).
|
||||
dump_lines("seed", SRC_BLOCK, 128)
|
||||
dump_lines("key", space:read_u32(0x1c0260), 128)
|
||||
dump_lines("aux", 0x1BED40, 0x2C0)
|
||||
stage = "vectors"
|
||||
next_vector()
|
||||
|
||||
elseif stage == "transform_run" then
|
||||
local inhex = vectors[idx]
|
||||
local outhex = hexbytes(CBUF, 16)
|
||||
write_out(idx, inhex, outhex)
|
||||
idx = idx + 1
|
||||
if idx % 25 == 0 or idx > #vectors then
|
||||
print(string.format("%s: %d/%d vectors", TAG, idx - 1, #vectors))
|
||||
end
|
||||
next_vector()
|
||||
|
||||
elseif stage == "free_run" then
|
||||
print(string.format("%s: free returned; exiting", TAG))
|
||||
restore_and_exit(0)
|
||||
end
|
||||
end, "vt1200-dwt-oracle")
|
||||
|
||||
dbg.execution_state = 'run'
|
||||
Reference in new issue
Block a user