-
Notifications
You must be signed in to change notification settings - Fork 280
Expand file tree
/
Copy pathwf_test_copy_expr.py
More file actions
322 lines (287 loc) · 15 KB
/
wf_test_copy_expr.py
File metadata and controls
322 lines (287 loc) · 15 KB
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
import functools
import json
import math
from binaryninja import Workflow, Activity, AnalysisContext, ReportCollection, \
FlowGraphReport, show_report_collection, DisassemblySettings, DisassemblyOption
from binaryninja.lowlevelil import *
from binaryninja.mediumlevelil import *
"""
This workflow copies every instruction in an IL function to a new IL function and then
verifies that they are exactly the same.
"""
def assert_llil_eq(old_insn: LowLevelILInstruction, new_insn: LowLevelILInstruction):
"""
Make sure that these two instructions are the same (probably correct). Asserts otherwise.
Note: This ignores when instructions reference other instructions by index directly
as that IL indices are not guaranteed to be consistent. So things like goto/if/jump_to
will check that the target of the branch is the same, but allow the target to have
a different instruction index.
"""
err_msg = (hex(old_insn.address), old_insn, new_insn)
assert old_insn.operation == new_insn.operation, err_msg
# assert old_insn.attributes == new_insn.attributes, err_msg
assert old_insn.size == new_insn.size, err_msg
assert old_insn.raw_flags == new_insn.raw_flags, err_msg
assert old_insn.source_location == new_insn.source_location, err_msg
assert len(old_insn.operands) == len(new_insn.operands), err_msg
# Can't compare operands directly since IL expression indices might change when
# copying an instruction to another function
for i, (old_op, new_op) in enumerate(zip(old_insn.detailed_operands, new_insn.detailed_operands)):
err_msg = (hex(old_insn.address), f'op {i}', old_insn, new_insn, old_op, new_op)
assert old_op[0] == new_op[0], err_msg # op name
assert old_op[2] == new_op[2], err_msg # op type
op_type = old_op[2]
if op_type == 'LowLevelILInstruction':
assert_llil_eq(old_op[1], new_op[1])
elif op_type == 'InstructionIndex' or \
(old_insn.operation == LowLevelILOperation.LLIL_GOTO and old_op[0] == 'dest') or \
(old_insn.operation == LowLevelILOperation.LLIL_IF and old_op[0] == 'true') or \
(old_insn.operation == LowLevelILOperation.LLIL_IF and old_op[0] == 'false'):
# These aren't consistent if the old function has instructions outside BBs
# (they are not copied), so just make sure the target instruction looks the same
assert old_insn.function[old_op[1]].operation == new_insn.function[new_op[1]].operation
elif op_type in [
'List[LowLevelILInstruction]',
'List[\'LowLevelILInstruction\']' # compat (ew)
]:
for old_sub, new_sub in zip(old_op[1], new_op[1]):
assert_llil_eq(old_sub, new_sub)
elif op_type == 'float':
if math.isnan(old_op[1]) and math.isnan(new_op[1]):
# both nan so they will compare not equal
pass
else:
assert old_op[1] == new_op[1], err_msg
elif old_insn.operation == LowLevelILOperation.LLIL_JUMP_TO and old_op[0] == 'targets':
for old_target, new_target in zip(sorted(old_op[1].items()), sorted(new_op[1].items())):
assert old_target[0] == new_target[0], err_msg
# Same as with instruction index
assert_llil_eq(old_insn.function[old_target[1]], new_insn.function[new_target[1]])
else:
# TODO: Any other types of ops need special behavior?
assert old_op[1] == new_op[1], err_msg
@functools.lru_cache(maxsize=8)
def get_mlil_maps(mlil: MediumLevelILFunction, builders: bool) -> Tuple[LLILSSAToMLILInstructionMapping, LLILSSAToMLILExpressionMapping]:
instr_map = mlil._get_llil_ssa_to_mlil_instr_map(builders)
expr_map = mlil._get_llil_ssa_to_mlil_expr_map(builders)
return instr_map, expr_map
def assert_mlil_eq(old_insn: MediumLevelILInstruction, new_insn: MediumLevelILInstruction):
"""
Make sure that these two instructions are the same (probably correct). Asserts otherwise.
Note: This ignores when instructions reference other instructions by index directly
as that IL indices are not guaranteed to be consistent. So things like goto/if/jump_to
will check that the target of the branch is the same, but allow the target to have
a different instruction index.
"""
err_msg = (hex(old_insn.address), old_insn, new_insn)
assert old_insn.operation == new_insn.operation, err_msg
assert old_insn.attributes == new_insn.attributes, err_msg
assert old_insn.size == new_insn.size, err_msg
assert old_insn.source_location == new_insn.source_location, err_msg
assert len(old_insn.operands) == len(new_insn.operands), err_msg
# Type only applies once we've generated SSA form (probably not consistent)
# assert old_insn.expr_type == new_insn.expr_type, f"{err_msg} {old_insn.expr_type} {new_insn.expr_type}"
instr_map, expr_map = get_mlil_maps(new_insn.function, True)
# Compare that the instruction's LLIL SSA map is the same as the old function
if old_insn.instr_index is not None and old_insn.function.get_expr_index_for_instruction(old_insn.instr_index) == old_insn.expr_index:
old_llil_ssa = old_insn.function.get_low_level_il_instruction_index(old_insn.instr_index)
if old_llil_ssa is not None:
assert [mlil for (llil, mlil) in instr_map.items() if llil == old_llil_ssa] == [new_insn.instr_index], err_msg
else:
assert [mlil for (llil, mlil) in instr_map.items() if llil == old_llil_ssa] == [], err_msg
# Can't compare operands directly since IL expression indices might change when
# copying an instruction to another function
for i, (old_op, new_op) in enumerate(zip(old_insn.detailed_operands, new_insn.detailed_operands)):
err_msg = (hex(old_insn.address), f'op {i}', old_insn, new_insn, old_op, new_op)
assert old_op[0] == new_op[0], err_msg # op name
assert old_op[2] == new_op[2], err_msg # op type
op_type = old_op[2]
if op_type == 'MediumLevelILInstruction':
assert_mlil_eq(old_op[1], new_op[1])
elif op_type == 'InstructionIndex' or \
(old_insn.operation == MediumLevelILOperation.MLIL_GOTO and old_op[0] == 'dest') or \
(old_insn.operation == MediumLevelILOperation.MLIL_IF and old_op[0] == 'true') or \
(old_insn.operation == MediumLevelILOperation.MLIL_IF and old_op[0] == 'false'):
# These aren't consistent if the old function has instructions outside BBs
# (they are not copied), so just make sure the target instruction looks the same
assert old_insn.function[old_op[1]].operation == new_insn.function[new_op[1]].operation
elif op_type == 'List[MediumLevelILInstruction]':
for old_sub, new_sub in zip(old_op[1], new_op[1]):
assert_mlil_eq(old_sub, new_sub)
elif op_type == 'float':
if math.isnan(old_op[1]) and math.isnan(new_op[1]):
# both nan so they will compare not equal
pass
else:
assert old_op[1] == new_op[1], err_msg
elif op_type == 'Variable':
assert old_op[1].core_variable == new_op[1].core_variable, err_msg
elif op_type == 'List[Variable]':
for old_sub, new_sub in zip(old_op[1], new_op[1]):
err_msg = (hex(old_insn.address), f'op {i}', old_insn, new_insn, old_op, new_op, old_sub, new_sub)
assert old_sub.core_variable == new_sub.core_variable, err_msg
elif op_type == 'SSAVariable':
assert old_op[1].var.core_variable == new_op[1].var.core_variable, err_msg
assert old_op[1].version == new_op[1].version, err_msg
elif old_insn.operation == MediumLevelILOperation.MLIL_JUMP_TO and old_op[0] == 'targets':
for old_target, new_target in zip(sorted(old_op[1].items()), sorted(new_op[1].items())):
err_msg = (hex(old_insn.address), f'op {i}', old_insn, new_insn, old_op, new_op, old_target, new_target)
assert old_target[0] == new_target[0], err_msg
# Same as with instruction index
assert_mlil_eq(old_insn.function[old_target[1]], new_insn.function[new_target[1]])
else:
# TODO: Any other types of ops need special behavior?
assert old_op[1] == new_op[1], err_msg
def lil_action(context: AnalysisContext):
def translate_instr(
new_func: LowLevelILFunction,
old_block: LowLevelILBasicBlock,
old_instr: LowLevelILInstruction,
):
# no-op copy
return old_instr.copy_to(
new_func,
lambda sub_instr: translate_instr(new_func, old_block, sub_instr)
)
old_lil = context.lifted_il
if old_lil is None:
return
new_lil = old_lil.translate(translate_instr)
new_lil.finalize()
if context.function.check_for_debug_report("copy_expr_test_lil"):
# debug the test :)
report = ReportCollection()
settings = DisassemblySettings()
settings.set_option(DisassemblyOption.ShowAddress, True)
report.append(FlowGraphReport("old graph", old_lil.create_graph_immediate(settings)))
report.append(FlowGraphReport("new graph", new_lil.create_graph_immediate(settings)))
show_report_collection("copy expr test", report)
# Check all BBs have all the same instructions
# Technically, this misses any instructions outside a BB, but those are not
# picked up by analysis anyway, and therefore don't matter.
assert len(old_lil.basic_blocks) == len(new_lil.basic_blocks)
for old_bb, new_bb in zip(old_lil.basic_blocks, new_lil.basic_blocks):
assert len(old_bb) == len(new_bb)
for old_insn, new_insn in zip(old_bb, new_bb):
assert_llil_eq(old_insn, new_insn)
def llil_action(context: AnalysisContext):
def translate_instr(
new_func: LowLevelILFunction,
old_block: LowLevelILBasicBlock,
old_instr: LowLevelILInstruction,
):
# no-op copy
return old_instr.copy_to(
new_func,
lambda sub_instr: translate_instr(new_func, old_block, sub_instr)
)
old_llil = context.llil
if old_llil is None:
return
new_llil = old_llil.translate(translate_instr)
new_llil.finalize()
new_llil.generate_ssa_form()
if context.function.check_for_debug_report("copy_expr_test_llil"):
# debug the test :)
report = ReportCollection()
settings = DisassemblySettings()
settings.set_option(DisassemblyOption.ShowAddress, True)
report.append(FlowGraphReport("old graph", old_llil.create_graph_immediate(settings)))
report.append(FlowGraphReport("new graph", new_llil.create_graph_immediate(settings)))
show_report_collection("copy expr test", report)
# Check all BBs have all the same instructions
# Technically, this misses any instructions outside a BB, but those are not
# picked up by analysis anyway, and therefore don't matter.
assert len(old_llil.basic_blocks) == len(new_llil.basic_blocks)
for old_bb, new_bb in zip(old_llil.basic_blocks, new_llil.basic_blocks):
assert len(old_bb) == len(new_bb)
for old_insn, new_insn in zip(old_bb, new_bb):
assert_llil_eq(old_insn, new_insn)
def mlil_action(context: AnalysisContext):
def translate_instr(
new_func: MediumLevelILFunction,
old_block: MediumLevelILBasicBlock,
old_instr: MediumLevelILInstruction,
):
# no-op copy
return old_instr.copy_to(
new_func,
lambda sub_instr: translate_instr(new_func, old_block, sub_instr)
)
old_mlil = context.mlil
if old_mlil is None:
return
new_mlil = old_mlil.translate(translate_instr)
new_mlil.finalize()
new_mlil.generate_ssa_form()
if context.function.check_for_debug_report("copy_expr_test_mlil"):
# debug the test :)
report = ReportCollection()
settings = DisassemblySettings()
settings.set_option(DisassemblyOption.ShowAddress, True)
report.append(FlowGraphReport("old graph", old_mlil.create_graph_immediate(settings)))
report.append(FlowGraphReport("new graph", new_mlil.create_graph_immediate(settings)))
show_report_collection("copy expr test", report)
# Check expr mappings are the same
new_map = list(sorted(new_mlil._get_llil_ssa_to_mlil_expr_map(True), key=lambda o: (o.lower_index, o.higher_index)))
old_map = list(sorted(old_mlil._get_llil_ssa_to_mlil_expr_map(False), key=lambda o: (o.lower_index, o.higher_index)))
assert old_map == new_map
# Check all BBs have all the same instructions
# Technically, this misses any instructions outside a BB, but those are not
# picked up by analysis anyway, and therefore don't matter.
assert len(old_mlil.basic_blocks) == len(new_mlil.basic_blocks)
for old_bb, new_bb in zip(old_mlil.basic_blocks, new_mlil.basic_blocks):
assert len(old_bb) == len(new_bb)
for old_insn, new_insn in zip(old_bb, new_bb):
assert_mlil_eq(old_insn, new_insn)
# Make sure mappings update correctly following set
new_map = list(sorted(new_mlil._get_llil_ssa_to_mlil_expr_map(True), key=lambda o: (o.lower_index, o.higher_index)))
context.mlil = new_mlil
newer_map = list(sorted(context.mlil._get_llil_ssa_to_mlil_expr_map(False), key=lambda o: (o.lower_index, o.higher_index)))
assert new_map == newer_map
wf = Workflow("core.function.metaAnalysis").clone("core.function.metaAnalysis")
# Define the custom activity configuration
wf.register_activity(Activity(
configuration=json.dumps({
"name": "extension.test_copy_expr.lil_action",
"title": "Lifted IL copy_expr Test",
"description": "Makes sure copy_expr works on Lifted IL functions.",
"eligibility": {
"auto": {
"default": False
}
}
}),
action=lil_action
))
wf.register_activity(Activity(
configuration=json.dumps({
"name": "extension.test_copy_expr.llil_action",
"title": "Low Level IL copy_expr Test",
"description": "Makes sure copy_expr works on Low Level IL functions.",
"eligibility": {
"auto": {
"default": False
}
}
}),
action=llil_action
))
wf.register_activity(Activity(
configuration=json.dumps({
"name": "extension.test_copy_expr.mlil_action",
"title": "Medium Level IL copy_expr Test",
"description": "Makes sure copy_expr works on Medium Level IL functions.",
"eligibility": {
"auto": {
"default": False
}
}
}),
action=mlil_action
))
wf.insert("core.function.analyzeAndExpandFlags", ["extension.test_copy_expr.lil_action"])
wf.insert("core.function.generateMediumLevelIL", ["extension.test_copy_expr.llil_action"])
wf.insert("core.function.generateHighLevelIL", ["extension.test_copy_expr.mlil_action"])
# TODO: MLIL and higher
wf.register()