From edc26855fded4382a51c58e2ebc9cf67a41768b1 Mon Sep 17 00:00:00 2001 From: SDD Date: Sun, 18 Oct 2026 17:25:18 +0300 Subject: [PATCH] dwt: full ROM dump over rdebug is byte-identical to vt1200_v22.bin (md5 match) --- dwt/notes/REAL_UNIT.md | 18 ++++++++++++ dwt/rom_dump.py | 66 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 dwt/rom_dump.py diff --git a/dwt/notes/REAL_UNIT.md b/dwt/notes/REAL_UNIT.md index 050e30d..e41039d 100644 --- a/dwt/notes/REAL_UNIT.md +++ b/dwt/notes/REAL_UNIT.md @@ -304,3 +304,21 @@ Evidence: `dwt/loader/evidence/real-unit-tftp/hostload_tftp_xhello.png`. This is the first fully self-contained delivery on the real unit: the terminal fetched the COFF over TFTP into `malloc` memory and ran it in a created process, with only the rdebug jump used to start the fetch. + +### Full ROM dump: byte-identical to the reference image + +`dwt/rom_dump.py` read 1,835,008 bytes at bit `0x3F200000` (1792 x 1 KiB +chunks, 2:06, ~14.6 KB/s) and compared the md5: + +``` +b46a3e442ccb86134d2356bd70d22fd2 (real unit) +b46a3e442ccb86134d2356bd70d22fd2 (vt1200_v22.bin) +MATCH +``` + +So the image the project has analysed and emulated is the ROM on the machine; +no revision or bank difference. The dump is reproducible with: + +```sh +python3 dwt/rom_dump.py --host 100.103.100.103 +``` diff --git a/dwt/rom_dump.py b/dwt/rom_dump.py new file mode 100644 index 0000000..7d2d2e5 --- /dev/null +++ b/dwt/rom_dump.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +"""Full ROM dump over rdebug `peek`, compared with the known v2.2 image. + +The ROM is mapped at bit `0x3F200000` for 0x1C0000 bytes (1.75 MiB). A +sequential `peek` of 1 KiB chunks returns the image; the md5 is compared with +the file the project has been using. + + python3 dwt/rom_dump.py --host 100.103.100.103 +""" +from __future__ import annotations + +import argparse +import hashlib +import sys + +import exploit + +ROM_BASE_BITS = 0x3F200000 +ROM_LEN = 0x1C0000 +KNOWN_MD5 = "b46a3e442ccb86134d2356bd70d22fd2" + + +def main(argv): + ap = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--host", default="100.103.100.103") + ap.add_argument("--port", type=int, default=771) + ap.add_argument("--timeout", type=float, default=10.0) + ap.add_argument("--out", default="dwt/evidence/real-unit-rom.bin") + ap.add_argument("--known", default=KNOWN_MD5) + a = ap.parse_args(argv[1:]) + + rd = exploit.RDebug(a.host, a.port, a.timeout, False) + rd.connect() + data = bytearray() + nchunks = (ROM_LEN + exploit.MAX_XFER - 1) // exploit.MAX_XFER + done = 0 + try: + for off in range(0, ROM_LEN, exploit.MAX_XFER): + n = min(exploit.MAX_XFER, ROM_LEN - off) + data += rd.peek(ROM_BASE_BITS + off * 8, n) + done += 1 + if done % 32 == 0 or done == nchunks: + print(f"\r[.] {done}/{nchunks} chunks", end="", file=sys.stderr, + flush=True) + finally: + rd.close() + print(file=sys.stderr) + + with open(a.out, "wb") as fh: + fh.write(data) + md5 = hashlib.md5(data).hexdigest() + print(f"[+] {len(data)} bytes -> {a.out}") + print(f"[+] md5 {md5}") + print(f"[+] known {a.known}") + print("[+] MATCH" if md5 == a.known else "[-] MISMATCH") + return 0 if md5 == a.known else 1 + + +if __name__ == "__main__": + try: + sys.exit(main(sys.argv)) + except (exploit.ProtocolError, OSError) as exc: + print(f"error: {exc}", file=sys.stderr) + sys.exit(2)