|
| 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