Commit Graph
148 Commits
Author SHA1 Message Date
arti 1364b37e83 NETWORK_REPORT: PCB offsets confirmed against struct inpcb
struct inpcb (sys/netinet/in_pcb.h) lays out inp_faddr, inp_fport, inp_laddr,
inp_lport at 12/16/18/22 with 16-bit alignment -- exactly the ROM offsets
0x60/0x80/0x90/0xB0, in the same order in_pcblookup tests them. A 32-bit
aligned build would put them at 12/16/20/24, so the match also pins the
firmware ABI to 16-bit alignment, consistent with struct protosw's 6/10.
2026-10-30 01:39:49 +02:00
arti b8899645d0 NETWORK_REPORT: finish the BSD mapping (rip_input, tcp_input switches, ICMP checksums)
0x3F61EC00 is rip_input() (raw_ip.c:58): PCB-list walk on protocol/laddr/
faddr, sbappendaddr delivery, and the ips_noproto counter we recorded at
0x96500. The dsj gate compares that PCB field against 1 -- the raw socket
bound to ICMP -- so the 5/8/13..18 skip list is the raw-ICMP filter for types
the kernel answers itself.

The two TCP state tables map onto tcp_input's two switch(tp->t_state) sites
(506 and 865); table A's shared handler for CLOSING/LAST_ACK/TIME_WAIT matches
the source's fallthrough group at 840-842. ICMP checksum verify and reflect
match ip_icmp.c:184 and 522-523.
2026-10-30 01:39:37 +02:00
arti 3ad5a691b8 NETWORK_REPORT: map every reversed function to 4.4BSD source
Adds the ROM -> BSD correspondence with the construct that identifies each
pair: ipintr/ip_input's inetsw[ip_protox[]] dispatch (both tables found in RAM
at 0x1E0F70 and 0xD6BE0, struct protosw offsets 6/10 match the dump),
icmp_input/icmp_reflect/icmp_send, tcp_input/tcp_dooptions/tcp_mss,
udp_input/udp_output, in_pcblookup (line-for-line: matchwild=3, lport, laddr,
faddr/fport, best-match), arpresolve/arplookup/in_arpinput/arprequest, and the
pmax LANCE driver's leinit/lereset/ledrinit/lestart/leintr/lerint.

lestart() writes the descriptor length as plain -len (if_le.c:485), which for
24 bytes is 0xFFE8 -- confirming section 50 from a third direction and showing
our 0x0FE8/0x0A0A were simply positive values.
2026-10-30 01:39:09 +02:00
arti b5fe6935e1 NETWORK_REPORT: CSRG tree provenance; IDON clearing shown to be optional
original-bsd spans 1979..4.4BSD-Lite2 and carries the same if_ether.c string as
the ROM. Its pmax (DECstation) if_le.c clears IDON before STRT, Linux does too,
but NetBSD's lance.c does not -- and neither does the VT1200 firmware itself
(0x3FDDCBE0 writes CSR1/2/3 then 0x7F41 and the live path goes INIT -> poll
IDON -> 0x0042 with no IDON clear). Since the firmware works on the real
machine, the IDON hypothesis is dead and the descriptor length encoding
(section 50) is the remaining explanation.
2026-10-30 01:35:58 +02:00
arti b534bab7d7 NETWORK_REPORT: provenance from 386BSD history, corroborating the ICMP case set
The local NetBSD tree reaches back to 1992 with the 386BSD-0.1 import
(a319363d6037). Its sys/netinet/if_ether.c:350 carries the exact ROM string
'arp: ether address is broadcast for IP address', which the modern tree no
longer uses ('link address'), placing the firmware in that lineage.

The 386BSD ip_icmp.c switch contains exactly the case labels that section 46
derived from the ROM's dsj + compare chain (REDIRECT, ECHO, TSTAMP,
TSTAMPREPLY, IREQ, IREQREPLY, MASKREQ, MASKREPLY), confirming that reading
from source rather than inference.
2026-10-30 01:31:48 +02:00
arti 874d1fbbf5 networking RE report + LANCE payload variants
harness/mame/NETWORK_REPORT.md: how the VT1200 firmware drives the LANCE,
from ROM disassembly and MAME traces. 50 sections, each claim tied to a bit
address, a trace count or a quoted source line. Covers the driver device
struct (0x23CBF0), init (0x3F9D4BC0), descriptor/DMA encoding, the live
handler (0x3F9D5B40) with per-status-bit dispatch, the RX poll (0x3F9D62B0),
ring arming (0x3F9D36C0), TX completion (0x3F9D8AF0) and the single transmit
path every protocol uses (0x3F6084E0 -> 0x3F9DC890 -> 0x3F9D4BC0), plus the
IP protocol dispatch (0x3F6270F0), ICMP (343-count chain), TCP state tables
and live TIME_WAIT, the ARP responder (0x3F6119A0) and the socket 4-tuple
demux confirmed by a memory dump.

