11import json
2+ import math
3+
24from binaryninja import Workflow , Activity , AnalysisContext , ReportCollection , \
35 FlowGraphReport , show_report_collection , DisassemblySettings , DisassemblyOption
46from binaryninja .lowlevelil import *
7+ from binaryninja .mediumlevelil import *
58
69"""
710This workflow copies every instruction in an IL function to a new IL function and then
@@ -20,7 +23,7 @@ def assert_llil_eq(old_insn: LowLevelILInstruction, new_insn: LowLevelILInstruct
2023 """
2124 err_msg = (hex (old_insn .address ), old_insn , new_insn )
2225 assert old_insn .operation == new_insn .operation , err_msg
23- assert old_insn .attributes == new_insn .attributes , err_msg
26+ # assert old_insn.attributes == new_insn.attributes, err_msg
2427 assert old_insn .size == new_insn .size , err_msg
2528 assert old_insn .raw_flags == new_insn .raw_flags , err_msg
2629 assert old_insn .source_location == new_insn .source_location , err_msg
@@ -48,6 +51,12 @@ def assert_llil_eq(old_insn: LowLevelILInstruction, new_insn: LowLevelILInstruct
4851 ]:
4952 for old_sub , new_sub in zip (old_op [1 ], new_op [1 ]):
5053 assert_llil_eq (old_sub , new_sub )
54+ elif op_type == 'float' :
55+ if math .isnan (old_op [1 ]) and math .isnan (new_op [1 ]):
56+ # both nan so they will compare not equal
57+ pass
58+ else :
59+ assert old_op [1 ] == new_op [1 ], err_msg
5160 elif old_insn .operation == LowLevelILOperation .LLIL_JUMP_TO and old_op [0 ] == 'targets' :
5261 for old_target , new_target in zip (sorted (old_op [1 ].items ()), sorted (new_op [1 ].items ())):
5362 assert old_target [0 ] == new_target [0 ], err_msg
@@ -58,6 +67,67 @@ def assert_llil_eq(old_insn: LowLevelILInstruction, new_insn: LowLevelILInstruct
5867 assert old_op [1 ] == new_op [1 ], err_msg
5968
6069
70+ def assert_mlil_eq (old_insn : LowLevelILInstruction , new_insn : LowLevelILInstruction ):
71+ """
72+ Make sure that these two instructions are the same (probably correct). Asserts otherwise.
73+
74+ Note: This ignores when instructions reference other instructions by index directly
75+ as that IL indices are not guaranteed to be consistent. So things like goto/if/jump_to
76+ will check that the target of the branch is the same, but allow the target to have
77+ a different instruction index.
78+ """
79+ err_msg = (hex (old_insn .address ), old_insn , new_insn )
80+ assert old_insn .operation == new_insn .operation , err_msg
81+ # assert old_insn.attributes == new_insn.attributes, err_msg
82+ assert old_insn .size == new_insn .size , err_msg
83+ assert old_insn .source_location == new_insn .source_location , err_msg
84+ assert len (old_insn .operands ) == len (new_insn .operands ), err_msg
85+ # Can't compare operands directly since IL expression indices might change when
86+ # copying an instruction to another function
87+ for i , (old_op , new_op ) in enumerate (zip (old_insn .detailed_operands , new_insn .detailed_operands )):
88+ err_msg = (hex (old_insn .address ), f'op { i } ' , old_insn , new_insn , old_op , new_op )
89+ assert old_op [0 ] == new_op [0 ], err_msg # op name
90+ assert old_op [2 ] == new_op [2 ], err_msg # op type
91+
92+ op_type = old_op [2 ]
93+ if op_type == 'MediumLevelILInstruction' :
94+ assert_mlil_eq (old_op [1 ], new_op [1 ])
95+ elif op_type == 'InstructionIndex' or \
96+ (old_insn .operation == MediumLevelILOperation .MLIL_GOTO and old_op [0 ] == 'dest' ) or \
97+ (old_insn .operation == MediumLevelILOperation .MLIL_IF and old_op [0 ] == 'true' ) or \
98+ (old_insn .operation == MediumLevelILOperation .MLIL_IF and old_op [0 ] == 'false' ):
99+ # These aren't consistent if the old function has instructions outside BBs
100+ # (they are not copied), so just make sure the target instruction looks the same
101+ assert old_insn .function [old_op [1 ]].operation == new_insn .function [new_op [1 ]].operation
102+ elif op_type == 'List[MediumLevelILInstruction]' :
103+ for old_sub , new_sub in zip (old_op [1 ], new_op [1 ]):
104+ assert_mlil_eq (old_sub , new_sub )
105+ elif op_type == 'float' :
106+ if math .isnan (old_op [1 ]) and math .isnan (new_op [1 ]):
107+ # both nan so they will compare not equal
108+ pass
109+ else :
110+ assert old_op [1 ] == new_op [1 ], err_msg
111+ elif op_type == 'Variable' :
112+ assert old_op [1 ].core_variable == new_op [1 ].core_variable , err_msg
113+ elif op_type == 'List[Variable]' :
114+ for old_sub , new_sub in zip (old_op [1 ], new_op [1 ]):
115+ err_msg = (hex (old_insn .address ), f'op { i } ' , old_insn , new_insn , old_op , new_op , old_sub , new_sub )
116+ assert old_sub .core_variable == new_sub .core_variable , err_msg
117+ elif op_type == 'SSAVariable' :
118+ assert old_op [1 ].var .core_variable == new_op [1 ].var .core_variable , err_msg
119+ assert old_op [1 ].version == new_op [1 ].version , err_msg
120+ elif old_insn .operation == MediumLevelILOperation .MLIL_JUMP_TO and old_op [0 ] == 'targets' :
121+ for old_target , new_target in zip (sorted (old_op [1 ].items ()), sorted (new_op [1 ].items ())):
122+ err_msg = (hex (old_insn .address ), f'op { i } ' , old_insn , new_insn , old_op , new_op , old_target , new_target )
123+ assert old_target [0 ] == new_target [0 ], err_msg
124+ # Same as with instruction index
125+ assert_mlil_eq (old_insn .function [old_target [1 ]], new_insn .function [new_target [1 ]])
126+ else :
127+ # TODO: Any other types of ops need special behavior?
128+ assert old_op [1 ] == new_op [1 ], err_msg
129+
130+
61131def lil_action (context : AnalysisContext ):
62132 def translate_instr (
63133 new_func : LowLevelILFunction ,
@@ -133,6 +203,44 @@ def translate_instr(
133203 assert_llil_eq (old_insn , new_insn )
134204
135205
206+ def mlil_action (context : AnalysisContext ):
207+ def translate_instr (
208+ new_func : MediumLevelILFunction ,
209+ old_block : MediumLevelILBasicBlock ,
210+ old_instr : MediumLevelILInstruction ,
211+ ):
212+ # no-op copy
213+ return old_instr .copy_to (
214+ new_func ,
215+ lambda sub_instr : translate_instr (new_func , old_block , sub_instr )
216+ )
217+
218+ old_mlil = context .mlil
219+ if old_mlil is None :
220+ return
221+ new_mlil = old_mlil .translate (translate_instr )
222+ new_mlil .finalize ()
223+ new_mlil .generate_ssa_form ()
224+
225+ if context .function .check_for_debug_report ("copy_expr_test_mlil" ):
226+ # debug the test :)
227+ report = ReportCollection ()
228+ settings = DisassemblySettings ()
229+ settings .set_option (DisassemblyOption .ShowAddress , True )
230+ report .append (FlowGraphReport ("old graph" , old_mlil .create_graph_immediate (settings )))
231+ report .append (FlowGraphReport ("new graph" , new_mlil .create_graph_immediate (settings )))
232+ show_report_collection ("copy expr test" , report )
233+
234+ # Check all BBs have all the same instructions
235+ # Technically, this misses any instructions outside a BB, but those are not
236+ # picked up by analysis anyway, and therefore don't matter.
237+ assert len (old_mlil .basic_blocks ) == len (new_mlil .basic_blocks )
238+ for old_bb , new_bb in zip (old_mlil .basic_blocks , new_mlil .basic_blocks ):
239+ assert len (old_bb ) == len (new_bb )
240+ for old_insn , new_insn in zip (old_bb , new_bb ):
241+ assert_mlil_eq (old_insn , new_insn )
242+
243+
136244wf = Workflow ("core.function.metaAnalysis" ).clone ("TestCopyExpr" )
137245
138246# Define the custom activity configuration
@@ -152,7 +260,16 @@ def translate_instr(
152260 }),
153261 action = llil_action
154262))
263+ wf .register_activity (Activity (
264+ configuration = json .dumps ({
265+ "name" : "extension.test_copy_expr.mlil_action" ,
266+ "title" : "Medium Level IL copy_expr Test" ,
267+ "description" : "Makes sure copy_expr works on Medium Level IL functions."
268+ }),
269+ action = mlil_action
270+ ))
155271wf .insert ("core.function.analyzeAndExpandFlags" , ["extension.test_copy_expr.lil_action" ])
156272wf .insert ("core.function.generateMediumLevelIL" , ["extension.test_copy_expr.llil_action" ])
273+ wf .insert ("core.function.generateHighLevelIL" , ["extension.test_copy_expr.mlil_action" ])
157274# TODO: MLIL and higher
158275wf .register ()
0 commit comments