├── .gitignore ├── AUTHORS.md ├── D810.py ├── LICENSE ├── README.md └── d810 ├── __init__.py ├── ast.py ├── cfg_utils.py ├── conf ├── __init__.py ├── default_instruction_only.json ├── default_unflattening_ollvm.json ├── default_unflattening_switch_case.json ├── example_anel.json ├── example_libobfuscated.json └── options.json ├── docs └── source │ └── images │ └── gui_plugin_configuration.png ├── emulator.py ├── errors.py ├── hexrays_formatters.py ├── hexrays_helpers.py ├── hexrays_hooks.py ├── ida_ui.py ├── log.ini ├── log.py ├── manager.py ├── manager_info.json ├── optimizers ├── __init__.py ├── flow │ ├── __init__.py │ ├── flattening │ │ ├── __init__.py │ │ ├── fix_pred_cond_jump_block.py │ │ ├── generic.py │ │ ├── unflattener.py │ │ ├── unflattener_fake_jump.py │ │ ├── unflattener_indirect.py │ │ ├── unflattener_switch_case.py │ │ └── utils.py │ ├── handler.py │ └── jumps │ │ ├── __init__.py │ │ ├── handler.py │ │ ├── opaque.py │ │ └── tricks.py ├── handler.py └── instructions │ ├── __init__.py │ ├── analysis │ ├── __init__.py │ ├── handler.py │ ├── pattern_guess.py │ └── utils.py │ ├── chain │ ├── __init__.py │ ├── chain_rules.py │ └── handler.py │ ├── early │ ├── __init__.py │ ├── handler.py │ └── mem_read.py │ ├── handler.py │ ├── pattern_matching │ ├── __init__.py │ ├── experimental.py │ ├── handler.py │ ├── rewrite_add.py │ ├── rewrite_and.py │ ├── rewrite_bnot.py │ ├── rewrite_cst.py │ ├── rewrite_mov.py │ ├── rewrite_mul.py │ ├── rewrite_neg.py │ ├── rewrite_or.py │ ├── rewrite_predicates.py │ ├── rewrite_sub.py │ ├── rewrite_xor.py │ └── weird.py │ └── z3 │ ├── __init__.py │ ├── cst.py │ ├── handler.py │ └── predicates.py ├── tracker.py ├── utils.py └── z3_utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__/ 2 | **/.idea/ 3 | **/.vscode/ 4 | **/venv/ 5 | **~ 6 | **.pyc 7 | **.log 8 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Authors list 2 | 3 | - Boris Batteux 4 | -------------------------------------------------------------------------------- /D810.py: -------------------------------------------------------------------------------- 1 | import os 2 | import idaapi 3 | import ida_hexrays 4 | import ida_kernwin 5 | 6 | 7 | from d810.conf import D810Configuration 8 | from d810.manager import D810State, D810_LOG_DIR_NAME 9 | from d810.log import configure_loggers, clear_logs 10 | 11 | 12 | D810_VERSION = "0.1" 13 | 14 | class D810Plugin(idaapi.plugin_t): 15 | # variables required by IDA 16 | flags = 0 # normal plugin 17 | wanted_name = "D-810" 18 | wanted_hotkey = "Ctrl-Shift-D" 19 | comment = "Interface to the D-810 plugin" 20 | help = "" 21 | initialized = False 22 | 23 | def __init__(self): 24 | super(D810Plugin, self).__init__() 25 | self.d810_config = None 26 | self.state = None 27 | self.initialized = False 28 | 29 | 30 | def reload_plugin(self): 31 | if self.initialized: 32 | self.term() 33 | 34 | self.d810_config = D810Configuration() 35 | 36 | #TO-DO: if [...].get raises an exception because log_dir is not found, handle exception 37 | real_log_dir = os.path.join(self.d810_config.get("log_dir"), D810_LOG_DIR_NAME) 38 | 39 | #TO-DO: if [...].get raises an exception because erase_logs_on_reload is not found, handle exception 40 | if self.d810_config.get("erase_logs_on_reload"): 41 | clear_logs(real_log_dir) 42 | 43 | configure_loggers(real_log_dir) 44 | self.state = D810State(self.d810_config) 45 | print("D-810 reloading...") 46 | self.state.start_plugin() 47 | self.initialized = True 48 | 49 | 50 | # IDA API methods: init, run, term 51 | def init(self): 52 | if not ida_hexrays.init_hexrays_plugin(): 53 | print("D-810 need Hex-Rays decompiler. Skipping") 54 | return idaapi.PLUGIN_SKIP 55 | 56 | kv = ida_kernwin.get_kernel_version().split(".") 57 | if (int(kv[0]) < 7) or (int(kv[1]) < 5): 58 | print("D-810 need IDA version >= 7.5. Skipping") 59 | return idaapi.PLUGIN_SKIP 60 | print("D-810 initialized (version {0})".format(D810_VERSION)) 61 | return idaapi.PLUGIN_OK 62 | 63 | 64 | def run(self, args): 65 | self.reload_plugin() 66 | 67 | 68 | def term(self): 69 | print("Terminating D-810...") 70 | if self.state is not None: 71 | self.state.stop_plugin() 72 | 73 | self.initialized = False 74 | 75 | 76 | def PLUGIN_ENTRY(): 77 | return D810Plugin() 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | # fork from https://gitlab.com/eshard/d810 3 | 4 | ## What is D-810 5 | 6 | D-810 is an IDA Pro plugin which can be used to deobfuscate code at decompilation time by modifying IDA Pro microcode. 7 | It was designed with the following goals in mind: 8 | 9 | * It should have as least as possible impact on our standard reverse engineering workflow 10 | * Fully integrated to IDA Pro 11 | * It should be easily extensible and configurable 12 | * Fast creation of new deobfuscation rules 13 | * Configurable so that we don't have to modify the source code to use rules for a specific project 14 | * Performance impact should be reasonable 15 | * Our goal is to be transparent for the reverse engineer 16 | * But we don't care if the decompilation of a function takes 1 more second if the resulting code is much more simplier. 17 | 18 | 19 | # Installation 20 | 21 | **Only IDA v7.5 or later is supported with Python 3.7 and higher** (since we need the microcode Python API) 22 | 23 | Copy this repository in `.idapro/plugins` 24 | 25 | We recommend to install Z3 to be able to use several features of D-810: 26 | ```bash 27 | pip3 install z3-solver 28 | ``` 29 | 30 | # Using D-810 31 | 32 | * Load the plugin by using the `Ctrl-Shift-D` shortcut, you should see this configuration GUI 33 | 34 | ![](d810/docs/source/images/gui_plugin_configuration.png) 35 | 36 | * Choose or create your project configuration 37 | * If you are not sure what to do here, leave *default_instruction_only.json*. 38 | * Click on the `Start` button to enable deobfuscation 39 | * Decompile an obfuscated function, the code should be simplified (hopefully) 40 | * When you want to disable deobfuscation, just click on the `Stop` button. 41 | 42 | # Warnings 43 | 44 | This plugin is still in early stage of development, so issues ~~may~~ will happen. 45 | 46 | * Modifying incorrectly IDA microcode may lead IDA to crash. We try to detect that as much as possible to avoid crash, but since it may still happen **save you IDA database often** 47 | * We only tested this plugin on Linux, but it should work on Windows too. 48 | 49 | # Documentation 50 | 51 | Work in progress 52 | 53 | Currently, you can read our [blog post](https://eshard.com/posts/) to get some information. 54 | 55 | 56 | # Licenses 57 | 58 | This library is licensed under LGPL V3 license. See the [LICENSE](LICENSE) file for details. 59 | 60 | ## Authors 61 | 62 | See [AUTHORS](AUTHORS.md) for the list of contributors to the project. 63 | 64 | # Acknowledgement 65 | 66 | Rolf Rolles for the huge work he has done with his [HexRaysDeob plugin](https://github.com/RolfRolles/HexRaysDeob) and all the information about Hex-Rays microcode internals described in his [blog post](https://www.hex-rays.com/blog/hex-rays-microcode-api-vs-obfuscating-compiler/). We are still using some part of his plugin in D-810. 67 | 68 | Dennis Elser for the [genmc plugin](https://github.com/patois/genmc) plugin which was very helpful for debugging D-810 errors. 69 | -------------------------------------------------------------------------------- /d810/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhkl0228/d810/680cc531b4eb91f14b2630268a571e7b68550931/d810/__init__.py -------------------------------------------------------------------------------- /d810/conf/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | 5 | class D810Configuration(object): 6 | def __init__(self): 7 | self.config_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) 8 | self.config_file = os.path.join(self.config_dir, "options.json") 9 | with open(self.config_file, "r") as fp: 10 | self._options = json.load(fp) 11 | 12 | def get(self, name): 13 | if (name == "log_dir") and (self._options[name] is None): 14 | return os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..")) 15 | return self._options[name] 16 | 17 | def set(self, name, value): 18 | self._options[name] = value 19 | 20 | def save(self): 21 | with open(self.config_file, "w") as fp: 22 | json.dump(self._options, fp, indent=2) 23 | 24 | 25 | class RuleConfiguration(object): 26 | def __init__(self, name=None, is_activated=False, config=None): 27 | self.name = name 28 | self.is_activated = is_activated 29 | self.config = config if config is not None else {} 30 | 31 | def to_dict(self): 32 | return { 33 | "name": self.name, 34 | "is_activated": self.is_activated, 35 | "config": self.config 36 | } 37 | 38 | @staticmethod 39 | def from_dict(kwargs): 40 | return RuleConfiguration(**kwargs) 41 | 42 | 43 | class ProjectConfiguration(object): 44 | def __init__(self, path=None, description=None, ins_rules=None, blk_rules=None, conf_dir=None): 45 | self.path = path 46 | self.description = description 47 | self.conf_dir = conf_dir 48 | self.ins_rules = [] if ins_rules is None else ins_rules 49 | self.blk_rules = [] if blk_rules is None else blk_rules 50 | self.additional_configuration = {} 51 | 52 | def load(self): 53 | try: 54 | with open(self.path, "r") as fp: 55 | project_conf = json.load(fp) 56 | except FileNotFoundError as e: 57 | if self.conf_dir is not None: 58 | self.path = os.path.join(self.conf_dir, self.path) 59 | with open(self.path, "r") as fp: 60 | project_conf = json.load(fp) 61 | 62 | self.description = project_conf["description"] 63 | self.ins_rules = [RuleConfiguration.from_dict(x) for x in project_conf["ins_rules"]] 64 | self.blk_rules = [RuleConfiguration.from_dict(x) for x in project_conf["blk_rules"]] 65 | 66 | def save(self): 67 | project_conf = { 68 | "description": self.description, 69 | "ins_rules": [x.to_dict() for x in self.ins_rules], 70 | "blk_rules": [x.to_dict() for x in self.blk_rules], 71 | } 72 | with open(self.path, "w") as fp: 73 | json.dump(project_conf, fp, indent=2) 74 | -------------------------------------------------------------------------------- /d810/conf/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "erase_logs_on_reload": true, 3 | "generate_z3_code": true, 4 | "dump_intermediate_microcode": true, 5 | "log_dir": null, 6 | "configurations": [ 7 | "default_instruction_only.json", 8 | "default_unflattening_ollvm.json", 9 | "default_unflattening_switch_case.json", 10 | "example_anel.json", 11 | "example_libobfuscated.json" 12 | ], 13 | "last_project_index": 0 14 | } -------------------------------------------------------------------------------- /d810/docs/source/images/gui_plugin_configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhkl0228/d810/680cc531b4eb91f14b2630268a571e7b68550931/d810/docs/source/images/gui_plugin_configuration.png -------------------------------------------------------------------------------- /d810/errors.py: -------------------------------------------------------------------------------- 1 | class D810Exception(Exception): 2 | pass 3 | 4 | 5 | class AstException(D810Exception): 6 | pass 7 | 8 | 9 | class AstEvaluationException(AstException): 10 | pass 11 | 12 | 13 | class D810Z3Exception(D810Exception): 14 | pass 15 | 16 | 17 | class ControlFlowException(D810Exception): 18 | pass 19 | 20 | 21 | class EmulationException(D810Exception): 22 | pass 23 | 24 | 25 | class EmulationIndirectJumpException(EmulationException): 26 | def __init__(self, message, dest_ea, dest_serial_list): 27 | super().__init__(message) 28 | self.dest_ea = dest_ea 29 | self.dest_serial_list = dest_serial_list 30 | 31 | 32 | class UnresolvedMopException(EmulationException): 33 | pass 34 | 35 | 36 | class WritableMemoryReadException(EmulationException): 37 | pass 38 | 39 | 40 | class UnsupportedInstructionException(EmulationException): 41 | pass 42 | -------------------------------------------------------------------------------- /d810/hexrays_formatters.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | from typing import List 4 | 5 | from d810.hexrays_helpers import OPCODES_INFO, MATURITY_TO_STRING_DICT, STRING_TO_MATURITY_DICT, MOP_TYPE_TO_STRING_DICT 6 | from ida_hexrays import minsn_t, mop_t, vd_printer_t, mbl_array_t 7 | 8 | 9 | logger = logging.getLogger('D810.helper') 10 | 11 | 12 | def format_minsn_t(ins: minsn_t) -> str: 13 | if ins is None: 14 | return "minsn_t is None" 15 | 16 | tmp = ins._print() 17 | pp_ins = "".join([c if 0x20 <= ord(c) <= 0x7e else "" for c in tmp]) 18 | return pp_ins 19 | 20 | 21 | def format_mop_t(mop_in: mop_t) -> str: 22 | if mop_in is None: 23 | return "mop_t is None" 24 | if mop_in.t > 15: 25 | # To avoid error 50581 26 | return "Unknown mop type {0}".format(mop_in.t) 27 | return mop_in.dstr() 28 | 29 | 30 | def format_mop_list(mop_list: List[mop_t]) -> str: 31 | return ", ".join([format_mop_t(x) for x in mop_list]) 32 | 33 | 34 | def maturity_to_string(maturity_level: int) -> str: 35 | return MATURITY_TO_STRING_DICT.get(maturity_level, "Unknown maturity: {0}".format(maturity_level)) 36 | 37 | 38 | def string_to_maturity(maturity_string: str) -> int: 39 | return STRING_TO_MATURITY_DICT.get(maturity_string) 40 | 41 | 42 | def mop_type_to_string(mop_type: int) -> str: 43 | return MOP_TYPE_TO_STRING_DICT.get(mop_type, "Unknown mop type: {0}".format(mop_type)) 44 | 45 | 46 | def opcode_to_string(opcode) -> str: 47 | try: 48 | return OPCODES_INFO[opcode]["name"] 49 | except KeyError: 50 | return "Unknown opcode: {0}".format(opcode) 51 | 52 | 53 | class mba_printer(vd_printer_t): 54 | def __init__(self): 55 | vd_printer_t.__init__(self) 56 | self.mc = [] 57 | 58 | def get_mc(self): 59 | return self.mc 60 | 61 | def _print(self, indent, line): 62 | self.mc.append("".join([c if 0x20 <= ord(c) <= 0x7e else "" for c in line])+"\n") 63 | return 1 64 | 65 | 66 | class block_printer(vd_printer_t): 67 | def __init__(self): 68 | vd_printer_t.__init__(self) 69 | self.block_ins = [] 70 | 71 | def get_block_mc(self): 72 | return "\n".join(self.block_ins) 73 | 74 | def _print(self, indent, line): 75 | self.block_ins.append("".join([c if 0x20 <= ord(c) <= 0x7e else "" for c in line])) 76 | return 1 77 | 78 | 79 | def write_mc_to_file(mba: mbl_array_t, filename: str, mba_flags: int = 0) -> bool: 80 | if not mba: 81 | return False 82 | 83 | vp = mba_printer() 84 | mba.set_mba_flags(mba_flags) 85 | mba._print(vp) 86 | 87 | with open(filename, "w") as f: 88 | f.writelines(vp.get_mc()) 89 | return True 90 | 91 | 92 | def dump_microcode_for_debug(mba: mbl_array_t, log_dir_path: str, name: str = ""): 93 | mc_filename = os.path.join(log_dir_path, "{0:x}_maturity_{1}_{2}.log".format(mba.entry_ea, mba.maturity, name)) 94 | logger.info("Dumping microcode in file {0}...".format(mc_filename)) 95 | write_mc_to_file(mba, mc_filename) 96 | -------------------------------------------------------------------------------- /d810/hexrays_helpers.py: -------------------------------------------------------------------------------- 1 | from ida_hexrays import * 2 | from typing import List, Tuple 3 | from ida_hexrays import mop_d, mop_n, m_stx, m_ldx, m_xdu, m_xds, mop_z, mop_fn, mop_S, mop_v, EQ_IGNSIZE, mop_b, \ 4 | mop_r, mop_f, mop_l, mop_a, mop_h, mop_str, mop_c, mop_p, mop_sc 5 | 6 | 7 | OPCODES_INFO = { 8 | m_nop: {"name": "nop", "nb_operands": 0, "is_commutative": True}, 9 | m_stx: {"name": "stx", "nb_operands": 2, "is_commutative": False}, 10 | m_ldx: {"name": "ldx", "nb_operands": 2, "is_commutative": False}, 11 | m_ldc: {"name": "ldc", "nb_operands": 1, "is_commutative": False}, 12 | m_mov: {"name": "mov", "nb_operands": 1, "is_commutative": False, "symbol": ""}, 13 | m_neg: {"name": "neg", "nb_operands": 1, "is_commutative": False, "symbol": "-"}, 14 | m_lnot: {"name": "lnot", "nb_operands": 1, "is_commutative": False, "symbol": "!"}, 15 | m_bnot: {"name": "bnot", "nb_operands": 1, "is_commutative": False, "symbol": "~"}, 16 | m_xds: {"name": "xds", "nb_operands": 1, "is_commutative": False, "symbol": "xds"}, 17 | m_xdu: {"name": "xdu", "nb_operands": 1, "is_commutative": False, "symbol": "xdu"}, 18 | m_low: {"name": "low", "nb_operands": 1, "is_commutative": False, "symbol": "low"}, 19 | m_high: {"name": "high", "nb_operands": 1, "is_commutative": False, "symbol": "high"}, 20 | m_add: {"name": "add", "nb_operands": 2, "is_commutative": True, "symbol": "+"}, 21 | m_sub: {"name": "sub", "nb_operands": 2, "is_commutative": False, "symbol": "-"}, 22 | m_mul: {"name": "mul", "nb_operands": 2, "is_commutative": True, "symbol": "*"}, 23 | m_udiv: {"name": "udiv", "nb_operands": 2, "is_commutative": False, "symbol": "UDiv"}, 24 | m_sdiv: {"name": "sdiv", "nb_operands": 2, "is_commutative": False, "symbol": "/"}, 25 | m_umod: {"name": "umod", "nb_operands": 2, "is_commutative": False, "symbol": "URem"}, 26 | m_smod: {"name": "smod", "nb_operands": 2, "is_commutative": False, "symbol": "%"}, 27 | m_or: {"name": "or", "nb_operands": 2, "is_commutative": True, "symbol": "|"}, 28 | m_and: {"name": "and", "nb_operands": 2, "is_commutative": True, "symbol": "&"}, 29 | m_xor: {"name": "xor", "nb_operands": 2, "is_commutative": True, "symbol": "^"}, 30 | m_shl: {"name": "shl", "nb_operands": 2, "is_commutative": False, "symbol": "<<"}, 31 | m_shr: {"name": "shr", "nb_operands": 2, "is_commutative": False, "symbol": "LShR"}, 32 | m_sar: {"name": "sar", "nb_operands": 2, "is_commutative": False, "symbol": ">>"}, 33 | m_cfadd: {"name": "cfadd", "nb_operands": 2, "is_commutative": True}, 34 | m_ofadd: {"name": "ofadd", "nb_operands": 2, "is_commutative": True}, 35 | m_cfshl: {"name": "cfshl", "nb_operands": 2, "is_commutative": False}, 36 | m_cfshr: {"name": "cfshr", "nb_operands": 2, "is_commutative": False}, 37 | m_sets: {"name": "sets", "nb_operands": 2, "is_commutative": False}, 38 | m_seto: {"name": "seto", "nb_operands": 2, "is_commutative": False}, 39 | m_setp: {"name": "setp", "nb_operands": 2, "is_commutative": False}, 40 | m_setnz: {"name": "setnz", "nb_operands": 2, "is_commutative": True, "symbol": "!="}, 41 | m_setz: {"name": "setz", "nb_operands": 2, "is_commutative": True, "symbol": "=="}, 42 | m_seta: {"name": "seta", "nb_operands": 2, "is_commutative": False, "symbol": ">"}, 43 | m_setae: {"name": "setae", "nb_operands": 2, "is_commutative": False, "symbol": ">="}, 44 | m_setb: {"name": "setb", "nb_operands": 2, "is_commutative": False, "symbol": "<"}, 45 | m_setbe: {"name": "setbe", "nb_operands": 2, "is_commutative": False, "symbol": "<="}, 46 | m_setg: {"name": "setg", "nb_operands": 2, "is_commutative": False, "symbol": "UGT"}, 47 | m_setge: {"name": "setge", "nb_operands": 2, "is_commutative": False, "symbol": "UGE"}, 48 | m_setl: {"name": "setl", "nb_operands": 2, "is_commutative": False, "symbol": "ULT"}, 49 | m_setle: {"name": "setle", "nb_operands": 2, "is_commutative": False, "symbol": "ULE"}, 50 | m_jcnd: {"name": "jcnd", "nb_operands": 1, "is_commutative": False}, 51 | m_jnz: {"name": "jnz", "nb_operands": 2, "is_commutative": True}, 52 | m_jz: {"name": "jz", "nb_operands": 2, "is_commutative": True}, 53 | m_jae: {"name": "jae", "nb_operands": 2, "is_commutative": False}, 54 | m_jb: {"name": "jb", "nb_operands": 2, "is_commutative": False}, 55 | m_ja: {"name": "ja", "nb_operands": 2, "is_commutative": False}, 56 | m_jbe: {"name": "jbe", "nb_operands": 2, "is_commutative": False}, 57 | m_jg: {"name": "jg", "nb_operands": 2, "is_commutative": False}, 58 | m_jge: {"name": "jge", "nb_operands": 2, "is_commutative": False}, 59 | m_jl: {"name": "jl", "nb_operands": 2, "is_commutative": False}, 60 | m_jle: {"name": "jle", "nb_operands": 2, "is_commutative": False}, 61 | m_jtbl: {"name": "jtbl", "nb_operands": 2, "is_commutative": False}, 62 | m_ijmp: {"name": "ijmp", "nb_operands": 2, "is_commutative": False}, 63 | m_goto: {"name": "goto", "nb_operands": 1, "is_commutative": False}, 64 | m_call: {"name": "call", "nb_operands": 2, "is_commutative": False}, 65 | m_icall: {"name": "icall", "nb_operands": 2, "is_commutative": False}, 66 | m_ret: {"name": "ret", "nb_operands": 0, "is_commutative": False}, 67 | m_push: {"name": "push", "nb_operands": 0, "is_commutative": False}, 68 | m_pop: {"name": "pop", "nb_operands": 0, "is_commutative": False}, 69 | m_und: {"name": "und", "nb_operands": 0, "is_commutative": False}, 70 | m_ext: {"name": "ext", "nb_operands": 0, "is_commutative": False}, 71 | m_f2i: {"name": "f2i", "nb_operands": 2, "is_commutative": False}, 72 | m_f2u: {"name": "f2u", "nb_operands": 2, "is_commutative": False}, 73 | m_i2f: {"name": "i2f", "nb_operands": 2, "is_commutative": False}, 74 | m_u2f: {"name": "u2f", "nb_operands": 2, "is_commutative": False}, 75 | m_f2f: {"name": "f2f", "nb_operands": 2, "is_commutative": False}, 76 | m_fneg: {"name": "fneg", "nb_operands": 2, "is_commutative": False}, 77 | m_fadd: {"name": "fadd", "nb_operands": 2, "is_commutative": True}, 78 | m_fsub: {"name": "fsub", "nb_operands": 2, "is_commutative": False}, 79 | m_fmul: {"name": "fmul", "nb_operands": 2, "is_commutative": True}, 80 | m_fdiv: {"name": "fdiv", "nb_operands": 2, "is_commutative": False}, 81 | } 82 | 83 | 84 | MATURITY_TO_STRING_DICT = { 85 | MMAT_ZERO: "MMAT_ZERO", 86 | MMAT_GENERATED: "MMAT_GENERATED", 87 | MMAT_PREOPTIMIZED: "MMAT_PREOPTIMIZED", 88 | MMAT_LOCOPT: "MMAT_LOCOPT", 89 | MMAT_CALLS: "MMAT_CALLS", 90 | MMAT_GLBOPT1: "MMAT_GLBOPT1", 91 | MMAT_GLBOPT2: "MMAT_GLBOPT2", 92 | MMAT_GLBOPT3: "MMAT_GLBOPT3", 93 | MMAT_LVARS: "MMAT_LVARS", 94 | } 95 | STRING_TO_MATURITY_DICT = {v: k for k, v in MATURITY_TO_STRING_DICT.items()} 96 | 97 | MOP_TYPE_TO_STRING_DICT = { 98 | mop_z: "mop_z", 99 | mop_r: "mop_r", 100 | mop_n: "mop_n", 101 | mop_str: "mop_str", 102 | mop_d: "mop_d", 103 | mop_S: "mop_S", 104 | mop_v: "mop_v", 105 | mop_b: "mop_b", 106 | mop_f: "mop_f", 107 | mop_l: "mop_l", 108 | mop_a: "mop_a", 109 | mop_h: "mop_h", 110 | mop_c: "mop_c", 111 | mop_fn: "mop_fn", 112 | mop_p: "mop_p", 113 | mop_sc: "mop_sc", 114 | } 115 | 116 | Z3_SPECIAL_OPERANDS = ["UDiv", "URem", "LShR", "UGT", "UGE", "ULT", "ULE"] 117 | 118 | BOOLEAN_OPCODES = [m_lnot, m_bnot, m_or, m_and, m_xor] 119 | ARITHMETICAL_OPCODES = [m_neg, m_add, m_sub, m_mul, m_udiv, m_sdiv, m_umod, m_smod] 120 | BIT_OPERATIONS_OPCODES = [m_shl, m_shr, m_sar, m_mov, m_xds, m_xdu, m_low, m_high] 121 | CHECK_OPCODES = [m_sets, m_seto, m_setp, m_setnz, m_setz, m_seta, m_setae, m_setb, 122 | m_setbe, m_setg, m_setge, m_setl, m_setle] 123 | 124 | MBA_RELATED_OPCODES = BOOLEAN_OPCODES + ARITHMETICAL_OPCODES + BIT_OPERATIONS_OPCODES + CHECK_OPCODES 125 | 126 | CONDITIONAL_JUMP_OPCODES = [m_jcnd, m_jnz, m_jz, m_jae, m_ja, m_jb, m_jbe, m_jg, m_jge, m_jl, m_jle, m_jtbl] 127 | UNCONDITIONAL_JUMP_OPCODES = [m_goto, m_ijmp] 128 | CONTROL_FLOW_OPCODES = CONDITIONAL_JUMP_OPCODES + UNCONDITIONAL_JUMP_OPCODES 129 | 130 | MINSN_TO_AST_FORBIDDEN_OPCODES = CONTROL_FLOW_OPCODES + [m_ret, m_nop, m_stx, m_push, m_pop, m_und, m_ext, m_call] 131 | 132 | SUB_TABLE = {1: 0x100, 2: 0x10000, 4: 0x100000000, 8: 0x10000000000000000, 16: 0x100000000000000000000000000000000} 133 | AND_TABLE = {1: 0xff, 2: 0xffff, 4: 0xffffffff, 8: 0xffffffffffffffff, 16: 0xffffffffffffffffffffffffffffffff} 134 | MSB_TABLE = {1: 0x80, 2: 0x8000, 4: 0x80000000, 8: 0x8000000000000000, 16: 0x80000000000000000000000000000000} 135 | 136 | 137 | # Hex-Rays mop equality checking 138 | def equal_bnot_cst(lo: mop_t, ro: mop_t, mop_size=None) -> bool: 139 | if (lo.t != mop_n) or (ro.t != mop_n): 140 | return False 141 | if lo.size != ro.size: 142 | return False 143 | if mop_size is None: 144 | mop_size = lo.size 145 | return lo.nnn.value ^ ro.nnn.value == AND_TABLE[mop_size] 146 | 147 | 148 | def equal_bnot_mop(lo: mop_t, ro: mop_t, test_two_sides=True) -> bool: 149 | if lo.t == mop_n: 150 | return equal_bnot_cst(lo, ro) 151 | 152 | # We first check for a bnot operand 153 | if (lo.t == mop_d) and lo.d.opcode == m_bnot: 154 | if equal_mops_ignore_size(lo.d.l, ro): 155 | return True 156 | 157 | # Otherwise Hexrays may have optimized using ~(-x) = x - 1 158 | if (lo.t == mop_d) and lo.d.opcode == m_neg: 159 | if (ro.t == mop_d) and ro.d.opcode == m_sub: 160 | if ro.d.r.t == mop_n and ro.d.r.nnn.value == 1: 161 | if equal_mops_ignore_size(ro.d.l, lo.d.l): 162 | return True 163 | 164 | if (lo.t == mop_d) and lo.d.opcode == m_xds: 165 | if equal_bnot_mop(lo.d.l, ro): 166 | return True 167 | 168 | if test_two_sides: 169 | return equal_bnot_mop(ro, lo, test_two_sides=False) 170 | return False 171 | 172 | 173 | def equal_ignore_msb_cst(lo: mop_t, ro: mop_t) -> bool: 174 | if (lo.t != mop_n) or (ro.t != mop_n): 175 | return False 176 | if lo.size != ro.size: 177 | return False 178 | mask = AND_TABLE[lo.size] ^ MSB_TABLE[lo.size] 179 | return lo.nnn.value & mask == ro.nnn.value & mask 180 | 181 | 182 | def equal_mops_bypass_xdu(lo: mop_t, ro: mop_t) -> bool: 183 | if (lo is None) or (ro is None): 184 | return False 185 | if (lo.t == mop_d) and (lo.d.opcode == m_xdu): 186 | return equal_mops_bypass_xdu(lo.d.l, ro) 187 | if (ro.t == mop_d) and (ro.d.opcode == m_xdu): 188 | return equal_mops_bypass_xdu(lo, ro.d.l) 189 | return equal_mops_ignore_size(lo, ro) 190 | 191 | 192 | def equal_mops_ignore_size(lo: mop_t, ro: mop_t) -> bool: 193 | if (lo is None) or (ro is None): 194 | return False 195 | if lo.t != ro.t: 196 | return False 197 | if lo.t == mop_z: 198 | return True 199 | elif lo.t == mop_fn: 200 | return lo.fpc == ro.fpc 201 | elif lo.t == mop_n: 202 | return lo.nnn.value == ro.nnn.value 203 | elif lo.t == mop_S: 204 | if lo.s == ro.s: 205 | return True 206 | if lo.s.off == ro.s.off: 207 | # Is it right? 208 | return True 209 | return False 210 | elif lo.t == mop_v: 211 | return lo.g == ro.g 212 | elif lo.t == mop_d: 213 | return lo.d.equal_insns(ro.d, EQ_IGNSIZE) 214 | # return lo.d.equal_insns(ro.d, EQ_IGNSIZE | EQ_IGNCODE) 215 | elif lo.t == mop_b: 216 | return lo.b == ro.b 217 | elif lo.t == mop_r: 218 | return lo.r == ro.r 219 | elif lo.t == mop_f: 220 | return False 221 | elif lo.t == mop_l: 222 | return lo.l == ro.l 223 | elif lo.t == mop_a: 224 | if lo.a.insize != ro.a.insize: 225 | return False 226 | if lo.a.outsize != ro.a.outsize: 227 | return False 228 | return equal_mops_ignore_size(lo.a, ro.a) 229 | elif lo.t == mop_h: 230 | return ro.helper == lo.helper 231 | elif lo.t == mop_str: 232 | return ro.cstr == lo.cstr 233 | elif lo.t == mop_c: 234 | return ro.c == lo.c 235 | elif lo.t == mop_p: 236 | return equal_mops_ignore_size(lo.pair.lop, ro.pair.lop) and equal_mops_ignore_size(lo.pair.hop, ro.pair.hop) 237 | elif lo.t == mop_sc: 238 | return False 239 | else: 240 | return False 241 | 242 | 243 | def is_check_mop(lo: mop_t) -> bool: 244 | if lo.t != mop_d: 245 | return False 246 | if lo.d.opcode in CHECK_OPCODES: 247 | return True 248 | if lo.d.opcode in [m_xds, m_xdu]: 249 | return is_check_mop(lo.d.l) 250 | return False 251 | 252 | 253 | def extract_num_mop(ins: minsn_t) -> Tuple[mop_t, mop_t]: 254 | num_mop = None 255 | other_mop = None 256 | 257 | if ins.l.t == mop_n: 258 | num_mop = ins.l 259 | other_mop = ins.r 260 | if ins.r.t == mop_n: 261 | num_mop = ins.r 262 | other_mop = ins.l 263 | return [num_mop, other_mop] 264 | 265 | 266 | def check_ins_mop_size_are_ok(ins: minsn_t) -> bool: 267 | """ 268 | This function can be used to check if a created instruction has consistent mop size 269 | Use it to avoid Hex-Rays decompilation errors when replacing instructions 270 | 271 | :param ins: 272 | :return: 273 | """ 274 | ins_dest_size = ins.d.size 275 | if ins.opcode in [m_stx, m_ldx]: 276 | if ins.r.t == mop_d: 277 | if not check_ins_mop_size_are_ok(ins.r.d): 278 | return False 279 | return True 280 | 281 | if ins.opcode in [m_xdu, m_xds, m_low, m_high]: 282 | if (ins.l.t == mop_d) and (not check_ins_mop_size_are_ok(ins.l.d)): 283 | return False 284 | return True 285 | 286 | if ins.opcode in [m_sar, m_shr, m_shl]: 287 | if ins.l.size != ins_dest_size: 288 | return False 289 | if (ins.l.t == mop_d) and (not check_ins_mop_size_are_ok(ins.l.d)): 290 | return False 291 | if (ins.r.t == mop_d) and (not check_ins_mop_size_are_ok(ins.r.d)): 292 | return False 293 | return True 294 | 295 | if ins.opcode in CHECK_OPCODES: 296 | if (ins.l.t == mop_d) and (not check_ins_mop_size_are_ok(ins.l.d)): 297 | return False 298 | if (ins.r.t == mop_d) and (not check_ins_mop_size_are_ok(ins.r.d)): 299 | return False 300 | return True 301 | 302 | if ins.l is not None: 303 | if ins.l.size != ins_dest_size: 304 | return False 305 | if ins.l.t == mop_d and (not check_ins_mop_size_are_ok(ins.l.d)): 306 | return False 307 | 308 | if ins.r is not None and ins.r.t != mop_z: 309 | if ins.r.size != ins_dest_size: 310 | return False 311 | if ins.r.t == mop_d and (not check_ins_mop_size_are_ok(ins.r.d)): 312 | return False 313 | return True 314 | 315 | 316 | def check_mop_is_result_of(lo: mop_t, mc) -> bool: 317 | if lo.t != mop_d: 318 | return False 319 | return lo.d.opcode == mc 320 | 321 | 322 | def extract_by_opcode_type(ins: minsn_t, mc) -> Tuple[mop_t, mop_t]: 323 | if check_mop_is_result_of(ins.l, mc): 324 | return [ins.l, ins.r] 325 | if check_mop_is_result_of(ins.r, mc): 326 | return [ins.r, ins.l] 327 | return [None, None] 328 | 329 | 330 | def check_ins_have_same_operands(ins1: minsn_t, ins2: minsn_t, ignore_order=False) -> bool: 331 | if equal_mops_ignore_size(ins1.l, ins2.l) and equal_mops_ignore_size(ins1.r, ins2.r): 332 | return True 333 | if not ignore_order: 334 | return False 335 | return equal_mops_ignore_size(ins1.l, ins2.r) and equal_mops_ignore_size(ins1.r, ins2.l) 336 | 337 | 338 | def get_mop_index(searched_mop: mop_t, mop_list) -> int: 339 | for i, test_mop in enumerate(mop_list): 340 | if equal_mops_ignore_size(searched_mop, test_mop): 341 | return i 342 | return -1 343 | 344 | 345 | def append_mop_if_not_in_list(mop: mop_t, mop_list) -> bool: 346 | mop_index = get_mop_index(mop, mop_list) 347 | if mop_index == -1: 348 | mop_list.append(mop) 349 | return True 350 | return False 351 | 352 | 353 | def get_blk_index(searched_blk: mblock_t, blk_list: List[mblock_t]) -> int: 354 | blk_serial_list = [blk.serial for blk in blk_list] 355 | try: 356 | return blk_serial_list.index(searched_blk.serial) 357 | except ValueError: 358 | return -1 359 | -------------------------------------------------------------------------------- /d810/hexrays_hooks.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import logging 3 | 4 | from ida_hexrays import * 5 | 6 | from d810.optimizers.instructions import PatternOptimizer, ChainOptimizer, Z3Optimizer, EarlyOptimizer, \ 7 | InstructionAnalyzer 8 | from d810.hexrays_helpers import check_ins_mop_size_are_ok, append_mop_if_not_in_list 9 | from d810.hexrays_formatters import format_minsn_t, format_mop_t, maturity_to_string, mop_type_to_string, \ 10 | dump_microcode_for_debug 11 | from d810.errors import D810Exception 12 | from d810.z3_utils import log_z3_instructions 13 | 14 | from typing import TYPE_CHECKING, List 15 | if TYPE_CHECKING: 16 | from d810.manager import D810Manager 17 | from d810.optimizers.instructions.handler import InstructionOptimizer, InstructionOptimizationRule 18 | from d810.optimizers.flow.handler import FlowOptimizationRule 19 | 20 | main_logger = logging.getLogger('D810') 21 | optimizer_logger = logging.getLogger('D810.optimizer') 22 | helper_logger = logging.getLogger('D810.helper') 23 | 24 | DEFAULT_OPTIMIZATION_PATTERN_MATURITIES = [MMAT_PREOPTIMIZED, MMAT_LOCOPT, MMAT_CALLS, MMAT_GLBOPT1] 25 | DEFAULT_OPTIMIZATION_CHAIN_MATURITIES = [MMAT_PREOPTIMIZED, MMAT_LOCOPT, MMAT_CALLS, MMAT_GLBOPT1] 26 | DEFAULT_OPTIMIZATION_Z3_MATURITIES = [MMAT_LOCOPT, MMAT_CALLS, MMAT_GLBOPT1] 27 | DEFAULT_OPTIMIZATION_EARLY_MATURITIES = [MMAT_GENERATED, MMAT_PREOPTIMIZED] 28 | DEFAULT_ANALYZER_MATURITIES = [MMAT_PREOPTIMIZED, MMAT_LOCOPT, MMAT_CALLS, MMAT_GLBOPT1] 29 | 30 | 31 | class InstructionDefUseCollector(mop_visitor_t): 32 | def __init__(self): 33 | super().__init__() 34 | self.unresolved_ins_mops = [] 35 | self.memory_unresolved_ins_mops = [] 36 | self.target_mops = [] 37 | 38 | def visit_mop(self, op: mop_t, op_type: int, is_target: bool): 39 | if is_target: 40 | append_mop_if_not_in_list(op, self.target_mops) 41 | else: 42 | # TODO whatever the case, in the end we will always return 0. May be this code can be better optimized. 43 | # TODO handle other special case (e.g. ldx ins, ...) 44 | if op.t == mop_S: 45 | append_mop_if_not_in_list(op, self.unresolved_ins_mops) 46 | elif op.t == mop_r: 47 | append_mop_if_not_in_list(op, self.unresolved_ins_mops) 48 | elif op.t == mop_v: 49 | append_mop_if_not_in_list(op, self.memory_unresolved_ins_mops) 50 | elif op.t == mop_a: 51 | if op.a.t == mop_v: 52 | return 0 53 | elif op.a.t == mop_S: 54 | return 0 55 | helper_logger.warning("Calling visit_mop with unsupported mop type {0} - {1}: '{2}'" 56 | .format(mop_type_to_string(op.t), mop_type_to_string(op.a.t), format_mop_t(op))) 57 | return 0 58 | elif op.t == mop_n: 59 | return 0 60 | elif op.t == mop_d: 61 | return 0 62 | elif op.t == mop_h: 63 | return 0 64 | elif op.t == mop_b: 65 | return 0 66 | else: 67 | helper_logger.warning("Calling visit_mop with unsupported mop type {0}: '{1}'" 68 | .format(mop_type_to_string(op.t), format_mop_t(op))) 69 | return 0 70 | 71 | 72 | class InstructionOptimizerManager(optinsn_t): 73 | def __init__(self, manager: D810Manager): 74 | optimizer_logger.debug("Initializing {0}...".format(self.__class__.__name__)) 75 | super().__init__() 76 | self.manager = manager 77 | self.instruction_visitor = InstructionVisitorManager(self) 78 | self._last_optimizer_tried = None 79 | self.current_maturity = None 80 | self.current_blk_serial = None 81 | self.generate_z3_code = False 82 | self.dump_intermediate_microcode = False 83 | 84 | self.instruction_optimizers = [] 85 | self.optimizer_usage_info = {} 86 | self.add_optimizer(PatternOptimizer(DEFAULT_OPTIMIZATION_PATTERN_MATURITIES, log_dir=self.manager.log_dir)) 87 | self.add_optimizer(ChainOptimizer(DEFAULT_OPTIMIZATION_CHAIN_MATURITIES, log_dir=self.manager.log_dir)) 88 | self.add_optimizer(Z3Optimizer(DEFAULT_OPTIMIZATION_Z3_MATURITIES, log_dir=self.manager.log_dir)) 89 | self.add_optimizer(EarlyOptimizer(DEFAULT_OPTIMIZATION_EARLY_MATURITIES, log_dir=self.manager.log_dir)) 90 | self.analyzer = InstructionAnalyzer(DEFAULT_ANALYZER_MATURITIES, log_dir=self.manager.log_dir) 91 | 92 | def func(self, blk: mblock_t, ins: minsn_t) -> bool: 93 | self.log_info_on_input(blk, ins) 94 | try: 95 | optimization_performed = self.optimize(blk, ins) 96 | 97 | if not optimization_performed: 98 | optimization_performed = ins.for_all_insns(self.instruction_visitor) 99 | 100 | if optimization_performed: 101 | ins.optimize_solo() 102 | 103 | if blk is not None: 104 | blk.mark_lists_dirty() 105 | blk.mba.verify(True) 106 | 107 | return optimization_performed 108 | except RuntimeError as e: 109 | optimizer_logger.error("RuntimeError while optimizing ins {0} with {1}: {2}" 110 | .format(format_minsn_t(ins), self._last_optimizer_tried, e)) 111 | except D810Exception as e: 112 | optimizer_logger.error("D810Exception while optimizing ins {0} with {1}: {2}" 113 | .format(format_minsn_t(ins), self._last_optimizer_tried, e)) 114 | return False 115 | 116 | def reset_rule_usage_statistic(self): 117 | self.optimizer_usage_info = {} 118 | for ins_optimizer in self.instruction_optimizers: 119 | self.optimizer_usage_info[ins_optimizer.name] = 0 120 | ins_optimizer.reset_rule_usage_statistic() 121 | 122 | def show_rule_usage_statistic(self): 123 | for optimizer_name, optimizer_nb_match in self.optimizer_usage_info.items(): 124 | if optimizer_nb_match > 0: 125 | main_logger.info("Instruction optimizer '{0}' has been used {1} times" 126 | .format(optimizer_name, optimizer_nb_match)) 127 | for ins_optimizer in self.instruction_optimizers: 128 | ins_optimizer.show_rule_usage_statistic() 129 | 130 | def log_info_on_input(self, blk: mblock_t, ins: minsn_t): 131 | if blk is None: 132 | return 133 | mba: mbl_array_t = blk.mba 134 | 135 | if (mba is not None) and (mba.maturity != self.current_maturity): 136 | self.current_maturity = mba.maturity 137 | main_logger.debug("Instruction optimization function called at maturity: {0}" 138 | .format(maturity_to_string(self.current_maturity))) 139 | self.analyzer.set_maturity(self.current_maturity) 140 | self.current_blk_serial = None 141 | 142 | for ins_optimizer in self.instruction_optimizers: 143 | ins_optimizer.cur_maturity = self.current_maturity 144 | 145 | if self.dump_intermediate_microcode: 146 | dump_microcode_for_debug(mba, self.manager.log_dir, "input_instruction_optimizer") 147 | 148 | if blk.serial != self.current_blk_serial: 149 | self.current_blk_serial = blk.serial 150 | 151 | def add_optimizer(self, optimizer: InstructionOptimizer): 152 | self.instruction_optimizers.append(optimizer) 153 | self.optimizer_usage_info[optimizer.name] = 0 154 | 155 | def add_rule(self, rule: InstructionOptimizationRule): 156 | # optimizer_log.info("Trying to add rule {0}".format(rule)) 157 | for ins_optimizer in self.instruction_optimizers: 158 | ins_optimizer.add_rule(rule) 159 | self.analyzer.add_rule(rule) 160 | 161 | def configure(self, generate_z3_code=False, dump_intermediate_microcode=False, **kwargs): 162 | self.generate_z3_code = generate_z3_code 163 | self.dump_intermediate_microcode = dump_intermediate_microcode 164 | 165 | def optimize(self, blk: mblock_t, ins: minsn_t) -> bool: 166 | # optimizer_log.info("Trying to optimize {0}".format(format_minsn_t(ins))) 167 | for ins_optimizer in self.instruction_optimizers: 168 | self._last_optimizer_tried = ins_optimizer 169 | new_ins = ins_optimizer.get_optimized_instruction(blk, ins) 170 | 171 | if new_ins is not None: 172 | if not check_ins_mop_size_are_ok(new_ins): 173 | if check_ins_mop_size_are_ok(ins): 174 | main_logger.error("Invalid optimized instruction: {0} (original was {1})".format( 175 | format_minsn_t(new_ins), format_minsn_t(ins))) 176 | else: 177 | main_logger.error("Invalid original instruction : {0} (original was {1})".format( 178 | format_minsn_t(new_ins), format_minsn_t(ins))) 179 | else: 180 | ins.swap(new_ins) 181 | self.optimizer_usage_info[ins_optimizer.name] += 1 182 | if self.generate_z3_code: 183 | try: 184 | log_z3_instructions(new_ins, ins) 185 | except KeyError: 186 | pass 187 | return True 188 | 189 | self.analyzer.analyze(blk, ins) 190 | return False 191 | 192 | 193 | class InstructionVisitorManager(minsn_visitor_t): 194 | def __init__(self, optimizer: InstructionOptimizerManager): 195 | optimizer_logger.debug("Initializing {0}...".format(self.__class__.__name__)) 196 | super().__init__() 197 | self.instruction_optimizer = optimizer 198 | 199 | def visit_minsn(self) -> bool: 200 | return self.instruction_optimizer.optimize(self.blk, self.curins) 201 | 202 | 203 | class BlockOptimizerManager(optblock_t): 204 | def __init__(self, manager: D810Manager): 205 | optimizer_logger.debug("Initializing {0}...".format(self.__class__.__name__)) 206 | super().__init__() 207 | self.manager = manager 208 | self.cfg_rules = set() 209 | 210 | self.current_maturity = None 211 | self.cfg_rules_usage_info = {} 212 | 213 | def func(self, blk: mblock_t): 214 | self.log_info_on_input(blk) 215 | nb_patch = self.optimize(blk) 216 | return nb_patch 217 | 218 | def reset_rule_usage_statistic(self): 219 | self.cfg_rules_usage_info = {} 220 | for rule in self.cfg_rules: 221 | self.cfg_rules_usage_info[rule.name] = [] 222 | 223 | def show_rule_usage_statistic(self): 224 | for rule_name, rule_nb_patch_list in self.cfg_rules_usage_info.items(): 225 | nb_use = len(rule_nb_patch_list) 226 | if nb_use > 0: 227 | main_logger.info("BlkRule '{0}' has been used {1} times for a total of {2} patches" 228 | .format(rule_name, nb_use, sum(rule_nb_patch_list))) 229 | 230 | def log_info_on_input(self, blk: mblock_t): 231 | if blk is None: 232 | return 233 | mba: mbl_array_t = blk.mba 234 | 235 | if (mba is not None) and (mba.maturity != self.current_maturity): 236 | main_logger.debug("BlockOptimizer called at maturity: {0}".format(maturity_to_string(mba.maturity))) 237 | self.current_maturity = mba.maturity 238 | 239 | def optimize(self, blk: mblock_t): 240 | for cfg_rule in self.cfg_rules: 241 | if self.check_if_rule_is_activated_for_address(cfg_rule, blk.mba.entry_ea): 242 | nb_patch = cfg_rule.optimize(blk) 243 | if nb_patch > 0: 244 | optimizer_logger.info("Rule {0} matched: {1} patches".format(cfg_rule.name, nb_patch)) 245 | self.cfg_rules_usage_info[cfg_rule.name].append(nb_patch) 246 | return nb_patch 247 | return 0 248 | 249 | def add_rule(self, cfg_rule: FlowOptimizationRule): 250 | optimizer_logger.info("Adding cfg rule {0}".format(cfg_rule)) 251 | self.cfg_rules.add(cfg_rule) 252 | self.cfg_rules_usage_info[cfg_rule.name] = [] 253 | 254 | def configure(self, **kwargs): 255 | pass 256 | 257 | def check_if_rule_is_activated_for_address(self, cfg_rule: FlowOptimizationRule, func_entry_ea: int): 258 | if cfg_rule.use_whitelist and (func_entry_ea not in cfg_rule.whitelisted_function_ea_list): 259 | return False 260 | if cfg_rule.use_blacklist and (func_entry_ea in cfg_rule.blacklisted_function_ea_list): 261 | return False 262 | return True 263 | 264 | 265 | class HexraysDecompilationHook(Hexrays_Hooks): 266 | def __init__(self, manager): 267 | super().__init__() 268 | self.manager = manager 269 | 270 | def prolog(self, mba: mbl_array_t, fc, reachable_blocks, decomp_flags) -> "int": 271 | main_logger.info("Starting decompilation of function at 0x{0:x}".format(mba.entry_ea)) 272 | self.manager.instruction_optimizer.reset_rule_usage_statistic() 273 | self.manager.block_optimizer.reset_rule_usage_statistic() 274 | return 0 275 | 276 | def glbopt(self, mba: mbl_array_t) -> "int": 277 | main_logger.info("glbopt finished for function at 0x{0:x}".format(mba.entry_ea)) 278 | self.manager.instruction_optimizer.show_rule_usage_statistic() 279 | self.manager.block_optimizer.show_rule_usage_statistic() 280 | return 0 281 | -------------------------------------------------------------------------------- /d810/log.ini: -------------------------------------------------------------------------------- 1 | [loggers] 2 | keys=root,D810,D810Ui,D810Optimizer,D810RulesChain,D810PatternSearch,D810BranchFixer,D810Unflat,D810Tracker,D810Emulator,D810Helper,D810Z3Test 3 | 4 | [handlers] 5 | keys=consoleHandler,defaultFileHandler,z3FileHandler 6 | 7 | [formatters] 8 | keys=defaultFormatter,rawFormatter 9 | 10 | [logger_root] 11 | level=DEBUG 12 | handlers=consoleHandler 13 | 14 | [logger_D810] 15 | level=DEBUG 16 | handlers=consoleHandler,defaultFileHandler 17 | qualname=D810 18 | propagate=0 19 | 20 | [logger_D810Ui] 21 | level=ERROR 22 | handlers=defaultFileHandler 23 | qualname=D810.ui 24 | propagate=0 25 | 26 | [logger_D810Optimizer] 27 | level=INFO 28 | handlers=defaultFileHandler 29 | qualname=D810.optimizer 30 | propagate=0 31 | 32 | [logger_D810RulesChain] 33 | level=INFO 34 | handlers=defaultFileHandler 35 | qualname=D810.chain 36 | propagate=0 37 | 38 | [logger_D810BranchFixer] 39 | level=INFO 40 | handlers=defaultFileHandler 41 | qualname=D810.branch_fixer 42 | propagate=0 43 | 44 | [logger_D810Unflat] 45 | level=INFO 46 | handlers=defaultFileHandler 47 | qualname=D810.unflat 48 | propagate=0 49 | 50 | [logger_D810Tracker] 51 | level=INFO 52 | handlers=defaultFileHandler 53 | qualname=D810.tracker 54 | propagate=0 55 | 56 | [logger_D810Emulator] 57 | level=WARNING 58 | handlers=defaultFileHandler 59 | qualname=D810.emulator 60 | propagate=0 61 | 62 | [logger_D810Helper] 63 | level=INFO 64 | handlers=defaultFileHandler 65 | qualname=D810.helper 66 | propagate=0 67 | 68 | [logger_D810PatternSearch] 69 | level=ERROR 70 | handlers=defaultFileHandler 71 | qualname=D810.pattern_search 72 | propagate=0 73 | 74 | [logger_D810Z3Test] 75 | level=INFO 76 | handlers=z3FileHandler 77 | qualname=D810.z3_test 78 | propagate=0 79 | 80 | [handler_consoleHandler] 81 | class=StreamHandler 82 | level=INFO 83 | formatter=defaultFormatter 84 | args=(sys.stdout,) 85 | 86 | [handler_defaultFileHandler] 87 | class=FileHandler 88 | level=DEBUG 89 | formatter=defaultFormatter 90 | args=('%(default_log_filename)s',) 91 | 92 | [handler_z3FileHandler] 93 | class=FileHandler 94 | level=DEBUG 95 | formatter=rawFormatter 96 | args=('%(z3_log_filename)s',) 97 | 98 | [formatter_defaultFormatter] 99 | format=%(asctime)s - %(name)s - %(levelname)s - %(message)s 100 | 101 | [formatter_rawFormatter] 102 | format=%(message)s -------------------------------------------------------------------------------- /d810/log.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import logging 4 | import logging.config 5 | from pathlib import Path 6 | 7 | LOG_CONFIG_FILENAME = "log.ini" 8 | LOG_FILENAME = "d810.log" 9 | Z3_TEST_FILENAME = "z3_check_instructions_substitution.py" 10 | 11 | 12 | def clear_logs(log_dir): 13 | shutil.rmtree(log_dir, ignore_errors=True) 14 | 15 | 16 | def configure_loggers(log_dir): 17 | os.makedirs(log_dir, exist_ok=True) 18 | log_main_file = Path(log_dir) / LOG_FILENAME 19 | z3_test_file = Path(log_dir) / Z3_TEST_FILENAME 20 | log_conf_file = Path(__file__).resolve().parent / LOG_CONFIG_FILENAME 21 | logging.config.fileConfig(log_conf_file.as_posix(), defaults={"default_log_filename": log_main_file.as_posix(), 22 | "z3_log_filename": z3_test_file.as_posix()}) 23 | z3_file_logger = logging.getLogger('D810.z3_test') 24 | z3_file_logger.info("from z3 import BitVec, BitVecVal, UDiv, URem, LShR, UGT, UGE, ULT, ULE, prove\n\n") 25 | -------------------------------------------------------------------------------- /d810/manager.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import os 3 | import json 4 | import logging 5 | import idaapi 6 | 7 | from typing import TYPE_CHECKING, List 8 | if TYPE_CHECKING: 9 | from d810.conf import D810Configuration, ProjectConfiguration 10 | 11 | 12 | # Note that imports are performed directly in the functions so that they are reloaded each time the plugin is restarted 13 | # This allow to load change code/drop new rules without having to reboot IDA 14 | d810_state = None 15 | 16 | D810_LOG_DIR_NAME = "d810_logs" 17 | 18 | MANAGER_INFO_FILENAME = "manager_info.json" 19 | logger = logging.getLogger('D810') 20 | 21 | 22 | def reload_all_modules(): 23 | manager_info_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), MANAGER_INFO_FILENAME) 24 | 25 | with open(manager_info_path, "r") as f: 26 | manager_info = json.load(f) 27 | 28 | for module_name in manager_info["module_list"]: 29 | idaapi.require(module_name) 30 | 31 | 32 | class D810Manager(object): 33 | def __init__(self, log_dir): 34 | self.instruction_optimizer_rules = [] 35 | self.instruction_optimizer_config = {} 36 | self.block_optimizer_rules = [] 37 | self.block_optimizer_config = {} 38 | self.instruction_optimizer = None 39 | self.block_optimizer = None 40 | self.hx_decompiler_hook = None 41 | self.log_dir = log_dir 42 | self.config = {} 43 | 44 | def configure(self, **kwargs): 45 | self.config = kwargs 46 | 47 | def reload(self): 48 | self.stop() 49 | logger.debug("Reloading manager...") 50 | 51 | from d810.hexrays_hooks import InstructionOptimizerManager, BlockOptimizerManager, HexraysDecompilationHook 52 | 53 | self.instruction_optimizer = InstructionOptimizerManager(self) 54 | self.instruction_optimizer.configure(**self.instruction_optimizer_config) 55 | self.block_optimizer = BlockOptimizerManager(self) 56 | self.block_optimizer.configure(**self.block_optimizer_config) 57 | 58 | for rule in self.instruction_optimizer_rules: 59 | rule.log_dir = self.log_dir 60 | self.instruction_optimizer.add_rule(rule) 61 | 62 | for cfg_rule in self.block_optimizer_rules: 63 | cfg_rule.log_dir = self.log_dir 64 | self.block_optimizer.add_rule(cfg_rule) 65 | 66 | self.instruction_optimizer.install() 67 | self.block_optimizer.install() 68 | 69 | self.hx_decompiler_hook = HexraysDecompilationHook(self) 70 | self.hx_decompiler_hook.hook() 71 | 72 | def configure_instruction_optimizer(self, rules, **kwargs): 73 | self.instruction_optimizer_rules = [rule for rule in rules] 74 | self.instruction_optimizer_config = kwargs 75 | 76 | def configure_block_optimizer(self, rules, **kwargs): 77 | self.block_optimizer_rules = [rule for rule in rules] 78 | self.block_optimizer_config = kwargs 79 | 80 | def stop(self): 81 | if self.instruction_optimizer is not None: 82 | logger.debug("Removing InstructionOptimizer...") 83 | self.instruction_optimizer.remove() 84 | self.instruction_optimizer = None 85 | if self.block_optimizer is not None: 86 | logger.debug("Removing ControlFlowFixer...") 87 | self.block_optimizer.remove() 88 | self.block_optimizer = None 89 | if self.hx_decompiler_hook is not None: 90 | logger.debug("Removing HexraysDecompilationHook...") 91 | self.hx_decompiler_hook.unhook() 92 | self.hx_decompiler_hook = None 93 | 94 | 95 | class D810State(object): 96 | def __init__(self, d810_config: D810Configuration): 97 | # For debugging purposes, to interact with this object from the console 98 | # Type in IDA Python shell 'from d810.manager import d810_state' to access it 99 | global d810_state 100 | d810_state = self 101 | reload_all_modules() 102 | 103 | self.d810_config = d810_config 104 | self.log_dir = os.path.join(self.d810_config.get("log_dir"), D810_LOG_DIR_NAME) 105 | self.manager = D810Manager(self.log_dir) 106 | 107 | from d810.optimizers.instructions import KNOWN_INS_RULES 108 | from d810.optimizers.flow import KNOWN_BLK_RULES 109 | self.known_ins_rules = [x for x in KNOWN_INS_RULES] 110 | self.known_blk_rules = [x for x in KNOWN_BLK_RULES] 111 | 112 | self.gui = None 113 | self.current_project = None 114 | self.projects: List[ProjectConfiguration] = [] 115 | self.current_project_index = self.d810_config.get("last_project_index") 116 | self.current_ins_rules = [] 117 | self.current_blk_rules = [] 118 | 119 | self.register_default_projects() 120 | self.load_project(self.current_project_index) 121 | 122 | def register_default_projects(self): 123 | from d810.conf import ProjectConfiguration 124 | self.projects = [] 125 | for project_configuration_path in self.d810_config.get("configurations"): 126 | project_configuration = ProjectConfiguration(project_configuration_path, 127 | conf_dir=self.d810_config.config_dir) 128 | project_configuration.load() 129 | self.projects.append(project_configuration) 130 | logger.debug("Rule configurations loaded: {0}".format(self.projects)) 131 | 132 | def add_project(self, config: ProjectConfiguration): 133 | self.projects.append(config) 134 | self.d810_config.get("configurations").append(config.path) 135 | self.d810_config.save() 136 | 137 | def update_project(self, old_config: ProjectConfiguration, new_config: ProjectConfiguration): 138 | old_config_index = self.projects.index(old_config) 139 | self.projects[old_config_index] = new_config 140 | 141 | def del_project(self, config: ProjectConfiguration): 142 | self.projects.remove(config) 143 | self.d810_config.get("configurations").remove(config.path) 144 | self.d810_config.save() 145 | os.remove(config.path) 146 | 147 | def load_project(self, project_index: int): 148 | self.current_project_index = project_index 149 | self.current_project = self.projects[project_index] 150 | self.current_ins_rules = [] 151 | self.current_blk_rules = [] 152 | 153 | for rule in self.known_ins_rules: 154 | for rule_conf in self.current_project.ins_rules: 155 | if rule.name == rule_conf.name: 156 | rule.configure(rule_conf.config) 157 | rule.set_log_dir(self.log_dir) 158 | self.current_ins_rules.append(rule) 159 | logger.debug("Instruction rules configured") 160 | for blk_rule in self.known_blk_rules: 161 | for rule_conf in self.current_project.blk_rules: 162 | if blk_rule.name == rule_conf.name: 163 | blk_rule.configure(rule_conf.config) 164 | blk_rule.set_log_dir(self.log_dir) 165 | self.current_blk_rules.append(blk_rule) 166 | logger.debug("Block rules configured") 167 | self.manager.configure(**self.current_project.additional_configuration) 168 | logger.debug("Project loaded.") 169 | 170 | def start_d810(self): 171 | print("D-810 ready to deobfuscate...") 172 | self.manager.configure_instruction_optimizer([rule for rule in self.current_ins_rules], 173 | generate_z3_code=self.d810_config.get("generate_z3_code"), 174 | dump_intermediate_microcode=self.d810_config.get( 175 | "dump_intermediate_microcode"), 176 | **self.current_project.additional_configuration) 177 | self.manager.configure_block_optimizer([rule for rule in self.current_blk_rules], 178 | **self.current_project.additional_configuration) 179 | self.manager.reload() 180 | self.d810_config.set("last_project_index", self.current_project_index) 181 | self.d810_config.save() 182 | 183 | def stop_d810(self): 184 | print("Stopping D-810...") 185 | self.manager.stop() 186 | 187 | def start_plugin(self): 188 | from d810.ida_ui import D810GUI 189 | self.gui = D810GUI(self) 190 | self.gui.show_windows() 191 | 192 | def stop_plugin(self): 193 | self.manager.stop() 194 | if self.gui: 195 | self.gui.term() 196 | self.gui = None 197 | -------------------------------------------------------------------------------- /d810/manager_info.json: -------------------------------------------------------------------------------- 1 | { 2 | "_comment": "Order of module in module list matters", 3 | "module_list": [ 4 | "d810.cfg_utils", 5 | "d810.emulator", 6 | "d810.ast", 7 | "d810.optimizers.handler", 8 | "d810.optimizers.instructions.handler", 9 | "d810.optimizers.instructions.pattern_matching.handler", 10 | "d810.optimizers.instructions.pattern_matching.rewrite_add", 11 | "d810.optimizers.instructions.pattern_matching.rewrite_and", 12 | "d810.optimizers.instructions.pattern_matching.rewrite_bnot", 13 | "d810.optimizers.instructions.pattern_matching.rewrite_cst", 14 | "d810.optimizers.instructions.pattern_matching.rewrite_mov", 15 | "d810.optimizers.instructions.pattern_matching.rewrite_mul", 16 | "d810.optimizers.instructions.pattern_matching.rewrite_neg", 17 | "d810.optimizers.instructions.pattern_matching.rewrite_or", 18 | "d810.optimizers.instructions.pattern_matching.rewrite_predicates", 19 | "d810.optimizers.instructions.pattern_matching.rewrite_sub", 20 | "d810.optimizers.instructions.pattern_matching.rewrite_xor", 21 | "d810.optimizers.instructions.pattern_matching.weird", 22 | "d810.optimizers.instructions.pattern_matching.experimental", 23 | "d810.optimizers.instructions.pattern_matching", 24 | "d810.optimizers.instructions.chain.handler", 25 | "d810.optimizers.instructions.chain.chain_rules", 26 | "d810.optimizers.instructions.chain", 27 | "d810.optimizers.instructions.z3.handler", 28 | "d810.optimizers.instructions.z3.cst", 29 | "d810.optimizers.instructions.z3.predicates", 30 | "d810.optimizers.instructions.z3", 31 | "d810.optimizers.instructions.analysis.utils", 32 | "d810.optimizers.instructions.analysis.handler", 33 | "d810.optimizers.instructions.analysis.pattern_guess", 34 | "d810.optimizers.instructions.analysis", 35 | "d810.optimizers.instructions.early.handler", 36 | "d810.optimizers.instructions.early.mem_read", 37 | "d810.optimizers.instructions.early", 38 | "d810.optimizers.instructions", 39 | "d810.optimizers.flow.handler", 40 | "d810.optimizers.flow.jumps.handler", 41 | "d810.optimizers.flow.jumps.opaque", 42 | "d810.optimizers.flow.jumps.tricks", 43 | "d810.optimizers.flow.jumps", 44 | "d810.optimizers.flow.flattening.utils", 45 | "d810.optimizers.flow.flattening.generic", 46 | "d810.optimizers.flow.flattening.unflattener", 47 | "d810.optimizers.flow.flattening.unflattener_fake_jump", 48 | "d810.optimizers.flow.flattening.unflattener_switch_case", 49 | "d810.optimizers.flow.flattening.unflattener_indirect", 50 | "d810.optimizers.flow.flattening.fix_pred_cond_jump_block", 51 | "d810.optimizers.flow.flattening", 52 | "d810.optimizers.flow", 53 | "d810.hexrays_helpers", 54 | "d810.hexrays_formatters", 55 | "d810.hexrays_hooks", 56 | "d810.ida_ui", 57 | "d810.log", 58 | "d810.tracker", 59 | "d810.utils", 60 | "d810.z3_utils" 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /d810/optimizers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhkl0228/d810/680cc531b4eb91f14b2630268a571e7b68550931/d810/optimizers/__init__.py -------------------------------------------------------------------------------- /d810/optimizers/flow/__init__.py: -------------------------------------------------------------------------------- 1 | from d810.optimizers.flow.flattening import UNFLATTENING_BLK_RULES 2 | from d810.optimizers.flow.jumps import JUMP_OPTIMIZATION_BLOCK_RULES, JUMP_OPTIMIZATION_RULES 3 | 4 | KNOWN_BLK_RULES = UNFLATTENING_BLK_RULES + JUMP_OPTIMIZATION_BLOCK_RULES 5 | 6 | -------------------------------------------------------------------------------- /d810/optimizers/flow/flattening/__init__.py: -------------------------------------------------------------------------------- 1 | from d810.optimizers.flow.flattening.unflattener import Unflattener 2 | from d810.optimizers.flow.flattening.unflattener_switch_case import UnflattenerSwitchCase 3 | from d810.optimizers.flow.flattening.unflattener_indirect import UnflattenerTigressIndirect 4 | from d810.optimizers.flow.flattening.unflattener_fake_jump import UnflattenerFakeJump 5 | from d810.optimizers.flow.flattening.fix_pred_cond_jump_block import FixPredecessorOfConditionalJumpBlock 6 | 7 | UNFLATTENING_BLK_RULES = [Unflattener(), UnflattenerSwitchCase(), UnflattenerTigressIndirect(), UnflattenerFakeJump(), 8 | FixPredecessorOfConditionalJumpBlock()] 9 | -------------------------------------------------------------------------------- /d810/optimizers/flow/flattening/fix_pred_cond_jump_block.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import List, Tuple 3 | from ida_hexrays import * 4 | 5 | from d810.tracker import MopTracker 6 | from d810.cfg_utils import duplicate_block, make_2way_block_goto, update_blk_successor 7 | 8 | from d810.hexrays_formatters import format_minsn_t, dump_microcode_for_debug 9 | from d810.optimizers.flow.flattening.utils import get_all_possibles_values 10 | from d810.optimizers.flow.flattening.generic import GenericUnflatteningRule 11 | from d810.utils import unsigned_to_signed 12 | 13 | 14 | unflat_logger = logging.getLogger('D810.unflat') 15 | 16 | JMP_OPCODE_HANDLED = [m_jnz, m_jz, m_jae, m_jb, m_ja, m_jbe, m_jge, m_jg, m_jl, m_jle] 17 | 18 | 19 | class FixPredecessorOfConditionalJumpBlock(GenericUnflatteningRule): 20 | DESCRIPTION = "Detect if a predecessor of a conditional block always takes the same path and patch it (works for O-LLVM style control flow flattening)" 21 | DEFAULT_UNFLATTENING_MATURITIES = [MMAT_CALLS, MMAT_GLBOPT1, MMAT_GLBOPT2] 22 | DEFAULT_MAX_PASSES = 100 23 | 24 | def is_jump_taken(self, jmp_blk: mblock_t, pred_comparison_values: List[int]) -> Tuple[bool, bool]: 25 | if len(pred_comparison_values) == 0: 26 | return False, False 27 | jmp_ins = jmp_blk.tail 28 | compared_value = jmp_ins.r.nnn.value 29 | compared_value_size = jmp_ins.r.size 30 | is_jmp_always_taken = False 31 | is_jmp_never_taken = False 32 | if jmp_ins.opcode == m_jnz: 33 | is_jmp_always_taken = all([possible_value != compared_value for possible_value in pred_comparison_values]) 34 | is_jmp_never_taken = all([possible_value == compared_value for possible_value in pred_comparison_values]) 35 | elif jmp_ins.opcode == m_jz: 36 | is_jmp_always_taken = all([possible_value == compared_value for possible_value in pred_comparison_values]) 37 | is_jmp_never_taken = all([possible_value != compared_value for possible_value in pred_comparison_values]) 38 | elif jmp_ins.opcode == m_jae: 39 | is_jmp_always_taken = all([possible_value >= compared_value for possible_value in pred_comparison_values]) 40 | is_jmp_never_taken = all([possible_value < compared_value for possible_value in pred_comparison_values]) 41 | elif jmp_ins.opcode == m_jb: 42 | is_jmp_always_taken = all([possible_value < compared_value for possible_value in pred_comparison_values]) 43 | is_jmp_never_taken = all([possible_value >= compared_value for possible_value in pred_comparison_values]) 44 | elif jmp_ins.opcode == m_ja: 45 | is_jmp_always_taken = all([possible_value > compared_value for possible_value in pred_comparison_values]) 46 | is_jmp_never_taken = all([possible_value <= compared_value for possible_value in pred_comparison_values]) 47 | elif jmp_ins.opcode == m_jbe: 48 | is_jmp_always_taken = all([unsigned_to_signed(possible_value, compared_value_size) > unsigned_to_signed( 49 | compared_value, compared_value_size) for possible_value in pred_comparison_values]) 50 | is_jmp_never_taken = all([unsigned_to_signed(possible_value, compared_value_size) <= unsigned_to_signed( 51 | compared_value, compared_value_size) for possible_value in pred_comparison_values]) 52 | elif jmp_ins.opcode == m_jg: 53 | is_jmp_always_taken = all([unsigned_to_signed(possible_value, compared_value_size) > unsigned_to_signed( 54 | compared_value, compared_value_size) for possible_value in pred_comparison_values]) 55 | is_jmp_never_taken = all([unsigned_to_signed(possible_value, compared_value_size) <= unsigned_to_signed( 56 | compared_value, compared_value_size) for possible_value in pred_comparison_values]) 57 | elif jmp_ins.opcode == m_jge: 58 | is_jmp_always_taken = all([unsigned_to_signed(possible_value, compared_value_size) >= unsigned_to_signed( 59 | compared_value, compared_value_size) for possible_value in pred_comparison_values]) 60 | is_jmp_never_taken = all([unsigned_to_signed(possible_value, compared_value_size) < unsigned_to_signed( 61 | compared_value, compared_value_size) for possible_value in pred_comparison_values]) 62 | elif jmp_ins.opcode == m_jl: 63 | is_jmp_always_taken = all([unsigned_to_signed(possible_value, compared_value_size) < unsigned_to_signed( 64 | compared_value, compared_value_size) for possible_value in pred_comparison_values]) 65 | is_jmp_never_taken = all([unsigned_to_signed(possible_value, compared_value_size) >= unsigned_to_signed( 66 | compared_value, compared_value_size) for possible_value in pred_comparison_values]) 67 | elif jmp_ins.opcode == m_jle: 68 | is_jmp_always_taken = all([unsigned_to_signed(possible_value, compared_value_size) <= unsigned_to_signed( 69 | compared_value, compared_value_size) for possible_value in pred_comparison_values]) 70 | is_jmp_never_taken = all([unsigned_to_signed(possible_value, compared_value_size) > unsigned_to_signed( 71 | compared_value, compared_value_size) for possible_value in pred_comparison_values]) 72 | return is_jmp_always_taken, is_jmp_never_taken 73 | 74 | def sort_predecessors(self, blk): 75 | # this function sorts the blk predecessors into three list: 76 | # - A list of predecessors where the jump is always taken 77 | # - A list of predecessors where the jump is never taken 78 | # - A list of predecessors where we don't know 79 | pred_jmp_always_taken = [] 80 | pred_jmp_never_taken = [] 81 | pred_jmp_unk = [] 82 | op_compared = mop_t(blk.tail.l) 83 | blk_preset_list = [x for x in blk.predset] 84 | for pred_serial in blk_preset_list: 85 | cmp_variable_tracker = MopTracker([op_compared], max_nb_block=100, max_path=1000) 86 | cmp_variable_tracker.reset() 87 | pred_blk = blk.mba.get_mblock(pred_serial) 88 | pred_histories = cmp_variable_tracker.search_backward(pred_blk, pred_blk.tail) 89 | pred_values = get_all_possibles_values(pred_histories, [op_compared]) 90 | pred_values = [x[0] for x in pred_values] 91 | unflat_logger.info("Pred {0} has {1} possible path ({2} different cst): {3}" 92 | .format(pred_blk.serial, len(pred_values), len(set(pred_values)), pred_values)) 93 | if None in pred_values: 94 | pred_jmp_unk.append(pred_blk) 95 | continue 96 | is_jmp_always_taken, is_jmp_never_taken = self.is_jump_taken(blk, pred_values) 97 | if is_jmp_always_taken and is_jmp_never_taken: 98 | # this should never happen 99 | unflat_logger.error("It seems that I am stupid: '{0}' is always taken and not taken when coming from {1}: {2}".format(format_minsn_t(blk.tail), pred_blk.serial, pred_values)) 100 | pred_jmp_unk.append(pred_blk) 101 | continue 102 | if is_jmp_always_taken: 103 | unflat_logger.info("It seems that '{0}' is always taken when coming from {1}: {2}".format(format_minsn_t(blk.tail), pred_blk.serial, pred_values)) 104 | pred_jmp_always_taken.append(pred_blk) 105 | if is_jmp_never_taken: 106 | unflat_logger.info("It seems that '{0}' is never taken when coming from {1}: {2}".format(format_minsn_t(blk.tail), pred_blk.serial, pred_values)) 107 | pred_jmp_never_taken.append(pred_blk) 108 | return pred_jmp_always_taken, pred_jmp_never_taken, pred_jmp_unk 109 | 110 | def analyze_blk(self, blk: mblock_t) -> int: 111 | if (blk.tail is None) or blk.tail.opcode not in JMP_OPCODE_HANDLED: 112 | return 0 113 | if blk.tail.r.t != mop_n: 114 | return 0 115 | unflat_logger.info("Checking if block {0} can be simplified: {1}".format(blk.serial, format_minsn_t(blk.tail))) 116 | pred_jmp_always_taken, pred_jmp_never_taken, pred_jmp_unk = self.sort_predecessors(blk) 117 | unflat_logger.info("Block {0} has {1} preds: {2} always jmp, {3} never jmp, {4} unk".format(blk.serial, blk.npred(), len(pred_jmp_always_taken), len(pred_jmp_never_taken), len(pred_jmp_unk))) 118 | nb_change = 0 119 | if len(pred_jmp_always_taken) > 0: 120 | dump_microcode_for_debug(self.mba, self.log_dir, "{0}_{1}_before_jmp_always_fix".format(self.cur_maturity_pass, blk.serial)) 121 | for pred_blk in pred_jmp_always_taken: 122 | new_jmp_block, new_default_block = duplicate_block(blk) 123 | make_2way_block_goto(new_jmp_block, blk.tail.d.b) 124 | update_blk_successor(pred_blk, blk.serial, new_jmp_block.serial) 125 | dump_microcode_for_debug(self.mba, self.log_dir, "{0}_{1}_after_jmp_always_fix".format(self.cur_maturity_pass, blk.serial)) 126 | nb_change += len(pred_jmp_always_taken) 127 | if len(pred_jmp_never_taken) > 0: 128 | dump_microcode_for_debug(self.mba, self.log_dir, "{0}_{1}_before_jmp_never_fix".format(self.cur_maturity_pass, blk.serial)) 129 | for pred_blk in pred_jmp_never_taken: 130 | new_jmp_block, new_default_block = duplicate_block(blk) 131 | make_2way_block_goto(new_jmp_block, blk.serial + 1) 132 | update_blk_successor(pred_blk, blk.serial, new_jmp_block.serial) 133 | dump_microcode_for_debug(self.mba, self.log_dir, "{0}_{1}_after_jmp_never_fix".format(self.cur_maturity_pass, blk.serial)) 134 | nb_change += len(pred_jmp_never_taken) 135 | return nb_change 136 | 137 | def optimize(self, blk: mblock_t) -> int: 138 | self.mba = blk.mba 139 | if not self.check_if_rule_should_be_used(blk): 140 | return 0 141 | self.last_pass_nb_patch_done = self.analyze_blk(blk) 142 | if self.last_pass_nb_patch_done > 0: 143 | self.mba.mark_chains_dirty() 144 | self.mba.optimize_local(0) 145 | self.mba.verify(True) 146 | return self.last_pass_nb_patch_done 147 | 148 | def check_if_rule_should_be_used(self, blk: mblock_t) -> bool: 149 | if self.cur_maturity != self.mba.maturity: 150 | self.cur_maturity = self.mba.maturity 151 | self.cur_maturity_pass = 0 152 | if self.cur_maturity not in self.maturities: 153 | return False 154 | if (self.DEFAULT_MAX_PASSES is not None) and (self.cur_maturity_pass >= self.DEFAULT_MAX_PASSES): 155 | return False 156 | if (blk.tail is None) or blk.tail.opcode not in JMP_OPCODE_HANDLED: 157 | return False 158 | if blk.tail.r.t != mop_n: 159 | return False 160 | self.cur_maturity_pass += 1 161 | return True 162 | -------------------------------------------------------------------------------- /d810/optimizers/flow/flattening/unflattener.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Tuple, List 3 | from ida_hexrays import * 4 | 5 | from d810.hexrays_helpers import extract_num_mop, append_mop_if_not_in_list 6 | from d810.optimizers.flow.flattening.generic import GenericDispatcherCollector, GenericDispatcherInfo, \ 7 | GenericDispatcherBlockInfo, GenericDispatcherUnflatteningRule 8 | 9 | 10 | unflat_logger = logging.getLogger('D810.unflat') 11 | FLATTENING_JUMP_OPCODES = [m_jnz, m_jz, m_jae, m_jb, m_ja, m_jbe, m_jg, m_jge, m_jl, m_jle] 12 | 13 | 14 | class OllvmDispatcherBlockInfo(GenericDispatcherBlockInfo): 15 | pass 16 | 17 | 18 | class OllvmDispatcherInfo(GenericDispatcherInfo): 19 | def explore(self, blk: mblock_t) -> bool: 20 | self.reset() 21 | if not self._is_candidate_for_dispatcher_entry_block(blk): 22 | return False 23 | self.entry_block = OllvmDispatcherBlockInfo(blk) 24 | self.entry_block.parse() 25 | for used_mop in self.entry_block.use_list: 26 | append_mop_if_not_in_list(used_mop, self.entry_block.assume_def_list) 27 | self.dispatcher_internal_blocks.append(self.entry_block) 28 | num_mop, self.mop_compared = self._get_comparison_info(self.entry_block.blk) 29 | self.comparison_values.append(num_mop.nnn.value) 30 | self._explore_children(self.entry_block) 31 | dispatcher_blk_with_external_father = self._get_dispatcher_blocks_with_external_father() 32 | # TODO: I think this can be wrong because we are too permissive in detection of dispatcher blocks 33 | if len(dispatcher_blk_with_external_father) != 0: 34 | return False 35 | return True 36 | 37 | def _is_candidate_for_dispatcher_entry_block(self, blk: mblock_t) -> bool: 38 | # blk must be a condition branch with one numerical operand 39 | num_mop, mop_compared = self._get_comparison_info(blk) 40 | if (num_mop is None) or (mop_compared is None): 41 | return False 42 | # Its fathers are not conditional branch with this mop 43 | for father_serial in blk.predset: 44 | father_blk = self.mba.get_mblock(father_serial) 45 | father_num_mop, father_mop_compared = self._get_comparison_info(father_blk) 46 | if (father_num_mop is not None) and (father_mop_compared is not None): 47 | if mop_compared.equal_mops(father_mop_compared, EQ_IGNSIZE): 48 | return False 49 | return True 50 | 51 | def _get_comparison_info(self, blk: mblock_t) -> Tuple[mop_t, mop_t]: 52 | # We check if blk is a good candidate for dispatcher entry block: blk.tail must be a conditional branch 53 | if (blk.tail is None) or (blk.tail.opcode not in FLATTENING_JUMP_OPCODES): 54 | return None, None 55 | # One operand must be numerical 56 | num_mop, mop_compared = extract_num_mop(blk.tail) 57 | if num_mop is None or mop_compared is None: 58 | return None, None 59 | return num_mop, mop_compared 60 | 61 | def is_part_of_dispatcher(self, block_info: OllvmDispatcherBlockInfo) -> bool: 62 | is_ok = block_info.does_only_need(block_info.father.assume_def_list) 63 | if not is_ok: 64 | return False 65 | if (block_info.blk.tail is not None) and (block_info.blk.tail.opcode not in FLATTENING_JUMP_OPCODES): 66 | return False 67 | return True 68 | 69 | def _explore_children(self, father_info: OllvmDispatcherBlockInfo): 70 | for child_serial in father_info.blk.succset: 71 | if child_serial in [blk_info.blk.serial for blk_info in self.dispatcher_internal_blocks]: 72 | return 73 | if child_serial in [blk_info.blk.serial for blk_info in self.dispatcher_exit_blocks]: 74 | return 75 | child_blk = self.mba.get_mblock(child_serial) 76 | child_info = OllvmDispatcherBlockInfo(child_blk, father_info) 77 | child_info.parse() 78 | if not self.is_part_of_dispatcher(child_info): 79 | self.dispatcher_exit_blocks.append(child_info) 80 | else: 81 | self.dispatcher_internal_blocks.append(child_info) 82 | if child_info.comparison_value is not None: 83 | self.comparison_values.append(child_info.comparison_value) 84 | self._explore_children(child_info) 85 | 86 | def _get_external_fathers(self, block_info: OllvmDispatcherBlockInfo) -> List[mblock_t]: 87 | internal_serials = [blk_info.blk.serial for blk_info in self.dispatcher_internal_blocks] 88 | external_fathers = [] 89 | for blk_father in block_info.blk.predset: 90 | if blk_father not in internal_serials: 91 | external_fathers.append(blk_father) 92 | return external_fathers 93 | 94 | def _get_dispatcher_blocks_with_external_father(self) -> List[mblock_t]: 95 | dispatcher_blocks_with_external_father = [] 96 | for blk_info in self.dispatcher_internal_blocks: 97 | if blk_info.blk.serial != self.entry_block.blk.serial: 98 | external_fathers = self._get_external_fathers(blk_info) 99 | if len(external_fathers) > 0: 100 | dispatcher_blocks_with_external_father.append(blk_info) 101 | return dispatcher_blocks_with_external_father 102 | 103 | 104 | class OllvmDispatcherCollector(GenericDispatcherCollector): 105 | DISPATCHER_CLASS = OllvmDispatcherInfo 106 | DEFAULT_DISPATCHER_MIN_INTERNAL_BLOCK = 2 107 | DEFAULT_DISPATCHER_MIN_EXIT_BLOCK = 3 108 | DEFAULT_DISPATCHER_MIN_COMPARISON_VALUE = 2 109 | 110 | 111 | class Unflattener(GenericDispatcherUnflatteningRule): 112 | DESCRIPTION = "Remove control flow flattening generated by OLLVM" 113 | DISPATCHER_COLLECTOR_CLASS = OllvmDispatcherCollector 114 | DEFAULT_UNFLATTENING_MATURITIES = [MMAT_CALLS, MMAT_GLBOPT1, MMAT_GLBOPT2] 115 | DEFAULT_MAX_DUPLICATION_PASSES = 20 116 | DEFAULT_MAX_PASSES = 5 117 | -------------------------------------------------------------------------------- /d810/optimizers/flow/flattening/unflattener_fake_jump.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import List 3 | from ida_hexrays import * 4 | 5 | from d810.tracker import MopTracker 6 | from d810.cfg_utils import change_1way_block_successor 7 | from d810.hexrays_formatters import format_minsn_t, dump_microcode_for_debug 8 | from d810.optimizers.flow.flattening.utils import get_all_possibles_values 9 | from d810.optimizers.flow.flattening.generic import GenericUnflatteningRule 10 | 11 | unflat_logger = logging.getLogger('D810.unflat') 12 | 13 | FAKE_LOOP_OPCODES = [m_jz, m_jnz] 14 | 15 | 16 | class UnflattenerFakeJump(GenericUnflatteningRule): 17 | DESCRIPTION = "Check if a jump is always taken for each father blocks and remove them" 18 | DEFAULT_UNFLATTENING_MATURITIES = [MMAT_CALLS, MMAT_GLBOPT1] 19 | DEFAULT_MAX_PASSES = None 20 | 21 | def analyze_blk(self, blk: mblock_t) -> int: 22 | if (blk.tail is None) or blk.tail.opcode not in FAKE_LOOP_OPCODES: 23 | return 0 24 | if blk.get_reginsn_qty() != 1: 25 | return 0 26 | if blk.tail.r.t != mop_n: 27 | return 0 28 | unflat_logger.info("Checking if block {0} is fake loop: {1}".format(blk.serial, format_minsn_t(blk.tail))) 29 | op_compared = mop_t(blk.tail.l) 30 | blk_preset_list = [x for x in blk.predset] 31 | nb_change = 0 32 | for pred_serial in blk_preset_list: 33 | cmp_variable_tracker = MopTracker([op_compared], max_nb_block=100, max_path=1000) 34 | cmp_variable_tracker.reset() 35 | pred_blk = blk.mba.get_mblock(pred_serial) 36 | pred_histories = cmp_variable_tracker.search_backward(pred_blk, pred_blk.tail) 37 | 38 | father_is_resolvable = all([father_history.is_resolved() for father_history in pred_histories]) 39 | if not father_is_resolvable: 40 | return 0 41 | pred_values = get_all_possibles_values(pred_histories, [op_compared]) 42 | pred_values = [x[0] for x in pred_values] 43 | if None in pred_values: 44 | unflat_logger.info("Some path are not resolved, can't fix jump") 45 | return 0 46 | unflat_logger.info("Pred {0} has {1} possible path ({2} different cst): {3}" 47 | .format(pred_blk.serial, len(pred_values), len(set(pred_values)), pred_values)) 48 | if self.fix_successor(blk, pred_blk, pred_values): 49 | nb_change += 1 50 | return nb_change 51 | 52 | def fix_successor(self, fake_loop_block: mblock_t, pred: mblock_t, pred_comparison_values: List[int]) -> bool: 53 | if len(pred_comparison_values) == 0: 54 | return False 55 | jmp_ins = fake_loop_block.tail 56 | compared_value = jmp_ins.r.nnn.value 57 | jmp_taken = False 58 | jmp_not_taken = False 59 | dst_serial = None 60 | if jmp_ins.opcode == m_jz: 61 | jmp_taken = all([possible_value == compared_value for possible_value in pred_comparison_values]) 62 | 63 | jmp_not_taken = all([possible_value != compared_value for possible_value in pred_comparison_values]) 64 | elif jmp_ins.opcode == m_jnz: 65 | jmp_taken = all([possible_value != compared_value for possible_value in pred_comparison_values]) 66 | jmp_not_taken = all([possible_value == compared_value for possible_value in pred_comparison_values]) 67 | # TODO: handles other jumps cases 68 | if jmp_taken: 69 | unflat_logger.info("It seems that '{0}' is always taken when coming from {1}: {2}" 70 | .format(format_minsn_t(jmp_ins), pred.serial, pred_comparison_values)) 71 | dst_serial = jmp_ins.d.b 72 | if jmp_not_taken: 73 | unflat_logger.info("It seems that '{0}' is never taken when coming from {1}: {2}" 74 | .format(format_minsn_t(jmp_ins), pred.serial, pred_comparison_values)) 75 | dst_serial = fake_loop_block.serial + 1 76 | if dst_serial is None: 77 | unflat_logger.debug("Jump seems legit '{0}' from {1}: {2}" 78 | .format(format_minsn_t(jmp_ins), pred.serial, pred_comparison_values)) 79 | return False 80 | dump_microcode_for_debug(self.mba, self.log_dir, "{0}_before_fake_jump".format(self.cur_maturity_pass)) 81 | unflat_logger.info("Making pred {0} with value {1} goto {2} ({3})" 82 | .format(pred.serial, pred_comparison_values, dst_serial, format_minsn_t(jmp_ins))) 83 | dump_microcode_for_debug(self.mba, self.log_dir, "{0}_after_fake_jump".format(self.cur_maturity_pass)) 84 | return change_1way_block_successor(pred, dst_serial) 85 | 86 | def optimize(self, blk: mblock_t) -> int: 87 | self.mba = blk.mba 88 | if not self.check_if_rule_should_be_used(blk): 89 | return 0 90 | self.last_pass_nb_patch_done = self.analyze_blk(blk) 91 | if self.last_pass_nb_patch_done > 0: 92 | self.mba.mark_chains_dirty() 93 | self.mba.optimize_local(0) 94 | self.mba.verify(True) 95 | return self.last_pass_nb_patch_done 96 | -------------------------------------------------------------------------------- /d810/optimizers/flow/flattening/unflattener_indirect.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import idaapi 3 | from typing import List 4 | from ida_hexrays import * 5 | 6 | from d810.hexrays_helpers import append_mop_if_not_in_list, AND_TABLE, CONTROL_FLOW_OPCODES 7 | from d810.tracker import MopTracker, MopHistory 8 | from d810.optimizers.flow.flattening.generic import GenericDispatcherBlockInfo, GenericDispatcherInfo, \ 9 | GenericDispatcherCollector, GenericDispatcherUnflatteningRule, NotDuplicableFatherException, DispatcherUnflatteningException, NotResolvableFatherException 10 | from d810.optimizers.flow.flattening.utils import configure_mop_tracker_log_verbosity, restore_mop_tracker_log_verbosity 11 | from d810.tracker import duplicate_histories 12 | from d810.cfg_utils import create_block, change_1way_block_successor 13 | from d810.hexrays_formatters import format_minsn_t, format_mop_t 14 | from d810.emulator import MicroCodeEnvironment, MicroCodeInterpreter 15 | 16 | unflat_logger = logging.getLogger('D810.unflat') 17 | FLATTENING_JUMP_OPCODES = [m_jtbl] 18 | 19 | 20 | class TigressIndirectDispatcherBlockInfo(GenericDispatcherBlockInfo): 21 | pass 22 | 23 | 24 | class TigressIndirectDispatcherInfo(GenericDispatcherInfo): 25 | def explore(self, blk: mblock_t): 26 | self.reset() 27 | if not self._is_candidate_for_dispatcher_entry_block(blk): 28 | return False 29 | self.mop_compared = self._get_comparison_info(blk) 30 | self.entry_block = TigressIndirectDispatcherBlockInfo(blk) 31 | self.entry_block.parse() 32 | for used_mop in self.entry_block.use_list: 33 | append_mop_if_not_in_list(used_mop, self.entry_block.assume_def_list) 34 | self.dispatcher_internal_blocks.append(self.entry_block) 35 | 36 | self.dispatcher_exit_blocks = [] 37 | self.comparison_values = [] 38 | return True 39 | 40 | def _get_comparison_info(self, blk: mblock_t): 41 | if (blk.tail is None) or (blk.tail.opcode != m_ijmp): 42 | return None, None 43 | return blk.tail.l 44 | 45 | def _is_candidate_for_dispatcher_entry_block(self, blk: mblock_t): 46 | if (blk.tail is None) or (blk.tail.opcode != m_ijmp): 47 | return False 48 | return True 49 | 50 | def should_emulation_continue(self, cur_blk: mblock_t): 51 | if (cur_blk is not None) and (cur_blk.serial == self.entry_block.serial): 52 | return True 53 | return False 54 | 55 | 56 | class TigressIndirectDispatcherCollector(GenericDispatcherCollector): 57 | DISPATCHER_CLASS = TigressIndirectDispatcherInfo 58 | DEFAULT_DISPATCHER_MIN_INTERNAL_BLOCK = 0 59 | DEFAULT_DISPATCHER_MIN_EXIT_BLOCK = 0 60 | DEFAULT_DISPATCHER_MIN_COMPARISON_VALUE = 0 61 | 62 | 63 | class LabelTableInfo(object): 64 | def __init__(self, sp_offset, mem_offset, nb_elt, ptr_size=8): 65 | self.sp_offset = sp_offset 66 | self.mem_offset = mem_offset 67 | self.nb_elt = nb_elt 68 | self.ptr_size = ptr_size 69 | 70 | def update_mop_tracker(self, mba: mbl_array_t, mop_tracker: MopTracker): 71 | stack_array_base_address = mba.stkoff_ida2vd(self.sp_offset) 72 | for i in range(self.nb_elt): 73 | tmp_mop = mop_t() 74 | tmp_mop.erase() 75 | tmp_mop._make_stkvar(mba, stack_array_base_address + self.ptr_size * i) 76 | tmp_mop.size = self.ptr_size 77 | mem_val = idaapi.get_qword(self.mem_offset + self.ptr_size * i) & AND_TABLE[self.ptr_size] 78 | mop_tracker.add_mop_definition(tmp_mop, mem_val) 79 | 80 | 81 | class UnflattenerTigressIndirect(GenericDispatcherUnflatteningRule): 82 | DESCRIPTION = "" 83 | DISPATCHER_COLLECTOR_CLASS = TigressIndirectDispatcherCollector 84 | DEFAULT_UNFLATTENING_MATURITIES = [MMAT_LOCOPT] 85 | DEFAULT_MAX_DUPLICATION_PASSES = 20 86 | DEFAULT_MAX_PASSES = 1 87 | 88 | def __init__(self): 89 | super().__init__() 90 | self.label_info = None 91 | self.goto_table_info = {} 92 | 93 | def configure(self, kwargs): 94 | super().configure(kwargs) 95 | if "goto_table_info" in self.config.keys(): 96 | for ea_str, table_info in self.config["goto_table_info"].items(): 97 | self.goto_table_info[int(ea_str, 16)] = LabelTableInfo(sp_offset=int(table_info["stack_table_offset"], 16), 98 | mem_offset=int(table_info["table_address"], 16), 99 | nb_elt=table_info["table_nb_elt"]) 100 | 101 | def check_if_rule_should_be_used(self, blk: mblock_t): 102 | if not super().check_if_rule_should_be_used(blk): 103 | return False 104 | if self.mba.entry_ea not in self.goto_table_info: 105 | return False 106 | if (self.cur_maturity_pass >= 1) and (self.last_pass_nb_patch_done == 0): 107 | return False 108 | self.label_info = self.goto_table_info[self.mba.entry_ea] 109 | return True 110 | 111 | def register_initialization_variables(self, mop_tracker: MopTracker): 112 | self.label_info.update_mop_tracker(self.mba, mop_tracker) 113 | 114 | def check_if_histories_are_resolved(self, mop_histories: List[MopHistory]): 115 | return True 116 | -------------------------------------------------------------------------------- /d810/optimizers/flow/flattening/unflattener_switch_case.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from ida_hexrays import * 3 | 4 | from d810.hexrays_helpers import append_mop_if_not_in_list 5 | from d810.optimizers.flow.flattening.generic import GenericDispatcherBlockInfo, GenericDispatcherInfo, \ 6 | GenericDispatcherCollector, GenericDispatcherUnflatteningRule 7 | 8 | 9 | unflat_logger = logging.getLogger('D810.unflat') 10 | FLATTENING_JUMP_OPCODES = [m_jtbl] 11 | 12 | 13 | class TigressSwitchDispatcherBlockInfo(GenericDispatcherBlockInfo): 14 | pass 15 | 16 | 17 | class TigressSwitchDispatcherInfo(GenericDispatcherInfo): 18 | def explore(self, blk: mblock_t): 19 | self.reset() 20 | if not self._is_candidate_for_dispatcher_entry_block(blk): 21 | return False 22 | self.mop_compared, mcases = self._get_comparison_info(blk) 23 | self.entry_block = TigressSwitchDispatcherBlockInfo(blk) 24 | self.entry_block.parse() 25 | for used_mop in self.entry_block.use_list: 26 | append_mop_if_not_in_list(used_mop, self.entry_block.assume_def_list) 27 | self.dispatcher_internal_blocks.append(self.entry_block) 28 | for possible_values, target_block_serial in zip(mcases.c.values, mcases.c.targets): 29 | if target_block_serial == self.entry_block.blk.serial: 30 | continue 31 | exit_block = TigressSwitchDispatcherBlockInfo(blk.mba.get_mblock(target_block_serial), self.entry_block) 32 | self.dispatcher_exit_blocks.append(exit_block) 33 | self.comparison_values.append(possible_values[0]) 34 | return True 35 | 36 | def _get_comparison_info(self, blk: mblock_t): 37 | # blk.tail must be a jtbl 38 | if (blk.tail is None) or (blk.tail.opcode != m_jtbl): 39 | return None, None 40 | return blk.tail.l, blk.tail.r 41 | 42 | def _is_candidate_for_dispatcher_entry_block(self, blk: mblock_t): 43 | if (blk.tail is None) or (blk.tail.opcode != m_jtbl): 44 | return False 45 | return True 46 | 47 | 48 | class TigressSwitchDispatcherCollector(GenericDispatcherCollector): 49 | DISPATCHER_CLASS = TigressSwitchDispatcherInfo 50 | DEFAULT_DISPATCHER_MIN_INTERNAL_BLOCK = 0 51 | DEFAULT_DISPATCHER_MIN_EXIT_BLOCK = 4 52 | DEFAULT_DISPATCHER_MIN_COMPARISON_VALUE = 4 53 | 54 | 55 | class UnflattenerSwitchCase(GenericDispatcherUnflatteningRule): 56 | DESCRIPTION = "Remove control flow flattening generated by Tigress with Switch case dispatcher" 57 | DISPATCHER_COLLECTOR_CLASS = TigressSwitchDispatcherCollector 58 | DEFAULT_UNFLATTENING_MATURITIES = [MMAT_GLBOPT1] 59 | DEFAULT_MAX_DUPLICATION_PASSES = 20 60 | DEFAULT_MAX_PASSES = 5 61 | -------------------------------------------------------------------------------- /d810/optimizers/flow/flattening/utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | tracker_logger = logging.getLogger('D810.tracker') 5 | emulator_logger = logging.getLogger('D810.emulator') 6 | 7 | 8 | class UnflatteningException(Exception): 9 | pass 10 | 11 | 12 | class DispatcherUnflatteningException(UnflatteningException): 13 | pass 14 | 15 | 16 | class NotDuplicableFatherException(UnflatteningException): 17 | pass 18 | 19 | 20 | class NotResolvableFatherException(UnflatteningException): 21 | pass 22 | 23 | 24 | 25 | 26 | def configure_mop_tracker_log_verbosity(verbose=False): 27 | tracker_log_level = tracker_logger.getEffectiveLevel() 28 | emulator_log_level = emulator_logger.getEffectiveLevel() 29 | if not verbose: 30 | tracker_logger.setLevel(logging.ERROR) 31 | emulator_logger.setLevel(logging.ERROR) 32 | return [tracker_log_level, emulator_log_level] 33 | 34 | 35 | def restore_mop_tracker_log_verbosity(tracker_log_level, emulator_log_level): 36 | tracker_logger.setLevel(tracker_log_level) 37 | emulator_logger.setLevel(emulator_log_level) 38 | 39 | 40 | def get_all_possibles_values(mop_histories, searched_mop_list, verbose=False): 41 | log_levels = configure_mop_tracker_log_verbosity(verbose) 42 | mop_cst_values_list = [] 43 | for mop_history in mop_histories: 44 | mop_cst_values_list.append([mop_history.get_mop_constant_value(searched_mop) 45 | for searched_mop in searched_mop_list]) 46 | restore_mop_tracker_log_verbosity(*log_levels) 47 | return mop_cst_values_list 48 | 49 | 50 | def check_if_all_values_are_found(mop_cst_values_list): 51 | all_values_are_found = True 52 | for cst_list in mop_cst_values_list: 53 | if None in cst_list: 54 | all_values_are_found = False 55 | break 56 | return all_values_are_found 57 | -------------------------------------------------------------------------------- /d810/optimizers/flow/handler.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import idc 3 | 4 | from d810.optimizers.handler import OptimizationRule, DEFAULT_FLOW_MATURITIES 5 | 6 | logger = logging.getLogger('D810.optimizer') 7 | 8 | 9 | class FlowOptimizationRule(OptimizationRule): 10 | def __init__(self): 11 | super().__init__() 12 | self.maturities = DEFAULT_FLOW_MATURITIES 13 | self.use_whitelist = False 14 | self.whitelisted_function_ea_list = [] 15 | self.use_blacklist = False 16 | self.blacklisted_function_ea_list = [] 17 | 18 | def configure(self, kwargs): 19 | super().configure(kwargs) 20 | self.use_whitelist = False 21 | self.whitelisted_function_ea_list = [] 22 | self.use_blacklist = False 23 | self.blacklisted_function_ea_list = [] 24 | if "whitelisted_functions" in self.config.keys(): 25 | self.use_whitelist = True 26 | for func_ea in self.config["whitelisted_functions"]: 27 | self.whitelisted_function_ea_list.append(int(func_ea, 16)) 28 | func_name_list = [idc.get_func_name(ea) for ea in self.whitelisted_function_ea_list] 29 | logger.info("Whitelisted functions for {0}: {1} -> {2}".format(self.__class__.__name__, 30 | self.whitelisted_function_ea_list, 31 | func_name_list)) 32 | if "blacklisted_functions" in self.config.keys(): 33 | self.use_blacklist = True 34 | for func_ea in self.config["whitelisted_functions"]: 35 | self.blacklisted_function_ea_list.append(int(func_ea, 16)) 36 | func_name_list = [idc.get_func_name(ea) for ea in self.blacklisted_function_ea_list] 37 | logger.info("Blacklisted functions for {0}: {1} -> {2}".format(self.__class__.__name__, 38 | self.blacklisted_function_ea_list, 39 | func_name_list)) 40 | -------------------------------------------------------------------------------- /d810/optimizers/flow/jumps/__init__.py: -------------------------------------------------------------------------------- 1 | from d810.utils import get_all_subclasses 2 | from d810.optimizers.flow.jumps.handler import JumpOptimizationRule, JumpFixer 3 | from d810.optimizers.flow.jumps.opaque import * 4 | from d810.optimizers.flow.jumps.tricks import * 5 | 6 | 7 | JUMP_OPTIMIZATION_RULES = [x() for x in get_all_subclasses(JumpOptimizationRule)] 8 | jump_fixer = JumpFixer() 9 | for jump_optimization_rule in JUMP_OPTIMIZATION_RULES: 10 | jump_fixer.register_rule(jump_optimization_rule) 11 | JUMP_OPTIMIZATION_BLOCK_RULES = [jump_fixer] 12 | -------------------------------------------------------------------------------- /d810/optimizers/flow/jumps/handler.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from ida_hexrays import * 3 | from typing import Union 4 | 5 | from d810.optimizers.instructions.handler import InstructionOptimizationRule 6 | from d810.optimizers.instructions.pattern_matching.handler import ast_generator 7 | from d810.ast import mop_to_ast, AstNode 8 | from d810.hexrays_formatters import format_minsn_t, opcode_to_string 9 | from d810.optimizers.flow.handler import FlowOptimizationRule 10 | from d810.cfg_utils import make_2way_block_goto, is_conditional_jump, change_2way_block_conditional_successor 11 | 12 | 13 | logger = logging.getLogger("D810.branch_fixer") 14 | optimizer_logger = logging.getLogger('D810.optimizer') 15 | 16 | 17 | class JumpOptimizationRule(InstructionOptimizationRule): 18 | ORIGINAL_JUMP_OPCODES = [] 19 | LEFT_PATTERN = None 20 | RIGHT_PATTERN = None 21 | 22 | REPLACEMENT_OPCODE = None 23 | REPLACEMENT_LEFT_PATTERN = None 24 | REPLACEMENT_RIGHT_PATTERN = None 25 | 26 | FUZZ_PATTERNS = True 27 | 28 | def __init__(self): 29 | super().__init__() 30 | self.fuzz_patterns = self.FUZZ_PATTERNS 31 | self.left_pattern_candidates = [] 32 | self.right_pattern_candidates = [] 33 | self.jump_original_block_serial = None 34 | self.direct_block_serial = None 35 | self.jump_replacement_block_serial = None 36 | 37 | def configure(self, fuzz_pattern=None, **kwargs): 38 | super().configure(kwargs) 39 | if fuzz_pattern is not None: 40 | self.fuzz_patterns = fuzz_pattern 41 | self._generate_pattern_candidates() 42 | 43 | def _generate_pattern_candidates(self): 44 | self.fuzz_patterns = self.FUZZ_PATTERNS 45 | if self.LEFT_PATTERN is not None: 46 | self.LEFT_PATTERN.reset_mops() 47 | if not self.fuzz_patterns: 48 | self.left_pattern_candidates = [self.LEFT_PATTERN] 49 | else: 50 | self.left_pattern_candidates = ast_generator(self.LEFT_PATTERN) 51 | if self.RIGHT_PATTERN is not None: 52 | self.RIGHT_PATTERN.reset_mops() 53 | if not self.fuzz_patterns: 54 | self.right_pattern_candidates = [self.RIGHT_PATTERN] 55 | else: 56 | self.right_pattern_candidates = ast_generator(self.RIGHT_PATTERN) 57 | 58 | def check_candidate(self, opcode, left_candidate: AstNode, right_candidate: AstNode): 59 | return False 60 | 61 | def get_valid_candidates(self, instruction, left_ast: AstNode, right_ast: AstNode, stop_early=True): 62 | valid_candidates = [] 63 | if left_ast is None or right_ast is None: 64 | return [] 65 | 66 | for left_candidate_pattern in self.left_pattern_candidates: 67 | if not left_candidate_pattern.check_pattern_and_copy_mops(left_ast): 68 | continue 69 | for right_candidate_pattern in self.right_pattern_candidates: 70 | if not right_candidate_pattern.check_pattern_and_copy_mops(right_ast): 71 | continue 72 | if not self.check_candidate(instruction.opcode, left_candidate_pattern, right_candidate_pattern): 73 | continue 74 | valid_candidates.append([left_candidate_pattern, right_candidate_pattern]) 75 | if stop_early: 76 | return valid_candidates 77 | return [] 78 | 79 | def check_pattern_and_replace(self, blk: mblock_t, instruction: minsn_t, left_ast: AstNode, right_ast: AstNode): 80 | if instruction.opcode not in self.ORIGINAL_JUMP_OPCODES: 81 | return None 82 | self.jump_original_block_serial = instruction.d.b 83 | self.direct_block_serial = blk.serial + 1 84 | self.jump_replacement_block_serial = None 85 | valid_candidates = self.get_valid_candidates(instruction, left_ast, right_ast, stop_early=True) 86 | if len(valid_candidates) == 0: 87 | return None 88 | if self.jump_original_block_serial is None: 89 | self.jump_replacement_block_serial = self.jump_original_block_serial 90 | left_candidate, right_candidate = valid_candidates[0] 91 | new_ins = self.get_replacement(instruction, left_candidate, right_candidate) 92 | return new_ins 93 | 94 | def get_replacement(self, original_ins: minsn_t, left_candidate: AstNode, right_candidate: AstNode): 95 | new_left_mop = None 96 | new_right_mop = None 97 | new_dst_mop = None 98 | 99 | if self.jump_original_block_serial is not None: 100 | new_dst_mop = mop_t() 101 | new_dst_mop.make_blkref(self.jump_replacement_block_serial) 102 | 103 | if self.REPLACEMENT_LEFT_PATTERN is not None: 104 | is_ok = self.REPLACEMENT_LEFT_PATTERN.update_leafs_mop(left_candidate, right_candidate) 105 | if not is_ok: 106 | return None 107 | new_left_mop = self.REPLACEMENT_LEFT_PATTERN.create_mop(original_ins.ea) 108 | if self.REPLACEMENT_RIGHT_PATTERN is not None: 109 | is_ok = self.REPLACEMENT_RIGHT_PATTERN.update_leafs_mop(left_candidate, right_candidate) 110 | if not is_ok: 111 | return None 112 | new_right_mop = self.REPLACEMENT_RIGHT_PATTERN.create_mop(original_ins.ea) 113 | 114 | new_ins = self.create_new_ins(original_ins, new_left_mop, new_right_mop, new_dst_mop) 115 | return new_ins 116 | 117 | def create_new_ins(self, original_ins: minsn_t, new_left_mop: mop_t, 118 | new_right_mop: Union[None, mop_t] = None, new_dst_mop: Union[None, mop_t] = None) -> minsn_t: 119 | new_ins = minsn_t(original_ins) 120 | new_ins.opcode = self.REPLACEMENT_OPCODE 121 | if self.REPLACEMENT_OPCODE == m_goto: 122 | new_ins.l = new_dst_mop 123 | new_ins.r.erase() 124 | new_ins.d.erase() 125 | return new_ins 126 | new_ins.l = new_left_mop 127 | if new_right_mop is not None: 128 | new_ins.r = new_right_mop 129 | if new_dst_mop is not None: 130 | new_ins.d = new_dst_mop 131 | return new_ins 132 | 133 | @property 134 | def description(self): 135 | self.LEFT_PATTERN.reset_mops() 136 | self.RIGHT_PATTERN.reset_mops() 137 | return "{0}: {1}, {2}".format(",".join([opcode_to_string(x) for x in self.JMP_OPCODES]), 138 | self.LEFT_PATTERN, self.RIGHT_PATTERN) 139 | 140 | 141 | class JumpFixer(FlowOptimizationRule): 142 | def __init__(self): 143 | super().__init__() 144 | self.known_rules = [] 145 | self.rules = [] 146 | 147 | def register_rule(self, rule: JumpOptimizationRule): 148 | self.known_rules.append(rule) 149 | 150 | def configure(self, kwargs): 151 | super().configure(kwargs) 152 | if "enabled_rules" in self.config.keys(): 153 | for rule in self.known_rules: 154 | if rule.name in self.config["enabled_rules"]: 155 | rule.configure() 156 | self.rules.append(rule) 157 | optimizer_logger.debug("JumpFixer enables rule {0}".format(rule.name)) 158 | else: 159 | optimizer_logger.debug("JumpFixer disables rule {0}".format(rule.name)) 160 | 161 | def optimize(self, blk: mblock_t) -> bool: 162 | if not is_conditional_jump(blk): 163 | return False 164 | left_ast = mop_to_ast(blk.tail.l) 165 | right_ast = mop_to_ast(blk.tail.r) 166 | for rule in self.rules: 167 | try: 168 | new_ins = rule.check_pattern_and_replace(blk, blk.tail, left_ast, right_ast) 169 | if new_ins: 170 | optimizer_logger.info("Rule {0} matched:".format(rule.name)) 171 | optimizer_logger.info(" orig: {0}".format(format_minsn_t(blk.tail))) 172 | optimizer_logger.info(" new : {0}".format(format_minsn_t(new_ins))) 173 | if new_ins.opcode == m_goto: 174 | make_2way_block_goto(blk, new_ins.d.b) 175 | else: 176 | change_2way_block_conditional_successor(blk, new_ins.d.b) 177 | blk.make_nop(blk.tail) 178 | blk.insert_into_block(new_ins, blk.tail) 179 | return True 180 | except RuntimeError as e: 181 | optimizer_logger.error("Error during rule {0} for instruction {1}: {2}" 182 | .format(rule, format_minsn_t(blk.tail), e)) 183 | return False 184 | -------------------------------------------------------------------------------- /d810/optimizers/flow/jumps/opaque.py: -------------------------------------------------------------------------------- 1 | from ida_hexrays import * 2 | 3 | from d810.ast import AstLeaf, AstConstant, AstNode 4 | from d810.optimizers.flow.jumps.handler import JumpOptimizationRule 5 | 6 | 7 | class JnzRule1(JumpOptimizationRule): 8 | ORIGINAL_JUMP_OPCODES = [m_jnz, m_jz] 9 | LEFT_PATTERN = AstNode(m_neg, 10 | AstNode(m_and, 11 | AstNode(m_bnot, 12 | AstLeaf("x_0")), 13 | AstConstant("1", 1))) 14 | RIGHT_PATTERN = AstLeaf("x_0") 15 | REPLACEMENT_OPCODE = m_goto 16 | 17 | def check_candidate(self, opcode, left_candidate, right_candidate): 18 | if opcode == m_jnz: 19 | self.jump_replacement_block_serial = self.jump_original_block_serial 20 | else: 21 | self.jump_replacement_block_serial = self.direct_block_serial 22 | return True 23 | 24 | 25 | class JnzRule2(JumpOptimizationRule): 26 | ORIGINAL_JUMP_OPCODES = [m_jnz, m_jz] 27 | LEFT_PATTERN = AstNode(m_or, 28 | AstNode(m_bnot, 29 | AstLeaf("x_0")), 30 | AstConstant("1", 1)) 31 | RIGHT_PATTERN = AstConstant("0", 0) 32 | REPLACEMENT_OPCODE = m_goto 33 | 34 | def check_candidate(self, opcode, left_candidate, right_candidate): 35 | if opcode == m_jnz: 36 | self.jump_replacement_block_serial = self.jump_original_block_serial 37 | else: 38 | self.jump_replacement_block_serial = self.direct_block_serial 39 | return True 40 | 41 | 42 | class JnzRule3(JumpOptimizationRule): 43 | ORIGINAL_JUMP_OPCODES = [m_jnz, m_jz] 44 | LEFT_PATTERN = AstNode(m_xor, 45 | AstNode(m_xor, 46 | AstLeaf("x_0"), 47 | AstConstant("c_1")), 48 | AstNode(m_and, 49 | AstLeaf("x_0"), 50 | AstConstant("c_2"))) 51 | RIGHT_PATTERN = AstConstant("0", 0) 52 | REPLACEMENT_OPCODE = m_goto 53 | 54 | def check_candidate(self, opcode, left_candidate, right_candidate): 55 | tmp = left_candidate["c_1"].value & left_candidate["c_2"].value 56 | if tmp == 0: 57 | return False 58 | if opcode == m_jnz: 59 | self.jump_replacement_block_serial = self.jump_original_block_serial 60 | else: 61 | self.jump_replacement_block_serial = self.direct_block_serial 62 | return True 63 | 64 | 65 | class JnzRule4(JumpOptimizationRule): 66 | ORIGINAL_JUMP_OPCODES = [m_jnz, m_jz] 67 | LEFT_PATTERN = AstNode(m_sub, 68 | AstConstant("3", 3), 69 | AstLeaf("x_0")) 70 | RIGHT_PATTERN = AstLeaf("x_0") 71 | REPLACEMENT_OPCODE = m_goto 72 | 73 | def check_candidate(self, opcode, left_candidate, right_candidate): 74 | if opcode == m_jnz: 75 | self.jump_replacement_block_serial = self.jump_original_block_serial 76 | else: 77 | self.jump_replacement_block_serial = self.direct_block_serial 78 | return True 79 | 80 | 81 | class JnzRule5(JumpOptimizationRule): 82 | ORIGINAL_JUMP_OPCODES = [m_jnz, m_jz] 83 | LEFT_PATTERN = AstNode(m_xor, 84 | AstNode(m_sub, 85 | AstConstant("3", 3), 86 | AstLeaf("x_0")), 87 | AstLeaf("x_0")) 88 | RIGHT_PATTERN = AstConstant("0", 0) 89 | REPLACEMENT_OPCODE = m_goto 90 | 91 | def check_candidate(self, opcode, left_candidate, right_candidate): 92 | if opcode == m_jnz: 93 | self.jump_replacement_block_serial = self.jump_original_block_serial 94 | else: 95 | self.jump_replacement_block_serial = self.direct_block_serial 96 | return True 97 | 98 | 99 | class JnzRule6(JumpOptimizationRule): 100 | ORIGINAL_JUMP_OPCODES = [m_jnz, m_jz] 101 | LEFT_PATTERN = AstNode(m_xor, 102 | AstNode(m_bnot, 103 | AstNode(m_sub, 104 | AstConstant("3", 3), 105 | AstLeaf("x_0"))), 106 | AstNode(m_bnot, 107 | AstLeaf("x_0"))) 108 | RIGHT_PATTERN = AstConstant("0", 0) 109 | REPLACEMENT_OPCODE = m_goto 110 | 111 | def check_candidate(self, opcode, left_candidate, right_candidate): 112 | if opcode == m_jnz: 113 | self.jump_replacement_block_serial = self.jump_original_block_serial 114 | else: 115 | self.jump_replacement_block_serial = self.direct_block_serial 116 | return True 117 | 118 | 119 | class JnzRule7(JumpOptimizationRule): 120 | ORIGINAL_JUMP_OPCODES = [m_jnz, m_jz] 121 | LEFT_PATTERN = AstNode(m_and, 122 | AstLeaf("x_0"), 123 | AstConstant("c_1")) 124 | RIGHT_PATTERN = AstConstant("c_2") 125 | REPLACEMENT_OPCODE = m_goto 126 | 127 | def check_candidate(self, opcode, left_candidate, right_candidate): 128 | tmp = left_candidate["c_1"].value & right_candidate["c_2"].value 129 | if tmp == right_candidate["c_2"].value: 130 | return False 131 | if opcode == m_jnz: 132 | self.jump_replacement_block_serial = self.jump_original_block_serial 133 | else: 134 | self.jump_replacement_block_serial = self.direct_block_serial 135 | return True 136 | 137 | 138 | class JnzRule8(JumpOptimizationRule): 139 | ORIGINAL_JUMP_OPCODES = [m_jnz, m_jz] 140 | PATTERN = AstNode(m_or, 141 | AstLeaf("x_0"), 142 | AstConstant("c_1")) 143 | RIGHT_PATTERN = AstConstant("c_2") 144 | REPLACEMENT_OPCODE = m_goto 145 | 146 | def check_candidate(self, opcode, left_candidate, right_candidate): 147 | tmp = left_candidate["c_1"].value & right_candidate["c_2"].value 148 | if tmp == left_candidate["c_1"].value: 149 | return False 150 | 151 | if opcode == m_jnz: 152 | self.jump_replacement_block_serial = self.jump_original_block_serial 153 | else: 154 | self.jump_replacement_block_serial = self.direct_block_serial 155 | return True 156 | 157 | 158 | class JbRule1(JumpOptimizationRule): 159 | ORIGINAL_JUMP_OPCODES = [m_jb] 160 | PATTERN = AstNode(m_xdu, 161 | AstNode(m_and, 162 | AstLeaf("x_0"), 163 | AstConstant("1", 1))) 164 | RIGHT_PATTERN = AstConstant("2", 2) 165 | REPLACEMENT_OPCODE = m_goto 166 | 167 | def check_candidate(self, opcode, left_candidate, right_candidate): 168 | self.jump_replacement_block_serial = self.jump_original_block_serial 169 | return True 170 | 171 | 172 | class JaeRule1(JumpOptimizationRule): 173 | ORIGINAL_JUMP_OPCODES = [m_jae] 174 | PATTERN = AstNode(m_and, 175 | AstLeaf("x_0"), 176 | AstConstant("c_1")) 177 | RIGHT_PATTERN = AstConstant("c_2") 178 | REPLACEMENT_OPCODE = m_goto 179 | 180 | def check_candidate(self, opcode, left_candidate, right_candidate): 181 | if left_candidate["c_1"].value >= right_candidate["c_2"].value: 182 | return False 183 | self.jump_replacement_block_serial = self.direct_block_serial 184 | return True 185 | -------------------------------------------------------------------------------- /d810/optimizers/flow/jumps/tricks.py: -------------------------------------------------------------------------------- 1 | from ida_hexrays import * 2 | 3 | from d810.ast import AstLeaf, AstConstant, AstNode 4 | from d810.hexrays_helpers import equal_mops_bypass_xdu, equal_bnot_mop 5 | from d810.optimizers.flow.jumps.handler import JumpOptimizationRule 6 | 7 | 8 | class CompareConstantRule1(JumpOptimizationRule): 9 | ORIGINAL_JUMP_OPCODES = [m_jge] 10 | LEFT_PATTERN = AstNode(m_and, 11 | AstNode(m_or, AstLeaf("xdu_x_0"), AstConstant("c_2")), 12 | AstNode(m_or, 13 | AstNode(m_xor, AstLeaf("x_0"), AstConstant("c_1")), 14 | AstNode(m_bnot, AstNode(m_sub, AstLeaf("x_0"), AstConstant("c_1"))))) 15 | RIGHT_PATTERN = AstConstant("0", 0) 16 | 17 | REPLACEMENT_OPCODE = m_jl 18 | REPLACEMENT_LEFT_PATTERN = AstLeaf("x_0") 19 | REPLACEMENT_RIGHT_PATTERN = AstLeaf("c_1") 20 | 21 | def check_candidate(self, opcode, left_candidate, right_candidate): 22 | if not equal_mops_bypass_xdu(left_candidate["xdu_x_0"].mop, left_candidate["x_0"].mop): 23 | return False 24 | if not equal_bnot_mop(left_candidate["c_2"].mop, left_candidate["c_1"].mop): 25 | return False 26 | self.jump_replacement_block_serial = self.jump_original_block_serial 27 | return True 28 | 29 | 30 | class CompareConstantRule2(JumpOptimizationRule): 31 | ORIGINAL_JUMP_OPCODES = [m_jge] 32 | LEFT_PATTERN = AstNode(m_or, 33 | AstNode(m_xdu, 34 | AstNode(m_and, 35 | AstNode(m_bnot, AstLeaf("x_0")), AstConstant("c_1"))), 36 | AstNode(m_and, 37 | AstNode(m_sub, AstLeaf('xdu_x_0'), AstConstant('xdu_c_1')), 38 | AstNode(m_bnot, AstNode(m_xdu, AstNode(m_xor, AstLeaf('xdu1_x_0'), AstConstant('xdu_c_1')))))) 39 | RIGHT_PATTERN = AstConstant("0", 0) 40 | 41 | REPLACEMENT_OPCODE = m_jge 42 | REPLACEMENT_LEFT_PATTERN = AstLeaf("x_0") 43 | REPLACEMENT_RIGHT_PATTERN = AstLeaf("c_1") 44 | 45 | def check_candidate(self, opcode, left_candidate, right_candidate): 46 | if not equal_mops_bypass_xdu(left_candidate["xdu_x_0"].mop, left_candidate["x_0"].mop): 47 | return False 48 | if not equal_mops_bypass_xdu(left_candidate["xdu1_x_0"].mop, left_candidate["x_0"].mop): 49 | return False 50 | self.jump_replacement_block_serial = self.jump_original_block_serial 51 | return True 52 | 53 | 54 | class CompareConstantRule3(JumpOptimizationRule): 55 | ORIGINAL_JUMP_OPCODES = [m_jge] 56 | LEFT_PATTERN = AstNode(m_and, 57 | AstNode(m_sub, AstLeaf('x_0'), AstConstant('c_1')), 58 | AstNode(m_bnot, AstLeaf("x_0"))) 59 | RIGHT_PATTERN = AstConstant("0", 0) 60 | 61 | REPLACEMENT_OPCODE = m_jg 62 | REPLACEMENT_LEFT_PATTERN = AstLeaf("x_0") 63 | REPLACEMENT_RIGHT_PATTERN = AstLeaf("c_1") 64 | 65 | def check_candidate(self, opcode, left_candidate, right_candidate): 66 | self.jump_replacement_block_serial = self.jump_original_block_serial 67 | return True 68 | 69 | 70 | class CompareConstantRule4(JumpOptimizationRule): 71 | ORIGINAL_JUMP_OPCODES = [m_jl, m_jge] 72 | LEFT_PATTERN = AstNode(m_and, 73 | AstNode(m_or, 74 | AstNode(m_bnot, 75 | AstNode(m_sub, 76 | AstLeaf('x_0'), 77 | AstConstant('c_1'))), 78 | AstNode(m_xor, 79 | AstLeaf('x_0'), 80 | AstConstant('c_1'))), 81 | AstNode(m_or, 82 | AstLeaf("xdu_x_0"), 83 | AstConstant('bnot_c_1'))) 84 | 85 | RIGHT_PATTERN = AstConstant("0", 0) 86 | 87 | REPLACEMENT_OPCODE = m_jge 88 | REPLACEMENT_LEFT_PATTERN = AstLeaf("x_0") 89 | REPLACEMENT_RIGHT_PATTERN = AstLeaf("c_1") 90 | 91 | def check_candidate(self, opcode, left_candidate, right_candidate): 92 | print("dflighdrth") 93 | if not equal_mops_bypass_xdu(left_candidate["xdu_x_0"].mop, left_candidate["x_0"].mop): 94 | return False 95 | if not equal_bnot_mop(left_candidate["c_1"].mop, left_candidate["bnot_c_1"].mop): 96 | return False 97 | self.jump_replacement_block_serial = self.jump_original_block_serial 98 | return True 99 | -------------------------------------------------------------------------------- /d810/optimizers/handler.py: -------------------------------------------------------------------------------- 1 | from ida_hexrays import * 2 | 3 | from d810.hexrays_formatters import string_to_maturity 4 | 5 | DEFAULT_INSTRUCTION_MATURITIES = [MMAT_LOCOPT, MMAT_CALLS, MMAT_GLBOPT1] 6 | DEFAULT_FLOW_MATURITIES = [MMAT_CALLS, MMAT_GLBOPT1] 7 | 8 | 9 | class OptimizationRule(object): 10 | NAME = None 11 | DESCRIPTION = None 12 | 13 | def __init__(self): 14 | self.maturities = [] 15 | self.config = {} 16 | self.log_dir = None 17 | 18 | def set_log_dir(self, log_dir): 19 | self.log_dir = log_dir 20 | 21 | def configure(self, kwargs): 22 | self.config = kwargs if kwargs is not None else {} 23 | if "maturities" in self.config.keys(): 24 | self.maturities = [string_to_maturity(x) for x in self.config["maturities"]] 25 | 26 | @property 27 | def name(self): 28 | if self.NAME is not None: 29 | return self.NAME 30 | return self.__class__.__name__ 31 | 32 | @property 33 | def description(self): 34 | if self.DESCRIPTION is not None: 35 | return self.DESCRIPTION 36 | return "No description available" 37 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/__init__.py: -------------------------------------------------------------------------------- 1 | from d810.optimizers.instructions.chain import CHAIN_RULES, ChainOptimizer 2 | from d810.optimizers.instructions.pattern_matching import PATTERN_MATCHING_RULES, PatternOptimizer 3 | from d810.optimizers.instructions.z3 import Z3_RULES, Z3Optimizer 4 | from d810.optimizers.instructions.analysis import INSTRUCTION_ANALYSIS_RULES, InstructionAnalyzer 5 | from d810.optimizers.instructions.early import EARLY_RULES, EarlyOptimizer 6 | 7 | KNOWN_INS_RULES = PATTERN_MATCHING_RULES + CHAIN_RULES + Z3_RULES + EARLY_RULES + INSTRUCTION_ANALYSIS_RULES 8 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/analysis/__init__.py: -------------------------------------------------------------------------------- 1 | from d810.utils import get_all_subclasses 2 | from d810.optimizers.instructions.analysis.handler import InstructionAnalyzer, InstructionAnalysisRule 3 | from d810.optimizers.instructions.analysis.pattern_guess import * 4 | 5 | INSTRUCTION_ANALYSIS_RULES = CHAIN_RULES = [x() for x in get_all_subclasses(InstructionAnalysisRule)] 6 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/analysis/handler.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from ida_hexrays import * 3 | from d810.hexrays_formatters import format_minsn_t 4 | from d810.optimizers.instructions.handler import InstructionOptimizer, InstructionOptimizationRule 5 | 6 | 7 | optimizer_logger = logging.getLogger('D810.optimizer') 8 | 9 | 10 | class InstructionAnalysisRule(InstructionOptimizationRule): 11 | def analyze_instruction(self, blk, ins): 12 | raise NotImplementedError 13 | 14 | 15 | class InstructionAnalyzer(InstructionOptimizer): 16 | RULE_CLASSES = [InstructionAnalysisRule] 17 | 18 | def set_maturity(self, maturity: int): 19 | self.cur_maturity = maturity 20 | for rule in self.rules: 21 | rule.set_maturity(self.cur_maturity) 22 | 23 | def analyze(self, blk: mblock_t, ins: minsn_t): 24 | if blk is not None: 25 | self.cur_maturity = blk.mba.maturity 26 | 27 | if self.cur_maturity not in self.maturities: 28 | return None 29 | 30 | for rule in self.rules: 31 | try: 32 | rule.analyze_instruction(blk, ins) 33 | except RuntimeError: 34 | optimizer_logger.error("error during rule {0} for instruction {1}".format(rule, format_minsn_t(ins))) 35 | return None 36 | 37 | 38 | @property 39 | def name(self): 40 | if self.NAME is not None: 41 | return self.NAME 42 | return self.__class__.__name__ 43 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/analysis/pattern_guess.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from d810.ast import minsn_to_ast 4 | from d810.hexrays_formatters import format_minsn_t, format_mop_t, maturity_to_string 5 | 6 | from d810.optimizers.handler import DEFAULT_INSTRUCTION_MATURITIES 7 | from d810.optimizers.instructions.analysis.handler import InstructionAnalysisRule 8 | from d810.optimizers.instructions.analysis.utils import get_possible_patterns 9 | 10 | 11 | class ExampleGuessingRule(InstructionAnalysisRule): 12 | DESCRIPTION = "Detect pattern with variable used multiple times and with multiple different opcodes" 13 | 14 | def __init__(self): 15 | super().__init__() 16 | self.maturities = DEFAULT_INSTRUCTION_MATURITIES 17 | self.cur_maturity = None 18 | self.min_nb_var = 1 19 | self.max_nb_var = 3 20 | self.min_nb_diff_opcodes = 3 21 | self.max_nb_diff_opcodes = -1 22 | 23 | self.cur_index = 0 24 | self.max_index = 1000 25 | self.cur_ins_guessed = [""] * self.max_index 26 | self.pattern_filename_path = None 27 | 28 | def log_info(self, message): 29 | with open(self.pattern_filename_path, "a") as f: 30 | f.write('{0}\n'.format(message)) 31 | 32 | def set_maturity(self, maturity): 33 | self.log_info("Patterns guessed at maturity {0}".format(maturity_to_string(maturity))) 34 | self.cur_maturity = maturity 35 | 36 | def set_log_dir(self, log_dir): 37 | super().set_log_dir(log_dir) 38 | self.pattern_filename_path = os.path.join(self.log_dir, "pattern_guess.log") 39 | f = open(self.pattern_filename_path, "w") 40 | f.close() 41 | 42 | def configure(self, kwargs): 43 | super().configure(kwargs) 44 | if "min_nb_var" in kwargs.keys(): 45 | self.min_nb_var = kwargs["min_nb_var"] 46 | if "max_nb_var" in kwargs.keys(): 47 | self.max_nb_var = kwargs["max_nb_var"] 48 | if "min_nb_diff_opcodes" in kwargs.keys(): 49 | self.min_nb_diff_opcodes = kwargs["min_nb_diff_opcodes"] 50 | if "max_nb_diff_opcodes" in kwargs.keys(): 51 | self.max_nb_diff_opcodes = kwargs["max_nb_diff_opcodes"] 52 | 53 | if self.max_nb_var == -1: 54 | self.max_nb_var = 0xff 55 | if self.max_nb_diff_opcodes == -1: 56 | self.max_nb_diff_opcodes = 0xff 57 | 58 | def analyze_instruction(self, blk, ins): 59 | if self.cur_maturity not in self.maturities: 60 | return None 61 | formatted_ins = str(format_minsn_t(ins)) 62 | if formatted_ins in self.cur_ins_guessed: 63 | return False 64 | tmp = minsn_to_ast(ins) 65 | if tmp is None: 66 | return False 67 | is_good_candidate = self.check_if_possible_pattern(tmp) 68 | if is_good_candidate: 69 | self.cur_ins_guessed[self.cur_index] = formatted_ins 70 | self.cur_index = (self.cur_index + 1) % self.max_index 71 | return is_good_candidate 72 | 73 | def check_if_possible_pattern(self, test_ast): 74 | patterns = get_possible_patterns(test_ast, min_nb_use=2, ref_ast_info_by_index=None, max_nb_pattern=64) 75 | for pattern in patterns: 76 | leaf_info_list, cst_leaf_values, opcodes = pattern.get_information() 77 | leaf_nb_use = [leaf_info.number_of_use for leaf_info in leaf_info_list] 78 | if not(self.min_nb_var <= len(leaf_info_list) <= self.max_nb_var): 79 | continue 80 | if not(self.min_nb_diff_opcodes <= len(set(opcodes)) <= self.max_nb_diff_opcodes): 81 | continue 82 | if not(min(leaf_nb_use) >= 2): 83 | continue 84 | ins = pattern.mop.d 85 | self.log_info("IR: 0x{0:x} - {1}".format(ins.ea, format_minsn_t(ins))) 86 | for leaf_info in leaf_info_list: 87 | self.log_info(" {0} -> {1}".format(leaf_info.ast, format_mop_t(leaf_info.ast.mop))) 88 | self.log_info("Pattern: {0}".format(pattern)) 89 | self.log_info("AstNode: {0}\n".format(pattern.get_pattern())) 90 | return True 91 | return False 92 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/analysis/utils.py: -------------------------------------------------------------------------------- 1 | from d810.ast import AstNode, AstLeaf 2 | 3 | 4 | def get_possible_patterns(ast, min_nb_use=2, ref_ast_info_by_index=None, max_nb_pattern=64): 5 | # max_nb_pattern is used to prevent memory explosion when very large patterns are parsed 6 | if ast.is_leaf(): 7 | return [ast] 8 | if ref_ast_info_by_index is None: 9 | if ast.ast_index not in ast.sub_ast_info_by_index.keys(): 10 | ast.compute_sub_ast() 11 | ref_ast_info_by_index = ast.sub_ast_info_by_index 12 | possible_patterns = [] 13 | if ref_ast_info_by_index[ast.ast_index].number_of_use >= min_nb_use: 14 | node_as_leaf = AstLeaf("x_{0}".format(ast.ast_index)) 15 | node_as_leaf.mop = ast.mop 16 | node_as_leaf.ast_index = ast.ast_index 17 | possible_patterns.append(node_as_leaf) 18 | left_patterns = [] 19 | right_patterns = [] 20 | if ast.left is not None: 21 | left_patterns = get_possible_patterns(ast.left, min_nb_use, ref_ast_info_by_index, max_nb_pattern) 22 | if ast.right is not None: 23 | right_patterns = get_possible_patterns(ast.right, min_nb_use, ref_ast_info_by_index, max_nb_pattern) 24 | 25 | for left_pattern in left_patterns: 26 | if ast.right is not None: 27 | for right_pattern in right_patterns: 28 | node = AstNode(ast.opcode, left_pattern, right_pattern) 29 | node.mop = ast.mop 30 | node.ast_index = ast.ast_index 31 | if len(possible_patterns) < max_nb_pattern: 32 | possible_patterns.append(node) 33 | else: 34 | node = AstNode(ast.opcode, left_pattern) 35 | node.mop = ast.mop 36 | node.ast_index = ast.ast_index 37 | if len(possible_patterns) < max_nb_pattern: 38 | possible_patterns.append(node) 39 | return possible_patterns 40 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/chain/__init__.py: -------------------------------------------------------------------------------- 1 | from d810.utils import get_all_subclasses 2 | from d810.optimizers.instructions.chain.handler import ChainSimplificationRule, ChainOptimizer 3 | from d810.optimizers.instructions.chain.chain_rules import * 4 | 5 | CHAIN_RULES = [x() for x in get_all_subclasses(ChainSimplificationRule)] 6 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/chain/handler.py: -------------------------------------------------------------------------------- 1 | from d810.optimizers.instructions.handler import InstructionOptimizationRule, InstructionOptimizer 2 | 3 | 4 | class ChainSimplificationRule(InstructionOptimizationRule): 5 | pass 6 | 7 | 8 | class ChainOptimizer(InstructionOptimizer): 9 | RULE_CLASSES = [ChainSimplificationRule] 10 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/early/__init__.py: -------------------------------------------------------------------------------- 1 | from d810.utils import get_all_subclasses 2 | from d810.optimizers.instructions.early.handler import EarlyRule, EarlyOptimizer 3 | from d810.optimizers.instructions.early.mem_read import * 4 | 5 | EARLY_RULES = [x() for x in get_all_subclasses(EarlyRule)] 6 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/early/handler.py: -------------------------------------------------------------------------------- 1 | from d810.optimizers.instructions.handler import GenericPatternRule, InstructionOptimizer 2 | 3 | 4 | class EarlyRule(GenericPatternRule): 5 | pass 6 | 7 | 8 | class EarlyOptimizer(InstructionOptimizer): 9 | RULE_CLASSES = [EarlyRule] 10 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/early/mem_read.py: -------------------------------------------------------------------------------- 1 | from ida_hexrays import * 2 | from idaapi import SEGPERM_READ, SEGPERM_WRITE, xrefblk_t, getseg, segment_t, XREF_DATA, dr_W, is_loaded 3 | 4 | from d810.optimizers.instructions.early.handler import EarlyRule 5 | from d810.ast import AstLeaf, AstConstant, AstNode 6 | 7 | 8 | class SetGlobalVariablesToZero(EarlyRule): 9 | DESCRIPTION = "This rule can be used to patch memory read" 10 | 11 | PATTERN = AstNode(m_mov, AstLeaf("ro_dword")) 12 | REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_res")) 13 | 14 | def __init__(self): 15 | super().__init__() 16 | self.ro_dword_min_ea = None 17 | self.ro_dword_max_ea = None 18 | 19 | def configure(self, kwargs): 20 | super().configure(kwargs) 21 | self.ro_dword_min_ea = None 22 | self.ro_dword_max_ea = None 23 | if "ro_dword_min_ea" in kwargs.keys(): 24 | self.ro_dword_min_ea = int(kwargs["ro_dword_min_ea"], 16) 25 | if "ro_dword_max_ea" in kwargs.keys(): 26 | self.ro_dword_max_ea = int(kwargs["ro_dword_max_ea"], 16) 27 | 28 | def check_candidate(self, candidate): 29 | if (self.ro_dword_min_ea is None) or (self.ro_dword_max_ea is None): 30 | return False 31 | if candidate["ro_dword"].mop.t != mop_v: 32 | return False 33 | mem_read_address = candidate["ro_dword"].mop.g 34 | if not(self.ro_dword_min_ea <= mem_read_address <= self.ro_dword_max_ea): 35 | return False 36 | 37 | candidate.add_constant_leaf("val_res", 0, candidate["ro_dword"].mop.size) 38 | return True 39 | 40 | 41 | # This rule is from 42 | # https://www.carbonblack.com/blog/defeating-compiler-level-obfuscations-used-in-apt10-malware/ 43 | class SetGlobalVariablesToZeroIfDetectedReadOnly(EarlyRule): 44 | DESCRIPTION = "WARNING: Use it only if you know what you are doing as it may patch data not related to obfuscation" 45 | 46 | PATTERN = AstNode(m_mov, AstLeaf("ro_dword")) 47 | REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_res")) 48 | 49 | def __init__(self): 50 | super().__init__() 51 | # If we optimized too early (in MMAT_GENERATED), we may replace something like 52 | # 'mov &($dword_10020CC8).4, eoff.4' by 'mov #0.4, eoff.4' 53 | # and this will lead to incorrect decompilation where MEMORY[0] is used 54 | # Thus, we explicitly specify the MMAT_PREOPTIMIZED maturity. 55 | self.maturities = [MMAT_PREOPTIMIZED] 56 | 57 | def is_read_only_inited_var(self, address): 58 | s: segment_t = getseg(address) 59 | if s is None: 60 | return False 61 | if s.perm != (SEGPERM_READ | SEGPERM_WRITE): 62 | return False 63 | if is_loaded(address): 64 | return False 65 | ref_finder = xrefblk_t() 66 | is_ok = ref_finder.first_to(address, XREF_DATA) 67 | while is_ok: 68 | if ref_finder.type == dr_W: 69 | return False 70 | is_ok = ref_finder.next_to() 71 | return True 72 | 73 | def check_candidate(self, candidate): 74 | mem_read_address = None 75 | if candidate["ro_dword"].mop.t == mop_v: 76 | mem_read_address = candidate["ro_dword"].mop.g 77 | elif candidate["ro_dword"].mop.t == mop_a: 78 | if candidate["ro_dword"].mop.a.t == mop_v: 79 | mem_read_address = candidate["ro_dword"].mop.a.g 80 | 81 | if mem_read_address is None: 82 | return False 83 | 84 | if not self.is_read_only_inited_var(mem_read_address): 85 | return False 86 | candidate.add_constant_leaf("val_res", 0, candidate["ro_dword"].mop.size) 87 | return True 88 | 89 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/handler.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import logging 3 | from typing import List 4 | from ida_hexrays import * 5 | 6 | from d810.optimizers.handler import OptimizationRule 7 | from d810.hexrays_formatters import format_minsn_t 8 | from d810.ast import minsn_to_ast, AstNode 9 | from d810.errors import D810Exception 10 | 11 | 12 | d810_logger = logging.getLogger('D810') 13 | optimizer_logger = logging.getLogger('D810.optimizer') 14 | 15 | 16 | class InstructionOptimizationRule(OptimizationRule): 17 | def __init__(self): 18 | super().__init__() 19 | self.maturities = [] 20 | 21 | def check_and_replace(self, blk, ins): 22 | return None 23 | 24 | 25 | class GenericPatternRule(InstructionOptimizationRule): 26 | PATTERN = None 27 | PATTERNS = None 28 | REPLACEMENT_PATTERN = None 29 | 30 | def __init__(self): 31 | super().__init__() 32 | self.pattern_candidates = [self.PATTERN] 33 | if self.PATTERNS is not None: 34 | self.pattern_candidates += self.PATTERNS 35 | 36 | def check_candidate(self, candidate: AstNode): 37 | # Perform rule specific checks 38 | return False 39 | 40 | def get_valid_candidates(self, instruction: minsn_t, stop_early=True): 41 | valid_candidates = [] 42 | tmp = minsn_to_ast(instruction) 43 | if tmp is None: 44 | return [] 45 | for candidate_pattern in self.pattern_candidates: 46 | if not candidate_pattern.check_pattern_and_copy_mops(tmp): 47 | continue 48 | if not self.check_candidate(candidate_pattern): 49 | continue 50 | valid_candidates.append(candidate_pattern) 51 | if stop_early: 52 | return valid_candidates 53 | return [] 54 | 55 | def get_replacement(self, candidate: AstNode): 56 | is_ok = self.REPLACEMENT_PATTERN.update_leafs_mop(candidate) 57 | if not is_ok: 58 | return None 59 | new_ins = self.REPLACEMENT_PATTERN.create_minsn(candidate.ea, candidate.dst_mop) 60 | return new_ins 61 | 62 | def check_and_replace(self, blk: mblock_t, instruction: minsn_t): 63 | valid_candidates = self.get_valid_candidates(instruction, stop_early=True) 64 | if len(valid_candidates) == 0: 65 | return None 66 | new_instruction = self.get_replacement(valid_candidates[0]) 67 | return new_instruction 68 | 69 | @property 70 | def description(self): 71 | if self.DESCRIPTION is not None: 72 | return self.DESCRIPTION 73 | if (self.PATTERN is None) or (self.REPLACEMENT_PATTERN is None): 74 | return "" 75 | self.PATTERN.reset_mops() 76 | self.REPLACEMENT_PATTERN.reset_mops() 77 | return "{0} => {1}".format(self.PATTERN, self.REPLACEMENT_PATTERN) 78 | 79 | 80 | class InstructionOptimizer(object): 81 | RULE_CLASSES = [] 82 | NAME = None 83 | 84 | def __init__(self, maturities: List[int], log_dir=None): 85 | self.rules = set() 86 | self.rules_usage_info = {} 87 | self.maturities = maturities 88 | self.log_dir = log_dir 89 | self.cur_maturity = MMAT_PREOPTIMIZED 90 | 91 | def add_rule(self, rule: InstructionOptimizationRule): 92 | is_valid_rule_class = False 93 | for rule_class in self.RULE_CLASSES: 94 | if isinstance(rule, rule_class): 95 | is_valid_rule_class = True 96 | break 97 | if not is_valid_rule_class: 98 | return False 99 | optimizer_logger.debug("Adding rule {0}".format(rule)) 100 | if len(rule.maturities) == 0: 101 | rule.maturities = self.maturities 102 | self.rules.add(rule) 103 | self.rules_usage_info[rule.name] = 0 104 | return True 105 | 106 | def reset_rule_usage_statistic(self): 107 | self.rules_usage_info = {} 108 | for rule in self.rules: 109 | self.rules_usage_info[rule.name] = 0 110 | 111 | def show_rule_usage_statistic(self): 112 | for rule_name, rule_nb_match in self.rules_usage_info.items(): 113 | if rule_nb_match > 0: 114 | d810_logger.info("Instruction Rule '{0}' has been used {1} times".format(rule_name, rule_nb_match)) 115 | 116 | def get_optimized_instruction(self, blk: mblock_t, ins: minsn_t): 117 | if blk is not None: 118 | self.cur_maturity = blk.mba.maturity 119 | # if self.cur_maturity not in self.maturities: 120 | # return None 121 | for rule in self.rules: 122 | if self.cur_maturity not in rule.maturities: 123 | continue 124 | try: 125 | new_ins = rule.check_and_replace(blk, ins) 126 | if new_ins is not None: 127 | self.rules_usage_info[rule.name] += 1 128 | optimizer_logger.info("Rule {0} matched:".format(rule.name)) 129 | optimizer_logger.info(" orig: {0}".format(format_minsn_t(ins))) 130 | optimizer_logger.info(" new : {0}".format(format_minsn_t(new_ins))) 131 | return new_ins 132 | except RuntimeError as e: 133 | optimizer_logger.error("Runtime error during rule {0} for instruction {1}: {2}".format(rule, format_minsn_t(ins), e)) 134 | except D810Exception as e: 135 | optimizer_logger.error("D810Exception during rule {0} for instruction {1}: {2}".format(rule, format_minsn_t(ins), e)) 136 | return None 137 | 138 | @property 139 | def name(self): 140 | if self.NAME is not None: 141 | return self.NAME 142 | return self.__class__.__name__ 143 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/pattern_matching/__init__.py: -------------------------------------------------------------------------------- 1 | from d810.utils import get_all_subclasses 2 | from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule, PatternOptimizer 3 | from d810.optimizers.instructions.pattern_matching.rewrite_add import * 4 | from d810.optimizers.instructions.pattern_matching.rewrite_and import * 5 | from d810.optimizers.instructions.pattern_matching.rewrite_bnot import * 6 | from d810.optimizers.instructions.pattern_matching.rewrite_cst import * 7 | from d810.optimizers.instructions.pattern_matching.rewrite_mov import * 8 | from d810.optimizers.instructions.pattern_matching.rewrite_mul import * 9 | from d810.optimizers.instructions.pattern_matching.rewrite_neg import * 10 | from d810.optimizers.instructions.pattern_matching.rewrite_predicates import * 11 | from d810.optimizers.instructions.pattern_matching.rewrite_or import * 12 | from d810.optimizers.instructions.pattern_matching.rewrite_sub import * 13 | from d810.optimizers.instructions.pattern_matching.rewrite_xor import * 14 | from d810.optimizers.instructions.pattern_matching.weird import * 15 | from d810.optimizers.instructions.pattern_matching.experimental import * 16 | 17 | PATTERN_MATCHING_RULES = [x() for x in get_all_subclasses(PatternMatchingRule)] 18 | 19 | 20 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/pattern_matching/experimental.py: -------------------------------------------------------------------------------- 1 | from ida_hexrays import * 2 | 3 | from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule 4 | from d810.ast import AstLeaf, AstConstant, AstNode 5 | from d810.hexrays_formatters import format_mop_t 6 | 7 | class ReplaceMovHigh(PatternMatchingRule): 8 | PATTERN = AstNode(m_mov, 9 | AstConstant('c_0')) 10 | REPLACEMENT_PATTERN = AstNode(m_or, AstConstant("new_c_0"), AstNode(m_and, AstLeaf("new_reg"), AstConstant("mask"))) 11 | 12 | def check_candidate(self, candidate): 13 | # IDA does not do constant propagation for pattern such as: 14 | # mov #0x65A4.2, r6.2 15 | # mov #0x210F.2, r6^2.2 16 | # jz r0.4, r6.4 17 | # Thus, we try to detect mov to r6^2 and replace by (or #0x210F0000.4, r6.4 & 0x0000ffff.4, r6.4 18 | # By doing that, IDA constant propagation will work again. 19 | 20 | if candidate.dst_mop.t != mop_r: 21 | return False 22 | dst_reg_name = format_mop_t(candidate.dst_mop) 23 | if dst_reg_name is None: 24 | return False 25 | if "^2" in dst_reg_name: 26 | if candidate["c_0"].mop.size != 2: 27 | return False 28 | candidate.add_constant_leaf("new_c_0", candidate["c_0"].value << 16, 4) 29 | candidate.add_constant_leaf("mask", 0xffff, 4) 30 | new_dst_reg = mop_t() 31 | new_dst_reg.make_reg(candidate.dst_mop.r - 2, 4) 32 | candidate.add_leaf("new_reg", new_dst_reg) 33 | candidate.dst_mop = new_dst_reg 34 | return True 35 | else: 36 | return False 37 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/pattern_matching/rewrite_add.py: -------------------------------------------------------------------------------- 1 | from ida_hexrays import * 2 | 3 | from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule 4 | from d810.ast import AstLeaf, AstConstant, AstNode 5 | from d810.hexrays_helpers import equal_ignore_msb_cst, equal_bnot_mop, AND_TABLE 6 | 7 | 8 | class Add_HackersDelightRule_1(PatternMatchingRule): 9 | PATTERN = AstNode(m_sub, 10 | AstLeaf("x_0"), 11 | AstNode(m_sub, 12 | AstNode(m_bnot, 13 | AstLeaf("x_1")), 14 | AstConstant("1", 1))) 15 | REPLACEMENT_PATTERN = AstNode(m_add, AstLeaf("x_0"), AstLeaf("x_1")) 16 | 17 | 18 | class Add_HackersDelightRule_2(PatternMatchingRule): 19 | PATTERN = AstNode(m_add, 20 | AstNode(m_xor, 21 | AstLeaf("x_0"), 22 | AstLeaf("x_1")), 23 | AstNode(m_mul, 24 | AstConstant("2", 2), 25 | AstNode(m_and, 26 | AstLeaf("x_0"), 27 | AstLeaf("x_1")))) 28 | REPLACEMENT_PATTERN = AstNode(m_add, AstLeaf("x_0"), AstLeaf("x_1")) 29 | 30 | 31 | class Add_HackersDelightRule_3(PatternMatchingRule): 32 | PATTERN = AstNode(m_add, 33 | AstNode(m_or, 34 | AstLeaf("x_0"), 35 | AstLeaf("x_1")), 36 | AstNode(m_and, 37 | AstLeaf("x_0"), 38 | AstLeaf("x_1"))) 39 | REPLACEMENT_PATTERN = AstNode(m_add, AstLeaf("x_0"), AstLeaf("x_1")) 40 | 41 | 42 | class Add_HackersDelightRule_4(PatternMatchingRule): 43 | PATTERN = AstNode(m_sub, 44 | AstNode(m_mul, 45 | AstConstant("2", 2), 46 | AstNode(m_or, 47 | AstLeaf("x_0"), 48 | AstLeaf("x_1"))), 49 | AstNode(m_xor, 50 | AstLeaf("x_0"), 51 | AstLeaf("x_1"))) 52 | REPLACEMENT_PATTERN = AstNode(m_add, AstLeaf("x_0"), AstLeaf("x_1")) 53 | 54 | 55 | class Add_HackersDelightRule_5(PatternMatchingRule): 56 | PATTERN = AstNode(m_sub, 57 | AstNode(m_mul, 58 | AstConstant("2", 2), 59 | AstNode(m_or, 60 | AstNode(m_or, 61 | AstLeaf("x_0"), 62 | AstLeaf("x_1")), 63 | AstLeaf("x_2"))), 64 | AstNode(m_xor, 65 | AstLeaf("x_0"), 66 | AstNode(m_or, 67 | AstLeaf("x_1"), 68 | AstLeaf("x_2")))) 69 | REPLACEMENT_PATTERN = AstNode(m_add, AstLeaf("x_0"), AstNode(m_or, AstLeaf("x_1"), AstLeaf("x_2"))) 70 | 71 | 72 | class Add_SpecialConstantRule_1(PatternMatchingRule): 73 | PATTERN = AstNode(m_add, 74 | AstNode(m_xor, 75 | AstLeaf("x_0"), 76 | AstConstant("c_1")), 77 | AstNode(m_mul, 78 | AstConstant("2", 2), 79 | AstNode(m_and, 80 | AstLeaf("x_0"), 81 | AstConstant("c_2")))) 82 | REPLACEMENT_PATTERN = AstNode(m_add, AstLeaf("x_0"), AstConstant("c_1")) 83 | 84 | def check_candidate(self, candidate): 85 | return equal_ignore_msb_cst(candidate["c_1"].mop, candidate["c_2"].mop) 86 | 87 | 88 | class Add_SpecialConstantRule_2(PatternMatchingRule): 89 | PATTERN = AstNode(m_add, 90 | AstNode(m_xor, 91 | AstNode(m_and, 92 | AstLeaf("x_0"), 93 | AstConstant("val_ff", 0xff)), 94 | AstConstant("c_1")), 95 | AstNode(m_mul, 96 | AstConstant("2", 2), 97 | AstNode(m_and, 98 | AstLeaf("x_0"), 99 | AstConstant("c_2")))) 100 | REPLACEMENT_PATTERN = AstNode(m_add, AstLeaf("x_0"), AstConstant("c_1")) 101 | 102 | def check_candidate(self, candidate): 103 | return (candidate["c_1"].value & 0xff) == candidate["c_2"].value 104 | 105 | 106 | class Add_SpecialConstantRule_3(PatternMatchingRule): 107 | PATTERN = AstNode(m_add, 108 | AstNode(m_xor, 109 | AstLeaf("x_0"), 110 | AstConstant("c_1")), 111 | AstNode(m_mul, 112 | AstConstant("2", 2), 113 | AstNode(m_or, 114 | AstLeaf("x_0"), 115 | AstConstant("c_2")))) 116 | REPLACEMENT_PATTERN = AstNode(m_add, AstLeaf("x_0"), AstConstant("val_res")) 117 | 118 | def check_candidate(self, candidate): 119 | if not equal_bnot_mop(candidate["c_1"].mop, candidate["c_2"].mop): 120 | return False 121 | candidate.add_constant_leaf("val_res", candidate["c_2"].value - 1, candidate["x_0"].size) 122 | return True 123 | 124 | 125 | class Add_OllvmRule_1(PatternMatchingRule): 126 | PATTERN = AstNode(m_add, 127 | AstNode(m_bnot, 128 | AstNode(m_xor, 129 | AstLeaf('x_0'), 130 | AstLeaf('x_1'))), 131 | AstNode(m_mul, 132 | AstConstant("2", 2), 133 | AstNode(m_or, 134 | AstLeaf('x_1'), 135 | AstLeaf('x_0')))) 136 | REPLACEMENT_PATTERN = AstNode(m_sub, 137 | AstNode(m_add, 138 | AstLeaf('x_0'), 139 | AstLeaf('x_1')), 140 | AstConstant("val_1")) 141 | 142 | def check_candidate(self, candidate): 143 | candidate.add_constant_leaf("val_1", 1, candidate.size) 144 | return True 145 | 146 | 147 | class Add_OllvmRule_2(PatternMatchingRule): 148 | PATTERN = AstNode(m_sub, 149 | AstNode(m_bnot, 150 | AstNode(m_xor, 151 | AstLeaf('x_0'), 152 | AstLeaf('x_1'))), 153 | AstNode(m_mul, 154 | AstConstant("val_fe"), 155 | AstNode(m_or, 156 | AstLeaf('x_0'), 157 | AstLeaf('x_1')))) 158 | REPLACEMENT_PATTERN = AstNode(m_sub, 159 | AstNode(m_add, 160 | AstLeaf('x_0'), 161 | AstLeaf('x_1')), 162 | AstConstant("val_1")) 163 | 164 | def check_candidate(self, candidate): 165 | if (candidate["val_fe"].value + 2) & AND_TABLE[candidate["val_fe"].size] != 0: 166 | return False 167 | candidate.add_constant_leaf("val_1", 1, candidate.size) 168 | return True 169 | 170 | 171 | class Add_OllvmRule_3(PatternMatchingRule): 172 | PATTERN = AstNode(m_add, 173 | AstNode(m_xor, 174 | AstLeaf('x_0'), 175 | AstLeaf('x_1')), 176 | AstNode(m_mul, 177 | AstConstant("2", 2), 178 | AstNode(m_and, 179 | AstLeaf('x_0'), 180 | AstLeaf('x_1')))) 181 | REPLACEMENT_PATTERN = AstNode(m_add, AstLeaf('x_0'), AstLeaf('x_1')) 182 | 183 | 184 | class Add_OllvmRule_4(PatternMatchingRule): 185 | PATTERN = AstNode(m_sub, 186 | AstNode(m_xor, 187 | AstLeaf('x_0'), 188 | AstLeaf('x_1')), 189 | AstNode(m_mul, 190 | AstConstant("val_fe"), 191 | AstNode(m_and, 192 | AstLeaf('x_0'), 193 | AstLeaf('x_1')))) 194 | REPLACEMENT_PATTERN = AstNode(m_add, AstLeaf('x_0'), AstLeaf('x_1')) 195 | 196 | 197 | class AddXor_Rule_1(PatternMatchingRule): 198 | PATTERN = AstNode(m_sub, 199 | AstNode(m_sub, 200 | AstLeaf("x_0"), 201 | AstLeaf("x_1")), 202 | AstNode(m_mul, 203 | AstConstant("2", 2), 204 | AstNode(m_or, 205 | AstLeaf("x_0"), 206 | AstLeaf("bnot_x_1")))) 207 | REPLACEMENT_PATTERN = AstNode(m_add, 208 | AstNode(m_xor, 209 | AstLeaf("x_0"), 210 | AstLeaf("x_1")), 211 | AstConstant("val_2")) 212 | 213 | def check_candidate(self, candidate): 214 | if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): 215 | return False 216 | candidate.add_constant_leaf("val_2", 2, candidate["x_0"].size) 217 | return True 218 | 219 | 220 | class AddXor_Rule_2(PatternMatchingRule): 221 | PATTERN = AstNode(m_sub, 222 | AstNode(m_sub, 223 | AstLeaf("x_0"), 224 | AstLeaf("x_1")), 225 | AstNode(m_mul, 226 | AstConstant("2", 2), 227 | AstNode(m_bnot, 228 | AstNode(m_and, 229 | AstLeaf("bnot_x_0"), 230 | AstLeaf("x_1"))))) 231 | 232 | REPLACEMENT_PATTERN = AstNode(m_add, 233 | AstNode(m_xor, 234 | AstLeaf("x_0"), 235 | AstLeaf("x_1")), 236 | AstLeaf("val_2")) 237 | 238 | def check_candidate(self, candidate): 239 | if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): 240 | return False 241 | candidate.add_constant_leaf("val_2", 2, candidate["x_0"].size) 242 | return True 243 | 244 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/pattern_matching/rewrite_and.py: -------------------------------------------------------------------------------- 1 | from ida_hexrays import * 2 | 3 | from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule 4 | from d810.ast import AstLeaf, AstConstant, AstNode 5 | from d810.hexrays_helpers import equal_bnot_mop, SUB_TABLE 6 | 7 | 8 | class And_HackersDelightRule_1(PatternMatchingRule): 9 | PATTERN = AstNode(m_sub, 10 | AstNode(m_or, 11 | AstNode(m_bnot, 12 | AstLeaf("x_0")), 13 | AstLeaf("x_1")), 14 | AstNode(m_bnot, AstLeaf("x_0"))) 15 | 16 | REPLACEMENT_PATTERN = AstNode(m_and, AstLeaf("x_0"), AstLeaf("x_1")) 17 | 18 | 19 | class And_HackersDelightRule_2(PatternMatchingRule): 20 | PATTERN = AstNode(m_add, 21 | AstNode(m_or, 22 | AstLeaf("bnot_x_0"), 23 | AstLeaf("x_1")), 24 | AstNode(m_add, 25 | AstLeaf("x_0"), 26 | AstConstant("1", 1))) 27 | 28 | REPLACEMENT_PATTERN = AstNode(m_and, AstLeaf("x_0"), AstLeaf("x_1")) 29 | 30 | def check_candidate(self, candidate): 31 | return equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop) 32 | 33 | 34 | class And_HackersDelightRule_3(PatternMatchingRule): 35 | PATTERN = AstNode(m_sub, 36 | AstNode(m_add, 37 | AstLeaf("x_0"), 38 | AstLeaf("x_1")), 39 | AstNode(m_or, 40 | AstLeaf("x_0"), 41 | AstLeaf("x_1"))) 42 | 43 | REPLACEMENT_PATTERN = AstNode(m_and, AstLeaf("x_0"), AstLeaf("x_1")) 44 | 45 | 46 | class And_HackersDelightRule_4(PatternMatchingRule): 47 | PATTERN = AstNode(m_sub, 48 | AstNode(m_or, 49 | AstLeaf('x_0'), 50 | AstLeaf('x_1')), 51 | AstNode(m_xor, 52 | AstLeaf('x_0'), 53 | AstLeaf('x_1'))) 54 | REPLACEMENT_PATTERN = AstNode(m_and, AstLeaf('x_0'), AstLeaf('x_1')) 55 | 56 | 57 | class And_OllvmRule_1(PatternMatchingRule): 58 | PATTERN = AstNode(m_and, 59 | AstNode(m_or, 60 | AstLeaf('x_0'), 61 | AstLeaf('x_1')), 62 | AstNode(m_bnot, 63 | AstNode(m_xor, 64 | AstLeaf('x_0'), 65 | AstLeaf('x_1')))) 66 | REPLACEMENT_PATTERN = AstNode(m_and, AstLeaf('x_0'), AstLeaf('x_1')) 67 | 68 | 69 | class And_OllvmRule_2(PatternMatchingRule): 70 | PATTERN = AstNode(m_and, 71 | AstNode(m_or, 72 | AstLeaf('x_0'), 73 | AstLeaf('x_1')), 74 | AstNode(m_xor, 75 | AstLeaf('x_0'), 76 | AstLeaf('bnot_x_1'))) 77 | REPLACEMENT_PATTERN = AstNode(m_and, AstLeaf('x_0'), AstLeaf('x_1')) 78 | 79 | def check_candidate(self, candidate): 80 | return equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop) 81 | 82 | 83 | class And_OllvmRule_3(PatternMatchingRule): 84 | PATTERN = AstNode(m_and, 85 | AstNode(m_and, 86 | AstLeaf('x_0'), 87 | AstLeaf('x_1')), 88 | AstNode(m_bnot, 89 | AstNode(m_xor, 90 | AstLeaf('x_0'), 91 | AstLeaf('x_1')))) 92 | REPLACEMENT_PATTERN = AstNode(m_and, AstLeaf('x_0'), AstLeaf('x_1')) 93 | 94 | 95 | 96 | class And_FactorRule_1(PatternMatchingRule): 97 | PATTERN = AstNode(m_and, 98 | AstNode(m_xor, 99 | AstLeaf("x_0"), 100 | AstLeaf("bnot_x_1")), 101 | AstLeaf("x_1")) 102 | REPLACEMENT_PATTERN = AstNode(m_and, AstLeaf("x_0"), AstLeaf("x_1")) 103 | 104 | def check_candidate(self, candidate): 105 | return equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop) 106 | 107 | 108 | class And_FactorRule_2(PatternMatchingRule): 109 | PATTERN = AstNode(m_and, 110 | AstLeaf('x_0'), 111 | AstNode(m_bnot, 112 | AstNode(m_xor, 113 | AstLeaf('x_0'), 114 | AstLeaf('x_1')))) 115 | REPLACEMENT_PATTERN = AstNode(m_and, AstLeaf('x_0'), AstLeaf('x_1')) 116 | 117 | 118 | class AndBnot_HackersDelightRule_1(PatternMatchingRule): 119 | PATTERN = AstNode(m_sub, 120 | AstNode(m_or, 121 | AstLeaf("x_0"), 122 | AstLeaf("x_1")), 123 | AstLeaf("x_1")) 124 | REPLACEMENT_PATTERN = AstNode(m_and, 125 | AstLeaf("x_0"), 126 | AstNode(m_bnot, AstLeaf("x_1"))) 127 | 128 | 129 | class AndBnot_HackersDelightRule_2(PatternMatchingRule): 130 | PATTERN = AstNode(m_sub, 131 | AstLeaf("x_0"), 132 | AstNode(m_and, 133 | AstLeaf("x_0"), 134 | AstLeaf("x_1"))) 135 | REPLACEMENT_PATTERN = AstNode(m_and, 136 | AstLeaf("x_0"), 137 | AstNode(m_bnot, AstLeaf("x_1"))) 138 | 139 | 140 | class AndBnot_FactorRule_1(PatternMatchingRule): 141 | PATTERN = AstNode(m_xor, 142 | AstLeaf("x_0"), 143 | AstNode(m_and, 144 | AstLeaf("x_0"), 145 | AstLeaf("x_1"))) 146 | 147 | REPLACEMENT_PATTERN = AstNode(m_and, 148 | AstLeaf("x_0"), 149 | AstNode(m_bnot, AstLeaf("x_1"))) 150 | 151 | 152 | class AndBnot_FactorRule_2(PatternMatchingRule): 153 | PATTERN = AstNode(m_and, 154 | AstLeaf("x_0"), 155 | AstNode(m_xor, 156 | AstLeaf("x_0"), 157 | AstLeaf("x_1"))) 158 | 159 | REPLACEMENT_PATTERN = AstNode(m_and, 160 | AstLeaf("x_0"), 161 | AstNode(m_bnot, AstLeaf("x_1"))) 162 | 163 | 164 | class AndBnot_FactorRule_3(PatternMatchingRule): 165 | PATTERN = AstNode(m_xor, 166 | AstNode(m_or, 167 | AstLeaf("x_0"), 168 | AstLeaf("x_1")), 169 | AstLeaf("x_1")) 170 | 171 | REPLACEMENT_PATTERN = AstNode(m_and, 172 | AstLeaf("x_0"), 173 | AstNode(m_bnot, AstLeaf("x_1"))) 174 | 175 | 176 | class AndBnot_FactorRule_4(PatternMatchingRule): 177 | PATTERN = AstNode(m_and, 178 | AstNode(m_xor, 179 | AstLeaf('x_1'), 180 | AstLeaf('x_0')), 181 | AstNode(m_bnot, 182 | AstNode(m_and, 183 | AstLeaf('x_0'), 184 | AstLeaf('bnot_x_1')))) 185 | 186 | REPLACEMENT_PATTERN = AstNode(m_and, 187 | AstLeaf("x_1"), 188 | AstNode(m_bnot, AstLeaf("x_0"))) 189 | 190 | def check_candidate(self, candidate): 191 | return equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop) 192 | 193 | 194 | class AndOr_FactorRule_1(PatternMatchingRule): 195 | PATTERN = AstNode(m_or, 196 | AstNode(m_and, 197 | AstLeaf("x_0"), 198 | AstLeaf("x_2")), 199 | AstNode(m_and, 200 | AstLeaf("x_1"), 201 | AstLeaf("x_2"))) 202 | REPLACEMENT_PATTERN = AstNode(m_and, 203 | AstNode(m_or, 204 | AstLeaf("x_0"), 205 | AstLeaf("x_1")), 206 | AstLeaf("x_2")) 207 | 208 | 209 | class AndXor_FactorRule_1(PatternMatchingRule): 210 | PATTERN = AstNode(m_xor, 211 | AstNode(m_and, 212 | AstLeaf("x_0"), 213 | AstLeaf("x_2")), 214 | AstNode(m_and, 215 | AstLeaf("x_1"), 216 | AstLeaf("x_2"))) 217 | REPLACEMENT_PATTERN = AstNode(m_and, 218 | AstNode(m_xor, 219 | AstLeaf("x_0"), 220 | AstLeaf("x_1")), 221 | AstLeaf("x_2")) 222 | 223 | 224 | class And1_MbaRule_1(PatternMatchingRule): 225 | PATTERN = AstNode(m_and, 226 | AstNode(m_mul, AstLeaf("x_0"), AstLeaf("x_0")), 227 | AstConstant("3", 3)) 228 | REPLACEMENT_PATTERN = AstNode(m_and, 229 | AstLeaf("x_0"), 230 | AstConstant("val_1")) 231 | 232 | def check_candidate(self, candidate): 233 | candidate.add_constant_leaf("val_1", 1, candidate.size) 234 | return True 235 | 236 | 237 | class AndGetUpperBits_FactorRule_1(PatternMatchingRule): 238 | PATTERN = AstNode(m_mul, 239 | AstConstant("c_1"), 240 | AstNode(m_and, 241 | AstNode(m_shr, 242 | AstLeaf('x_0'), 243 | AstConstant("c_2")), 244 | AstConstant("c_3"))) 245 | 246 | REPLACEMENT_PATTERN = AstNode(m_and, AstLeaf('x_0'), AstConstant("c_res")) 247 | 248 | def check_candidate(self, candidate): 249 | if (2 ** candidate["c_2"].value) != candidate["c_1"].value: 250 | return False 251 | c_res = (SUB_TABLE[candidate["c_1"].size] - candidate["c_1"].value) & candidate["c_3"].value 252 | candidate.add_constant_leaf("c_res", c_res, candidate["x_0"].size) 253 | return True 254 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/pattern_matching/rewrite_bnot.py: -------------------------------------------------------------------------------- 1 | from ida_hexrays import * 2 | 3 | from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule 4 | from d810.ast import AstLeaf, AstConstant, AstNode 5 | from d810.hexrays_helpers import equal_bnot_mop, SUB_TABLE 6 | 7 | 8 | class Bnot_HackersDelightRule_1(PatternMatchingRule): 9 | PATTERN = AstNode(m_sub, 10 | AstNode(m_neg, 11 | AstLeaf("x_0")), 12 | AstConstant("1", 1)) 13 | REPLACEMENT_PATTERN = AstNode(m_bnot, AstLeaf("x_0")) 14 | 15 | 16 | class Bnot_HackersDelightRule_2(PatternMatchingRule): 17 | PATTERN = AstNode(m_or, 18 | AstNode(m_bnot, 19 | AstNode(m_or, 20 | AstLeaf("x_0"), 21 | AstLeaf("x_1"))), 22 | AstNode(m_bnot, 23 | AstLeaf("x_1"))) 24 | REPLACEMENT_PATTERN = AstNode(m_bnot, AstLeaf("x_1")) 25 | 26 | 27 | class Bnot_MbaRule_1(PatternMatchingRule): 28 | PATTERN = AstNode(m_sub, 29 | AstNode(m_sub, 30 | AstLeaf("x_0"), 31 | AstConstant("1", 1)), 32 | AstNode(m_mul, 33 | AstConstant("2", 2), 34 | AstLeaf("x_0"))) 35 | REPLACEMENT_PATTERN = AstNode(m_bnot, AstLeaf("x_0")) 36 | 37 | 38 | class Bnot_FactorRule_1(PatternMatchingRule): 39 | PATTERN = AstNode(m_xor, 40 | AstNode(m_bnot, 41 | AstNode(m_xor, 42 | AstLeaf("x_0"), 43 | AstLeaf("x_1"))), 44 | AstLeaf("x_1")) 45 | REPLACEMENT_PATTERN = AstNode(m_bnot, AstLeaf("x_0")) 46 | 47 | 48 | class Bnot_FactorRule_2(PatternMatchingRule): 49 | PATTERN = AstNode(m_sub, 50 | AstConstant("minus_1"), 51 | AstLeaf("x_0")) 52 | REPLACEMENT_PATTERN = AstNode(m_bnot, AstLeaf("x_0")) 53 | 54 | def check_candidate(self, candidate): 55 | if candidate["minus_1"].value != SUB_TABLE[candidate["minus_1"].size] - 1: 56 | return False 57 | return True 58 | 59 | 60 | class Bnot_FactorRule_3(PatternMatchingRule): 61 | PATTERN = AstNode(m_xor, 62 | AstNode(m_and, 63 | AstLeaf('x_0'), 64 | AstLeaf('x_1')), 65 | AstNode(m_or, 66 | AstLeaf('x_0'), 67 | AstLeaf('bnot_x_1'))) 68 | REPLACEMENT_PATTERN = AstNode(m_bnot, AstLeaf("x_1")) 69 | 70 | def check_candidate(self, candidate): 71 | if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): 72 | return False 73 | return True 74 | 75 | 76 | class Bnot_FactorRule_4(PatternMatchingRule): 77 | PATTERN = AstNode(m_xor, 78 | AstNode(m_bnot, AstLeaf('x_0')), 79 | AstNode(m_bnot, AstLeaf('x_1'))) 80 | REPLACEMENT_PATTERN = AstNode(m_xor, AstLeaf('x_0'), AstLeaf("x_1")) 81 | 82 | 83 | class BnotXor_Rule_1(PatternMatchingRule): 84 | PATTERN = AstNode(m_or, 85 | AstNode(m_and, 86 | AstLeaf("x_0"), 87 | AstLeaf("x_1")), 88 | AstNode(m_and, 89 | AstLeaf("bnot_x_0"), 90 | AstLeaf("bnot_x_1"))) 91 | 92 | REPLACEMENT_PATTERN = AstNode(m_bnot, 93 | AstNode(m_xor, AstLeaf("x_0"), AstLeaf("x_1"))) 94 | 95 | def check_candidate(self, candidate): 96 | if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): 97 | return False 98 | if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): 99 | return False 100 | return True 101 | 102 | 103 | class BnotXor_Rule_2(PatternMatchingRule): 104 | PATTERN = AstNode(m_xor, 105 | AstNode(m_or, 106 | AstLeaf('x_0'), 107 | AstLeaf('x_1')), 108 | AstNode(m_or, 109 | AstLeaf('bnot_x_0'), 110 | AstLeaf('bnot_x_1'))) 111 | 112 | REPLACEMENT_PATTERN = AstNode(m_bnot, 113 | AstNode(m_xor, AstLeaf("x_0"), AstLeaf("x_1"))) 114 | 115 | def check_candidate(self, candidate): 116 | if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): 117 | return False 118 | if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): 119 | return False 120 | return True 121 | 122 | class BnotXor_Rule_3(PatternMatchingRule): 123 | PATTERN = AstNode(m_and, 124 | AstNode(m_or, 125 | AstLeaf('x_0'), 126 | AstLeaf('bnot_x_1')), 127 | AstNode(m_or, 128 | AstLeaf('bnot_x_0'), 129 | AstLeaf('x_1'))) 130 | 131 | REPLACEMENT_PATTERN = AstNode(m_bnot, 132 | AstNode(m_xor, AstLeaf("x_0"), AstLeaf("x_1"))) 133 | 134 | def check_candidate(self, candidate): 135 | if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): 136 | return False 137 | if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): 138 | return False 139 | return True 140 | 141 | 142 | class BnotXor_FactorRule_1(PatternMatchingRule): 143 | PATTERN = AstNode(m_xor, 144 | AstLeaf("x_0"), 145 | AstNode(m_bnot, 146 | AstLeaf("x_1"))) 147 | 148 | REPLACEMENT_PATTERN = AstNode(m_bnot, 149 | AstNode(m_xor, AstLeaf("x_0"), AstLeaf("x_1"))) 150 | 151 | 152 | class BnotAnd_FactorRule_1(PatternMatchingRule): 153 | PATTERN = AstNode(m_or, 154 | AstNode(m_xor, 155 | AstLeaf("x_0"), 156 | AstLeaf("x_1")), 157 | AstNode(m_bnot, 158 | AstNode(m_or, 159 | AstLeaf("x_0"), 160 | AstLeaf("x_1")))) 161 | 162 | REPLACEMENT_PATTERN = AstNode(m_bnot, 163 | AstNode(m_and, AstLeaf("x_0"), AstLeaf("x_1"))) 164 | 165 | 166 | 167 | class BnotAnd_FactorRule_2(PatternMatchingRule): 168 | PATTERN = AstNode(m_or, 169 | AstNode(m_or, 170 | AstLeaf("bnot_x_0"), 171 | AstLeaf("bnot_x_1")), 172 | AstNode(m_xor, 173 | AstLeaf("x_0"), 174 | AstLeaf("x_1"))) 175 | 176 | REPLACEMENT_PATTERN = AstNode(m_bnot, 177 | AstNode(m_and, AstLeaf("x_0"), AstLeaf("x_1"))) 178 | 179 | def check_candidate(self, candidate): 180 | if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): 181 | return False 182 | if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): 183 | return False 184 | return True 185 | 186 | 187 | class BnotAnd_FactorRule_3(PatternMatchingRule): 188 | PATTERN = AstNode(m_or, 189 | AstNode(m_bnot, 190 | AstLeaf("x_0")), 191 | AstNode(m_bnot, 192 | AstLeaf("x_1"))) 193 | 194 | REPLACEMENT_PATTERN = AstNode(m_bnot, 195 | AstNode(m_and, AstLeaf("x_0"), AstLeaf("x_1"))) 196 | 197 | 198 | class BnotAnd_FactorRule_4(PatternMatchingRule): 199 | PATTERN = AstNode(m_or, 200 | AstLeaf("bnot_x_0"), 201 | AstNode(m_xor, 202 | AstLeaf("x_0"), 203 | AstLeaf("x_1"))) 204 | REPLACEMENT_PATTERN = AstNode(m_bnot, 205 | AstNode(m_and, AstLeaf("x_0"), AstLeaf("x_1"))) 206 | 207 | def check_candidate(self, candidate): 208 | if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): 209 | return False 210 | return True 211 | 212 | 213 | class BnotOr_FactorRule_1(PatternMatchingRule): 214 | PATTERN = AstNode(m_and, 215 | AstNode(m_bnot, 216 | AstLeaf("x_0")), 217 | AstNode(m_bnot, 218 | AstLeaf("x_1"))) 219 | 220 | REPLACEMENT_PATTERN = AstNode(m_bnot, 221 | AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1"))) 222 | 223 | 224 | class BnotAdd_MbaRule_1(PatternMatchingRule): 225 | PATTERN = AstNode(m_sub, 226 | AstNode(m_xor, 227 | AstLeaf("x_0"), 228 | AstLeaf("bnot_x_1")), 229 | AstNode(m_mul, 230 | AstConstant("2", 2), 231 | AstNode(m_and, 232 | AstLeaf("x_0"), 233 | AstLeaf("x_1")))) 234 | 235 | REPLACEMENT_PATTERN = AstNode(m_bnot, 236 | AstNode(m_add, AstLeaf("x_0"), AstLeaf("x_1"))) 237 | 238 | def check_candidate(self, candidate): 239 | if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): 240 | return False 241 | return True 242 | 243 | 244 | class Bnot_Rule_1(PatternMatchingRule): 245 | PATTERN = AstNode(m_or, 246 | AstNode(m_and, 247 | AstLeaf("x_0"), 248 | AstLeaf("bnot_x_1")), 249 | AstNode(m_bnot, 250 | AstNode(m_or, 251 | AstLeaf("x_0"), 252 | AstLeaf("x_1")))) 253 | 254 | REPLACEMENT_PATTERN = AstNode(m_mov, AstLeaf("bnot_x_1")) 255 | 256 | def check_candidate(self, candidate): 257 | if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): 258 | return False 259 | return True 260 | 261 | 262 | class Bnot_XorRule_1(PatternMatchingRule): 263 | PATTERN = AstNode(m_or, 264 | AstNode(m_and, 265 | AstLeaf("x_0"), 266 | AstLeaf("x_1")), 267 | AstNode(m_bnot, 268 | AstNode(m_or, 269 | AstLeaf("x_0"), 270 | AstLeaf("x_1")))) 271 | 272 | REPLACEMENT_PATTERN = AstNode(m_bnot, AstNode(m_xor, AstLeaf("x_0"), AstLeaf("x_1"))) 273 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/pattern_matching/rewrite_mov.py: -------------------------------------------------------------------------------- 1 | from ida_hexrays import * 2 | 3 | 4 | from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule 5 | from d810.ast import AstLeaf, AstConstant, AstNode 6 | from d810.hexrays_helpers import equal_bnot_mop, AND_TABLE 7 | 8 | 9 | # GetIdentRule1: ((x_0 & x_1) + (x_0 & ~x_1)) == x_0 10 | class GetIdentRule1(PatternMatchingRule): 11 | PATTERN = AstNode(m_add, 12 | AstNode(m_and, 13 | AstLeaf('x_0'), 14 | AstLeaf('x_1')), 15 | AstNode(m_and, 16 | AstLeaf('x_0'), 17 | AstLeaf('bnot_x_1'))) 18 | REPLACEMENT_PATTERN = AstNode(m_mov, AstLeaf("x_0")) 19 | 20 | def check_candidate(self, candidate): 21 | if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): 22 | return False 23 | return True 24 | 25 | 26 | # GetIdentRule2: ((x_0 & x_1) ^ (x_0 & ~x_1)) == x_0 i 27 | class GetIdentRule2(PatternMatchingRule): 28 | PATTERN = AstNode(m_xor, 29 | AstNode(m_and, 30 | AstLeaf('x_0'), 31 | AstLeaf('x_1')), 32 | AstNode(m_and, 33 | AstLeaf('x_0'), 34 | AstLeaf('bnot_x_1'))) 35 | REPLACEMENT_PATTERN = AstNode(m_mov, AstLeaf("x_0")) 36 | 37 | def check_candidate(self, candidate): 38 | if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): 39 | return False 40 | return True 41 | 42 | 43 | class GetIdentRule3(PatternMatchingRule): 44 | PATTERN = AstNode(m_and, 45 | AstLeaf("x_0"), 46 | AstNode(m_or, 47 | AstLeaf("x_0"), 48 | AstLeaf("x_1"))) 49 | 50 | REPLACEMENT_PATTERN = AstNode(m_mov, AstLeaf("x_0")) 51 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/pattern_matching/rewrite_mul.py: -------------------------------------------------------------------------------- 1 | from ida_hexrays import * 2 | 3 | from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule 4 | from d810.ast import AstLeaf, AstConstant, AstNode 5 | from d810.hexrays_helpers import equal_bnot_mop, is_check_mop, SUB_TABLE 6 | 7 | 8 | class Mul_MbaRule_1(PatternMatchingRule): 9 | PATTERN = AstNode(m_add, 10 | AstNode(m_mul, 11 | AstNode(m_or, 12 | AstLeaf('x_0'), 13 | AstLeaf('x_1')), 14 | AstNode(m_and, 15 | AstLeaf('x_0'), 16 | AstLeaf('x_1'))), 17 | AstNode(m_mul, 18 | AstNode(m_and, 19 | AstLeaf('x_0'), 20 | AstLeaf('bnot_x_1')), 21 | AstNode(m_and, 22 | AstLeaf('x_1'), 23 | AstLeaf('bnot_x_0')))) 24 | REPLACEMENT_PATTERN = AstNode(m_mul, AstLeaf("x_0"), AstLeaf("x_1")) 25 | 26 | def check_candidate(self, candidate): 27 | if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): 28 | return False 29 | if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): 30 | return False 31 | return True 32 | 33 | 34 | class Mul_MbaRule_2(PatternMatchingRule): 35 | PATTERN = AstNode(m_add, 36 | AstNode(m_mul, 37 | AstNode(m_or, 38 | AstLeaf('x_0'), 39 | AstConstant('c_1')), 40 | AstLeaf('x_0')), 41 | AstNode(m_mul, 42 | AstNode(m_and, 43 | AstLeaf('x_0'), 44 | AstConstant('bnot_c_1')), 45 | AstNode(m_and, 46 | AstConstant('c_1'), 47 | AstLeaf('bnot_x_0')))) 48 | REPLACEMENT_PATTERN = AstNode(m_mul, AstLeaf("x_0"), AstConstant('c_1')) 49 | 50 | def check_candidate(self, candidate): 51 | if not is_check_mop(candidate["x_0"].mop): 52 | return False 53 | if candidate["c_1"].value & 0x1 != 1: 54 | return False 55 | if not equal_bnot_mop(candidate["c_1"].mop, candidate["bnot_c_1"].mop): 56 | return False 57 | if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): 58 | return False 59 | return True 60 | 61 | 62 | class Mul_MbaRule_3(PatternMatchingRule): 63 | PATTERN = AstNode(m_add, 64 | AstNode(m_mul, 65 | AstNode(m_or, 66 | AstLeaf('x_0'), 67 | AstConstant('c_1')), 68 | AstNode(m_and, 69 | AstLeaf('x_0'), 70 | AstConstant('c_1'))), 71 | AstNode(m_mul, 72 | AstLeaf('x_0'), 73 | AstNode(m_and, 74 | AstConstant('c_1'), 75 | AstLeaf('bnot_x_0')))) 76 | REPLACEMENT_PATTERN = AstNode(m_mul, AstLeaf("x_0"), AstConstant('c_1')) 77 | 78 | def check_candidate(self, candidate): 79 | if not is_check_mop(candidate["x_0"].mop): 80 | return False 81 | if candidate["c_1"].value & 0x1 == 1: 82 | return False 83 | if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): 84 | return False 85 | return True 86 | 87 | 88 | class Mul_MbaRule_4(PatternMatchingRule): 89 | PATTERN = AstNode(m_add, 90 | AstNode(m_mul, 91 | AstNode(m_or, 92 | AstLeaf("x_0"), 93 | AstLeaf("x_1")), 94 | AstNode(m_and, 95 | AstLeaf("x_0"), 96 | AstLeaf("x_1"))), 97 | AstNode(m_mul, 98 | AstNode(m_bnot, 99 | AstNode(m_or, 100 | AstLeaf("x_0"), 101 | AstLeaf("bnot_x_1"))), 102 | AstNode(m_and, 103 | AstLeaf("x_0"), 104 | AstLeaf("bnot_x_1")))) 105 | REPLACEMENT_PATTERN = AstNode(m_mul, AstLeaf("x_0"), AstLeaf("x_1")) 106 | 107 | def check_candidate(self, candidate): 108 | if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): 109 | return False 110 | return True 111 | 112 | 113 | class Mul_FactorRule_1(PatternMatchingRule): 114 | PATTERN = AstNode(m_add, 115 | AstConstant("2", 2), 116 | AstNode(m_mul, 117 | AstConstant("2", 2), 118 | AstNode(m_add, 119 | AstLeaf("x_1"), 120 | AstNode(m_or, 121 | AstLeaf("x_0"), 122 | AstLeaf("bnot_x_1"))))) 123 | 124 | REPLACEMENT_PATTERN = AstNode(m_mul, 125 | AstConstant("2", 2), 126 | AstNode(m_and, 127 | AstLeaf("x_0"), 128 | AstLeaf("x_1"))) 129 | 130 | def check_candidate(self, candidate): 131 | if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): 132 | return False 133 | return True 134 | 135 | 136 | class Mul_FactorRule_2(PatternMatchingRule): 137 | PATTERN = AstNode(m_sub, 138 | AstNode(m_neg, 139 | AstNode(m_and, 140 | AstLeaf("x_0"), 141 | AstLeaf("x_1"))), 142 | AstNode(m_and, 143 | AstLeaf("x_0"), 144 | AstLeaf("x_1"))) 145 | REPLACEMENT_PATTERN = AstNode(m_mul, 146 | AstConstant("val_fe"), 147 | AstNode(m_and, 148 | AstLeaf("x_0"), 149 | AstLeaf("x_1"))) 150 | 151 | def check_candidate(self, candidate): 152 | candidate.add_constant_leaf("val_fe", SUB_TABLE[candidate.size] - 2, candidate.size) 153 | return True -------------------------------------------------------------------------------- /d810/optimizers/instructions/pattern_matching/rewrite_neg.py: -------------------------------------------------------------------------------- 1 | from ida_hexrays import * 2 | 3 | from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule 4 | from d810.hexrays_helpers import AND_TABLE 5 | from d810.ast import AstLeaf, AstConstant, AstNode 6 | 7 | 8 | class Neg_HackersDelightRule_1(PatternMatchingRule): 9 | PATTERN = AstNode(m_add, 10 | AstNode(m_bnot, 11 | AstLeaf("x_0")), 12 | AstConstant("1", 1)) 13 | REPLACEMENT_PATTERN = AstNode(m_neg, AstLeaf("x_0")) 14 | 15 | 16 | class Neg_HackersDelightRule_2(PatternMatchingRule): 17 | PATTERN = AstNode(m_bnot, 18 | AstNode(m_sub, 19 | AstLeaf("x_0"), 20 | AstConstant("1", 1))) 21 | REPLACEMENT_PATTERN = AstNode(m_neg, AstLeaf("x_0")) 22 | 23 | 24 | class NegAdd_HackersDelightRule_1(PatternMatchingRule): 25 | PATTERN = AstNode(m_sub, 26 | AstNode(m_xor, 27 | AstLeaf("x_0"), 28 | AstLeaf("x_1")), 29 | AstNode(m_mul, 30 | AstConstant("2", 2), 31 | AstNode(m_or, 32 | AstLeaf("x_0"), 33 | AstLeaf("x_1")))) 34 | REPLACEMENT_PATTERN = AstNode(m_neg, 35 | AstNode(m_add, 36 | AstLeaf("x_0"), 37 | AstLeaf("x_1"))) 38 | 39 | 40 | class NegAdd_HackersDelightRule_2(PatternMatchingRule): 41 | PATTERN = AstNode(m_sub, 42 | AstNode(m_xor, 43 | AstLeaf("x_0"), 44 | AstNode(m_or, 45 | AstLeaf("x_1"), 46 | AstLeaf("x_2"))), 47 | AstNode(m_mul, 48 | AstConstant("2", 2), 49 | AstNode(m_or, 50 | AstNode(m_or, 51 | AstLeaf("x_0"), 52 | AstLeaf("x_1")), 53 | AstLeaf("x_2")))) 54 | REPLACEMENT_PATTERN = AstNode(m_neg, 55 | AstNode(m_add, 56 | AstLeaf("x_0"), 57 | AstNode(m_or, 58 | AstLeaf("x_1"), 59 | AstLeaf("x_2")))) 60 | 61 | 62 | class NegAdd_HackersDelightRule_1(PatternMatchingRule): 63 | PATTERN = AstNode(m_add, 64 | AstNode(m_mul, 65 | AstConstant('val_fe'), 66 | AstNode(m_or, 67 | AstLeaf('x_0'), 68 | AstLeaf('x_1'))), 69 | AstNode(m_xor, 70 | AstLeaf('x_0'), 71 | AstLeaf('x_1'))) 72 | 73 | REPLACEMENT_PATTERN = AstNode(m_neg, 74 | AstNode(m_add, 75 | AstLeaf("x_0"), 76 | AstLeaf("x_1"))) 77 | 78 | 79 | def check_candidate(self, candidate): 80 | if (candidate["val_fe"].value + 2) & AND_TABLE[candidate["val_fe"].size] != 0: 81 | return False 82 | return True 83 | 84 | class NegOr_HackersDelightRule_1(PatternMatchingRule): 85 | PATTERN = AstNode(m_sub, 86 | AstNode(m_and, 87 | AstLeaf("x_0"), 88 | AstLeaf("x_1")), 89 | AstNode(m_add, 90 | AstLeaf("x_0"), 91 | AstLeaf("x_1"))) 92 | 93 | REPLACEMENT_PATTERN = AstNode(m_neg, 94 | AstNode(m_or, 95 | AstLeaf("x_0"), 96 | AstLeaf("x_1"))) 97 | 98 | 99 | class NegXor_HackersDelightRule_1(PatternMatchingRule): 100 | PATTERN = AstNode(m_sub, 101 | AstNode(m_and, 102 | AstLeaf('x_0'), 103 | AstLeaf('x_1')), 104 | AstNode(m_or, 105 | AstLeaf('x_0'), 106 | AstLeaf('x_1'))) 107 | REPLACEMENT_PATTERN = AstNode(m_neg, 108 | AstNode(m_xor, 109 | AstLeaf("x_0"), 110 | AstLeaf("x_1"))) 111 | 112 | 113 | class NegXor_HackersDelightRule_2(PatternMatchingRule): 114 | PATTERN = AstNode(m_sub, 115 | AstNode(m_add, 116 | AstLeaf('x_0'), 117 | AstLeaf('x_1')), 118 | AstNode(m_mul, 119 | AstConstant('2', 2), 120 | AstNode(m_or, 121 | AstLeaf('x_0'), 122 | AstLeaf('x_1')))) 123 | REPLACEMENT_PATTERN = AstNode(m_neg, 124 | AstNode(m_xor, 125 | AstLeaf("x_0"), 126 | AstLeaf("x_1"))) 127 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/pattern_matching/rewrite_or.py: -------------------------------------------------------------------------------- 1 | from ida_hexrays import * 2 | 3 | from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule 4 | from d810.ast import AstLeaf, AstConstant, AstNode 5 | from d810.hexrays_helpers import equal_bnot_mop 6 | 7 | 8 | class Or_HackersDelightRule_1(PatternMatchingRule): 9 | PATTERN = AstNode(m_add, 10 | AstNode(m_and, 11 | AstLeaf("x_0"), 12 | AstLeaf("bnot_x_1")), 13 | AstLeaf("x_1")) 14 | 15 | REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) 16 | 17 | def check_candidate(self, candidate): 18 | if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): 19 | return False 20 | return True 21 | 22 | 23 | class Or_HackersDelightRule_2(PatternMatchingRule): 24 | PATTERN = AstNode(m_sub, 25 | AstNode(m_add, 26 | AstLeaf("x_0"), 27 | AstLeaf("x_1")), 28 | AstNode(m_and, 29 | AstLeaf("x_0"), 30 | AstLeaf("x_1"))) 31 | 32 | REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) 33 | 34 | 35 | class Or_HackersDelightRule_2_variant_1(PatternMatchingRule): 36 | PATTERN = AstNode(m_sub, 37 | AstNode(m_sub, 38 | AstLeaf("x_0"), 39 | AstLeaf("x_1")), 40 | AstNode(m_and, 41 | AstLeaf("x_0"), 42 | AstNode(m_neg, AstLeaf("x_1")))) 43 | REPLACEMENT_PATTERN = AstNode(m_or, 44 | AstLeaf("x_0"), 45 | AstNode(m_neg, AstLeaf("x_1"))) 46 | 47 | 48 | class Or_MbaRule_1(PatternMatchingRule): 49 | PATTERN = AstNode(m_add, 50 | AstNode(m_and, 51 | AstLeaf("x_0"), 52 | AstLeaf("x_1")), 53 | AstNode(m_xor, 54 | AstLeaf("x_0"), 55 | AstLeaf("x_1"))) 56 | REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) 57 | 58 | 59 | class Or_MbaRule_2(PatternMatchingRule): 60 | PATTERN = AstNode(m_add, 61 | AstNode(m_add, 62 | AstNode(m_add, 63 | AstLeaf('x_0'), 64 | AstLeaf('x_1')), 65 | AstConstant('1', 1)), 66 | AstNode(m_bnot, 67 | AstNode(m_and, 68 | AstLeaf('x_1'), 69 | AstLeaf('x_0')))) 70 | REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) 71 | 72 | 73 | class Or_MbaRule_3(PatternMatchingRule): 74 | PATTERN = AstNode(m_sub, 75 | AstNode(m_add, 76 | AstLeaf('x_0'), 77 | AstNode(m_xor, 78 | AstLeaf('x_0'), 79 | AstLeaf('x_1'))), 80 | AstNode(m_and, 81 | AstLeaf('x_0'), 82 | AstNode(m_bnot, 83 | AstLeaf('x_1')))) 84 | REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) 85 | 86 | 87 | class Or_FactorRule_1(PatternMatchingRule): 88 | PATTERN = AstNode(m_or, 89 | AstNode(m_and, 90 | AstLeaf("x_0"), 91 | AstLeaf("x_1")), 92 | AstNode(m_xor, 93 | AstLeaf("x_0"), 94 | AstLeaf("x_1"))) 95 | REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) 96 | 97 | 98 | class Or_FactorRule_2(PatternMatchingRule): 99 | PATTERN = AstNode(m_or, 100 | AstNode(m_and, 101 | AstLeaf("x_0"), 102 | AstNode(m_xor, 103 | AstLeaf("x_1"), 104 | AstLeaf("x_2"))), 105 | AstNode(m_xor, 106 | AstNode(m_xor, 107 | AstLeaf("x_0"), 108 | AstLeaf("x_1")), 109 | AstLeaf("x_2"))) 110 | REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstNode(m_xor, AstLeaf("x_1"), AstLeaf("x_2"))) 111 | 112 | 113 | class Or_FactorRule_3(PatternMatchingRule): 114 | PATTERN = AstNode(m_or, 115 | AstNode(m_or, 116 | AstLeaf("x_0"), 117 | AstLeaf("x_1")), 118 | AstNode(m_xor, 119 | AstLeaf("bnot_x_0"), 120 | AstLeaf("bnot_x_1"))) 121 | 122 | REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) 123 | 124 | def check_candidate(self, candidate): 125 | if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): 126 | return False 127 | if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): 128 | return False 129 | return True 130 | 131 | 132 | class Or_OllvmRule_1(PatternMatchingRule): 133 | PATTERN = AstNode(m_or, 134 | AstNode(m_and, 135 | AstLeaf("x_0"), 136 | AstLeaf("x_1")), 137 | AstNode(m_bnot, 138 | AstNode(m_xor, 139 | AstLeaf("bnot_x_0"), 140 | AstLeaf("x_1")))) 141 | 142 | REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) 143 | 144 | def check_candidate(self, candidate): 145 | if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): 146 | return False 147 | return True 148 | 149 | 150 | class Or_Rule_1(PatternMatchingRule): 151 | PATTERN = AstNode(m_or, 152 | AstNode(m_and, 153 | AstLeaf("bnot_x_0"), 154 | AstLeaf("x_1")), 155 | AstLeaf("x_0")) 156 | 157 | REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) 158 | 159 | def check_candidate(self, candidate): 160 | if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): 161 | return False 162 | return True 163 | 164 | 165 | class Or_Rule_2(PatternMatchingRule): 166 | PATTERN = AstNode(m_or, 167 | AstNode(m_xor, 168 | AstLeaf("x_0"), 169 | AstLeaf("x_1")), 170 | AstLeaf("x_1")) 171 | 172 | REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) 173 | 174 | 175 | class Or_Rule_3(PatternMatchingRule): 176 | PATTERN = AstNode(m_or, 177 | AstNode(m_bnot, 178 | AstNode(m_or, 179 | AstLeaf('bnot_x_0'), 180 | AstLeaf('bnot_x_1'))), 181 | AstNode(m_xor, 182 | AstLeaf('x_0'), 183 | AstLeaf('x_1'))) 184 | 185 | REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) 186 | 187 | def check_candidate(self, candidate): 188 | if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): 189 | return False 190 | if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): 191 | return False 192 | return True 193 | 194 | 195 | class Or_Rule_4(PatternMatchingRule): 196 | PATTERN = AstNode(m_xor, 197 | AstNode(m_and, 198 | AstLeaf("x_0"), 199 | AstLeaf("x_1")), 200 | AstNode(m_xor, 201 | AstLeaf("x_0"), 202 | AstLeaf("x_1"))) 203 | 204 | REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) 205 | 206 | 207 | class OrBnot_FactorRule_1(PatternMatchingRule): 208 | PATTERN = AstNode(m_xor, 209 | AstNode(m_bnot, 210 | AstLeaf("x_0")), 211 | AstNode(m_and, 212 | AstLeaf("x_0"), 213 | AstLeaf("x_1"))) 214 | REPLACEMENT_PATTERN = AstNode(m_or, 215 | AstNode(m_bnot, 216 | AstLeaf("x_0")), 217 | AstLeaf("x_1")) 218 | 219 | 220 | class OrBnot_FactorRule_2(PatternMatchingRule): 221 | PATTERN = AstNode(m_xor, 222 | AstLeaf("x_0"), 223 | AstNode(m_and, 224 | AstNode(m_bnot, 225 | AstLeaf("x_0")), 226 | AstLeaf("x_1"))) 227 | REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstLeaf("x_1")) 228 | 229 | 230 | class OrBnot_FactorRule_3(PatternMatchingRule): 231 | PATTERN = AstNode(m_add, 232 | AstNode(m_sub, 233 | AstLeaf("x_0"), 234 | AstLeaf("x_1")), 235 | AstNode(m_or, 236 | AstLeaf("bnot_x_0"), 237 | AstLeaf("x_1"))) 238 | REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstNode(m_bnot, AstLeaf("x_1"))) 239 | 240 | def check_candidate(self, candidate): 241 | if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): 242 | return False 243 | return True 244 | 245 | 246 | class OrBnot_FactorRule_4(PatternMatchingRule): 247 | PATTERN = AstNode(m_xor, 248 | AstNode(m_or, 249 | AstLeaf("bnot_x_0"), 250 | AstLeaf("x_1")), 251 | AstNode(m_xor, 252 | AstLeaf("x_0"), 253 | AstLeaf("x_1"))) 254 | REPLACEMENT_PATTERN = AstNode(m_or, AstLeaf("x_0"), AstNode(m_bnot, AstLeaf("x_1"))) 255 | 256 | def check_candidate(self, candidate): 257 | if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): 258 | return False 259 | return True 260 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/pattern_matching/rewrite_sub.py: -------------------------------------------------------------------------------- 1 | from ida_hexrays import * 2 | 3 | from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule 4 | from d810.ast import AstLeaf, AstConstant, AstNode 5 | from d810.hexrays_helpers import equal_bnot_mop, SUB_TABLE 6 | 7 | 8 | class Sub_HackersDelightRule_1(PatternMatchingRule): 9 | PATTERN = AstNode(m_add, 10 | AstLeaf("x_0"), 11 | AstNode(m_add, 12 | AstNode(m_bnot, 13 | AstLeaf("x_1")), 14 | AstConstant("1", 1))) 15 | REPLACEMENT_PATTERN = AstNode(m_sub, AstLeaf("x_0"), AstLeaf("x_1")) 16 | 17 | 18 | class Sub_HackersDelightRule_2(PatternMatchingRule): 19 | PATTERN = AstNode(m_sub, 20 | AstNode(m_xor, 21 | AstLeaf("x_0"), 22 | AstLeaf("x_1")), 23 | AstNode(m_mul, 24 | AstConstant("2", 2), 25 | AstNode(m_and, 26 | AstNode(m_bnot, 27 | AstLeaf("x_0")), 28 | AstLeaf("x_1")))) 29 | REPLACEMENT_PATTERN = AstNode(m_sub, AstLeaf("x_0"), AstLeaf("x_1")) 30 | 31 | 32 | class Sub_HackersDelightRule_3(PatternMatchingRule): 33 | PATTERN = AstNode(m_sub, 34 | AstNode(m_and, 35 | AstLeaf("x_0"), 36 | AstLeaf("bnot_x_1")), 37 | AstNode(m_and, 38 | AstLeaf("bnot_x_0"), 39 | AstLeaf("x_1"))) 40 | REPLACEMENT_PATTERN = AstNode(m_sub, AstLeaf("x_0"), AstLeaf("x_1")) 41 | 42 | def check_candidate(self, candidate): 43 | if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): 44 | return False 45 | if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): 46 | return False 47 | return True 48 | 49 | 50 | class Sub_HackersDelightRule_4(PatternMatchingRule): 51 | PATTERN = AstNode(m_sub, 52 | AstNode(m_mul, 53 | AstConstant("2", 2), 54 | AstNode(m_and, 55 | AstLeaf("x_0"), 56 | AstLeaf("bnot_x_1"))), 57 | AstNode(m_xor, 58 | AstLeaf("x_0"), 59 | AstLeaf("x_1"))) 60 | REPLACEMENT_PATTERN = AstNode(m_sub, AstLeaf("x_0"), AstLeaf("x_1")) 61 | 62 | def check_candidate(self, candidate): 63 | if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): 64 | return False 65 | return True 66 | 67 | 68 | class Sub1_FactorRule_1(PatternMatchingRule): 69 | PATTERN = AstNode(m_sub, 70 | AstNode(m_sub, 71 | AstNode(m_neg, 72 | AstLeaf('x_0')), 73 | AstConstant('1', 1)), 74 | AstNode(m_mul, 75 | AstConstant('c_minus_2'), 76 | AstLeaf('x_0'))) 77 | REPLACEMENT_PATTERN = AstNode(m_sub, AstLeaf("x_0"), AstConstant("val_1")) 78 | 79 | def check_candidate(self, candidate): 80 | if candidate["c_minus_2"].value != SUB_TABLE[candidate["c_minus_2"].size] - 2: 81 | return False 82 | candidate.add_constant_leaf("val_1", 1, candidate["x_0"].size) 83 | return True 84 | 85 | 86 | class Sub1_FactorRule_2(PatternMatchingRule): 87 | PATTERN = AstNode(m_add, 88 | AstNode(m_mul, 89 | AstConstant("2", 2), 90 | AstLeaf("x_0")), 91 | AstNode(m_bnot, 92 | AstLeaf("x_0"))) 93 | 94 | REPLACEMENT_PATTERN = AstNode(m_sub, AstLeaf("x_0"), AstConstant("1", 1)) 95 | 96 | 97 | class Sub1Add_HackersDelightRule_1(PatternMatchingRule): 98 | PATTERN = AstNode(m_add, 99 | AstNode(m_mul, 100 | AstConstant("2", 2), 101 | AstNode(m_or, 102 | AstLeaf("x_0"), 103 | AstLeaf("x_1"))), 104 | AstNode(m_xor, 105 | AstLeaf("x_0"), 106 | AstLeaf("bnot_x_1"))) 107 | REPLACEMENT_PATTERN = AstNode(m_sub, 108 | AstNode(m_add, 109 | AstLeaf("x_0"), 110 | AstLeaf("x_1")), 111 | AstConstant("val_1")) 112 | 113 | def check_candidate(self, candidate): 114 | if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): 115 | return False 116 | candidate.add_constant_leaf("val_1", 1, candidate["x_1"].size) 117 | return True 118 | 119 | 120 | class Sub1And_HackersDelightRule_1(PatternMatchingRule): 121 | PATTERN = AstNode(m_add, 122 | AstNode(m_or, 123 | AstLeaf("x_0"), 124 | AstLeaf("bnot_x_1")), 125 | AstLeaf("x_1")) 126 | 127 | REPLACEMENT_PATTERN = AstNode(m_sub, 128 | AstNode(m_and, 129 | AstLeaf("x_0"), 130 | AstLeaf("x_1")), 131 | AstConstant("val_1")) 132 | 133 | def check_candidate(self, candidate): 134 | if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): 135 | return False 136 | candidate.add_constant_leaf("val_1", 1, candidate["x_0"].size) 137 | return True 138 | 139 | 140 | class Sub1Or_MbaRule_1(PatternMatchingRule): 141 | PATTERN = AstNode(m_add, 142 | AstNode(m_add, 143 | AstLeaf("x_0"), 144 | AstLeaf("x_1")), 145 | AstNode(m_bnot, 146 | AstNode(m_and, 147 | AstLeaf("x_0"), 148 | AstLeaf("x_1")))) 149 | REPLACEMENT_PATTERN = AstNode(m_sub, 150 | AstNode(m_or, 151 | AstLeaf("x_0"), 152 | AstLeaf("x_1")), 153 | AstConstant("val_1")) 154 | 155 | 156 | def check_candidate(self, candidate): 157 | candidate.add_constant_leaf("val_1", 1, candidate.size) 158 | return True 159 | 160 | 161 | class Sub1And1_MbaRule_1(PatternMatchingRule): 162 | PATTERN = AstNode(m_add, 163 | AstNode(m_or, 164 | AstNode(m_bnot, 165 | AstLeaf('x_0')), 166 | AstConstant("1", 1)), 167 | AstLeaf('x_0')) 168 | REPLACEMENT_PATTERN = AstNode(m_sub, 169 | AstNode(m_and, 170 | AstLeaf('x_0'), 171 | AstConstant("val_1_1")), 172 | AstConstant("val_1_2")) 173 | 174 | def check_candidate(self, candidate): 175 | candidate.add_constant_leaf("val_1_1", 1, candidate["x_0"].size) 176 | candidate.add_constant_leaf("val_1_2", 1, candidate["x_0"].size) 177 | return True 178 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/pattern_matching/weird.py: -------------------------------------------------------------------------------- 1 | from ida_hexrays import * 2 | from d810.optimizers.instructions.pattern_matching.handler import PatternMatchingRule 3 | from d810.ast import AstLeaf, AstConstant, AstNode 4 | from d810.hexrays_helpers import equal_bnot_mop 5 | 6 | 7 | class WeirdRule1(PatternMatchingRule): 8 | PATTERN = AstNode(m_sub, 9 | AstLeaf("x_0"), 10 | AstNode(m_or, 11 | AstLeaf("x_0"), 12 | AstLeaf("x_1"))) 13 | REPLACEMENT_PATTERN = AstNode(m_add, 14 | AstNode(m_or, 15 | AstLeaf("x_0"), 16 | AstNode(m_bnot, AstLeaf("x_1"))), 17 | AstConstant("val_1")) 18 | 19 | def check_candidate(self, candidate): 20 | candidate.add_constant_leaf("val_1", 1, candidate.size) 21 | return True 22 | 23 | 24 | class WeirdRule2(PatternMatchingRule): 25 | PATTERN = AstNode(m_sub, 26 | AstNode(m_mul, 27 | AstConstant("2", 2), 28 | AstLeaf("x_0")), 29 | AstNode(m_and, 30 | AstLeaf("x_0"), 31 | AstNode(m_bnot, AstLeaf("x_1")))) 32 | REPLACEMENT_PATTERN = AstNode(m_add, 33 | AstLeaf("x_0"), 34 | AstNode(m_and, 35 | AstLeaf("x_0"), 36 | AstLeaf("x_1"))) 37 | 38 | 39 | class WeirdRule3(PatternMatchingRule): 40 | PATTERN = AstNode(m_sub, 41 | AstNode(m_and, 42 | AstLeaf("x_0"), 43 | AstNode(m_bnot, AstLeaf("x_1"))), 44 | AstNode(m_mul, 45 | AstConstant("2", 2), 46 | AstLeaf("x_0"))) 47 | REPLACEMENT_PATTERN = AstNode(m_neg, 48 | AstNode(m_add, 49 | AstLeaf("x_0"), 50 | AstNode(m_and, 51 | AstLeaf("x_0"), 52 | AstLeaf("x_1")))) 53 | 54 | 55 | class WeirdRule4(PatternMatchingRule): 56 | PATTERN = AstNode(m_sub, 57 | AstNode(m_and, 58 | AstLeaf("x_0"), 59 | AstLeaf("bnot_x_1")), 60 | AstNode(m_and, 61 | AstLeaf("x_0"), 62 | AstLeaf("x_1"))) 63 | REPLACEMENT_PATTERN = AstNode(m_sub, 64 | AstNode(m_xor, 65 | AstLeaf("x_0"), 66 | AstLeaf("x_1")), 67 | AstLeaf("x_1")) 68 | 69 | def check_candidate(self, candidate): 70 | if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): 71 | return False 72 | return True 73 | 74 | 75 | class WeirdRule5(PatternMatchingRule): 76 | PATTERN = AstNode(m_sub, 77 | AstNode(m_add, 78 | AstNode(m_or, 79 | AstLeaf("bnot_x_0"), 80 | AstNode(m_and, 81 | AstLeaf("bnot_x_1"), 82 | AstLeaf("x_2"))), 83 | AstNode(m_add, 84 | AstLeaf("x_0"), 85 | AstNode(m_and, 86 | AstLeaf("x_1"), 87 | AstLeaf("x_2")))), 88 | AstLeaf("x_2")) 89 | REPLACEMENT_PATTERN = AstNode(m_or, 90 | AstLeaf("x_0"), 91 | AstNode(m_or, 92 | AstLeaf("x_1"), 93 | AstNode(m_bnot, 94 | AstLeaf("x_2")))) 95 | 96 | def check_candidate(self, candidate): 97 | if not equal_bnot_mop(candidate["x_0"].mop, candidate["bnot_x_0"].mop): 98 | return False 99 | if not equal_bnot_mop(candidate["x_1"].mop, candidate["bnot_x_1"].mop): 100 | return False 101 | return True 102 | 103 | 104 | class WeirdRule6(PatternMatchingRule): 105 | PATTERN = AstNode(m_add, 106 | AstNode(m_or, 107 | AstLeaf('x_0'), 108 | AstLeaf('x_1')), 109 | AstNode(m_and, 110 | AstLeaf('x_0'), 111 | AstNode(m_bnot, 112 | AstLeaf('x_1')))) 113 | REPLACEMENT_PATTERN = AstNode(m_add, 114 | AstNode(m_xor, 115 | AstLeaf("x_0"), 116 | AstLeaf("x_1")), 117 | AstLeaf('x_0')) 118 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/z3/__init__.py: -------------------------------------------------------------------------------- 1 | from d810.utils import get_all_subclasses 2 | from d810.optimizers.instructions.z3.handler import Z3Rule, Z3Optimizer 3 | from d810.optimizers.instructions.z3.cst import * 4 | from d810.optimizers.instructions.z3.predicates import * 5 | 6 | 7 | Z3_RULES = [x() for x in get_all_subclasses(Z3Rule)] 8 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/z3/cst.py: -------------------------------------------------------------------------------- 1 | from ida_hexrays import * 2 | from d810.optimizers.instructions.z3.handler import Z3Rule 3 | from d810.ast import AstConstant, AstNode 4 | from d810.ast import minsn_to_ast 5 | from d810.errors import AstEvaluationException 6 | from d810.z3_utils import z3_check_mop_equality 7 | 8 | 9 | class Z3ConstantOptimization(Z3Rule): 10 | DESCRIPTION = "Detect and replace obfuscated constants" 11 | REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("c_res")) 12 | 13 | def __init__(self): 14 | super().__init__() 15 | self.min_nb_opcode = 3 16 | self.min_nb_constant = 3 17 | 18 | def configure(self, kwargs): 19 | super().configure(kwargs) 20 | if "min_nb_opcode" in kwargs.keys(): 21 | self.min_nb_opcode = kwargs["min_nb_opcode"] 22 | if "min_nb_constant" in kwargs.keys(): 23 | self.min_nb_constant = kwargs["min_nb_constant"] 24 | 25 | def check_and_replace(self, blk, instruction): 26 | tmp = minsn_to_ast(instruction) 27 | if tmp is None: 28 | return None 29 | leaf_info_list, cst_leaf_values, opcodes = tmp.get_information() 30 | if len(leaf_info_list) == 1 and \ 31 | len(opcodes) >= self.min_nb_opcode and \ 32 | (len(cst_leaf_values) >= self.min_nb_constant): 33 | try: 34 | val_0 = tmp.evaluate_with_leaf_info(leaf_info_list, [0]) 35 | val_1 = tmp.evaluate_with_leaf_info(leaf_info_list, [0xffffffff]) 36 | 37 | if val_0 == val_1: 38 | c_res_mop = mop_t() 39 | c_res_mop.make_number(val_0, tmp.mop.size) 40 | is_ok = z3_check_mop_equality(tmp.mop, c_res_mop) 41 | if is_ok: 42 | tmp.add_leaf("c_res", c_res_mop) 43 | new_instruction = self.get_replacement(tmp) 44 | return new_instruction 45 | return None 46 | except ZeroDivisionError: 47 | pass 48 | except AstEvaluationException as e: 49 | print("Error while evaluating {0}: {1}".format(tmp, e)) 50 | pass 51 | return None 52 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/z3/handler.py: -------------------------------------------------------------------------------- 1 | from d810.optimizers.instructions.handler import GenericPatternRule, InstructionOptimizer 2 | 3 | 4 | class Z3Rule(GenericPatternRule): 5 | pass 6 | 7 | 8 | class Z3Optimizer(InstructionOptimizer): 9 | RULE_CLASSES = [Z3Rule] 10 | -------------------------------------------------------------------------------- /d810/optimizers/instructions/z3/predicates.py: -------------------------------------------------------------------------------- 1 | from ida_hexrays import * 2 | 3 | from d810.optimizers.instructions.z3.handler import Z3Rule 4 | from d810.ast import AstLeaf, AstConstant, AstNode 5 | from d810.z3_utils import z3_check_mop_equality, z3_check_mop_inequality 6 | 7 | 8 | class Z3setzRuleGeneric(Z3Rule): 9 | DESCRIPTION = "Check with Z3 if a m_setz check is always True or False" 10 | PATTERN = AstNode(m_setz, 11 | AstLeaf("x_0"), 12 | AstLeaf("x_1")) 13 | REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_res")) 14 | 15 | def check_candidate(self, candidate): 16 | if z3_check_mop_equality(candidate["x_0"].mop, candidate["x_1"].mop): 17 | candidate.add_constant_leaf("val_res", 1, candidate.size) 18 | return True 19 | if z3_check_mop_inequality(candidate["x_0"].mop, candidate["x_1"].mop): 20 | candidate.add_constant_leaf("val_res", 0, candidate.size) 21 | return True 22 | return False 23 | 24 | 25 | class Z3setnzRuleGeneric(Z3Rule): 26 | DESCRIPTION = "Check with Z3 if a m_setnz check is always True or False" 27 | PATTERN = AstNode(m_setnz, 28 | AstLeaf("x_0"), 29 | AstLeaf("x_1")) 30 | REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_res")) 31 | 32 | def check_candidate(self, candidate): 33 | if z3_check_mop_equality(candidate["x_0"].mop, candidate["x_1"].mop): 34 | candidate.add_constant_leaf("val_res", 0, candidate.size) 35 | return True 36 | if z3_check_mop_inequality(candidate["x_0"].mop, candidate["x_1"].mop): 37 | candidate.add_constant_leaf("val_res", 1, candidate.size) 38 | return True 39 | return False 40 | 41 | 42 | class Z3lnotRuleGeneric(Z3Rule): 43 | DESCRIPTION = "Check with Z3 if a m_lnot check is always True or False" 44 | PATTERN = AstNode(m_lnot, 45 | AstLeaf("x_0")) 46 | REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_res")) 47 | 48 | def check_candidate(self, candidate): 49 | val_0_mop = mop_t() 50 | val_0_mop.make_number(0, candidate["x_0"].size) 51 | if z3_check_mop_equality(candidate["x_0"].mop, val_0_mop): 52 | candidate.add_constant_leaf("val_res", 1, candidate.size) 53 | return True 54 | if z3_check_mop_inequality(candidate["x_0"].mop, val_0_mop): 55 | candidate.add_constant_leaf("val_res", 0, candidate.size) 56 | return True 57 | return False 58 | 59 | 60 | class Z3SmodRuleGeneric(Z3Rule): 61 | DESCRIPTION = "Check with Z3 if a m_setz check is always True or False" 62 | PATTERN = AstNode(m_smod, 63 | AstLeaf("x_0"), 64 | AstConstant("2", 2)) 65 | REPLACEMENT_PATTERN = AstNode(m_mov, AstConstant("val_res")) 66 | 67 | def check_candidate(self, candidate): 68 | cst_0_mop = mop_t() 69 | cst_0_mop.make_number(0, candidate.size) 70 | if z3_check_mop_equality(candidate.mop, cst_0_mop): 71 | candidate.add_leaf("val_res", cst_0_mop) 72 | return True 73 | cst_1_mop = mop_t() 74 | cst_1_mop.make_number(1, candidate.size) 75 | if z3_check_mop_equality(candidate.mop, cst_1_mop): 76 | candidate.add_leaf("val_res", cst_1_mop) 77 | return True 78 | return False 79 | -------------------------------------------------------------------------------- /d810/utils.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | 3 | from d810.hexrays_helpers import MSB_TABLE 4 | 5 | CTYPE_SIGNED_TABLE = {1: ctypes.c_int8, 2: ctypes.c_int16, 4: ctypes.c_int32, 8: ctypes.c_int64} 6 | CTYPE_UNSIGNED_TABLE = {1: ctypes.c_uint8, 2: ctypes.c_uint16, 4: ctypes.c_uint32, 8: ctypes.c_uint64} 7 | 8 | 9 | def get_all_subclasses(python_class): 10 | python_class.__subclasses__() 11 | 12 | subclasses = set() 13 | check_these = [python_class] 14 | 15 | while check_these: 16 | parent = check_these.pop() 17 | for child in parent.__subclasses__(): 18 | if child not in subclasses: 19 | subclasses.add(child) 20 | check_these.append(child) 21 | 22 | return sorted(subclasses, key=lambda x: x.__name__) 23 | 24 | 25 | def unsigned_to_signed(unsigned_value, nb_bytes): 26 | return CTYPE_SIGNED_TABLE[nb_bytes](unsigned_value).value 27 | 28 | 29 | def signed_to_unsigned(signed_value, nb_bytes): 30 | return CTYPE_UNSIGNED_TABLE[nb_bytes](signed_value).value 31 | 32 | 33 | def get_msb(value, nb_bytes): 34 | return (value & MSB_TABLE[nb_bytes]) >> (nb_bytes * 8 - 1) 35 | 36 | 37 | def get_add_cf(op1, op2, nb_bytes): 38 | res = op1 + op2 39 | return get_msb((((op1 ^ op2) ^ res) ^ ((op1 ^ res) & (~(op1 ^ op2)))), nb_bytes) 40 | 41 | 42 | def get_add_of(op1, op2, nb_bytes): 43 | res = op1 + op2 44 | return get_msb(((op1 ^ res) & (~(op1 ^ op2))), nb_bytes) 45 | 46 | 47 | def get_sub_cf(op1, op2, nb_bytes): 48 | res = op1 - op2 49 | return get_msb((((op1 ^ op2) ^ res) ^ ((op1 ^ res) & (op1 ^ op2))), nb_bytes) 50 | 51 | 52 | def get_sub_of(op1, op2, nb_bytes): 53 | res = op1 - op2 54 | return get_msb(((op1 ^ res) & (op1 ^ op2)), nb_bytes) 55 | 56 | 57 | def get_parity_flag(op1, op2, nb_bytes): 58 | tmp = CTYPE_UNSIGNED_TABLE[nb_bytes](op1 - op2).value 59 | return (bin(tmp).count("1") + 1) % 2 60 | 61 | 62 | def ror(x, n, nb_bits=32): 63 | mask = (2 ** n) - 1 64 | mask_bits = x & mask 65 | return (x >> n) | (mask_bits << (nb_bits - n)) 66 | 67 | 68 | def rol(x, n, nb_bits=32): 69 | return ror(x, nb_bits - n, nb_bits) 70 | -------------------------------------------------------------------------------- /d810/z3_utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import List, Union 3 | from ida_hexrays import * 4 | 5 | from d810.hexrays_helpers import get_mop_index 6 | from d810.hexrays_formatters import format_minsn_t, opcode_to_string 7 | from d810.ast import mop_to_ast, minsn_to_ast, AstLeaf, AstNode 8 | from d810.errors import D810Z3Exception 9 | 10 | logger = logging.getLogger('D810.plugin') 11 | z3_file_logger = logging.getLogger('D810.z3_test') 12 | 13 | try: 14 | import z3 15 | Z3_INSTALLED = True 16 | # Since version 4.8.2, when Z3 is creating a BitVec, it relies on _str_to_bytes which uses sys.stdout.encoding 17 | # However, in IDA Pro (7.6sp1) sys.stdout is an object of type IDAPythonStdOut 18 | # which doesn't have a 'encoding' attribute, thus we set it to something, so that Z3 works 19 | try: 20 | x = sys.stdout.encoding 21 | except AttributeError: 22 | logger.debug("Couldn't find sys.stdout.encoding, setting it to utf-8") 23 | sys.stdout.encoding = "utf-8" 24 | except ImportError: 25 | logger.info("Z3 features disabled. Install Z3 to enable them") 26 | Z3_INSTALLED = False 27 | 28 | 29 | def create_z3_vars(leaf_list: List[AstLeaf]): 30 | if not Z3_INSTALLED: 31 | raise D810Z3Exception("Z3 is not installed") 32 | known_leaf_list = [] 33 | known_leaf_z3_var_list = [] 34 | for leaf in leaf_list: 35 | if not leaf.is_constant(): 36 | leaf_index = get_mop_index(leaf.mop, known_leaf_list) 37 | if leaf_index == -1: 38 | known_leaf_list.append(leaf.mop) 39 | leaf_index = len(known_leaf_list) - 1 40 | if leaf.mop.size in [1, 2, 4, 8]: 41 | # Normally, we should create variable based on their size 42 | # but for now it can cause issue when instructions like XDU are used, hence this ugly fix 43 | # known_leaf_z3_var_list.append(z3.BitVec("x_{0}".format(leaf_index), 8 * leaf.mop.size)) 44 | known_leaf_z3_var_list.append(z3.BitVec("x_{0}".format(leaf_index), 32)) 45 | pass 46 | else: 47 | known_leaf_z3_var_list.append(z3.BitVec("x_{0}".format(leaf_index), 32)) 48 | leaf.z3_var = known_leaf_z3_var_list[leaf_index] 49 | leaf.z3_var_name = "x_{0}".format(leaf_index) 50 | return known_leaf_z3_var_list 51 | 52 | 53 | def ast_to_z3_expression(ast: Union[AstNode, AstLeaf], use_bitvecval=False): 54 | if not Z3_INSTALLED: 55 | raise D810Z3Exception("Z3 is not installed") 56 | if isinstance(ast, AstLeaf): 57 | if ast.is_constant(): 58 | return z3.BitVecVal(ast.value, 32) 59 | return ast.z3_var 60 | if ast.opcode == m_neg: 61 | return -(ast_to_z3_expression(ast.left, use_bitvecval)) 62 | elif ast.opcode == m_lnot: 63 | return not (ast_to_z3_expression(ast.left, use_bitvecval)) 64 | elif ast.opcode == m_bnot: 65 | return ~(ast_to_z3_expression(ast.left, use_bitvecval)) 66 | elif ast.opcode == m_add: 67 | return (ast_to_z3_expression(ast.left, use_bitvecval)) + (ast_to_z3_expression(ast.right, use_bitvecval)) 68 | elif ast.opcode == m_sub: 69 | return (ast_to_z3_expression(ast.left, use_bitvecval)) - (ast_to_z3_expression(ast.right, use_bitvecval)) 70 | elif ast.opcode == m_mul: 71 | return (ast_to_z3_expression(ast.left, use_bitvecval)) * (ast_to_z3_expression(ast.right, use_bitvecval)) 72 | elif ast.opcode == m_udiv: 73 | return z3.UDiv(ast_to_z3_expression(ast.left, use_bitvecval=True), 74 | ast_to_z3_expression(ast.right, use_bitvecval=True)) 75 | elif ast.opcode == m_sdiv: 76 | return (ast_to_z3_expression(ast.left, use_bitvecval)) / (ast_to_z3_expression(ast.right, use_bitvecval)) 77 | elif ast.opcode == m_umod: 78 | return z3.URem(ast_to_z3_expression(ast.left, use_bitvecval), ast_to_z3_expression(ast.right, use_bitvecval)) 79 | elif ast.opcode == m_smod: 80 | return (ast_to_z3_expression(ast.left, use_bitvecval)) % (ast_to_z3_expression(ast.right, use_bitvecval)) 81 | elif ast.opcode == m_or: 82 | return (ast_to_z3_expression(ast.left, use_bitvecval)) | (ast_to_z3_expression(ast.right, use_bitvecval)) 83 | elif ast.opcode == m_and: 84 | return (ast_to_z3_expression(ast.left, use_bitvecval)) & (ast_to_z3_expression(ast.right, use_bitvecval)) 85 | elif ast.opcode == m_xor: 86 | return (ast_to_z3_expression(ast.left, use_bitvecval)) ^ (ast_to_z3_expression(ast.right, use_bitvecval)) 87 | elif ast.opcode == m_shl: 88 | return (ast_to_z3_expression(ast.left, use_bitvecval)) << (ast_to_z3_expression(ast.right, use_bitvecval)) 89 | elif ast.opcode == m_shr: 90 | return z3.LShR(ast_to_z3_expression(ast.left, use_bitvecval), ast_to_z3_expression(ast.right, use_bitvecval)) 91 | elif ast.opcode == m_sar: 92 | return (ast_to_z3_expression(ast.left, use_bitvecval)) >> (ast_to_z3_expression(ast.right, use_bitvecval)) 93 | elif ast.opcode in [m_xdu, m_xds, m_low, m_high]: 94 | return ast_to_z3_expression(ast.left, use_bitvecval) 95 | raise D810Z3Exception("Z3 evaluation: Unknown opcode {0} for {1}".format(opcode_to_string(ast.opcode), ast)) 96 | 97 | 98 | def mop_list_to_z3_expression_list(mop_list: List[mop_t]): 99 | if not Z3_INSTALLED: 100 | raise D810Z3Exception("Z3 is not installed") 101 | ast_list = [mop_to_ast(mop) for mop in mop_list] 102 | ast_leaf_list = [] 103 | for ast in ast_list: 104 | ast_leaf_list += ast.get_leaf_list() 105 | _ = create_z3_vars(ast_leaf_list) 106 | return [ast_to_z3_expression(ast) for ast in ast_list] 107 | 108 | 109 | def z3_check_mop_equality(mop1: mop_t, mop2: mop_t) -> bool: 110 | if not Z3_INSTALLED: 111 | raise D810Z3Exception("Z3 is not installed") 112 | z3_mop1, z3_mop2 = mop_list_to_z3_expression_list([mop1, mop2]) 113 | s = z3.Solver() 114 | s.add(z3.Not(z3_mop1 == z3_mop2)) 115 | if s.check().r == -1: 116 | return True 117 | return False 118 | 119 | 120 | def z3_check_mop_inequality(mop1: mop_t, mop2: mop_t) -> bool: 121 | if not Z3_INSTALLED: 122 | raise D810Z3Exception("Z3 is not installed") 123 | z3_mop1, z3_mop2 = mop_list_to_z3_expression_list([mop1, mop2]) 124 | s = z3.Solver() 125 | s.add(z3_mop1 == z3_mop2) 126 | if s.check().r == -1: 127 | return True 128 | return False 129 | 130 | 131 | def rename_leafs(leaf_list: List[AstLeaf]) -> List[str]: 132 | if not Z3_INSTALLED: 133 | raise D810Z3Exception("Z3 is not installed") 134 | known_leaf_list = [] 135 | for leaf in leaf_list: 136 | if not leaf.is_constant() and leaf.mop.t != mop_z: 137 | leaf_index = get_mop_index(leaf.mop, known_leaf_list) 138 | if leaf_index == -1: 139 | known_leaf_list.append(leaf.mop) 140 | leaf_index = len(known_leaf_list) - 1 141 | leaf.z3_var_name = "x_{0}".format(leaf_index) 142 | 143 | return ["x_{0} = BitVec('x_{0}', {1})".format(i, 8 * leaf.size) for i, leaf in enumerate(known_leaf_list)] 144 | 145 | 146 | def log_z3_instructions(original_ins: minsn_t, new_ins: minsn_t): 147 | if not Z3_INSTALLED: 148 | raise D810Z3Exception("Z3 is not installed") 149 | orig_mba_tree = minsn_to_ast(original_ins) 150 | new_mba_tree = minsn_to_ast(new_ins) 151 | if orig_mba_tree is None or new_mba_tree is None: 152 | return None 153 | orig_leaf_list = orig_mba_tree.get_leaf_list() 154 | new_leaf_list = new_mba_tree.get_leaf_list() 155 | 156 | var_def_list = rename_leafs(orig_leaf_list + new_leaf_list) 157 | 158 | z3_file_logger.info("print('Testing: {0} == {1}')".format(format_minsn_t(original_ins), format_minsn_t(new_ins))) 159 | for var_def in var_def_list: 160 | z3_file_logger.info("{0}".format(var_def)) 161 | 162 | removed_xdu = "{0}".format(orig_mba_tree).replace("xdu","") 163 | z3_file_logger.info("original_expr = {0}".format(removed_xdu)) 164 | removed_xdu = "{0}".format(new_mba_tree).replace("xdu","") 165 | z3_file_logger.info("new_expr = {0}".format(removed_xdu)) 166 | z3_file_logger.info("prove(original_expr == new_expr)\n") 167 | --------------------------------------------------------------------------------