38 Commits
Author SHA1 Message Date
arti 41b0979aef c-hello: first C program on the VT1200, running on real hardware
The 1987 TI K&R toolchain already compiled and linked a fib test, but nothing
had ever been executed on a VT1200. This is that program: hello.c draws HELLO
into video RAM (1 bpp, bit 0x04000000, no X11) and is loaded through the COFF
loader with hostload.py, on the real unit and in MAME.

Two runtime traps shaped the design, both recorded in the README:

  * boot.obj's exit() is `for(;;)` and _c_int00 ends in `reti`.  That is a
    standalone-ROM runtime, not a process: the child spun forever, and because
    the VT1200 OS is cooperative it took the whole terminal down (illegal
    opcode on hardware).  entry.asm replaces boot.obj -- set STK/FP, call
    _main, `rets` back to the create_process trampoline, the same return
    xhello uses -- and the image carries no .cinit (tables are built in code).
  * Pointers are bit addresses and pointer arithmetic scales by object size,
    so p + n on a char * is n bytes.  The pre-fix frame in evidence/ is that
    bug: 32 one-byte bars spread across a row.

Framebuffer geometry is not portable: the real unit is 1280 px wide at a
256-byte row pitch, MAME's canvas is 1024 px at 128.  build.sh builds both
(ROW_BYTES), and the README records how the real pitch was measured from a
real-unit frame rather than taken from MAME.

Evidence: real-unit frames in harness/c-hello/evidence/ (the pre-fix stripes
and the working HELLO), and dwt/loader/evidence/real-unit-xhello/ for the X
window that ran on the unit earlier.
2026-10-29 18:25:38 +02:00
SDD f3d26ad687 dwt: trace the bottom diagnostics message box to @0xCB640 and the RAM message table 2026-10-18 17:49:13 +03:00
SDD 225394e1ae dwt: xtext draws text in a real X window on the VT1200; TFTP retarget fetches the COFF 2026-10-18 17:28:08 +03:00
SDD 3c16824f5b dwt: record that the owner moved windows on the real unit (extra windows are not a bug) 2026-10-18 17:28:01 +03:00
SDD edc26855fd dwt: full ROM dump over rdebug is byte-identical to vt1200_v22.bin (md5 match) 2026-10-18 17:25:18 +03:00
SDD 09f8607466 dwt/tcpip: LAN TFTP client (100.103.255.254) and a verified self-contained fetch + load on the real unit 2026-10-18 17:12:00 +03:00
SDD efef682b63 dwt/evidence: real-unit Diagnostics dialog and the session-manager hang after the first ROM-test run 2026-10-18 17:07:11 +03:00
SDD e2c18ee70b dwt: NVRAM dump over rdebug (settings block) and LANCE address ROM read; both match the model 2026-10-18 17:06:12 +03:00
SDD 0db6dd2e65 dwt: real-unit menu-driven Ethernet Address ROM test passes (All Diagnostics Passed); 24 is the separate port/termination test 2026-10-18 17:02:55 +03:00
SDD 6195556524 dwt: mailbox_watch.py -- poll 0x27C190/0x16F0 across a boot; results are consumed before rdebug is reachable 2026-10-18 16:57:45 +03:00
SDD 2df00d32e5 dwt: real-unit MOP console works; SHOW reports Monitor Type VR319; TEST refused with DWT IN USE while a window is open 2026-10-18 16:40:19 +03:00
SDD 1c1f804a13 dwt: real X11 app via rdebug -- hostload --real; xhello window on the terminal (status 0, stage 6) 2026-10-18 16:32:58 +03:00
SDD 736e2c84a4 dwt: 50%-pattern real-unit capture at the correct 1280x1024/256-stride geometry 2026-10-18 16:30:30 +03:00
SDD 479d822aca dwt/screenshot: real geometry is 1280x1024 with a 256-byte stride (160B image + 96B mask); --real captures the full screen 2026-10-18 16:26:37 +03:00
SDD 6adc04ba02 dwt/screenshot: --real reads the 1024x2048 interleaved framebuffer and keeps the even rows 1:1; corrects the earlier doubling 2026-10-18 16:22:24 +03:00
SDD 71588e5405 dwt/screenshot: --field even for the real unit's line-interleaved framebuffer; the capture now matches the glass 2026-10-18 16:18:27 +03:00
SDD d6d6a7d44a dwt: real-unit note -- root background is white, TM stipple is a display-mode/fetch effect, not a setting 2026-10-18 16:12:44 +03:00
SDD f696a2e8f6 dwt/screenshot: --stretch for the real unit's 1024->1280 display; record geometry and the background stipple 2026-10-18 16:11:24 +03:00
SDD 4a0e1235c7 dwt: first real-unit contact -- TCP 771 and the computed challenge work; ROM peek matches v2.2; screenshot over peek 2026-10-18 15:55:48 +03:00
SDD 8588340527 dwt/loader: end-to-end host loader over rdebug
hostload.py drives one rdebug connection: jump(malloc) for the COFF buffer,
poke the COFF, poke the loader at 0x01880000, write the descriptor, call the
loader, and verify status 0 plus app evidence (marker cells or xhello stage 6
and a VRAM screenshot). --tftp uses the existing tftpc.bin fetch instead of a
host poke. Adds evidence for two/three fresh boots and appends the integrate
section to dwt/notes/LOADER.md, including the bit-offset result-block layout
and the create_process clobber of the descriptor.
2026-10-18 15:34:25 +03:00
SDD 01923ecb69 dwt/tcpip: map the socket API and fetch a file over TFTP into RAM
The firmware's tftp_open hangs in its resolver from the rdebug process, so
the payload speaks RFC 783 over the firmware socket API: socket(2,2,0),
sendto(RRQ), recvfrom(DATA)/ACK into a malloc'd buffer, close. Verified
live: 600 B fetched from 192.168.99.1 into the malloc buffer, server RRQ
logged, and rdebug peek matches probe.bin byte-for-byte.

