mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-30 12:03:54 +02:00
88 lines
3.7 KiB
Markdown
88 lines
3.7 KiB
Markdown
# Payload memory and the firmware heap
|
|
|
|
Where the injected payloads live, and what the firmware's allocator actually
|
|
owns. Measured on a default `VT1200_SKIP_LEVEL1=1` boot, 2026-10-18.
|
|
|
|
## Short answer
|
|
|
|
* Payloads are poked to **fixed scratch addresses** at bit `0x01800000`
|
|
(byte `0x00300000`, 3 MiB) and above — the top of the 4 MiB RAM.
|
|
* That region is **not allocated**. On a default boot it lies **inside the
|
|
largest free block** of the firmware's heap (`byte 0x12B772..0x400000`,
|
|
2,967,694 bytes). Nothing protects it; the OS may hand it out.
|
|
* There **is** a real allocator — `malloc`/`free`/`realloc` — and it is
|
|
callable from a payload. `rdebug`'s own handlers already use it.
|
|
|
|
## The allocator
|
|
|
|
| what | address |
|
|
|---|---|
|
|
| `malloc` | Ghidra `0x07EE0246` / bit `0x3F701230` |
|
|
| `free` | Ghidra `0x07EE0488` / bit `0x3F702440` |
|
|
| `realloc` | near Ghidra `0x07EE0B84` (string `realloc: bad address %x not allocated`, bit `0x3F705C20`) |
|
|
| `calloc` | present (used by `rdebug_gather_usage_stats` and `remote_debug`) |
|
|
|
|
It is a **bit-addressed first-fit** allocator over free lists:
|
|
|
|
* free-bytes counter, 32-bit, **in bits**: bit `0x1E8BA0`
|
|
* global free-list head: bit `0x1E8AC0`
|
|
* three arenas at bit `0x1E8930`, `0x1E89B0`, `0x1E8A30`; each free-list head at
|
|
`+0x60` (so `0x1E8990`, `0x1E8A10`, `0x1E8A90`)
|
|
* a block is `{ u32 size_in_bits; ...; u32 next_bitaddr at +0x20 }`; the header
|
|
is `0x40` bits (8 bytes), so the **returned pointer is `block + 8 bytes`**
|
|
* `malloc` returns `0` on failure
|
|
|
|
`create_process` (`0x07EE0D48`) mallocs a 0x39-byte PCB and the stack;
|
|
`rdebug_display_message` mallocs `len+2`; `jump` mallocs the argument block.
|
|
So allocation from our payload's context is already proven by the firmware
|
|
itself.
|
|
|
|
## Measured boot
|
|
|
|
```
|
|
free counter @0x1E8BA0 = 24,795,312 bits = 3,099,414 bytes (3099 KB decimal)
|
|
global free list: 9 blocks
|
|
[0] byte 0x0F5B5A 196 B
|
|
...
|
|
[8] byte 0x12B772 2,967,694 B end byte 0x400000
|
|
```
|
|
|
|
The last block runs to the top of RAM. `0x01800000` (byte `0x300000`) falls
|
|
**inside** it — about 1.9 MB from the block's start and 1 MiB below its end.
|
|
That is why the payloads work, and why they are not safe indefinitely: the
|
|
allocator splits this block from its start, so roughly 1.9 MB of allocation
|
|
from it would reach the payload.
|
|
|
|
The Memory bar's `3099 KB` is this counter in decimal kilobytes, a useful
|
|
cross-check.
|
|
|
|
**The per-arena lists are not safe to follow.** Reading them has twice killed
|
|
the terminal's network stack (`TimeoutError`, then `No route to host`), while
|
|
the global list reads cleanly every time. `dwt/heapmap.py` therefore reads the
|
|
global list by default and treats `--arenas` as an experiment; do the same, and
|
|
treat `peek` of an unvalidated pointer as able to take the machine down.
|
|
|
|
## Safe pattern for a payload
|
|
|
|
1. **Bootstrap**: the code itself must live somewhere before `malloc` runs.
|
|
Poke it to a fixed scratch address as today.
|
|
2. Call `malloc(size)` from the payload's own process (the `create_process`
|
|
child, or the launcher before it starts Xlib).
|
|
3. Copy any long-lived buffers — or the payload itself — into the returned
|
|
block, keep the pointer in a **callee-saved register** or a fixed marker, and
|
|
never write before `ptr-8` (the header).
|
|
4. One allocation at startup is safest. The allocator's lock is a per-process
|
|
byte at `[@0x1B3900]+0x1C1`; assume cooperative scheduling, do not allocate
|
|
from an interrupt handler, and do not race it from two payloads.
|
|
5. Check for `0`, and remember sizes and addresses are **bits** internally even
|
|
though `malloc` takes bytes.
|
|
|
|
## Tool
|
|
|
|
`dwt/heapmap.py` walks the free lists over `peek`:
|
|
|
|
```sh
|
|
python3 dwt/heapmap.py --host 192.168.99.2 --blocks
|
|
python3 dwt/heapmap.py --host 192.168.99.2 --at 0x01800000 # is my payload safe?
|
|
```
|