#!/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