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
|
"""Phase 3: decompile every named function in every build and normalise the text
so that only SEMANTICS remain.
Normalisation rules (and the reasoning):
* symbolic autonames (sub_/loc_/off_/dword_/byte_/word_/unk_/jpt_ + hex) carry a
build-specific address -> replaced by a generic token. A *named* callee keeps
its name, because that is semantic.
* bare hex that falls INSIDE the image's own VA range is an address -> ADDR.
Anything outside that range is a real constant and is KEPT VERBATIM. This is
the crux: a bounds value like 0x80, a mask like 0x7FF or a size like 0x1400
must survive, because fixes of exactly that shape are what the previous
mnemonic-only diff was blind to.
* Hex-Rays locals (v3, a1, ...) are renumbered by order of first appearance, so
that adding one local does not shift every name and fake a diff.
* comments are stripped (they include our own port-tier markers).
Determinism is asserted: decompiling the same build twice must be byte-identical.
"""
import glob, os, re, sys, json, struct, hashlib
import idapro, idautils, idc, ida_hexrays
SYMRE = re.compile(r'\b(?:sub|loc|off|dword|byte|word|qword|unk|jpt|algn|asc|flt|stru)_[0-9A-Fa-f]+\b')
HEXRE = re.compile(r'\b0x([0-9A-Fa-f]+)\b')
VARRE = re.compile(r'\b([av])(\d+)\b')
def elf_range(p):
raw=open(p,'rb').read(); ph=struct.unpack('<I',raw[28:32])[0]
o,v,pa,f=struct.unpack('<IIII',raw[ph+4:ph+20])
return v, v+f
def normalise(txt, lo, hi):
txt = re.sub(r'//.*', '', txt) # strip comments
txt = SYMRE.sub('SYM', txt) # build-specific autonames
def hx(m):
val=int(m.group(1),16)
return 'ADDR' if lo <= val < hi else m.group(0) # literals preserved
txt = HEXRE.sub(hx, txt)
order={}
def vr(m):
k=m.group(0)
if k not in order: order[k]='%s%d'%(m.group(1), len(order))
return order[k]
txt = VARRE.sub(vr, txt)
return re.sub(r'\s+', ' ', txt).strip()
def run(elf):
lo,hi = elf_range(elf)
rc=idapro.open_database(elf, run_auto_analysis=False)
if rc!=0: return None
ida_hexrays.init_hexrays_plugin()
out={}
for f in idautils.Functions():
n=idc.get_func_name(f)
if n.startswith('sub_') or n.startswith('nullsub'): continue # unnamed -> not comparable
try: t=str(ida_hexrays.decompile(f))
except Exception: continue
out[n]=normalise(t, lo, hi)
idapro.close_database(save=False)
return out
if __name__=='__main__':
fams=sys.argv[1:] or ['hdcp','sos','sys_drv','asd']
os.makedirs('phase3', exist_ok=True)
for fam in fams:
for elf in sorted(glob.glob('handover/elf/versions/%s/*.elf'%fam)):
tag=os.path.basename(elf).replace('.elf','')
dst='phase3/%s.json'%tag
d=run(elf)
if d is None: print(" FAILED %s"%tag, flush=True); continue
json.dump(d, open(dst,'w'))
print(" %-42s %5d functions decompiled"%(tag, len(d)), flush=True)
|