Expand description
The reoxide
Rust crate offers two different things:
- Allow driving the Ghidra decompiler from Rust.
- Allow writing plugins for the Ghidra decompiler using Rust.
Both require a working ReOxide setup, make sure you can run the
reoxide
command in the environment where you run your Rust builds
from.
§Driving the Ghidra decompiler from Rust
use std::env;
use std::path::Path;
use reoxide::driver::*;
const PROGRAM: &[u8] = &[
0x55, 0x48, 0x89, 0xe5, 0x53, 0x48, 0x83, 0xec, 0x18, 0x89, 0x7d, 0xec, 0x83, 0x7d, 0xec, 0x00,
0x74, 0x06, 0x83, 0x7d, 0xec, 0x01, 0x75, 0x07, 0xb8, 0x01, 0x00, 0x00, 0x00, 0xeb, 0x1e, 0x8b,
0x45, 0xec, 0x83, 0xe8, 0x01, 0x89, 0xc7, 0xe8, 0xfb, 0xfe, 0xff, 0xff, 0x89, 0xc3, 0x8b, 0x45,
0xec, 0x83, 0xe8, 0x02, 0x89, 0xc7, 0xe8, 0xec, 0xfe, 0xff, 0xff, 0x01, 0xd8, 0x48, 0x8b, 0x5d,
0xf8, 0xc9, 0xc3,
];
fn main() -> Result<(), DecompError> {
let ghidra_dir = env::var("GHIDRA_INSTALL_DIR").expect(
"Please provide an environment variable \
GHIDRA_INSTALL_DIR with the path to the Ghidra root directory. \
This is needed to find the SLEIGH specification files",
);
initialize_decompiler(Path::new(&ghidra_dir), true)?;
let mut ctx = RawFileContext::from_bytes("x86:LE:64:default", &PROGRAM)?;
let addr = 0x0;
ctx.define_function(addr, "fib")?;
let decomp = ctx.decompile_function(addr)?;
println!("{}", decomp);
Ok(())
}
The decompile
example shows how to use the decompiler driver. When
you write programs by using the decompiler driver, you have to
tell the program where to find the reoxide
shared library,
libreoxide.so
. The build.rs
build step should automatically link
the library correctly if it can run the reoxide
command, but
for running the example you might have to set the LD_LIBRARY_PATH
manually:
LD_LIBRARY_PATH=$(reoxide print-ld-library-path)
Start off by importing the driver functions:
use reoxide::driver::*;
Currently the driver needs a full Ghidra installation for the processor definition files. You need to set the path to the root folder of the Ghidra installation, for example:
initialize_decompiler(Path::new("/opt/ghidra"), true).unwrap();
You can also enable or disable the ReOxide plugin system with the last parameter. Afterwards you initialize a decompiler context with the raw bytes of the program and the architecture specification string from Ghidra:
let mut ctx = RawFileContext::from_bytes("x86:LE:64:default", &PROGRAM).unwrap();
Decompilation works on the function level, so you first need to define a function at the address you want to decompile:
let addr = 0x0;
ctx.define_function(addr, "fibonacci").unwrap();
Finally, you can decompile the function at the address:
let decomp = ctx.decompile_function(addr).unwrap();
println!("{}", decomp);
§Writing a ReOxide plugin
Similar to the C++ plugin template, we also have a plugin template
for Rust. The Rust API introduces a C API layer on top of the C++ codebase,
which makes it more stable in terms of ABI. Currently you do not get the
same full functionality as the C++ API, because the binding generator
only generates code for functionality currently used by existing rules
and actions. Moreover, the way Ghidra implements things does not map well
to safe Rust code, which might cause the current API to not feel like
idiomatic Rust and there might be soundness issues. These API bindings
are therefore highly experimental.
To start off, you need to mark the crate as a cdylib
in the Cargo.toml
:
[package]
name = "simple_plugin"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib"]
[dependencies]
reoxide = "0.7.0"
Before proceeding, we recommend that you read through the plugin development guide
which covers the creation of C++ plugins. The Rust example here reimplements
the C++ tutorial plugin in Rust.
In your lib.rs
, you declare a plugin with the plugin!
proc macro.
In a minimal example, you need a plugin context, which can be an empty
struct. Then you can define a plugin like this:
use reoxide::plugin::*;
pub struct SimplePlugin;
plugin!(
context = SimplePlugin,
rules = [],
actions = []
);
This plugin does not do anything, because it does not contain any actions
or rules. You can define an action with the action
attribute:
#[action(name = "my_action")]
pub struct SimpleAction<'a> {
reox: &'a mut ReOxideInterface,
}
The name
field for the action
and rule
attributes uniquely identifies
your action in the Ghidra pipeline definition. You can then list the action
in the plugin definition:
plugin!(
context = SimplePlugin,
rules = [],
actions = [SimpleAction]
);
This will not compile, because all actions have to implement the Action trait
as well. The trait works the same way as overriding the virtual functions in the
C++ API does:
impl<'a> Action<'a> for SimpleAction<'a> {
type PluginContext = SimplePlugin;
fn new(context: CreationContext<'a, '_, Self::PluginContext>) -> Self {
SimpleAction {
reox: context.reoxide,
}
}
fn apply(&mut self, _data: &mut Funcdata) -> ApplyResult {
self.reox.send_string("Hello from Rust Action!");
ApplyResult::NotApplied
}
}
You can reimplement the SimpleRule
from the C++ template in a similar manner.
Here we also see the limits of the current Rust API, as we have to resort to
using unsafe
for retrieving the FuncCallSpecs
pointer:
pub struct SimpleRule<'a> {
_simple: &'a mut SimplePlugin,
reox: &'a mut ReOxideInterface,
}
impl<'a> Rule<'a> for SimpleRule<'a> {
type PluginContext = SimplePlugin;
fn new(context: CreationContext<'a, '_, Self::PluginContext>) -> Self {
SimpleRule {
_simple: context.plugin,
reox: context.reoxide,
}
}
fn op_list(&self) -> Vec<OpCode> {
vec![OpCode::Call]
}
fn apply(&mut self, op: &mut PcodeOp, _data: &mut Funcdata) -> ApplyResult {
self.reox.send_string("Hello from Rust Rule!");
let vn = match op.get_in(0) {
Some(v) => v,
None => return ApplyResult::NotApplied,
};
let spc = match vn.get_space() {
Some(s) => s,
None => return ApplyResult::NotApplied,
};
if !matches!(spc.get_type(), Spacetype::Fspec) {
return ApplyResult::NotApplied;
}
let fc_ptr = core::ptr::null::<FuncCallSpecs>().with_addr(vn.get_offset() as usize);
let fc = unsafe { &*fc_ptr };
if let Some(s) = fc.get_name() {
if let Ok(name) = s.from_utf8() {
self.reox.send_string(name);
}
}
ApplyResult::NotApplied
}
}
For building, the same rules apply as for the decompile driver: The reoxide
program needs to be available in your environment.
After building you have to manually copy the libsimple_plugin.so
from the build
directory to your ReOxide plugin directory, which you can find using the
reoxide
command:
reoxide print-plugin-dir