#!/usr/bin/env python3 """Dump the VT1200 NVR (user settings) over rdebug `peek`. The NVR is at bit `0x6000000`, a 16-bit-wide region. The firmware's `io_settings_copy_expand` copies **0x800 settings bytes, one per 16-bit word** (and `io_settings_copy_linear` writes them back the same way). So: raw region 4 KiB (0x800 words x 2 bytes) settings 2 KiB (the low byte of each word) The low byte of a little-endian word is the even byte of the linear byte stream, so `raw[0::2]` is the settings block. python3 dwt/nvram_dump.py --host 100.103.100.103 python3 dwt/nvram_dump.py --host HOST --out nvram.bin --raw nvram-raw.bin """ from __future__ import annotations import argparse import sys import exploit NVR_BASE = 0x6000000 NVR_BYTES = 0x1000 # 0x800 words x 2 bytes SETTINGS_LEN = 0x800 # one byte per word 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=8.0) ap.add_argument("--out", default="dwt/evidence/nvram-settings.bin") ap.add_argument("--raw", default=None, help="also write the 4 KiB raw region") ap.add_argument("--ascii", action="store_true", default=True, help="print strings/ASCII (default)") a = ap.parse_args(argv[1:]) rd = exploit.RDebug(a.host, a.port, a.timeout, False) rd.connect() raw = bytearray() try: for off in range(0, NVR_BYTES, exploit.MAX_XFER): n = min(exploit.MAX_XFER, NVR_BYTES - off) raw += rd.peek(NVR_BASE + off * 8, n) finally: rd.close() settings = bytes(raw[0::2]) with open(a.out, "wb") as fh: fh.write(settings) print(f"[+] raw {len(raw)} bytes at bit {NVR_BASE:#010x}") print(f"[+] settings {len(settings)} bytes -> {a.out}") if a.raw: with open(a.raw, "wb") as fh: fh.write(raw) print(f"[+] raw copy -> {a.raw}") # Known landmarks from the NVRAM report and the unit's settings. for needle in (b"ROM", b"100.103", b"LAT_", b"DECW$FONT", b"192.168", b":69"): idx = settings.find(needle) if idx >= 0: print(f" {needle!r} at settings offset 0x{idx:03x}") # Printable ASCII runs, useful for reading the settings block. runs, cur = [], bytearray() for i, b in enumerate(settings): if 32 <= b < 127: cur.append(b) else: if len(cur) >= 3: runs.append((i - len(cur), bytes(cur))) cur.clear() if len(cur) >= 3: runs.append((len(settings) - len(cur), bytes(cur))) print(f"[+] {len(runs)} printable runs:") for off, s in runs[:80]: print(f" 0x{off:03x} {s.decode('latin-1')}") 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)