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
|
#!/usr/bin/env python3
"""Cross-function form of detector class 9: a copy length read from a buffer that some OTHER
function fills by copy.
Class 9 (`ta_bufferfield_audit.py`) only saw objects copy-filled inside the same function, and
so missed the original `sub_E04B68` defect, whose staging buffer is filled by `sub_E1D6BC`.
This version marks every call site that passes an accessor-derived buffer as an argument, then
looks inside those callees for copy lengths read from it, with forward-alias propagation (the
callee typically does `mov r4, r1` first -- omitting that made the control fail).
Controls: `sub_E04B68` with `[r4+0x88]` must be found; functions whose length is a stacked
argument must not be.
from ta_bufferfield_xfn import run
passes, hits = run("sys_drv", 0xe0b148) # slot accessor, all field offsets
passes, hits = run("sys_drv", 0xe0b0f4) # channel lookup
Swept over both accessors and every field offset, SYS_DRV yields exactly two hits
(`sub_E04B68` and `sub_E04DFC`, both on `slot[0x4c]`), and the channel object yields none.
"""
import sys, re
sys.path.insert(0,'/home/user/dev/amd-psp-re/fw')
import ta_capacity_audit as CAP
ARGS=["r0","r1","r2","r3"]
def run(mod, accessor, slot_off=None):
b,base,fns=CAP.load(mod); fns=CAP.split_merged(b,base,fns)
hp=CAP.helpers(b,base,fns); passes=set()
for ea,sz in fns:
o=ea-base
if o<0 or sz<=0 or o+sz>len(b): continue
ins=list(CAP.THUMB.disasm(b[o:o+sz], ea)); taint=set(); buf={}
for i in ins:
op=i.op_str or ''
if i.mnemonic in("bl","blx") and op=="#%#x"%accessor: taint={"r0"}; continue
m=re.match(r"(\w+),\s*(\w+)$",op)
if i.mnemonic.startswith("mov") and m:
if m.group(2) in taint: taint.add(m.group(1))
if m.group(2) in buf: buf[m.group(1)]=buf[m.group(2)]
mm=re.match(r"(\w+),\s*\[(\w+),\s*#(0x[0-9a-f]+|\d+)\]$",op)
if i.mnemonic.startswith("ldr") and mm and mm.group(2) in taint:
off=int(mm.group(3),0)
if slot_off is None or off==slot_off: buf[mm.group(1)]=off
if i.mnemonic in("bl","blx") and op.startswith("#"):
t=int(op[1:],16)
for a in ARGS:
if a in buf: passes.add((t,a,buf[a]))
for a in ARGS: taint.discard(a); buf.pop(a,None)
hits=[]
for ea,sz in fns:
rel={a:o for (t,a,o) in passes if t==ea}
if not rel: continue
ins=list(CAP.THUMB.disasm(b[ea-base:ea-base+sz], ea))
for n,i in enumerate(ins):
if not (i.mnemonic in("blx","bl") and (i.op_str or '').startswith('#')): continue
if int(i.op_str[1:],16) not in hp: continue
alias=dict(rel)
for j in ins[:n]:
m=re.match(r"(\w+),\s*(\w+)$", j.op_str or '')
if j.mnemonic.startswith("mov") and m:
if m.group(2) in alias: alias[m.group(1)]=alias[m.group(2)]
elif m.group(1) in alias: alias.pop(m.group(1))
cur,at="r2",n; src=None
for _ in range(5):
for k in range(at-1,-1,-1):
j=ins[k]
mm2=re.match(r"(%s),\s*(.+)$"%re.escape(cur), j.op_str or '')
if not mm2 or j.mnemonic.startswith(("cmp","cb","str","push","tst","b")): continue
if j.mnemonic.startswith("ldr"):
m3=re.match(r"\w+,\s*\[(\w+),\s*#(0x[0-9a-f]+|\d+)\]$", j.op_str or '')
if m3: src=(m3.group(1),int(m3.group(2),0))
break
m4=re.fullmatch(r"(r\d+|sb|sl|fp|ip)", mm2.group(2).strip())
if m4: cur,at=m4.group(1),k; break
break
else: break
if src: break
if src and src[0] in alias: hits.append((ea,i.address,src[0],src[1],alias[src[0]]))
return passes,hits
|