mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-30 12:03:54 +02:00
249 lines
7.9 KiB
Lua
249 lines
7.9 KiB
Lua
-- 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'
|