mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-30 12:03:54 +02:00
dwt/exploit.py: default to the computed dwt_prf answer -- echo is the known-failing mode and made a bare --peek look like a closed socket
This commit is contained in:
1 parent
0e4075958b
commit
2cc3ab1c79
1 file changed
+25
-18
+25
-18
@@ -8,10 +8,12 @@ Protocol (see dwt/notes/rdebug-static.md and dwt/notes/EXPLOIT_ATTEMPT.md):
|
||||
|
||||
Before the first opcode >= 2 the dispatcher runs a 16-byte challenge
|
||||
(dwt_prf, ROM bit 0x3F59FDC0): it sends one frame with flag 0x7F carrying 16
|
||||
bytes and then reads exactly 16 raw bytes back. It compares the 16 bytes it
|
||||
sent with the 16 bytes it received (the "transform" in between renders the
|
||||
reply through the font code into a separate global buffer and does not modify
|
||||
the reply buffer), so the answer is the challenge itself.
|
||||
bytes, reads exactly 16 raw bytes back, rewrites them **in place** through a
|
||||
fixed-key 16-round Feistel, and compares the result with the challenge. The
|
||||
required wire reply is ``swap4(invert(swap4(challenge)))``; ``dwt/challenge.py``
|
||||
implements it and is the default here. ``echo`` is kept only because it was
|
||||
the historical first attempt -- it does **not** pass, and using it makes the
|
||||
firmware close the session with no reply.
|
||||
|
||||
Examples:
|
||||
|
||||
@@ -46,6 +48,8 @@ import struct
|
||||
import sys
|
||||
import time
|
||||
|
||||
import challenge as _challenge # the reversed dwt_prf transform (same dir)
|
||||
|
||||
OP_NAMES = {
|
||||
0: "pager (exits after)",
|
||||
1: "printfs off",
|
||||
@@ -99,7 +103,7 @@ class RDebug:
|
||||
self.verbose = verbose
|
||||
self.sock: socket.socket | None = None
|
||||
self.challenged = False
|
||||
self.challenge_type = "echo"
|
||||
self.challenge_type = "computed"
|
||||
self.challenge_raw: bytes | None = None
|
||||
|
||||
# ---- transport -------------------------------------------------------
|
||||
@@ -151,25 +155,25 @@ class RDebug:
|
||||
def challenge_answer(challenge: bytes, mode: str) -> bytes:
|
||||
"""Compute the 16-byte answer.
|
||||
|
||||
The firmware stores four runtime words in host order at frame+0x20,
|
||||
byte-swaps each one (`ntohl`, ROM 0x07F65828) into frame+0, and sends
|
||||
*those* 16 bytes with flag 0x7F. After reading 16 bytes back it runs
|
||||
them through a fixed-key font/cipher pipeline that rewrites the reply
|
||||
buffer in place, and compares frame+0x20 (host order) with the result.
|
||||
The firmware stores four runtime words in host order, byte-swaps each
|
||||
one (`ntohl`, ROM 0x07F65828), sends *those* 16 bytes with flag 0x7F,
|
||||
reads 16 bytes back, rewrites them in place through a fixed-key
|
||||
16-round Feistel, and compares the result with the host-order
|
||||
challenge. So the reply must be ``swap4(invert(swap4(challenge)))``,
|
||||
which is what ``dwt/challenge.py`` implements.
|
||||
|
||||
`echo` and `computed` implement the two layers that were established
|
||||
live: `echo` returns the challenge unchanged; `computed` byte-swaps
|
||||
each 32-bit word, which fixes the host/network order layer. The
|
||||
firmware's in-place transform is a second, nonlinear layer; on the
|
||||
tested image neither mode passes it (memcmp returns -1 for echo and 1
|
||||
for computed). `--challenge-raw` allows a fully custom answer once the
|
||||
transform is reversed.
|
||||
``computed`` (the default) uses that inverse and passes. ``echo``
|
||||
returns the challenge unchanged and ``swap`` only fixes the byte
|
||||
order; both fail and make the firmware kill the session, and are kept
|
||||
for reference only.
|
||||
"""
|
||||
if len(challenge) != 16:
|
||||
raise ProtocolError(f"challenge is {len(challenge)} bytes, not 16")
|
||||
if mode == "echo":
|
||||
return bytes(challenge)
|
||||
if mode == "computed":
|
||||
return _challenge.respond(bytes(challenge))
|
||||
if mode == "swap": # historical: byte-order layer only, fails
|
||||
out = bytearray()
|
||||
for i in range(0, 16, 4):
|
||||
out += challenge[i:i + 4][::-1]
|
||||
@@ -360,7 +364,10 @@ def main(argv: list[str]) -> int:
|
||||
ap.add_argument("--port", type=int, default=771)
|
||||
ap.add_argument("--timeout", type=float, default=5.0,
|
||||
help="socket timeout for ordinary command replies")
|
||||
ap.add_argument("--challenge", choices=("echo", "computed"), default="echo")
|
||||
ap.add_argument("--challenge", choices=("computed", "echo", "swap"),
|
||||
default="computed",
|
||||
help="challenge answer: computed (default, passes), or "
|
||||
"echo/swap, which are historical and fail")
|
||||
ap.add_argument("--challenge-raw", metavar="HEX",
|
||||
help="override the 16-byte challenge answer (experiment)")
|
||||
ap.add_argument("--byte-addr", action="store_true",
|
||||
|
||||
Reference in new issue
Block a user