From c36200ab8eccbe1bcf8f674507cdf2c8a7019db9 Mon Sep 17 00:00:00 2001 From: ergrelet Date: Sat, 20 Apr 2024 02:36:26 +0200 Subject: [PATCH] WIP: Implement a Binary Ninja plugin --- binja_plugin/__init__.py | 64 +++++++++++++++++++++++++++++++++ plugin.json | 24 +++++++++++++ themida_unmutate/main.py | 2 +- themida_unmutate/miasm_utils.py | 20 ++++++----- themida_unmutate/unwrapping.py | 2 +- 5 files changed, 102 insertions(+), 10 deletions(-) create mode 100644 binja_plugin/__init__.py create mode 100644 plugin.json diff --git a/binja_plugin/__init__.py b/binja_plugin/__init__.py new file mode 100644 index 0000000..45f9abb --- /dev/null +++ b/binja_plugin/__init__.py @@ -0,0 +1,64 @@ +from binaryninja import * + +from miasm.analysis.binary import Container +from miasm.analysis.machine import Machine +from miasm.core.locationdb import LocationDB + +from themida_unmutate.symbolic_execution import disassemble_and_simplify_functions +from themida_unmutate.miasm_utils import MiasmContext + +SUPPORTED_ARCHS = ["x86_64"] + + +def deobfuscate_at_address(bv: BinaryView, address: int) -> None: + arch = str(bv.platform.arch) + if arch not in SUPPORTED_ARCHS: + return + + binary_data = get_binary_data(bv) + miasm_ctx = create_miasm_context(arch, binary_data, bv.original_base) + + print("Resolving mutated's functions' addresses...") + # mutated_func_addrs = unwrap_functions(args.protected_binary, [address]) + mutated_func_addrs = [address] + + # Disassemble mutated functions and simplify them + print("Deobfuscating mutated functions...") + simplified_func_asmcfgs = disassemble_and_simplify_functions(miasm_ctx, mutated_func_addrs) + + print(simplified_func_asmcfgs) + + # bv.update_analysis() + + +def get_binary_data(bv: BinaryView) -> bytearray: + # Sort sections by start address + sections = list(bv.sections.values()) + sorted_section = sorted(sections, key=lambda s: s.start) + + br = BinaryReader(bv) + last_section_address = bv.original_base + exe_data = bytearray() + for section in sorted_section: + # Pad with zeroes + padding_size = section.start - last_section_address + exe_data += b"\x00" * padding_size + exe_data += br.read(section.length, section.start) + last_section_address = section.start + section.length + + return exe_data + + +def create_miasm_context(arch: str, binary_data: bytearray, binary_base_address: int) -> MiasmContext: + loc_db = LocationDB() + machine = Machine(arch) + assert machine.dis_engine is not None + container = Container.from_string(binary_data, loc_db, addr=binary_base_address) + mdis = machine.dis_engine(container.bin_stream, loc_db=loc_db) + lifter = machine.lifter(loc_db) + + return MiasmContext(loc_db, container, machine, mdis, lifter) + + +PluginCommand.register_for_address("themida-unmutate", "Deobfuscate mutated code from this address", + deobfuscate_at_address) diff --git a/plugin.json b/plugin.json new file mode 100644 index 0000000..4f914d3 --- /dev/null +++ b/plugin.json @@ -0,0 +1,24 @@ +{ + "pluginmetadataversion": 2, + "name": "themida-unmutate-bn", + "type": ["core"], + "api": ["python3"], + "description": "Static deobfuscator for Themida's mutation-based obfuscation, powered by Miasm.", + "longdescription": "", + "license": { + "name": "GPL-3.0-or-later", + "text": "" + }, + "platforms": ["Darwin", "Linux", "Windows"], + "installinstructions": { + "Darwin": "", + "Linux": "", + "Windows": "" + }, + "dependencies": { + "pip": ["themida-unmutate"] + }, + "version": "0.1.0", + "author": "Erwan Grelet", + "minimumbinaryninjaversion": 3164 +} diff --git a/themida_unmutate/main.py b/themida_unmutate/main.py index 8b4380e..2e9ed4a 100644 --- a/themida_unmutate/main.py +++ b/themida_unmutate/main.py @@ -23,7 +23,7 @@ def entry_point() -> None: setup_logger(args.verbose) # Setup disassembler and lifter - miasm_ctx = MiasmContext(args.protected_binary) + miasm_ctx = MiasmContext.from_binary_file(args.protected_binary) # Resolve mutated functions' addresses if needed protected_func_addrs = list(map(lambda addr: int(addr, 0), args.addresses)) diff --git a/themida_unmutate/miasm_utils.py b/themida_unmutate/miasm_utils.py index 73e8104..eb15a6c 100644 --- a/themida_unmutate/miasm_utils.py +++ b/themida_unmutate/miasm_utils.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from typing import Self import miasm.expression.expression as m2_expr @@ -20,18 +21,21 @@ class MiasmContext: mdis: disasmEngine lifter: Lifter - def __init__(self, target_binary_path: str) -> None: + @classmethod + def from_binary_file(cls, target_binary_path: str) -> Self: """ - Initialize our Miasm context, targeted at x86_64 binaries. + Initialize our Miasm context from a binary file. """ - self.loc_db = LocationDB() + loc_db = LocationDB() with open(target_binary_path, 'rb') as target_binary: - self.container = Container.from_stream(target_binary, self.loc_db) - self.machine = Machine(self.container.arch) - assert self.machine.dis_engine is not None + container = Container.from_stream(target_binary, loc_db) + machine = Machine(container.arch) + assert machine.dis_engine is not None - self.mdis = self.machine.dis_engine(self.container.bin_stream, loc_db=self.loc_db) - self.lifter = self.machine.lifter(self.loc_db) + mdis = machine.dis_engine(container.bin_stream, loc_db=loc_db) + lifter = machine.lifter(loc_db) + + return cls(loc_db, container, machine, mdis, lifter) @property def arch(self) -> str: diff --git a/themida_unmutate/unwrapping.py b/themida_unmutate/unwrapping.py index 389701a..ada6c2e 100644 --- a/themida_unmutate/unwrapping.py +++ b/themida_unmutate/unwrapping.py @@ -8,7 +8,7 @@ def unwrap_function(target_bin_path: str, target_addr: int) -> int: # Setup disassembler and lifter - miasm_ctx = MiasmContext(target_bin_path) + miasm_ctx = MiasmContext.from_binary_file(target_bin_path) # Disassemble trampoline miasm_ctx.mdis.follow_call = True