Skip to content

Commit

Permalink
Add plugin files
Browse files Browse the repository at this point in the history
  • Loading branch information
ergrelet committed Jul 6, 2024
1 parent 5a96873 commit 294f47f
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 2 deletions.
24 changes: 22 additions & 2 deletions README.md
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.
10 changes: 10 additions & 0 deletions __init__.py
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
17 changes: 17 additions & 0 deletions binja_plugin/__init__.py
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)
64 changes: 64 additions & 0 deletions binja_plugin/actions.py
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}!")
93 changes: 93 additions & 0 deletions binja_plugin/common.py
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)
1 change: 1 addition & 0 deletions binja_plugin/plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NAME = "themida-unmutate-bn"
24 changes: 24 additions & 0 deletions plugin.json
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
}
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
miasm
themida-unmutate

0 comments on commit 294f47f

Please sign in to comment.