Skip to content

Commit 64633f6

Browse files
lukbukkitrbran
authored andcommitted
[Rust] Implement custom data renderer API
Also adds an example plugin and misc rust fixes / documentation. This is a continuation of #6721 Co-authored-by: rbran <lgit@rubens.io>
1 parent e6e4ebe commit 64633f6

File tree

10 files changed

+604
-4
lines changed

10 files changed

+604
-4
lines changed

Cargo.lock

Lines changed: 12 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ members = [
88
"rust",
99
"arch/riscv",
1010
"arch/msp430",
11+
"rust/plugin_examples/data_renderer",
1112
"view/minidump",
1213
"plugins/dwarf/dwarf_import",
1314
"plugins/dwarf/dwarf_export",
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "example_data_renderer"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
crate-type = ["cdylib"]
8+
9+
[dependencies]
10+
binaryninjacore-sys = { path = "../../binaryninjacore-sys" }
11+
binaryninja = { path = "../.." }
12+
uuid = "1.18.1"
13+
log = "0.4.27"
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Data Renderer Example
2+
3+
This example implements a simple data renderer for the Mach-O load command LC_UUID.
4+
You can try the renderer by loading the `/bin/cat` binary from macOS.
5+
6+
We're implementing a functionality similar to the one described in the Python data renderer blog post:
7+
https://binary.ninja/2024/04/08/customizing-data-display.html.
8+
9+
## Building
10+
11+
```sh
12+
# Build from the root directory (binaryninja-api)
13+
cargo build --manifest-path rust/plugin_examples/data_renderer/Cargo.toml
14+
# Link binary on macOS
15+
ln -sf $PWD/target/debug/libexample_data_renderer.dylib ~/Library/Application\ Support/Binary\ Ninja/plugins
16+
```
17+
18+
## Result
19+
20+
The following Mach-O load command be will be transformed from
21+
22+
```c
23+
struct uuid __macho_load_command_[10] =
24+
{
25+
enum load_command_type_t cmd = LC_UUID
26+
uint32_t cmdsize = 0x18
27+
uint8_t uuid[0x10] =
28+
{
29+
[0x0] = 0x74
30+
[0x1] = 0xa0
31+
[0x2] = 0x3a
32+
[0x3] = 0xbd
33+
[0x4] = 0x1e
34+
[0x5] = 0x19
35+
[0x6] = 0x32
36+
[0x7] = 0x67
37+
[0x8] = 0x9a
38+
[0x9] = 0xdc
39+
[0xa] = 0x42
40+
[0xb] = 0x99
41+
[0xc] = 0x4e
42+
[0xd] = 0x26
43+
[0xe] = 0xa2
44+
[0xf] = 0xb7
45+
}
46+
}
47+
```
48+
49+
into the following representation
50+
51+
```c
52+
struct uuid __macho_load_command_[10] =
53+
{
54+
enum load_command_type_t cmd = LC_UUID
55+
uint32_t cmdsize = 0x18
56+
uint8_t uuid[0x10] = UUID("74a03abd-1e19-3267-9adc-42994e26a2b7")
57+
}
58+
```
59+
60+
You can compare the shown UUID with the output of otool:
61+
```sh
62+
otool -arch all -l /bin/cat
63+
```
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
fn main() {
2+
let link_path = std::env::var_os("DEP_BINARYNINJACORE_PATH")
3+
.expect("DEP_BINARYNINJACORE_PATH not specified");
4+
5+
println!("cargo::rustc-link-lib=dylib=binaryninjacore");
6+
println!("cargo::rustc-link-search={}", link_path.to_str().unwrap());
7+
8+
#[cfg(target_os = "linux")]
9+
{
10+
println!(
11+
"cargo::rustc-link-arg=-Wl,-rpath,{0},-L{0}",
12+
link_path.to_string_lossy()
13+
);
14+
}
15+
16+
#[cfg(target_os = "macos")]
17+
{
18+
let crate_name = std::env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME not set");
19+
let lib_name = crate_name.replace('-', "_");
20+
println!(
21+
"cargo::rustc-link-arg=-Wl,-install_name,@rpath/lib{}.dylib",
22+
lib_name
23+
);
24+
}
25+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
use binaryninja::binary_view::{BinaryView, BinaryViewBase};
2+
use binaryninja::data_renderer::{
3+
register_data_renderer, CustomDataRenderer, RegistrationType, TypeContext,
4+
};
5+
use binaryninja::disassembly::{
6+
DisassemblyTextLine, InstructionTextToken, InstructionTextTokenKind,
7+
};
8+
use binaryninja::types::{Type, TypeClass};
9+
use uuid::Uuid;
10+
11+
struct UuidDataRenderer {}
12+
13+
impl CustomDataRenderer for UuidDataRenderer {
14+
const REGISTRATION_TYPE: RegistrationType = RegistrationType::Specific;
15+
16+
fn is_valid_for_data(
17+
&self,
18+
_view: &BinaryView,
19+
_addr: u64,
20+
type_: &Type,
21+
types: &[TypeContext],
22+
) -> bool {
23+
// We only want to render arrays with a size of 16 elements
24+
if type_.type_class() != TypeClass::ArrayTypeClass {
25+
return false;
26+
}
27+
if type_.count() != 0x10 {
28+
return false;
29+
}
30+
31+
// The array elements must be of the type uint8_t
32+
let Some(element_type_conf) = type_.element_type() else {
33+
return false;
34+
};
35+
let element_type = element_type_conf.contents;
36+
if element_type.type_class() != TypeClass::IntegerTypeClass {
37+
return false;
38+
}
39+
if element_type.width() != 1 {
40+
return false;
41+
}
42+
43+
// The array should be embedded in a named type reference with the id macho:["uuid"]
44+
for type_ctx in types {
45+
if type_ctx.ty().type_class() != TypeClass::NamedTypeReferenceClass {
46+
continue;
47+
}
48+
49+
let Some(name_ref) = type_ctx.ty().get_named_type_reference() else {
50+
continue;
51+
};
52+
53+
if name_ref.id() == "macho:[\"uuid\"]" {
54+
return true;
55+
}
56+
}
57+
58+
false
59+
}
60+
61+
fn lines_for_data(
62+
&self,
63+
view: &BinaryView,
64+
addr: u64,
65+
_type_: &Type,
66+
prefix: Vec<InstructionTextToken>,
67+
_width: usize,
68+
_types_ctx: &[TypeContext],
69+
_language: &str,
70+
) -> Vec<DisassemblyTextLine> {
71+
let mut tokens = prefix.clone();
72+
73+
let mut buf = [0u8; 0x10];
74+
let bytes_read = view.read(&mut buf, addr);
75+
76+
// Make sure that we've read all UUID bytes and convert them to token
77+
if bytes_read == 0x10 {
78+
tokens.extend([
79+
InstructionTextToken::new("UUID(\"", InstructionTextTokenKind::Text),
80+
InstructionTextToken::new(
81+
Uuid::from_bytes(buf).to_string(),
82+
InstructionTextTokenKind::String { value: 0 },
83+
),
84+
InstructionTextToken::new("\")", InstructionTextTokenKind::Text),
85+
]);
86+
} else {
87+
tokens.push(InstructionTextToken::new(
88+
"error: cannot read 0x10 bytes",
89+
InstructionTextTokenKind::Annotation,
90+
));
91+
}
92+
93+
vec![DisassemblyTextLine::new_with_addr(tokens, addr)]
94+
}
95+
}
96+
97+
/// # Safety
98+
/// This function is called from Binary Ninja once to initialize the plugin.
99+
#[allow(non_snake_case)]
100+
#[unsafe(no_mangle)]
101+
pub unsafe extern "C" fn CorePluginInit() -> bool {
102+
// Initialize logging
103+
binaryninja::logger::Logger::new("UUID Data Renderer")
104+
.with_level(log::LevelFilter::Debug)
105+
.init();
106+
107+
// Register data renderer
108+
register_data_renderer(UuidDataRenderer {});
109+
110+
true
111+
}

0 commit comments

Comments
 (0)