Files
vt1200/dwt/nvram_dump.py
T

91 lines
3.1 KiB
Python

#!/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)