aboutsummaryrefslogtreecommitdiffstats
path: root/psp_out/consume_scan.py
blob: 23d15f25b6232a5dd1b4cc1b390b89ecd5d340ee (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
"""Hunt corruption primitives whose corrupted data is CONSUMED before the canary check.

Rationale (ANALYSIS-HDCP-OVERFLOW-IS-DEFINITIVELY-DoS.md): a stack overflow is
only escalatable if a value it corrupts is loaded back BEFORE the epilogue. In
the HDCP case every post-overflow local sits BELOW the buffer, so nothing
corrupted is consumed -- which is why it is DoS regardless of a canary leak.

So the discriminator is: variable-length write to SP+D, followed by a load from
SP+off with off > D. Run headless over every module.
"""
import idapro, idc, idautils, ida_auto, re, sys
def frame_of(f,e):
    a=f
    while a<min(e,f+0x24):
        d=idc.GetDisasm(a).replace(' ','')
        m=re.match(r'SUB(?:\.W)?SP,SP,#(0x[0-9A-Fa-f]+|\d+)',d)
        if m: return int(m.group(1),0)
        a+=idc.get_item_size(a) or 2
    return None
def var_off(dd):
    s=dd.replace(' ','')
    m=re.search(r'\[SP,#(0x[0-9A-Fa-f]+|\d+)\+var_([0-9A-Fa-f]+)\]',s)
    if m: return int(m.group(1),0)-int(m.group(2),16)
    m=re.search(r'\[SP,#(0x[0-9A-Fa-f]+|\d+)\]',s)
    if m: return int(m.group(1),0)
    return None
def run(path):
    idapro.open_database(path, False)
    ida_auto.auto_wait()
    if len(list(idautils.Functions()))==0:
        print('  %s: 0 functions -> forcing reanalysis'%path); idc.plan_and_wait(idc.get_inf_attr(idc.INF_MIN_EA), idc.get_inf_attr(idc.INF_MAX_EA)); ida_auto.auto_wait()
    hits=[]; nf=0
    for f in idautils.Functions():
        nf+=1
        e=idc.get_func_attr(f, idc.FUNCATTR_END)
        frame=frame_of(f,e)
        if not frame or frame<0x20: continue
        a=f
        while a<e:
            d=idc.GetDisasm(a)
            if ('BLX' in d or 'BL ' in d) and re.search(r'memcpy|memmove|bzero|memset',d):
                dst=None; szreg=False; x=a; back=0
                while x>f and back<16:
                    x-=2
                    if idc.get_item_size(x) is None: continue
                    dd=idc.GetDisasm(x).replace(' ','')
                    if dst is None:
                        m=re.match(r'ADD(?:\.W)?R0,SP,#(0x[0-9A-Fa-f]+|\d+)\+var_([0-9A-Fa-f]+)',dd)
                        if m: dst=int(m.group(1),0)-int(m.group(2),16)
                    if not szreg and re.match(r'MOVR(?:1|2),R\d+$',dd): szreg=True
                    back+=1
                if dst is not None and szreg:
                    above=[]
                    x=a
                    while x<e:
                        dd=idc.GetDisasm(x)
                        if dd.strip().startswith('LDR'):
                            o=var_off(dd)
                            if o is not None and o>dst: above.append((x,o))
                        x+=idc.get_item_size(x) or 2
                    if above: hits.append((idc.get_func_name(f),a,dst,frame,above[:4]))
            a+=idc.get_item_size(a) or 2
    print("  %-14s %3d functions ; %d consumable-corruption candidate(s)"%(path.split('/')[-1],nf,len(hits)))
    for nm,site,dst,frame,above in hits:
        print("     %-38s write@%#x dst=SP+%#x frame=%#x above=%s"%(nm[:38],site,dst,frame,[(hex(x),hex(o)) for x,o in above]))
    idapro.close_database()
for p in sys.argv[1:]: run(p)