#!/usr/bin/env python3 """Dump the VT1200 firmware heap free lists over rdebug `peek`. The firmware has a real bit-addressed allocator (malloc `0x07EE0246`, free `0x07EE0488`, realloc near `0x07EE0B84`). It keeps: * a free-bytes counter at bit `0x1E8BA0` (32-bit, in **bits**) * a global free-list head at bit `0x1E8AC0` * three arenas at `0x1E8930` / `0x1E89B0` / `0x1E8A30`, each with its free-list head at `+0x60` Each free block is a 0x40-bit (8-byte) header: +0x00 size in bits (includes the 8-byte header) +0x20 next block, a **bit** address This tool walks them, so payload placement can be checked against what the OS may actually hand out. Note that the firmware's payload scratch addresses (bit `0x01800000` = byte `0x00300000`) sit **inside** the largest free block on a default boot -- see `dwt/notes/HEAP.md`. The **global** list is the one that matters for payload placement and is reliable. The per-arena lists are optional (`--arenas`); following one has been observed to kill the rdebug process, so each list is read on its own connection. python3 dwt/heapmap.py --host 192.168.99.2 python3 dwt/heapmap.py --host 192.168.99.2 --at 0x01800000 python3 dwt/heapmap.py --host 192.168.99.2 --blocks --arenas """ from __future__ import annotations import argparse import struct import sys import exploit FREE_COUNTER = 0x1E8BA0 GLOBAL = ("global", 0x1E8AC0) ARENAS = ( ("arena0", 0x1E8990), ("arena1", 0x1E8A10), ("arena2", 0x1E8A90), ) RAM_BITS = 0x02000000 # 4 MiB, as bit addresses HDR_NEXT = 0x20 # bits HDR_BYTES = 8 def u32(rd: exploit.RDebug, bit: int) -> int: return struct.unpack("= HDR_BYTES * 8) print(f"{name:6s}: {len(blocks)} blocks, byte 0x{lo>>3:06X}..0x{hi>>3:06X}, " f"{usable} bytes usable") all_blocks += [(name, b, s) for b, s, _ in blocks] if a.blocks: for b, s, nxt in blocks: print(f" bit 0x{b:08X} byte 0x{b>>3:06X} " f"size {s//8:>9} B end 0x{(b+s)>>3:06X} next 0x{nxt:08X}") if a.at is not None: hits = [(n, b, s) for n, b, s in all_blocks if b <= a.at < b + s] print(f"check bit 0x{a.at:08X} (byte 0x{a.at>>3:06X}): " + ("INSIDE a free block" if hits else "outside every free block")) for n, b, s in hits: print(f" {n}: block bit 0x{b:08X}, " f"{(b+s-a.at)//8} bytes of headroom to its end") return 0 if __name__ == "__main__": try: sys.exit(main(sys.argv)) except (exploit.ProtocolError, OSError) as exc: print(f"error: {exc}", file=sys.stderr) sys.exit(1)