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
|
#!/usr/bin/env python3
"""Locate a DIVERGED function in an older build and print a side-by-side
instruction diff.
A diverged function cannot be found by mnemonic match (that is what made it
diverged), so we locate it positionally: find the nearest surrounding functions
that DID match, derive the local code shift from them, and disassemble the old
image at the implied address.
usage: vdiff_func.py <new.elf> <funcs.json> <old.elf> <func_name>
"""
import json, sys, os
from collections import defaultdict
from capstone import Cs, CS_ARCH_ARM, CS_MODE_THUMB, CS_MODE_LITTLE_ENDIAN
OFF = 0x1000
NGRAM = 8
CODE_RANGES = [(0x0, 0x7022), (0x12000, 0x17dd0)]
md = Cs(CS_ARCH_ARM, CS_MODE_THUMB | CS_MODE_LITTLE_ENDIAN)
def seq(buf, va, maxn=None):
out = []
for ins in md.disasm(buf, va):
out.append(ins.mnemonic)
if maxn and len(out) >= maxn:
break
return tuple(out)
def build_index(old):
idx = defaultdict(list)
for lo, hi in CODE_RANGES:
for va in range(lo, hi, 2):
fo = va + OFF
if fo + 64 > len(old):
continue
s = seq(old[fo:fo + 64], va, NGRAM)
if len(s) == NGRAM:
idx[s].append(va)
return idx
def main():
new_p, funcs_p, old_p, target = sys.argv[1:5]
new = open(new_p, 'rb').read()
old = open(old_p, 'rb').read()
funcs = json.load(open(funcs_p))
F = sorted(((int(k, 16), v[0], v[1]) for k, v in funcs.items()))
idx = build_index(old)
# map every matchable function to its old address -> local shift
shifts = {}
tgt = None
for st, nm, en in F:
if nm == target:
tgt = (st, en)
size = en - st
body = new[st + OFF:st + OFF + size]
s_new = seq(body, st)
if len(s_new) < NGRAM:
continue
for cand in idx.get(s_new[:NGRAM], ()):
if seq(old[cand + OFF:cand + OFF + size], cand) == s_new:
shifts[st] = cand - st
break
if tgt is None:
print("function not found:", target); return
st, en = tgt
before = [a for a in shifts if a < st]
after = [a for a in shifts if a > st]
db = shifts[max(before)] if before else 0
da = shifts[min(after)] if after else db
print("target %s @0x%05x size=%d" % (target, st, en - st))
print("nearest matched before: 0x%05x shift=%+d" % (max(before), db) if before else "none")
print("nearest matched after : 0x%05x shift=%+d" % (min(after), da) if after else "none")
size = en - st
for shift in sorted({db, da}):
oa = st + shift
print("\n===== OLD @0x%05x (shift %+d) vs NEW @0x%05x =====" % (oa, shift, st))
A = list(md.disasm(new[st + OFF:st + OFF + size], st))
B = list(md.disasm(old[oa + OFF:oa + OFF + size + 16], oa))
for i in range(max(len(A), len(B))):
l = "%-9s %-7s %s" % (hex(A[i].address), A[i].mnemonic, A[i].op_str) if i < len(A) else ""
r = "%-9s %-7s %s" % (hex(B[i].address), B[i].mnemonic, B[i].op_str) if i < len(B) else ""
mark = ""
if i < len(A) and i < len(B) and A[i].mnemonic != B[i].mnemonic:
mark = " <<<"
print("%-46s | %-46s%s" % (l, r, mark))
if __name__ == "__main__":
main()
|