ripr is a plugin for Binary Ninja that automatically extracts and packages snippets of machine code into a functionally identical python class backed by Unicorn-Engine. This allows one to quickly and easily reuse logic embedded in binaries, from python.
In the past two weeks, I’ve found time to revisit the project, add several new features, and fix a number of bugs. This blogpost will touch on some of the major updates to ripr.
New features include: Automatic Function Argument Mapping, a “Basic Block” mode, and an uninitialized variable detection analysis. Additionally, ripr’s dependency on PyQt5 has been removed.
Automatic Function Argument Mapping
A tedious (but necessary) part of the old ripr-workflow was manually using the unicorn API to pass arguments to your emulated function.
Now, ripr will automatically use the
function_type attribute (when available) for the target
function class when generating the python abstraction.
For example, given an x86-64 function whose declaration looks like:
int addNumbers(int a, int b)
ripr will generate a
run function that looks like:
def run(self, arg_1, arg_2)
Furthermore, if your target-function takes “single-depth” pointer arguments, ripr will generate code that allows you to pass in a python string containing the data you want available at the pointer-target. Specifically, ripr will:
- Map a page in memory for your data
- Copy your supplied data to that page
- Place the address of that page into the expected argument location (e.g. register or stack offset)
Note that when dealing with pointer arguments of depths greater than 1 (e.g
int ***, etc..), you will have to manually set up the environment as before.
Basic Block Mode
It is now possible to “pick and choose” specific basic blocks from a function. This is particularly useful when targeting code structures embedded deep inside large, complicated functions.
For example, imagine a function that has a significant amount of inlined functionality; and has also been run through an obfuscator like obfuscator-llvm .
From the right click context menu, one can now tag any number of basic blocks for extraction via ripr. Clicking “Generate” will now produce a python script encapsulating only the logic of the selected basic blocks.
Uninitialized Variable Identification
Another common pain-point encountered when using ripr is determining what variables (say, registers or memory) must be set for a given block of code to run correctly. This is common for code packaged with ripr’s “Basic Block” mode, as you can no longer rely on the structure provided by a function and assume arguments are the only “missing data”.
Using Binary Ninja’s Medium Level Intermediate Language (MLIL) features, ripr will now automatically identify which variables must be initialized for a given piece of code to run as expected.
For example, the basic block shown below has had this analysis run against it:
The first Instruction to access a variable identified as uninitialized will get highlighted in orange. Additionally, ripr includes comments in the generated python code that report the uninitialized variable, its type, and how the variable is referenced in code.
# int64_t var_10_1 --> rbp#1 - 8 # int64_t var_18_1 --> rbp#1 - 0x10 # int32_t var_1c_1 --> rbp#1 - 0x14
In many cases, this analysis can pinpoint what data must be supplied. It should be noted that this feature is considered experimental, and assumes that the underlying MLIL is correct.
Removing the PyQt5 Dependency
The biggest headache with using ripr in the past has been its dependency on external PyQt5 bindings.
Binary Ninja does not expose the Qt APIs to plugin developers, and the
binaryninja.interaction APIs were in their infancy when ripr v1.0 was written (for Shmoocon 2017).
binaryninja.interaction class now incorporates enough functionality that the core ripr UI components can be built on top of it.
As a result, I have rewritten most of ripr’s UI code, and made “non-portable” components (such as the Dock and custom context-menus) fail silently if their dependencies are not available.
Installing ripr should now be as simple as cloning the repo and dropping it into the Binary Ninja plugins folder.
Over the summer, Otto Ebeling (@loginuid ) submitted a pull request to ripr that added basic support for radare2 . This makes the core features of ripr available via a r2pipe script; allowing users to quickly extract & package functions from the r2 console.
Although I do not plan on extensively supporting r2 or ensuring feature-parity, I do plan on not breaking compatibility with the current state.
Calls to local functions being identified as both code and data dependencies
Due to a change in the Binary Ninja API, ripr would sometimes misidentify call-targets as data-references and produce malformed output. Now, ripr correctly differentiates between call-targets and potential data references.
Headless mode breaks while ripr is installed
Binary Ninja loads plugins at startup, even when running in headless mode. During initialization, ripr would always attempt to instantiate its UI elements without considering whether it was running with or without a GUI context (e.g in ‘headless’ mode). Ultimately, this would result in a crash.
ripr now tries to determine whether or not it is running in a GUI context before initializing and installing its UI elements.
ripr aims to make reusing binary code from a higher level language as quick and simple as possible.
This project is certainly more of a “work in progress”, and I welcome bug-reports, feature requests, and contributions from the community.