diff --git a/dwt/exploit.py b/dwt/exploit.py index 4afa3f6..fd57980 100644 --- a/dwt/exploit.py +++ b/dwt/exploit.py @@ -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",