aboutsummaryrefslogtreecommitdiffstats
path: root/bridge/_obsolete/patch_hammer.py
blob: a5483463819859f7dd5cfc67931e31c127dc7e42 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#!/usr/bin/env python3
"""
Patch fw_tinygrad.bin to add the GCP TOCTOU hammer behind vendor opcode 0xEB.

Mirrors app/patch_e7_fastread.py but with the OFFSETS FOR fw_tinygrad.bin (the
stock E7 patcher's 0x1411c is wrong for this image; the dispatch is at 0x140E0).
It repurposes the unused E2 vendor command (tinygrad uses E1/E3/E4/E5/8A):

  1. 0x140E1: cjne a,#0xE2  ->  cjne a,#0xEB            (select our opcode)
  2. 0x140E4: lcall 0xD945  ->  lcall 0x7D24            (-> handler entry)
  3. 0x7D00 : inject the linked handler blob (237 B, free/zero region)

Handler entry is 0x7D24 (tlp_write_fixed sits first at 0x7D00; see build/h.map).

Every patch verifies the expected original bytes first and REFUSES on mismatch,
so running it against a different image is inert rather than corrupting.
"""
import sys, hashlib
from pathlib import Path

ROOT = Path(__file__).parent
SRC  = ROOT / "fw_tinygrad.bin"
OUT  = ROOT / "fw_tinygrad_hammer.bin"
BLOB = ROOT / "build" / "handler_bytes.bin"

# expected sha256 prefix of the pristine fw_tinygrad.bin this patcher was built against
EXPECT_SRC_SHA16 = "e496176650b83985"

HANDLER_BASE   = 0x7D00
HANDLER_ENTRY  = 0x7D24

# (offset, expected_original, replacement)
PATCH_OPCODE = (0x140E1, b"\xE2", b"\xEB")
PATCH_LCALL  = (0x140E4, b"\xD9\x45", b"\x7D\x24")   # lcall 0xD945 -> lcall 0x7D24

def main():
    data = bytearray(SRC.read_bytes())
    got = hashlib.sha256(data).hexdigest()[:16]
    if got != EXPECT_SRC_SHA16:
        print(f"REFUSE: source sha256 {got} != expected {EXPECT_SRC_SHA16}.")
        print("  This patcher's offsets were derived from a specific fw_tinygrad.bin.")
        print("  Re-derive the dispatch offset (cjne a,#0xe2) before patching another image.")
        sys.exit(1)

    blob = BLOB.read_bytes()
    assert HANDLER_BASE + len(blob) <= 0x7E00, "handler overflows free slot 0x7D00..0x7E00"

    ok = True
    for off, old, new in (PATCH_OPCODE, PATCH_LCALL):
        if data[off:off+len(old)] != old:
            print(f"REFUSE: at {off:#x} expected {old.hex()} found {data[off:off+len(old)].hex()}")
            ok = False
    # handler slot must be empty
    if any(data[HANDLER_BASE:HANDLER_BASE+len(blob)]):
        print(f"REFUSE: handler slot {HANDLER_BASE:#x} is not zero-filled")
        ok = False
    if not ok:
        sys.exit(1)

    for off, old, new in (PATCH_OPCODE, PATCH_LCALL):
        data[off:off+len(new)] = new
        print(f"patched {off:#x}: {old.hex()} -> {new.hex()}")
    data[HANDLER_BASE:HANDLER_BASE+len(blob)] = blob
    print(f"injected {len(blob)}-byte handler at {HANDLER_BASE:#x} (entry {HANDLER_ENTRY:#x})")

    OUT.write_bytes(bytes(data))
    print(f"wrote {OUT}  sha256={hashlib.sha256(bytes(data)).hexdigest()[:16]}  ({len(data)} bytes)")
    # sanity: re-decode the hooked dispatch
    d = bytes(data)
    assert d[0x140E0:0x140E6] == b"\xB4\xEB\x0E\x12\x7D\x24", d[0x140E0:0x140E6].hex()
    print("verify: dispatch now 'cjne a,#0xEB,+14 ; lcall 0x7D24'  OK")

if __name__ == "__main__":
    main()