Skip to content
Closed
14 changes: 12 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ members = [
"rust",
"arch/riscv",
"arch/msp430",
"rust/plugin_examples/*",
"view/minidump",
"plugins/dwarf/dwarf_import",
"plugins/dwarf/dwarf_export",
Expand Down
13 changes: 13 additions & 0 deletions rust/plugin_examples/data_renderer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "example_data_renderer"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
uuid = "1.18.1"
binaryninjacore-sys = { path = "../../binaryninjacore-sys", default-features = false }
binaryninja = { path = "../.." }
log = "0.4.27"
63 changes: 63 additions & 0 deletions rust/plugin_examples/data_renderer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Data Renderer Example

This example implements a simple data renderer for the Mach-O load command LC_UUID.
You can try the renderer by loading the `/bin/cat` binary from macOS.

We're implementing a functionality similar to the one described in the Python data renderer blog post:
https://binary.ninja/2024/04/08/customizing-data-display.html.

## Building

```sh
# Build from the root directory (binaryninja-api)
cargo build --manifest-path rust/plugin_examples/data_renderer/Cargo.toml
# Link binary on macOS
ln -sf $PWD/target/debug/libexample_data_renderer.dylib ~/Library/Application\ Support/Binary\ Ninja/plugins
```

## Result

The following Mach-O load command be will be transformed from

```c
struct uuid __macho_load_command_[10] =
{
enum load_command_type_t cmd = LC_UUID
uint32_t cmdsize = 0x18
uint8_t uuid[0x10] =
{
[0x0] = 0x74
[0x1] = 0xa0
[0x2] = 0x3a
[0x3] = 0xbd
[0x4] = 0x1e
[0x5] = 0x19
[0x6] = 0x32
[0x7] = 0x67
[0x8] = 0x9a
[0x9] = 0xdc
[0xa] = 0x42
[0xb] = 0x99
[0xc] = 0x4e
[0xd] = 0x26
[0xe] = 0xa2
[0xf] = 0xb7
}
}
```

into the following representation

```c
struct uuid __macho_load_command_[10] =
{
enum load_command_type_t cmd = LC_UUID
uint32_t cmdsize = 0x18
uint8_t uuid[0x10] = UUID("74a03abd-1e19-3267-9adc-42994e26a2b7")
}
```

You can compare the shown UUID with the output of otool:
```sh
otool -arch all -l /bin/cat
```
25 changes: 25 additions & 0 deletions rust/plugin_examples/data_renderer/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
fn main() {
let link_path = std::env::var_os("DEP_BINARYNINJACORE_PATH")
.expect("DEP_BINARYNINJACORE_PATH not specified");

println!("cargo::rustc-link-lib=dylib=binaryninjacore");
println!("cargo::rustc-link-search={}", link_path.to_str().unwrap());

#[cfg(target_os = "linux")]
{
println!(
"cargo::rustc-link-arg=-Wl,-rpath,{0},-L{0}",
link_path.to_string_lossy()
);
}

#[cfg(target_os = "macos")]
{
let crate_name = std::env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME not set");
let lib_name = crate_name.replace('-', "_");
println!(
"cargo::rustc-link-arg=-Wl,-install_name,@rpath/lib{}.dylib",
lib_name
);
}
}
109 changes: 109 additions & 0 deletions rust/plugin_examples/data_renderer/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use binaryninja::binary_view::{BinaryView, BinaryViewBase};
use binaryninja::data_renderer::{
register_specific_data_renderer, CustomDataRenderer, TypeContext,
};
use binaryninja::disassembly::{
DisassemblyTextLine, InstructionTextToken, InstructionTextTokenKind,
};
use binaryninja::types::{Type, TypeClass};
use uuid::Uuid;

struct UuidDataRenderer {}

impl CustomDataRenderer for UuidDataRenderer {
fn is_valid_for_data(
&self,
_view: &BinaryView,
_addr: u64,
type_: &Type,
types: &[TypeContext],
) -> bool {
// We only want to render arrays with a size of 16 elements
if type_.type_class() != TypeClass::ArrayTypeClass {
return false;
}
if type_.count() != 0x10 {
return false;
}

// The array elements must be of the type uint8_t
let Some(element_type_conf) = type_.element_type() else {
return false;
};
let element_type = element_type_conf.contents;
if element_type.type_class() != TypeClass::IntegerTypeClass {
return false;
}
if element_type.width() != 1 {
return false;
}

// The array should be embedded in a named type reference with the id macho:["uuid"]
for type_ctx in types {
if type_ctx.type_().type_class() != TypeClass::NamedTypeReferenceClass {
continue;
}

let Some(name_ref) = type_ctx.type_().get_named_type_reference() else {
continue;
};

if name_ref.id() == "macho:[\"uuid\"]" {
return true;
}
}

false
}

fn lines_for_data(
&self,
view: &BinaryView,
addr: u64,
_type_: &Type,
prefix: Vec<InstructionTextToken>,
_width: usize,
_types_ctx: &[TypeContext],
_language: &str,
) -> Vec<DisassemblyTextLine> {
let mut tokens = prefix.clone();

let mut buf = [0u8; 0x10];
let bytes_read = view.read(&mut buf, addr);

// Make sure that we've read all UUID bytes and convert them to token
if bytes_read == 0x10 {
tokens.extend([
InstructionTextToken::new("UUID(\"", InstructionTextTokenKind::Text),
InstructionTextToken::new(
Uuid::from_bytes(buf).to_string(),
InstructionTextTokenKind::String { value: 0 },
),
InstructionTextToken::new("\")", InstructionTextTokenKind::Text),
]);
} else {
tokens.push(InstructionTextToken::new(
"error: cannot read 0x10 bytes",
InstructionTextTokenKind::Annotation,
));
}

vec![DisassemblyTextLine::new_with_addr(tokens, addr)]
}
}

/// # Safety
/// This function is called from Binary Ninja once to initialize the plugin.
#[allow(non_snake_case)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn CorePluginInit() -> bool {
// Initialize logging
binaryninja::logger::Logger::new("UUID Data Renderer")
.with_level(log::LevelFilter::Debug)
.init();

// Register data renderer
register_specific_data_renderer(UuidDataRenderer {});

true
}
Loading
Loading