aboutsummaryrefslogtreecommitdiffstats
path: root/psp_out/fuse_reach.py
blob: 293a5f4e14af288d2cbc2f6ff379025c18c37b52 (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
75
76
77
78
79
80
#!/usr/bin/env python3
"""Is any fuse/debug function reachable from a SYS_DRV RPC opcode?

fuzz_sysdrv's sample (634 blocks) reached none of them, but it only sweeps its
ACL ranges and never touches the crypto dispatcher. This sweeps the opcode space
directly through the real entry (sub_E087E4), with the same validated hooks.

Controls: the dispatcher/allowlist/entry hooks must fire, and the crypto
dispatcher must fire too this time (low opcodes are included), otherwise the
sweep is not covering what it claims.
"""
import collections, json, sys
sys.path.insert(0, "/home/user/dev/amd-psp-re/psp_emu")
from pspemu import sysdrv_env
from unicorn import UC_HOOK_CODE, UC_HOOK_BLOCK
import struct

syms = json.load(open("/home/user/dev/amd-psp-re/psp_out/v53/sys_drv_53_symbols.json"))["map"]
ENTRY = 0xE087E4

CONTROLS = {"high disp E105A8": 0xE105A8, "allowlist E0AB44": 0xE0AB44,
            "entry E087E4": ENTRY, "crypto disp E1DC30": 0xE1DC30}
TNAMES = ("fuse_program_bits", "fuse_write_field", "fuse_write_thunk",
          "debug_unlock_apply", "debug_policy_value", "debug_allowed",
          "debug_port_enabled", "secure_boot_state", "dma_transfer",
          "fuse_read_config", "fuse_read_flag", "fuse_feature_check",
          "fuse_decode_flags", "debug_disabled", "debug_unlock_apply",
          "spirom_write_checked", "spirom_read_checked", "rom_parse_directory",
          "secure_channel_setup", "fuse_read_pair")
TARGETS = {n: syms[n]["va"] for n in TNAMES if n in syms}

sd = sysdrv_env.make()
sysdrv_env.run_init(sd)
hits = collections.Counter()
first_op = {}
cur = {"op": None}
for n, va in {**CONTROLS, **TARGETS}.items():
    def mk(nm):
        def h(u, a, s, x):
            hits.update([nm])
            first_op.setdefault(nm, cur["op"])
        return h
    sd.uc.hook_add(UC_HOOK_CODE, mk(n), begin=va, end=va)
blocks = set()
sd.uc.hook_add(UC_HOOK_BLOCK, lambda u, a, s, x: blocks.add(a), begin=0xE00100, end=0xEB7000)
sd.uc.ctl_flush_tb()

req = sd.alloc(0x100)
cfg = sd.alloc(0x400); sd.write(cfg, b"\x00" * 0x400)
buf = sd.alloc(0x2000); sd.write(buf, b"\x00" * 0x2000)

ops = list(range(0x0000, 0x0100)) + list(range(0x1000, 0x2000))
print("sweeping %d opcodes through the real entry..." % len(ops))
for n, op in enumerate(ops):
    cur["op"] = op
    d = [0] * 31
    d[0] = op; d[1] = 2; d[2] = buf; d[3] = 0x100; d[4] = buf; d[5] = 0x100; d[18] = cfg
    sd.write(req, b"\x00" * 0x100)
    sd.write(req, struct.pack("<31I", *d))
    try:
        sd.run_function(ENTRY, (req, 2, 0, 0), count=3_000_000)
    except Exception:
        pass
    if n % 512 == 0:
        print("   ...%d/%d, blocks=%d" % (n, len(ops), len(blocks)), flush=True)

print("\nCONTROLS:")
for n in CONTROLS:
    print("   %-24s %s" % (n, hits[n] or "NOT REACHED"))
print("\nTARGETS:")
any_hit = False
for n in sorted(TARGETS):
    if hits[n]:
        any_hit = True
        print("   %-24s %-8d first reached by opcode %#06x" % (n, hits[n], first_op[n]))
    else:
        print("   %-24s -" % n)
print("\nSYS_DRV blocks covered: %d" % len(blocks))
print("RESULT: %s" % ("SOME FUSE/DEBUG FUNCTION IS RPC-REACHABLE" if any_hit
                      else "none reachable from any swept opcode"))