mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-30 12:03:54 +02:00
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).
473 lines
23 KiB
Markdown
473 lines
23 KiB
Markdown
# How the VT1200's GUI apps are built — and what a payload can do with that
|
||
|
||
Status: **static architecture DONE; injection verdict measured, not assumed.**
|
||
A payload *can* call ROM routines with the firmware calling convention (proved
|
||
live: `strlen("0::0")` returned 4), but `XOpenDisplay` from the injected process
|
||
**never completes**: the run stays at stage 2, the display pointer stays 0, the
|
||
X server's client watermark does not move, and the rdebug service stops answering.
|
||
Creating a *new* X window from a payload is therefore **hard**, not medium.
|
||
|
||
All work is static (Ghidra on `vt1200_v22.bin`) plus one live experiment on a
|
||
private NVRAM/cfg under the documented `VT1200_SKIP_LEVEL1=1` divergence. ROM
|
||
untouched (`md5 b46a3e442ccb86134d2356bd70d22fd2`), no driver edits.
|
||
|
||
Address convention used here: **Ghidra byte address** and **firmware bit address
|
||
(= Ghidra × 8)**. Ghidra address = `0x7E40000 + ROM file offset`.
|
||
|
||
---
|
||
|
||
## 1. The model in one paragraph
|
||
|
||
The terminal runs a small **process-per-application** OS on one TMS34010. RAM is
|
||
shared and unprotected; processes are contexts (a PCB from `create_process`, a
|
||
stack, a private file table at `*(0x1B3900)+0x100`). The graphics system is a
|
||
**real X11 server in ROM** (`dix`, the DEC/Athena server: strings
|
||
`Hello, this is the X server`, `CreateWellKnownSockets`,
|
||
`Internal error in ConfigureWindow`, `BadWindow - parameter not a Window`). The
|
||
GUI applications are **X clients built on Xlib, which is also in ROM**
|
||
(`XConnectDisplay`, `Xlib: sequence lost`, `XCreateCursor`-class request
|
||
builders). There is **no separate DWT window API**: the "DWT Window Manager" is
|
||
an ordinary X client that speaks the X11 WM protocol plus DECwindows atoms
|
||
(`DEC_WM_HINTS`, `WM_MOVED`, `DEC_WM_ICON_STATE`, `DEC_WM_TAKE_FOCUS`).
|
||
|
||
Because RAM is shared, app *state* (the Messages list, window ids, cursors) is
|
||
global and a routine in one process can update another app's data — that is why
|
||
rdebug opcode 8 can write the Terminal Manager's Messages box without a
|
||
connection of its own. The X protocol traffic, however, is done by each app's
|
||
own process through its own connection (`Display *` in a global: TM `@0xB78E0`,
|
||
WM `@0x27AA80`).
|
||
|
||
## 2. Function table
|
||
|
||
### 2.1 Processes / app lifecycle
|
||
|
||
| what | Ghidra | bits | notes |
|
||
|---|---|---|---|
|
||
| `create_process(entry,name,stack,1,arg)` | `0x07EE0D48` | `0x3F706A40` | mallocs a 0x39-byte PCB, copies the name, allocates the stack, links it at `@0x1B3860` |
|
||
| current-process pointer | — | `0x1B3900` | `+0x100` = user area (fd table) |
|
||
| process list head | — | `0x1B3860` | |
|
||
| `Main Program` (creates SERVER + SESSIONMGR) | `0x07E69B6A`…`0x07E69C1A` | `0x3F34DB50` | itself a created process (name `Main Program`) |
|
||
| X server entry **SERVER** | `0x07E6C522` | `0x3F362910` | dix main; prints `Hello, this is the X server` |
|
||
| Session Manager / Terminal Manager **SESSIONMGR_PROC** | `0x07E5CDBC` | `0x3F2E6DE0` | *is* the Terminal Manager (see §3) |
|
||
| Window Manager **WM** | `0x07F5702C` | `0x3FAB8160` | DWT WM; prints `DWT Window Manager is here to stay` |
|
||
| terminal emulator **VTE** | `0x07E9B60A` | `0x3F4DB050` | DECterm/VTE |
|
||
|
||
`create_process` argument order (from the pushes at `0x7E69B72`): the first
|
||
argument is the **entry point (bit address)**, the second the **name string**,
|
||
the third the **stack size**, the fourth a constant `1`, the fifth a pointer/arg.
|
||
Example (`main`, `0x07E69BE2`):
|
||
|
||
```
|
||
07E69BC8 MOVK 0x1,A9 ; arg3 = 1
|
||
07E69BCC MOVI 0x4E20,A9 ; arg2 = 0x4E20 stack
|
||
07E69BD2 MOVI 0x3F34E2E0,A8 ; arg1 = "SESSIONMGR_PROC"
|
||
07E69BDA MOVI 0x3F2E6DE0,A9 ; arg0 = entry
|
||
07E69BE2 CALLA 0x07EE0D48 ; create_process
|
||
```
|
||
|
||
### 2.2 Xlib (client side, all in ROM)
|
||
|
||
The request-builder opcode is visible in each function (`MOVB 0xNN` into the
|
||
request buffer), which is how these were named; the DB26 `XDrawString` and DB25
|
||
`XCreateSimpleWindow` shapes were then checked against their argument blocks.
|
||
|
||
| Xlib function | Ghidra | bits | shape seen |
|
||
|---|---|---|---|
|
||
| `XOpenDisplay(name)` | `0x07F384AE` | `0x3F9C2570` | mallocs 0x4D8, parses the name, connects |
|
||
| `XParseDisplay` / service lookup | `0x07FA7538` | `0x3FD3A9C0` | splits `host:display.screen`, resolves `"xserver"` |
|
||
| `XCreateSimpleWindow(d,p,x,y,w,h,bw,border,bg)` | `0x07FA8372` | `0x3FD41B90` | opcode 1, 9 args |
|
||
| `XMapWindow(d,w)` | `0x07FAC8B6` | `0x3FD645B0` | opcode 8, 2 args |
|
||
| `XMapSubwindows(d,w)` | `0x07FAC97E` | `0x3FD64BF0` | opcode 9 |
|
||
| `XSelectInput(d,w,mask)` | `0x07FB2FAA` | `0x3FD97D50` | ChangeWindowAttributes, mask 0x800 |
|
||
| `XChangeWindowAttributes` | `0x07FA70EE` | `0x3FD38770` | opcode 2 |
|
||
| `XConfigureWindow` | `0x07FA74A2` | `0x3FD3A510` | opcode 12 |
|
||
| `XInternAtom(d,name,only_if_exists)` | `0x07FABC84` | `0x3FD5E420` | opcode 16 |
|
||
| `XChangeProperty` | `0x07FA6E66` | `0x3FD37330` | opcode 18 |
|
||
| `XCreateGC(d,drawable,mask,values)` | `0x07F32E00` | `0x3F997000` | opcode 55; template copied from ROM `0x2317D0` |
|
||
| `XChangeGC` | `0x07F333F0` | `0x3F999F80` | opcode 56 |
|
||
| `XSetForeground(d,gc,pixel)` | `0x07FB334C` | `0x3FD99A60` | caches in GC, marks dirty |
|
||
| `XSync(d,discard)` | `0x07FB3ED6` | `0x3FD9F6B0` | sends GetInputFocus (43), waits |
|
||
| `XDrawString(d,drawable,gc,x,y,s,n)` | `0x07FB4000` | `0x3FDA0000` | opcode 74 (PolyText8) |
|
||
| `XLoadFont(d,name)` | `0x07FA95D0` | `0x3FD4AE80` | opcode 45 (OpenFont) |
|
||
| `XCopyArea` | `0x07FA7C44` | `0x3FD3E220` | opcode 62 |
|
||
| internal PolyFillRectangle fast path | `0x07F33D1E` | `0x3F99E8F0` | opcode 70; not a public entry point |
|
||
| `strlen` | `0x07F68B1A` | `0x3FB458D0` | used as the ABI check |
|
||
|
||
### 2.3 X server internals that matter
|
||
|
||
| what | Ghidra | bits |
|
||
|---|---|---|
|
||
| connection setup / accept entry | `0x07EB1700` | `0x3F58B800` |
|
||
| auth reject logger (`0x07E7E8D0`) | `0x07E7E8D0` | `0x3F3F4680` |
|
||
| client table (32 × 0x80) | RAM | `0x1B6600` |
|
||
| client watermark | RAM | `0x1B8620` |
|
||
| client bitmap (32 bits) | RAM | `0x1B86A0` |
|
||
| well-known socket / service name | ROM `"xserver"` | `0x3F591F08` |
|
||
|
||
The server's connection path reads the 12-byte X11 setup (`0xEBB1CE(conn,12,buf)`),
|
||
checks the byte-order byte, then reads the auth name/data and looks the peer up
|
||
in the user/node table. Failure goes to a routine that logs
|
||
`Client is not authorized to access server` and closes.
|
||
|
||
## 3. Terminal Manager: what it is and how it starts
|
||
|
||
**The Terminal Manager is not a separate application.** It is the UI half of the
|
||
process `main()` creates as `SESSIONMGR_PROC` (entry `0x3F2E6DE0`). The single
|
||
function that begins at `0x7E5CDBC` runs straight through the display setup
|
||
(`0x7E5D0F0`), the TM window creation (`0x7E5D2D0`), the WM launch
|
||
(`0x7E5D760`) and the VTE launch (`0x7E5E676`). This is continuous code: the
|
||
disassembly has no return between `0x7E5CDBC` and `0x7E5F7xx`.
|
||
|
||
Startup, quoted:
|
||
|
||
```
|
||
07E5D1FA MOVI 0x3F30DC00,A8 ; "0::0" at ROM 0x7E61B80
|
||
07E5D204 CALLA 0x07F384AE ; XOpenDisplay("0::0")
|
||
07E5D20A MOVE A8,@0xB78E0 ; TM's Display * (global)
|
||
07E5D212 JRNE 0x07E5D228
|
||
07E5D216 MOVI 0x3F30DC28,A8 ; "DWTM Failed to open display\n"
|
||
07E5D21E CALLA 0x07F6683C ; printf
|
||
```
|
||
|
||
The title string `Terminal Manager V2.2` is at ROM `0x7E61D8B`
|
||
(bit `0x3F30EC58`) and is referenced from the same function (`0x7E5E928`).
|
||
`Terminal Manager XIO error... restarting...` (`0x7E61B53`) confirms the TM is an
|
||
**XIO/Xlib client**, not a private drawing API.
|
||
|
||
The TM's first window is a 1×1 "WM protocol" window; the visible TM box is built
|
||
with the widget toolkit (the code at `0x7E7F24A`, `0x7E8465A`, `0x7E873FE`,
|
||
`0x7E94880`, … that calls `XCreateSimpleWindow` + `XMapWindow`).
|
||
|
||
**Launched from:** `main()`, not a menu or an init list. A menu does exist for
|
||
secondary apps — the `Create` menu (`Terminal Window on Host Port`, `TELNET
|
||
Terminal Window…`, `PRINT`) calls `create_process` at `0x7E91BA0`/`0x7E91C26`.
|
||
|
||
## 4. Minimum "hello window" call sequence
|
||
|
||
From the TM's own code (`0x7E5D2B4`–`0x7E5D2D0`) and the WM's
|
||
(`0x7F57044`–`0x7F5704E`). The calling convention is **not** "push on SP": the
|
||
caller reserves one slot with `MOVE A14,-*SP,1`, pushes arguments **upward**
|
||
with `MOVE arg,*A14+,1` (last argument first), and the callee restores A14/SP.
|
||
|
||
```
|
||
; root = *(*(display+0x480)+0x40) (TM 0x7E5D290..0x7E5D2AA)
|
||
MOVE @0xB78E0,A11 ; display
|
||
MOVE @0xB7F80,A9 ; default screen number
|
||
SLL 0x9,A9 / SLL 0x7,A7 / ADD A7,A9 ; * 0x280 (screen struct)
|
||
MOVE *A11(0x480),A7 / ADD A9,A7 ; screens[]
|
||
MOVE *A7(0x40),A9 / MOVE A9,@0xB7960 ; root window
|
||
|
||
; window = XCreateSimpleWindow(display, root, 0,0, 1,1, 0, 0, 1)
|
||
MOVE A14,-*SP,1
|
||
MOVK 1,A7 / MOVE A7,*A14+,1 ; background = 1
|
||
SUB A7,A7 / MOVE A7,*A14+,1 ; border
|
||
MOVE A7,*A14+,1 ; border width
|
||
MOVK 1,A7 / MOVE A7,*A14+,1 ; height
|
||
MOVE A7,*A14+,1 ; width
|
||
SUB A7,A7 / MOVE A7,*A14+,1 ; y
|
||
MOVE A7,*A14+,1 ; x
|
||
MOVE A9,*A14+,1 ; parent = root
|
||
MOVE A11,*A14+,1 ; display
|
||
CALLA 0x07FA8372 ; -> window id
|
||
```
|
||
|
||
The full minimum is **4 Xlib calls**:
|
||
|
||
1. `XOpenDisplay("0::0")` — `0x3F9C2570`
|
||
2. `XCreateSimpleWindow(d, root, x, y, w, h, bw, border, background)` —
|
||
`0x3FD41B90`
|
||
3. `XMapWindow(d, win)` — `0x3FD645B0`
|
||
4. `XSync(d, 0)` — `0x3FD9F6B0` (flush + round trip)
|
||
|
||
To draw text add `XLoadFont(d,"fixed")` (`0x3FD4AE80`), `XCreateGC`
|
||
(`0x3F997000`) with `GCFont=0x4000`, `XSetForeground` (`0x3FD99A60`) and
|
||
`XDrawString` (`0x3FDA0000`). No event loop is needed for a static window; the
|
||
server keeps it after the client goes idle.
|
||
|
||
Globals: only the `Display *` is per-app. There is **no global "current
|
||
display" to initialise**; `XOpenDisplay` allocates and returns it. Window ids,
|
||
GCs and fonts are server-side resources and are valid for any client.
|
||
|
||
## 5. The injection experiment (run) and the verdict
|
||
|
||
Payload: `dwt/gui/xhello.asm` → `dwt/gui/build/xhello.bin` (390 B), linked at bit
|
||
`0x01840000`, built with the TI SDK flow (`dwt/gui/build.sh`). It first calls
|
||
`strlen("0::0")` as an ABI check (must return 4; if not it returns without
|
||
touching Xlib), then `XOpenDisplay`, root lookup, `XCreateSimpleWindow`,
|
||
`XMapWindow`, `XSync`, storing a stage counter at bit `0x01850140` and the
|
||
display/window at `0x01850100`/`0x01850120`.
|
||
|
||
Run: `VT1200_SKIP_LEVEL1=1`, private `-nvram_directory`/`-cfg_directory`,
|
||
`-networkprovider taptun`, BOOTP/TFTP from `harness/serve.py --bootp`,
|
||
`-nothrottle`; frame/marker probe `dwt/gui/probe2.lua`. Injected with
|
||
`dwt/visual/inject.py` (`poke` + `jump`, challenge answered by
|
||
`dwt/challenge.respond`). The terminal booted to the Terminal Manager, answered
|
||
rdebug, and the payload was poked and jumped with no breakpoints.
|
||
|
||
Result:
|
||
|
||
```
|
||
strlen = 4 <- the firmware calling convention works
|
||
stage = 2 <- reached the XOpenDisplay call
|
||
display = 0 <- never returned
|
||
window = 0
|
||
xclient watermark = 1 (unchanged: no new client slot was taken)
|
||
jump: NO RETURN within 20 s; the same connection then times out
|
||
```
|
||
|
||
The machine stays alive (frames advance, the TM keeps running) but the
|
||
remote-debug process sits in Xlib's `select` wrapper (`0x3FD969F0`, waiting on
|
||
the display fd) and the rdebug service stops answering. No window appeared in
|
||
the frame dump (`dwt/gui/run/gui_frame15571.png` is the normal TM).
|
||
|
||
**Verdict: HARD** for "a payload creates its own window through XOpenDisplay".
|
||
The window API is ordinary Xlib — the missing piece is the **local transport
|
||
handshake**: a connection opened from the injected process is never accepted by
|
||
the server (client watermark does not move), so Xlib waits forever. The one
|
||
thing that decides it is whether the `"xserver"` service connection can be
|
||
established from a process other than the boot-time clients; that is not an API
|
||
question and is not answered by the ROM alone.
|
||
|
||
**MEDIUM** for a narrower goal: *influencing* an existing window. The shared-RAM
|
||
UI path is proven — `rdebug` opcode 8 calls the TM's append routine
|
||
`0x3F309350` and the text appears in the Messages pane with no connection of its
|
||
own. Any payload can call the same TK/UI routines, but they mutate the owning
|
||
app's data structures so that the *owner* does the X drawing.
|
||
|
||
## 6. Internal IPC to the X server
|
||
|
||
Both boot-time clients open the display with the literal string **`"0::0"`**
|
||
(TM at ROM `0x7E61B80`, bit `0x3F30DC00`; WM at `0x7F59D29`, bit `0x3FACE948`).
|
||
|
||
`XParseDisplay` (`0x7FA7538`) does **not** build a TCP address. It resolves a
|
||
**named local service**:
|
||
|
||
```
|
||
07FA766C CALLA 0x07F65C68 ; kernel_lock (per-process critical flag)
|
||
07FA7672 MOVI 0x3FD3DCA0,A8 ; "xserver"
|
||
07FA767C CALLA 0x07EE160C ; service attach by name
|
||
07FA7688 JREQ ... ; null -> unlock, sleep 100 ms, retry
|
||
```
|
||
|
||
`0x7EE160C` is a **service-table lookup/attach**; the lookup itself
|
||
(`0x7EE1B70`) scans a **32-entry table at `0x1E8D70`** (4-byte entries) with
|
||
`strcmp` against the name. The X server registers the same name on its side
|
||
(`"xserver"` appears next to `CreateWellKnownSockets` at `0x7E6A4B0`, and next to
|
||
the TCP accept `X Server's TCP Port` at `0x7E591F08`).
|
||
|
||
So the internal route is: **named local service `xserver`**, reached by
|
||
`XOpenDisplay("0::0")`, not TCP and not a Unix socket path. TCP port 6000 is a
|
||
*separate* listener; it is gated by the user/node authorisation list
|
||
(`Connection request from user %s on node %s rejected`, auth check
|
||
`0x7E7E8D0`), which is why an external `xdpyinfo` gets
|
||
`Client is not authorized to access server` while the local service clients do
|
||
not.
|
||
|
||
## 7. App inventory
|
||
|
||
GUI (X clients):
|
||
|
||
| process | entry (bits) | role |
|
||
|---|---|---|
|
||
| `SERVER` | `0x3F362910` | X11 dix server, well-known socket `xserver` + TCP 6000 |
|
||
| `SESSIONMGR_PROC` | `0x3F2E6DE0` | Session Manager **and** Terminal Manager UI (Messages, Ethernet/IP/LAT, memory bar, Customize/Print Screen panels) |
|
||
| `WM` | `0x3FAB8160` | DWT Window Manager: frames, title bars, icon box, DEC WM atoms |
|
||
| `VTE` | `0x3F4DB050` | terminal emulator window (created on demand by the TM) |
|
||
| `PRINT` | `0x3F3E2590` → `0x7E7C4B2` | print manager, launched from the `Print Screen`/`Create` menu |
|
||
|
||
Non-GUI processes (same `create_process` mechanism, for completeness):
|
||
`INETSERVER`, LAT server (`0x3F2BD3F0`), MOP server (`0x3FAED480`), LANCE server
|
||
(`0x3F9DB9A0`), `TCPIP_FONTS` (`0x3F95EB10`), `resolver` (`0x3F5B7EF0`),
|
||
`Telnet` (`0x3F6D2F90`), `Route` (`0x3F5BF380`), `rtvalidate` (`0x3F663360`),
|
||
`VTE_listen` (`0x3F50F6A0`), `VTE_listen_shr` (`0x3F50F860`), `Bootp`
|
||
(`0x3F57B710`), `Inetd` (`0x3F587C00`), `domain_name` (`0x3F5B9510`),
|
||
`remote debug` (`0x3F59F320`), `TCP_UP` (`0x3F22F940`), `Terminate_tcp`
|
||
(`0x3F22E7F0`), `Main Program` (`0x3F34DB50`), `DWTM`/`VTE` helpers.
|
||
|
||
There is no file manager, editor, clock or cardfiler in this image; the only
|
||
GUI apps are the five above, plus the TM's own modal/panel code in the
|
||
`0x7E7xxxx`–`0x7E9xxxx` toolkit range.
|
||
|
||
## 8. The experiment that would settle the transport question
|
||
|
||
Run the same payload but **instrument the server side**: breakpoint the server
|
||
accept entry `0x3F58B800` and the auth check `0x3F3F4680`, log hits to a file
|
||
from a Lua frame hook (MAME's `printf` action did not reach stdout with
|
||
`-debugger none` in this run, so use a `wpset`/`bpset` action that increments a
|
||
RAM counter, or a Lua hook that samples `PC` every frame), then inject
|
||
`xhello.bin`. Three outcomes:
|
||
|
||
1. Accept never hit → the service connect from a non-boot process never reaches
|
||
the server; the next step is to call `0x7EE160C("xserver")` by hand under the
|
||
debugger and inspect its return value / the fd it installs.
|
||
2. Accept hit, auth rejects → authorise the injected client (the same
|
||
user/node table the config UI edits), then the window should appear.
|
||
3. Accept hit, auth passes, no reply → the setup packet is wrong; compare the
|
||
bytes Xlib writes from the WM's connection with ours.
|
||
|
||
A second, independent route worth trying: `create_process` a fresh process whose
|
||
entry is the payload (`create_process(0x01840000, "GUI", 0x2000, 1, 0)`), so the
|
||
X connection is made by a normal new process rather than by the rdebug
|
||
dispatcher. That isolates "wrong process context" from "wrong transport".
|
||
|
||
Not run: the owner's MAME was not running, but the shared tap
|
||
(`tap-mess-1000-0` / bridge `vt1200`) was used only after confirming no
|
||
`mame0289/vt1200` process existed; a private NVRAM and cfg directory were used
|
||
and the run was stopped afterwards. No second MAME was started while another was
|
||
live.
|
||
|
||
## 9. Evidence files
|
||
|
||
| path | contents |
|
||
|---|---|
|
||
| `dwt/gui/xhello.asm` | the payload (ABI check + 4 Xlib calls) |
|
||
| `dwt/gui/build.sh` | TI SDK build wrapper, output under `dwt/gui/` |
|
||
| `dwt/gui/build/xhello.bin`, `.lst`, `.map` | assembled payload, listing, map |
|
||
| `dwt/gui/probe.lua`, `probe2.lua`, `probe3.lua` | frame snapshots + marker/PC telemetry + accept breakpoint |
|
||
| `dwt/gui/run/mame.log` | boot, markers (`strlen=4`, `stage=2`, `disp=0`, `xclients=1`) |
|
||
| `dwt/gui/run/gui_frame15571.png` | post-injection frame: normal Terminal Manager, no new window |
|
||
|
||
## 10. Transport settled: a `create_process` child opens a window
|
||
|
||
**Verdict: H3 holds, with one payload bug on top.** A process created by the
|
||
firmware's own `create_process` connects to the `"xserver"` local service,
|
||
completes the X11 setup handshake, creates and maps a window. The final,
|
||
**breakpoint-free** run shows it at frame 2838:
|
||
`dwt/gui/evidence/xhello_window_frame2838.png` (black 400x200 window at 100,100
|
||
with the WM frame/title bar, over the Terminal Manager).
|
||
|
||
The ROM is untouched (`md5 b46a3e442ccb86134d2356bd70d22fd2`); no driver edits.
|
||
Run configuration is the documented `VT1200_SKIP_LEVEL1=1` divergence, private
|
||
`-cfg_directory`/`-nvram_directory`, BOOTP from `harness/serve.py --bootp`.
|
||
|
||
### 10.1 H1: the connect *does* reach the server accept — even from rdebug
|
||
|
||
The old probe address `0x3F58B800` was wrong: it is the **epilogue** of the
|
||
function that creates the "remote debug" process (`mmfm sp,a7,a9,a11,a13; rets`),
|
||
not the X accept. The local-service path is:
|
||
|
||
| stage | bit address |
|
||
|---|---|
|
||
| `XOpenDisplay` | `3F9C2570` |
|
||
| `XParseDisplay` → service attach by name | `3FD3B3E0` |
|
||
| service attach / lookup | `3F70B060` / `3F70DB80` |
|
||
| server-side accept callback (registration arg 1) | `3F34EC50` |
|
||
| local accept worker, 12-byte setup read | `3F34F190` / `3F350170` |
|
||
| setup read implementation | `3F70C5C0` |
|
||
| TCP accept (separate, auth-guarded) | `3F58B830` |
|
||
|
||
Instrumentation of the **old** experiment (xhello called in the rdebug process)
|
||
shows the connect is accepted, not rejected:
|
||
|
||
```
|
||
TR OPEN entry pc=3F9C2570 a14=01852020 ...
|
||
TR ATTACH entry pc=3F70B060 ... proc=003FFBD0
|
||
TR ATTACH ret a8=008D3050
|
||
TR SETUP-WRITE pre ...
|
||
TR ACCEPT-CB entry proc=00414F90 ; SERVER runs the service callback
|
||
TR REGISTER ret a8=007ADA30 ; re-registers for the next client
|
||
(no READ12)
|
||
```
|
||
|
||
`proc=00414F90` is `SERVER` (confirmed from the process list). So the service
|
||
table lookup, the attach and the accept callback all succeed; the failure is
|
||
downstream of accept.
|
||
|
||
### 10.2 H2: no auth check on the local path
|
||
|
||
`3F3F4680` (the "Client is not authorized" path) is **never hit** for a local
|
||
connect. The auth/user-node table belongs to the TCP listener (`3F58B830`,
|
||
`X Server's TCP Port`), not to the named service. Nothing rejects `"0::0"`.
|
||
|
||
### 10.3 What the rdebug process does *not* get
|
||
|
||
After the accept callback, the server's local accept worker must dequeue the
|
||
connection and read the 12-byte X11 setup. In the rdebug-process run it never
|
||
does — there is no `READ12` after the injected `ACCEPT-CB`; the client then sits
|
||
in Xlib's `select` (`3FD969F0`), which is the original observation. The server
|
||
side was alive (boot clients' own `READ12` calls occur earlier in the same log).
|
||
|
||
A fresh `create_process` child is dequeued normally. Why the rdebug context is
|
||
not dequeued was not reduced to a single ROM check; what is measured is the
|
||
difference in outcome: same payload, same service, same server, only the
|
||
calling process differs.
|
||
|
||
### 10.4 H3: `create_process` makes it work
|
||
|
||
`dwt/gui/xlaunch.asm` (128 B, bit `01860000`) is the launcher. Its call shape is
|
||
the Telnet handler's (`0x3F6D4340`–`0x3F6D4440`):
|
||
|
||
```
|
||
create_process(entry, name, stack, 1, arg) ; entry = 01840000, stack = 6000
|
||
```
|
||
|
||
It is invoked by the real rdebug `jump`; `create_process` returns PCB
|
||
`003FFBD0` and the child's `xhello` runs. The trace then shows the full
|
||
handshake:
|
||
|
||
```
|
||
TR CREATE_PROC entry entryarg=003F30DC ... proc=004043D0
|
||
TR OPEN entry ... (child) TR ATTACH ret a8=008D3050
|
||
TR ACCEPT-CB entry proc=00414F90
|
||
TR READ12 ret a8=0000000C ; server read the 12-byte setup
|
||
TR SRV-WRITE ret a8=00000008 ; setup reply header
|
||
TR SRV-WRITE ret a8=00000090 ; vendor + screen list
|
||
TR XREAD ret a8=00000008 expect=00000008 ; client read both
|
||
TR XREAD ret a8=00000090 expect=00000090
|
||
TR CreateSimpleWindow
|
||
TR MapWindow
|
||
TR XSync
|
||
```
|
||
|
||
Raw excerpt: `dwt/gui/evidence/transport_evidence.txt`; full trace:
|
||
`dwt/gui/run12/debug.log` (instrumented) and `dwt/gui/run13/mame.log`
|
||
(breakpoint-free).
|
||
|
||
### 10.5 The second bug: do not reload `Display *` from the marker cell
|
||
|
||
With H3 alone the child still crashed in `XCreateSimpleWindow`. The cause is in
|
||
`xhello`, not in the transport: `XOpenDisplay` really returns `0x7C7290`
|
||
(`DISP-RET2 a8=007C7290`), but the value read back from the old RAM marker cell
|
||
was `0x006B7290`. The root lookup then dereferenced the wrong block (all `0x5555`
|
||
fill) and produced garbage; the call went wild.
|
||
|
||
The fix, in `dwt/gui/xhello.asm`, is to keep the Display in **a11** and the
|
||
window in **a10** — both are callee-saved by every Xlib entry point
|
||
(`mmtm sp,a5,a7,a9,a10,a11,a13`) — instead of reloading it from RAM, and to
|
||
re-run `setf 16,0,0; setf 32,0,1` after every Xlib call because Xlib changes the
|
||
CPU's field state. The markers are diagnostic only now.
|
||
|
||
### 10.6 Reproduce
|
||
|
||
```sh
|
||
./dwt/gui/build.sh dwt/gui/xhello.asm 01840000 # 390 B
|
||
./dwt/gui/build.sh dwt/gui/xlaunch.asm 01860000 # 128 B
|
||
|
||
# boot with the private cfg/nvram and the breakpoint-free probe
|
||
VT1200_SKIP_LEVEL1=1 MSG_TRIGGER=$PWD/trigger MSG_DIR=$PWD \
|
||
/home/arti/code/k-space/mame0289/vt1200 vt1200 \
|
||
-rompath harness/mame/roms -sound none -skip_gameinfo \
|
||
-networkprovider taptun -video none -nothrottle \
|
||
-cfg_directory $PWD/cfg -nvram_directory $PWD/nvram \
|
||
-autoboot_script $PWD/probe_win.lua -autoboot_delay 0
|
||
|
||
# after 192.168.99.2:771 answers (one rdebug connection per poke/jump)
|
||
python3 dwt/visual/inject.py --host 192.168.99.2 \
|
||
--poke-file 0x01840000 --file dwt/gui/build/xhello.bin
|
||
python3 dwt/visual/inject.py --host 192.168.99.2 \
|
||
--poke-file 0x01860000 --file dwt/gui/build/xlaunch.bin \
|
||
--jump 0x01860000 --jump-wait 8
|
||
```
|
||
|
||
`probe_win.lua` contains no `bpset`/`wpset`; the final frame is produced by the
|
||
payload alone. Stage markers: `01890140` reaches 6, `01890160` = 4.
|
||
|
||
### 10.7 Limits
|
||
|
||
* The demonstration depends on the `VT1200_SKIP_LEVEL1=1` divergence (as did the
|
||
original experiment); it is evidence about the OS/X server, not a real POST.
|
||
* The window is an empty black `XCreateSimpleWindow` (400x200, border 2) on the
|
||
root window. The WM frames it; no text is drawn. Adding text needs
|
||
`XLoadFont`/`XCreateGC`/`XDrawString` on the same working connection.
|
||
* The rdebug process's own connect remains unusable; use `create_process`.
|