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
81
82
83
84
85
86
87
88
89
|
#!/usr/bin/env python3
"""Do TA builds rejected by 49.0.54 load on rolled-back firmware?
The corpus holds 78 distinct TA builds; 63 load on the shipping firmware and the rest are
refused. Those refusals are a property of the *running* firmware's loader, and the rollback
finding means the attacker chooses that firmware. Any build that loads only on an older set is
attack surface this audit has never exercised, since every TA fuzz ran against the builds
49.0.54 accepts.
argv[1]: container path to roll back to, or 'identity' for the shipping firmware
argv[2], argv[3]: index range into the sorted TA list (session wall is ~28 loads per boot)
Every successful load is unloaded immediately so the session wall is not hit; a row showing
status 0xb is the wall, not a refusal, and is labelled as such.
"""
import ctypes, glob, os, sys
sys.path.insert(0, '/root/psp/hw')
import tinygrad.runtime.support.am.amdev as amdev
import tinygrad.runtime.autogen.am.am as am
H = am.struct_psp_firmware_header_v2_0
DS = ctypes.sizeof(am.struct_psp_fw_bin_desc)
FO = H.psp_fw_bin.offset
def load_container(path):
b = open(path, 'rb').read()
h = H.from_buffer_copy(b)
uoff = h.header.ucode_array_offset_bytes
out = {}
for i in range(h.psp_fw_bin_count):
d = am.struct_psp_fw_bin_desc.from_buffer_copy(b, FO + i * DS)
s = d.offset_bytes + uoff
if d.size_bytes and s + d.size_bytes <= len(b):
out[d.fw_type] = b[s:s + d.size_bytes]
return out
target = sys.argv[1]
lo = int(sys.argv[2]) if len(sys.argv) > 2 else 0
hi = int(sys.argv[3]) if len(sys.argv) > 3 else 24
newset = None if target == 'identity' else load_container(target)
orig = amdev.AMFirmware.__init__
def patched(self, adev):
orig(self, adev)
if newset is None:
print('FW identity', flush=True); return
for t, blob in newset.items():
if t in self.sos_fw:
self.sos_fw[t] = memoryview(bytearray(blob))
print('FW substituted %d components' % len(newset), flush=True)
amdev.AMFirmware.__init__ = patched
from tinygrad import Device
adev = Device["AMD"].iface.dev_impl
psp = adev.psp
P = psp.reg_pref
print('SOSVER _58=%#x' % adev.reg(f"{P}_58").read(), flush=True)
def alloc(n):
pa = adev.mm.palloc(n, align=0x1000, zero=True, boot=False)
return pa, adev.paddr2mc(pa)
files = sorted(glob.glob('/root/psp/contall/ta_*'))
print('TACOUNT %d range %d..%d' % (len(files), lo, hi), flush=True)
ap_, amc = alloc(0x200000); cp, cmc = alloc(0x8000)
for path in files[lo:hi]:
blob = open(path, 'rb').read()
n = (len(blob) + 3) & ~3
if n > 0x200000:
print(' TA %-42s SKIP_TOO_BIG' % os.path.basename(path)[:42], flush=True); continue
adev.vram.view(ap_, n)[:] = blob + b'\x00' * (n - len(blob)); adev.gmc.flush_hdp()
c = am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_LOAD_TA); t = c.cmd.cmd_load_ta
t.app_phy_addr_lo, t.app_phy_addr_hi = amc & 0xFFFFFFFF, amc >> 32
t.app_len = len(blob)
t.cmd_buf_phy_addr_lo, t.cmd_buf_phy_addr_hi = cmc & 0xFFFFFFFF, cmc >> 32
t.cmd_buf_len = 0x4000
try:
r = psp._ring_submit(c)
st = r.resp.status & 0xFFFFFFFF; sid = r.resp.session_id
except Exception as e:
try: st = int(str(e).strip().split()[-1]) & 0xFFFFFFFF
except Exception: st = -1
sid = None
tag = ' SESSION_WALL' if st == 0xb else ''
print(' TA %-42s status=%#-11x sid=%s%s'
% (os.path.basename(path)[:42], st, sid, tag), flush=True)
if st == 0 and sid is not None:
u = am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_UNLOAD_TA)
u.cmd.cmd_unload_ta.session_id = sid
try: psp._ring_submit(u)
except Exception: pass
|