It also records what was withdrawn: five successive explanations for the
real-silicon CSR0=0x1BB / TMD1=0x8338 result, and the descriptor-length
encoding bug found by comparing Linux 7990.c and NetBSD am7990.c (length must
be 0xF000 | -len; MAME masks the difference in get_buf_length).

harness/udpstream: the LANCE loopback/takeover payload and the variants used
to test the init/kick orderings in MAME (bare TDMD, STOP before INIT, double
INIT, long wait with CSR0 sampling, IDON clear, corrected descriptor length).
build-lance.sh takes LANCE_SRC/LANCE_OUT so the variants build from one script.
2026-10-30 01:25:21 +02:00
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
arti 6338095683 tools: add as34/ld34 TMS34010 binutils, ported to 64-bit Linux
Andrew Morton's Unix-hosted assembler/linker for the TMS34010, fetched from the gsptools archive and patched to build on x86-64. The K&R sources silently truncate pointers there: implicit int parameter declarations and fixed-argument printf wrappers. The patch fixes those and the flex 2.6 yy_init mismatch.

hello.as34.g is harness/hello-world/hello.asm in as34 syntax. as34/ld34 reproduce the GSPA-built payload byte-for-byte (82 bytes, md5 db91788991ebba476b55a11127a36475), and the MAME hello.lua run reports the same markers, the same 1024-word VRAM block and a byte-identical frame.

build.sh downloads the upstream tarball at build time (sha256 pinned); the tree is not vendored.
2026-10-22 14:19:14 +03: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 bebc0ccbc1 reference: map the ROM's third-party code -- MIT X11 R4, BSD 4.3 TCP/IP + BIND + telnet, TI RTS, and the font licences 2026-10-18 15:25:43 +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
SDD be32e8a138 harness: regenerate duart2_default_tm_frame3000.png -- the cited evidence image predated the LANCE and contradicted the README (no MAC on screen) 2026-10-18 01:21:41 +03:00
SDD 1608a68cdb hello-world: the TMS34010 SDK assembles code that runs on the emulated VT1200
An 82-byte payload written in GSPA assembly, linked at bit 01800000h and
loaded into main RAM by harness/mame/hello.lua, executes on the CPU: it
writes DEAD/BEEF and a 1024-iteration counter into RAM and fills a 256x64
block in video RAM, which appears in the frame dump at x 256..511,
y 136..199.  Entry is a debugger breakpoint on the level-0 menu idle loop,
not a delivery mechanism.  Includes the build script, a COFF .text
extractor (gsprom's hex addresses wrap at 64 KB), the run log, RAM excerpt,
frame crops including a no-load control, and HELLO_WORLD_REPORT.md.
2026-10-18 01:20:38 +03:00
SDD 0c3d24b93b tools/tms34010-sdk: the two dis34 bugs are fixed (af7d3565a5) -- record it, with the 2623/6235-line scale of the correction 2026-10-18 00:55:12 +03:00
SDD af7d3565a5 dis34: print long relative JR as jr and surface B-file SP as b15
The 16-bit-displacement jump (low byte 0x00, two words) is JRcc, not the
three-word JAcc (low byte 0x80).  Printing it as ja made linear
disassembly report the wrong control flow and made every such branch grow
by a word when reassembled.

Register 15 is the stack pointer in either file: 0x0F is A15 and 0x1F is
B15.  The disassembler printed both as sp, hiding B-file stack-pointer
code.  Print b15 when the R bit selects the B file.

Validated with the 1987 TI GSPA/GSPLNK/GSPROM round trip: the 32 KB region
at bit 3F208000 still reassembles byte-for-byte (md5
43adb6669571041985f3e26fd9ff7cb9), as do the 4 KB and 16 KB spans and the
0x8000/3F240000 region.  The converter no longer needs its ja->jr or
sp->b15 rewrites (5 fixups -> 3); a whole-ROM diff contains only those two
output categories.  The ROM is unchanged
(b46a3e442ccb86134d2356bd70d22fd2).
2026-10-18 00:54:34 +03:00