Skip to content

Commit 1769880

Browse files
committed
Example workflow testing the copy_expr function
1 parent 4078942 commit 1769880

File tree

1 file changed

+158
-0
lines changed

1 file changed

+158
-0
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import json
2+
from binaryninja import Workflow, Activity, AnalysisContext, ReportCollection, \
3+
FlowGraphReport, show_report_collection, DisassemblySettings, DisassemblyOption
4+
from binaryninja.lowlevelil import *
5+
6+
"""
7+
This workflow copies every instruction in an IL function to a new IL function and then
8+
verifies that they are exactly the same.
9+
"""
10+
11+
12+
def assert_llil_eq(old_insn: LowLevelILInstruction, new_insn: LowLevelILInstruction):
13+
"""
14+
Make sure that these two instructions are the same (probably correct). Asserts otherwise.
15+
16+
Note: This ignores when instructions reference other instructions by index directly
17+
as that IL indices are not guaranteed to be consistent. So things like goto/if/jump_to
18+
will check that the target of the branch is the same, but allow the target to have
19+
a different instruction index.
20+
"""
21+
err_msg = (hex(old_insn.address), old_insn, new_insn)
22+
assert old_insn.operation == new_insn.operation, err_msg
23+
assert old_insn.attributes == new_insn.attributes, err_msg
24+
assert old_insn.size == new_insn.size, err_msg
25+
assert old_insn.raw_flags == new_insn.raw_flags, err_msg
26+
assert old_insn.source_location == new_insn.source_location, err_msg
27+
assert len(old_insn.operands) == len(new_insn.operands), err_msg
28+
# Can't compare operands directly since IL expression indices might change when
29+
# copying an instruction to another function
30+
for i, (old_op, new_op) in enumerate(zip(old_insn.detailed_operands, new_insn.detailed_operands)):
31+
err_msg = (hex(old_insn.address), f'op {i}', old_insn, new_insn, old_op, new_op)
32+
assert old_op[0] == new_op[0], err_msg # op name
33+
assert old_op[2] == new_op[2], err_msg # op type
34+
35+
op_type = old_op[2]
36+
if op_type == 'LowLevelILInstruction':
37+
assert_llil_eq(old_op[1], new_op[1])
38+
elif op_type == 'InstructionIndex' or \
39+
(old_insn.operation == LowLevelILOperation.LLIL_GOTO and old_op[0] == 'dest') or \
40+
(old_insn.operation == LowLevelILOperation.LLIL_IF and old_op[0] == 'true') or \
41+
(old_insn.operation == LowLevelILOperation.LLIL_IF and old_op[0] == 'false'):
42+
# These aren't consistent if the old function has instructions outside BBs
43+
# (they are not copied), so just make sure the target instruction looks the same
44+
assert_llil_eq(old_insn.function[old_op[1]], new_insn.function[new_op[1]])
45+
elif op_type in [
46+
'List[LowLevelILInstruction]',
47+
'List[\'LowLevelILInstruction\']' # compat (ew)
48+
]:
49+
for old_sub, new_sub in zip(old_op[1], new_op[1]):
50+
assert_llil_eq(old_sub, new_sub)
51+
elif old_insn.operation == LowLevelILOperation.LLIL_JUMP_TO and old_op[0] == 'targets':
52+
for old_target, new_target in zip(sorted(old_op[1].items()), sorted(new_op[1].items())):
53+
assert old_target[0] == new_target[0], err_msg
54+
# Same as with instruction index
55+
assert_llil_eq(old_insn.function[old_target[1]], new_insn.function[new_target[1]])
56+
else:
57+
# TODO: Any other types of ops need special behavior?
58+
assert old_op[1] == new_op[1], err_msg
59+
60+
61+
def lil_action(context: AnalysisContext):
62+
def translate_instr(
63+
new_func: LowLevelILFunction,
64+
old_block: LowLevelILBasicBlock,
65+
old_instr: LowLevelILInstruction,
66+
):
67+
# no-op copy
68+
return old_instr.copy_to(
69+
new_func,
70+
lambda sub_instr: translate_instr(new_func, old_block, sub_instr)
71+
)
72+
73+
old_lil = context.lifted_il
74+
if old_lil is None:
75+
return
76+
new_lil = old_lil.translate(translate_instr)
77+
new_lil.finalize()
78+
79+
if context.function.check_for_debug_report("copy_expr_test_lil"):
80+
# debug the test :)
81+
report = ReportCollection()
82+
settings = DisassemblySettings()
83+
settings.set_option(DisassemblyOption.ShowAddress, True)
84+
report.append(FlowGraphReport("old graph", old_lil.create_graph_immediate(settings)))
85+
report.append(FlowGraphReport("new graph", new_lil.create_graph_immediate(settings)))
86+
show_report_collection("copy expr test", report)
87+
88+
# Check all BBs have all the same instructions
89+
# Technically, this misses any instructions outside a BB, but those are not
90+
# picked up by analysis anyway, and therefore don't matter.
91+
assert len(old_lil.basic_blocks) == len(new_lil.basic_blocks)
92+
for old_bb, new_bb in zip(old_lil.basic_blocks, new_lil.basic_blocks):
93+
assert len(old_bb) == len(new_bb)
94+
for old_insn, new_insn in zip(old_bb, new_bb):
95+
assert_llil_eq(old_insn, new_insn)
96+
97+
98+
def llil_action(context: AnalysisContext):
99+
def translate_instr(
100+
new_func: LowLevelILFunction,
101+
old_block: LowLevelILBasicBlock,
102+
old_instr: LowLevelILInstruction,
103+
):
104+
# no-op copy
105+
return old_instr.copy_to(
106+
new_func,
107+
lambda sub_instr: translate_instr(new_func, old_block, sub_instr)
108+
)
109+
110+
old_llil = context.llil
111+
if old_llil is None:
112+
return
113+
new_llil = old_llil.translate(translate_instr)
114+
new_llil.finalize()
115+
new_llil.generate_ssa_form()
116+
117+
if context.function.check_for_debug_report("copy_expr_test_llil"):
118+
# debug the test :)
119+
report = ReportCollection()
120+
settings = DisassemblySettings()
121+
settings.set_option(DisassemblyOption.ShowAddress, True)
122+
report.append(FlowGraphReport("old graph", old_llil.create_graph_immediate(settings)))
123+
report.append(FlowGraphReport("new graph", new_llil.create_graph_immediate(settings)))
124+
show_report_collection("copy expr test", report)
125+
126+
# Check all BBs have all the same instructions
127+
# Technically, this misses any instructions outside a BB, but those are not
128+
# picked up by analysis anyway, and therefore don't matter.
129+
assert len(old_llil.basic_blocks) == len(new_llil.basic_blocks)
130+
for old_bb, new_bb in zip(old_llil.basic_blocks, new_llil.basic_blocks):
131+
assert len(old_bb) == len(new_bb)
132+
for old_insn, new_insn in zip(old_bb, new_bb):
133+
assert_llil_eq(old_insn, new_insn)
134+
135+
136+
wf = Workflow("core.function.metaAnalysis").clone("TestCopyExpr")
137+
138+
# Define the custom activity configuration
139+
wf.register_activity(Activity(
140+
configuration=json.dumps({
141+
"name": "extension.test_copy_expr.lil_action",
142+
"title": "Lifted IL copy_expr Test",
143+
"description": "Makes sure copy_expr works on Lifted IL functions."
144+
}),
145+
action=lil_action
146+
))
147+
wf.register_activity(Activity(
148+
configuration=json.dumps({
149+
"name": "extension.test_copy_expr.llil_action",
150+
"title": "Low Level IL copy_expr Test",
151+
"description": "Makes sure copy_expr works on Low Level IL functions."
152+
}),
153+
action=llil_action
154+
))
155+
wf.insert("core.function.analyzeAndExpandFlags", ["extension.test_copy_expr.lil_action"])
156+
wf.insert("core.function.generateMediumLevelIL", ["extension.test_copy_expr.llil_action"])
157+
# TODO: MLIL and higher
158+
wf.register()

0 commit comments

Comments
 (0)