-- 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 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'