-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
233 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,22 @@ | ||
# themida-unmutate-bn | ||
A Binary Ninja plugin to deobfuscate Themida, WinLicense and Code Virtualizer 3.x's mutation-based obfuscation. | ||
# themida-unmutate-bn (v0.1.0) | ||
|
||
Author: **Erwan Grelet** | ||
|
||
_A Binary Ninja plugin to deobfuscate Themida, WinLicense and Code Virtualizer 3.x's mutation-based obfuscation._ | ||
|
||
## Minimum Version | ||
|
||
This plugin requires the following minimum version of Binary Ninja: | ||
|
||
- 3164 | ||
|
||
## Required Dependencies | ||
|
||
The following dependencies are required for this plugin: | ||
|
||
- pip - miasm | ||
- pip - themida-unmutate | ||
|
||
## License | ||
|
||
This plugin is released under a GPL-3.0 license. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Note(ergrelet): this file is meant to be used by Binary Ninja when loading our plugin. | ||
try: | ||
import importlib | ||
|
||
importlib.import_module("binaryninja") | ||
from .binja_plugin import plugin_init | ||
|
||
plugin_init() | ||
except ImportError: | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
from binaryninja import PluginCommand # type:ignore | ||
|
||
from . import actions, plugin | ||
|
||
plugin_commands = [ | ||
( | ||
f"{plugin.NAME}\\Deobfuscate mutated code from this address", | ||
"Deobfuscate mutated code from this address", | ||
PluginCommand.register_for_address, | ||
actions.deobfuscate_at_address, | ||
), | ||
] | ||
|
||
|
||
def plugin_init(): | ||
for (command_name, command_description, command_registrator, command_action) in plugin_commands: | ||
command_registrator(name=command_name, description=command_description, action=command_action) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
from typing import Self | ||
|
||
from binaryninja import BinaryView # type:ignore | ||
from binaryninja.log import Logger # type:ignore | ||
from binaryninja.plugin import BackgroundTaskThread # type:ignore | ||
from themida_unmutate.unwrapping import unwrap_functions | ||
from themida_unmutate.symbolic_execution import disassemble_and_simplify_functions | ||
|
||
from . import common, plugin | ||
|
||
SUPPORTED_ARCHS = ["x86_64"] | ||
|
||
logger = Logger(session_id=0, logger_name=plugin.NAME) | ||
|
||
|
||
def deobfuscate_at_address(bv: BinaryView, address: int) -> None: | ||
DeobfuscateCodeAtAddressTask(bv=bv, address=address).start() | ||
|
||
|
||
class DeobfuscateCodeAtAddressTask(BackgroundTaskThread): | ||
|
||
def __init__(self, bv: BinaryView, address: int): | ||
super().__init__( | ||
initial_progress_text=f"Deobfuscating code at 0x{address:x}", | ||
can_cancel=False, | ||
) | ||
self.bv = bv | ||
self.address = address | ||
|
||
def run(self: Self) -> None: | ||
if self.bv.arch is None: | ||
logger.log_error("Could not get architecture of current binary view") | ||
return | ||
|
||
arch = str(self.bv.platform.arch) | ||
if arch not in SUPPORTED_ARCHS: | ||
logger.log_error("Current binary view's architecture isn't supported") | ||
return | ||
logger.log_info(f"Deobfuscating code at 0x{self.address:x}") | ||
|
||
protected_func_addrs = [self.address] | ||
binary_data = common.get_binary_data(self.bv) | ||
miasm_ctx = common.create_miasm_context(arch, self.bv.original_base, binary_data) | ||
|
||
logger.log_info("Resolving mutated's function' address...") | ||
mutated_func_addrs = unwrap_functions(miasm_ctx, protected_func_addrs) | ||
|
||
# Disassemble mutated functions and simplify them | ||
logger.log_info("Deobfuscating mutated function...") | ||
simplified_func_asmcfgs = disassemble_and_simplify_functions(miasm_ctx, mutated_func_addrs) | ||
|
||
# Map protected functions' addresses to their corresponding simplified `AsmCFG` | ||
func_addr_to_simplified_cfg = { | ||
protected_func_addrs[i]: asm_cfg | ||
for i, asm_cfg in enumerate(simplified_func_asmcfgs) | ||
} | ||
|
||
# Rewrite the protected binary with the simplified function | ||
logger.log_info("Patching binary file...") | ||
common.rebuild_simplified_binary(miasm_ctx, func_addr_to_simplified_cfg, self.bv) | ||
|
||
# Relaunch analysis to take our changes into account | ||
self.bv.update_analysis() | ||
logger.log_info(f"Successfully simplified code at 0x{self.address:x}!") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
from binaryninja import BinaryView, BinaryReader, BinaryWriter # type:ignore | ||
|
||
from miasm.analysis.binary import Container | ||
from miasm.analysis.machine import Machine | ||
from miasm.core.asmblock import AsmCFG, asm_resolve_final | ||
from miasm.core.locationdb import LocationDB | ||
from themida_unmutate.miasm_utils import MiasmContext, MiasmFunctionInterval, generate_code_redirect_patch | ||
|
||
|
||
def get_binary_data(bv: BinaryView) -> bytearray: | ||
""" | ||
Retrieve binary data from `bv` as single `bytearray`. | ||
Note: spaces between sections are replaced with 0s. | ||
""" | ||
# 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_base_address: int, binary_data: bytearray) -> MiasmContext: | ||
""" | ||
Create `MiasmContext` from a `bytearray`, given the architecture and base address. | ||
""" | ||
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) | ||
|
||
|
||
def rebuild_simplified_binary( | ||
miasm_ctx: MiasmContext, | ||
func_addr_to_simplified_cfg: dict[int, tuple[AsmCFG, MiasmFunctionInterval]], | ||
bv: BinaryView, | ||
) -> None: | ||
""" | ||
Regenerate simplified machine code and patch the binary in place via `bv`. | ||
""" | ||
bw = BinaryWriter(bv) | ||
|
||
# Reassemble simplified AsmCFGs | ||
original_to_simplified: dict[int, int] = {} | ||
for protected_func_addr, val in func_addr_to_simplified_cfg.items(): | ||
simplified_asmcfg, orignal_asmcfg_interval = val | ||
|
||
# Unpin blocks to be able to relocate the CFG | ||
head = simplified_asmcfg.heads()[0] | ||
for asm_block in simplified_asmcfg.blocks: | ||
miasm_ctx.loc_db.unset_location_offset(asm_block.loc_key) | ||
|
||
# Start rewriting at the first part of the interval (i.e., at the start | ||
# of the mutated code) | ||
target_addr: int = orignal_asmcfg_interval.intervals[0][0] | ||
# Unpin loc_key if it's pinned | ||
original_loc = miasm_ctx.loc_db.get_offset_location(target_addr) | ||
if original_loc is not None: | ||
miasm_ctx.loc_db.unset_location_offset(original_loc) | ||
|
||
# Relocate the function's entry block | ||
miasm_ctx.loc_db.set_location_offset(head, target_addr) | ||
|
||
# Generate the simplified machine code | ||
new_section_patches = asm_resolve_final(miasm_ctx.mdis.arch, | ||
simplified_asmcfg, | ||
dst_interval=orignal_asmcfg_interval) | ||
|
||
# Apply patches | ||
for address, data in new_section_patches.items(): | ||
bw.write(bytes(data), address) | ||
|
||
# Associate original addr to simplified addr | ||
original_to_simplified[protected_func_addr] = min(new_section_patches.keys()) | ||
|
||
# Redirect functions to their simplified versions | ||
for target_addr in func_addr_to_simplified_cfg.keys(): | ||
simplified_func_addr = original_to_simplified[target_addr] | ||
address, data = generate_code_redirect_patch(miasm_ctx, target_addr, simplified_func_addr) | ||
bw.write(data, address) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
NAME = "themida-unmutate-bn" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
"pluginmetadataversion": 2, | ||
"name": "themida-unmutate-bn", | ||
"type": ["helper"], | ||
"api": ["python3"], | ||
"description": "Static deobfuscator for Themida, WinLicense and Code Virtualizer 3.x's mutation-based obfuscation.", | ||
"longdescription": "", | ||
"license": { | ||
"name": "GPL-3.0-or-later", | ||
"text": "" | ||
}, | ||
"platforms": ["Darwin", "Linux", "Windows"], | ||
"installinstructions": { | ||
"Darwin": "", | ||
"Linux": "", | ||
"Windows": "" | ||
}, | ||
"dependencies": { | ||
"pip": ["miasm", "themida-unmutate"] | ||
}, | ||
"version": "0.1.0", | ||
"author": "Erwan Grelet", | ||
"minimumbinaryninjaversion": 3164 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
miasm | ||
themida-unmutate |