Notes: dwt/notes/TCPIP.md (function table, ABI, limits) and the
loader/transport hand-off in dwt/notes/LOADER_CROSS.md.
2026-10-18 15:24:34 +03:00
SDD 139d89a10c dwt/loader: COFF loader places GSPLNK images and runs them via create_process 2026-10-18 15:19:44 +03:00
SDD dcca6bee4d dwt: map the firmware heap -- payload scratch sits inside the largest free block; document malloc/free and add heapmap.py 2026-10-18 14:47:14 +03:00
SDD a94e99579f dwt/gui: injected X window works via create_process
The xserver local service is reachable from a payload, but a connect made
inside the rdebug process is accepted at the service layer and then never
dequeued: the server's accept callback runs (3F34EC50), but no 12-byte X11
setup read follows and Xlib waits in select.

create_process(entry,name,stack,1,arg) from a new process fixes it. xlaunch
creates the child; the server reads the setup (READ12 ret 0x0C), writes the
reply (8 + 0x90), and xhello creates/maps/syncs a 400x200 window. The payload
also had to keep Display* in a11 (callee-saved) instead of reloading it from a
RAM cell, and re-run SETF after each Xlib call.

Final breakpoint-free frame: dwt/gui/evidence/xhello_window_frame2838.png.
ROM untouched (b46a3e442ccb86134d2356bd70d22fd2).
2026-10-18 14:28:58 +03:00
SDD a5769d3d2e dwt: GUI/X architecture note; Xlib callable but XOpenDisplay hangs
Static: the VT1200 GUI is an X11 dix server in ROM plus Xlib clients
(SERVER, SESSIONMGR/Terminal Manager, WM, VTE).  Internal clients use the
local service "xserver" via the display string "0::0"; TCP 6000 is a
separate authorised listener.  Function table and the 4-call hello-window
sequence are in dwt/notes/GUI_APPS.md.

Dynamic: a poke+jump payload proved the firmware ABI (strlen=4) and then
called XOpenDisplay; it never returns (stage 2, display 0, X-server client
watermark unchanged) and the rdebug service dies.  Verdict: hard for a
self-contained window; shared-RAM UI mutation (opcode 8) is the working
route.
2026-10-18 14:08:42 +03:00
SDD c0f142300b dwt/screenshot.py: network screenshot over rdebug peek (VRAM -> PNG), verified against the live OS 2026-10-18 13:49:19 +03:00
SDD 2cc3ab1c79 dwt/exploit.py: default to the computed dwt_prf answer -- echo is the known-failing mode and made a bare --peek look like a closed socket 2026-10-18 11:28:00 +03:00
SDD 0e4075958b dwt: rdebug opcode 8 puts attacker text in the TM Messages box 2026-10-18 11:03:01 +03:00
SDD 619aff0a93 dwt: visual injection -- confirm the persistent box on the live OS, and record that the blink is not established (toggle period is ~1536 frames, not 30) 2026-10-18 03:24:38 +03:00
SDD 05f1895bf5 dwt: visual injection experiments via the returning rdebug jump 2026-10-18 03:22:59 +03:00
SDD 1e5c095b72 dwt: reverse the dwt_prf challenge; remote poke/jump now works
The firmware transforms the reply (16-round Feistel per 8-byte record) and
compares it with the host-order copy of the challenge, so the answer is
swap4(inverse_transform(swap4(challenge))).  The font stages are memcpy in
this path because dwt_prf initialises only mode 1.

challenge.py implements the transform and its inverse using the firmware's
fixed key/SP table; solve.py answers the challenge over TCP using the
unmodified exploit client.  320 forward vectors, the firmware's own inverse
and three live sessions validate it.  A live run with no breakpoints poked
the 82-byte hello-world payload and ran it: MAGIC DEAD/BEEF/0400, VRAM
1024/1024.  Notes and evidence under dwt/notes and dwt/evidence.
2026-10-18 02:36:23 +03:00
SDD 041c82cb73 dwt: runtime oracle and ground-truth vectors for the dwt_prf challenge transform 2026-10-18 02:30:49 +03:00
SDD 5645bb00b3 dwt: exploit client and gate analysis; remote execution blocked at the challenge 2026-10-18 02:20:16 +03:00
SDD 2c7b2e8c2e dwt: gate B is real; challenge args must follow the answer 2026-10-18 02:19:54 +03:00
SDD bd58af3569 dwt: gate A is a 2-bit field and is open; correct the static reading 2026-10-18 02:07:18 +03:00
SDD a18881f45d dwt: MOP console carrier client, protocol notes, and emulator evidence 2026-10-18 02:02:17 +03:00
SDD 0a1042e81f dwt: correct the Done\n reply length (payload includes its NUL, len field = payload+1) 2026-10-18 01:55:56 +03:00
SDD 68a1aeabdc dwt: static analysis of rdebug / DWT console -- poke+jump exist, gated by a config word and a 16-byte handshake 2026-10-18 01:55:41 +03:00