Skip to content

Commit 1fea541

Browse files
committed
Add BNTL utility plugin
Allow users to easily create, diff, dump and validate type libraries Supports the following formats: - C header files (via core type parsers) - Binary files (collects exported and imported functions) - WinMD files (via `windows-metadata` crate) - Existing type library files (for easy fixups) - Apiset files (to resolve through forwarded windows dlls) Can be invoked as a regular plugin via UI commands or via CLI headless application. Processing of type libraries inherently requires external linking, processing will automatically merge and deduplicate colliding type libraries so prefer to use inside a project or a directory and process all information (for a given platform) at once, rather than smaller invocations.
1 parent 8fbc076 commit 1fea541

32 files changed

+5516
-21
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ members = [
2525
"plugins/warp/examples/headless",
2626
"plugins/workflow_objc",
2727
"plugins/workflow_objc/demo",
28+
"plugins/bntl_utils",
29+
"plugins/bntl_utils/cli",
2830
]
2931

3032
[workspace.dependencies]

plugins/bntl_utils/CMakeLists.txt

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
2+
3+
project(bntl_utils)
4+
5+
if(NOT BN_API_BUILD_EXAMPLES AND NOT BN_INTERNAL_BUILD)
6+
if(NOT BN_API_PATH)
7+
# If we have not already defined the API source directory try and find it.
8+
find_path(
9+
BN_API_PATH
10+
NAMES binaryninjaapi.h
11+
# List of paths to search for the clone of the api
12+
HINTS ../../.. ../../binaryninja/api/ binaryninjaapi binaryninja-api $ENV{BN_API_PATH}
13+
REQUIRED
14+
)
15+
endif()
16+
set(CARGO_STABLE_VERSION 1.91.1)
17+
add_subdirectory(${BN_API_PATH} binaryninjaapi)
18+
endif()
19+
20+
file(GLOB_RECURSE PLUGIN_SOURCES CONFIGURE_DEPENDS
21+
${PROJECT_SOURCE_DIR}/Cargo.toml
22+
${PROJECT_SOURCE_DIR}/src/*.rs)
23+
24+
if(CMAKE_BUILD_TYPE MATCHES Debug)
25+
if(DEMO)
26+
set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/dev-demo)
27+
set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target --profile=dev-demo)
28+
else()
29+
set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/debug)
30+
set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target)
31+
endif()
32+
else()
33+
if(DEMO)
34+
set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/release-demo)
35+
set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target --profile=release-demo)
36+
else()
37+
set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/release)
38+
set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target --release)
39+
endif()
40+
endif()
41+
42+
if(FORCE_COLORED_OUTPUT)
43+
set(CARGO_OPTS ${CARGO_OPTS} --color always)
44+
endif()
45+
46+
if(DEMO)
47+
set(CARGO_FEATURES --features demo --manifest-path ${PROJECT_SOURCE_DIR}/demo/Cargo.toml)
48+
49+
set(OUTPUT_FILE_NAME ${CMAKE_STATIC_LIBRARY_PREFIX}${PROJECT_NAME}_static${CMAKE_STATIC_LIBRARY_SUFFIX})
50+
set(OUTPUT_PDB_NAME ${CMAKE_STATIC_LIBRARY_PREFIX}${PROJECT_NAME}.pdb)
51+
set(OUTPUT_FILE_PATH ${CMAKE_BINARY_DIR}/${OUTPUT_FILE_NAME})
52+
set(OUTPUT_PDB_PATH ${CMAKE_BINARY_DIR}/${OUTPUT_PDB_NAME})
53+
54+
set(BINJA_LIB_DIR $<TARGET_FILE_DIR:binaryninjacore>)
55+
else()
56+
# NOTE: --no-default-features is set to disable building artifacts used for testing
57+
# NOTE: the linker is looking in the target dir and linking on it apparently.
58+
set(CARGO_FEATURES "--no-default-features")
59+
60+
set(OUTPUT_FILE_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}${PROJECT_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX})
61+
set(OUTPUT_PDB_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}${PROJECT_NAME}.pdb)
62+
set(OUTPUT_FILE_PATH ${BN_CORE_PLUGIN_DIR}/${OUTPUT_FILE_NAME})
63+
set(OUTPUT_PDB_PATH ${BN_CORE_PLUGIN_DIR}/${OUTPUT_PDB_NAME})
64+
65+
set(BINJA_LIB_DIR ${BN_INSTALL_BIN_DIR})
66+
endif()
67+
68+
69+
add_custom_target(${PROJECT_NAME} ALL DEPENDS ${OUTPUT_FILE_PATH})
70+
add_dependencies(${PROJECT_NAME} binaryninjaapi)
71+
get_target_property(BN_API_SOURCE_DIR binaryninjaapi SOURCE_DIR)
72+
list(APPEND CMAKE_MODULE_PATH "${BN_API_SOURCE_DIR}/cmake")
73+
find_package(BinaryNinjaCore REQUIRED)
74+
75+
set_property(TARGET ${PROJECT_NAME} PROPERTY OUTPUT_FILE_PATH ${OUTPUT_FILE_PATH})
76+
77+
# Add the whole api to the depends too
78+
file(GLOB API_SOURCES CONFIGURE_DEPENDS
79+
${BN_API_SOURCE_DIR}/binaryninjacore.h
80+
${BN_API_SOURCE_DIR}/rust/src/*.rs
81+
${BN_API_SOURCE_DIR}/rust/binaryninjacore-sys/src/*.rs)
82+
83+
find_program(RUSTUP_PATH rustup REQUIRED HINTS ~/.cargo/bin)
84+
set(RUSTUP_COMMAND ${RUSTUP_PATH} run ${CARGO_STABLE_VERSION} cargo)
85+
86+
if(APPLE)
87+
if(UNIVERSAL)
88+
if(CMAKE_BUILD_TYPE MATCHES Debug)
89+
if(DEMO)
90+
set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/dev-demo/${OUTPUT_FILE_NAME})
91+
set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/dev-demo/${OUTPUT_FILE_NAME})
92+
else()
93+
set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/debug/${OUTPUT_FILE_NAME})
94+
set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/debug/${OUTPUT_FILE_NAME})
95+
endif()
96+
else()
97+
if(DEMO)
98+
set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/release-demo/${OUTPUT_FILE_NAME})
99+
set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/release-demo/${OUTPUT_FILE_NAME})
100+
else()
101+
set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/release/${OUTPUT_FILE_NAME})
102+
set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/release/${OUTPUT_FILE_NAME})
103+
endif()
104+
endif()
105+
106+
add_custom_command(
107+
OUTPUT ${OUTPUT_FILE_PATH}
108+
COMMAND ${CMAKE_COMMAND} -E env
109+
MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR}
110+
${RUSTUP_COMMAND} clean --target=aarch64-apple-darwin ${CARGO_OPTS} --package binaryninjacore-sys
111+
COMMAND ${CMAKE_COMMAND} -E env
112+
MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR}
113+
${RUSTUP_COMMAND} clean --target=x86_64-apple-darwin ${CARGO_OPTS} --package binaryninjacore-sys
114+
COMMAND ${CMAKE_COMMAND} -E env
115+
MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR}
116+
${RUSTUP_COMMAND} build --target=aarch64-apple-darwin ${CARGO_OPTS} ${CARGO_FEATURES}
117+
COMMAND ${CMAKE_COMMAND} -E env
118+
MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR}
119+
${RUSTUP_COMMAND} build --target=x86_64-apple-darwin ${CARGO_OPTS} ${CARGO_FEATURES}
120+
COMMAND lipo -create ${AARCH64_LIB_PATH} ${X86_64_LIB_PATH} -output ${OUTPUT_FILE_PATH}
121+
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
122+
DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}
123+
)
124+
else()
125+
add_custom_command(
126+
OUTPUT ${OUTPUT_FILE_PATH}
127+
COMMAND ${CMAKE_COMMAND} -E env
128+
MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR}
129+
${RUSTUP_COMMAND} clean ${CARGO_OPTS} --package binaryninjacore-sys
130+
COMMAND ${CMAKE_COMMAND} -E env
131+
MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR}
132+
${RUSTUP_COMMAND} build ${CARGO_OPTS} ${CARGO_FEATURES}
133+
COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_FILE_NAME} ${OUTPUT_FILE_PATH}
134+
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
135+
DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}
136+
)
137+
endif()
138+
elseif(WIN32)
139+
if(DEMO)
140+
add_custom_command(
141+
OUTPUT ${OUTPUT_FILE_PATH}
142+
COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} clean ${CARGO_OPTS} --package binaryninjacore-sys
143+
COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} build ${CARGO_OPTS} ${CARGO_FEATURES}
144+
COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_FILE_NAME} ${OUTPUT_FILE_PATH}
145+
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
146+
DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}
147+
)
148+
else()
149+
add_custom_command(
150+
OUTPUT ${OUTPUT_FILE_PATH}
151+
COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} clean ${CARGO_OPTS} --package binaryninjacore-sys
152+
COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} build ${CARGO_OPTS} ${CARGO_FEATURES}
153+
COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_FILE_NAME} ${OUTPUT_FILE_PATH}
154+
COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_PDB_NAME} ${OUTPUT_PDB_PATH}
155+
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
156+
DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}
157+
)
158+
endif()
159+
else()
160+
add_custom_command(
161+
OUTPUT ${OUTPUT_FILE_PATH}
162+
COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} clean ${CARGO_OPTS} --package binaryninjacore-sys
163+
COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} build ${CARGO_OPTS} ${CARGO_FEATURES}
164+
COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_FILE_NAME} ${OUTPUT_FILE_PATH}
165+
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
166+
DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}
167+
)
168+
endif()

plugins/bntl_utils/Cargo.toml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
[package]
2+
name = "bntl_utils"
3+
version = "0.1.0"
4+
edition = "2021"
5+
license = "Apache-2.0"
6+
publish = false
7+
8+
[lib]
9+
crate-type = ["cdylib", "lib"]
10+
11+
[dependencies]
12+
binaryninja.workspace = true
13+
binaryninjacore-sys.workspace = true
14+
tracing = "0.1"
15+
thiserror = "2.0"
16+
similar = "2.7.0"
17+
serde = { version = "1.0", features = ["derive"] }
18+
serde_json = "1.0"
19+
tempdir = "0.3"
20+
nt-apiset = "0.1.0"
21+
url = "2.5"
22+
uuid = "1.20"
23+
walkdir = "2.5"
24+
dashmap = "6.1"
25+
26+
# For reports
27+
minijinja = "2.10.2"
28+
minijinja-embed = "2.10.2"
29+
30+
[build-dependencies]
31+
minijinja-embed = "2.10.2"
32+
33+
# TODO: We need to depend on latest because the windows-metadata crate has not yet been bumped, but depending on the crate
34+
# TODO: with git will mean we pull in all of the data of the crate instead of just the necessary bits, we likely need to
35+
# TODO: wait until the windows-metadata crate is bumped before merging this PR.
36+
# TODO: Relevant PR: https://github.com/microsoft/windows-rs/pull/3799
37+
# TODO: Relevant issue: https://github.com/microsoft/windows-rs/issues/3887
38+
[dependencies.windows-metadata]
39+
git = "https://github.com/microsoft/windows-rs"
40+
tag = "72"

plugins/bntl_utils/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# BNTL Utilities
2+
3+
A plugin and CLI tool for processing Binary Ninja type libraries (BNTL).
4+
5+
For CLI build instructions and usage see [here](./cli/README.md).

plugins/bntl_utils/build.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use std::path::PathBuf;
2+
3+
fn main() {
4+
let link_path = std::env::var_os("DEP_BINARYNINJACORE_PATH")
5+
.expect("DEP_BINARYNINJACORE_PATH not specified");
6+
7+
println!("cargo::rustc-link-lib=dylib=binaryninjacore");
8+
println!("cargo::rustc-link-search={}", link_path.to_str().unwrap());
9+
10+
#[cfg(target_os = "linux")]
11+
{
12+
println!(
13+
"cargo::rustc-link-arg=-Wl,-rpath,{0},-L{0}",
14+
link_path.to_string_lossy()
15+
);
16+
}
17+
18+
#[cfg(target_os = "macos")]
19+
{
20+
let crate_name = std::env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME not set");
21+
let lib_name = crate_name.replace('-', "_");
22+
println!(
23+
"cargo::rustc-link-arg=-Wl,-install_name,@rpath/lib{}.dylib",
24+
lib_name
25+
);
26+
}
27+
28+
let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR specified");
29+
let out_dir_path = PathBuf::from(out_dir);
30+
31+
// Copy all binaries to OUT_DIR for unit tests.
32+
let bin_dir: PathBuf = "fixtures/".into();
33+
if let Ok(entries) = std::fs::read_dir(bin_dir) {
34+
for entry in entries {
35+
let entry = entry.unwrap();
36+
let path = entry.path();
37+
if path.is_file() {
38+
let file_name = path.file_name().unwrap();
39+
let dest_path = out_dir_path.join(file_name);
40+
std::fs::copy(&path, &dest_path).expect("failed to copy binary to OUT_DIR");
41+
}
42+
}
43+
}
44+
45+
println!("cargo::rerun-if-changed=src/templates");
46+
// Templates used for rendering reports.
47+
minijinja_embed::embed_templates!("src/templates");
48+
}

plugins/bntl_utils/cli/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "bntl_cli"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
binaryninja.workspace = true
8+
binaryninjacore-sys.workspace = true
9+
bntl_utils = { path = "../" }
10+
tracing = "0.1"
11+
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
12+
clap = { version = "4.5.58", features = ["derive"] }
13+
rayon = "1.11"
14+
serde_json = "1.0"
15+
thiserror = "2.0"

plugins/bntl_utils/cli/README.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Headless BNTL Processor
2+
3+
Provides headless support for generating, inspecting, and validating Binary Ninja type libraries (BNTL).
4+
5+
### Building
6+
7+
> Assuming you have the following:
8+
> - A compatible Binary Ninja with headless usage (see [this documentation](https://docs.binary.ninja/dev/batch.html#batch-processing-and-other-automation-tips) for more information)
9+
> - Clang
10+
> - Rust (currently tested for 1.91.1)
11+
> - Set `BINARYNINJADIR` env variable to your installation directory (see [here](https://docs.binary.ninja/guide/#binary-path) for more details)
12+
> - If this is not set, the -sys crate will try and locate using the default installation path and last run location.
13+
14+
1. Clone this repository (`git clone https://github.com/Vector35/binaryninja-api/tree/dev`)
15+
2. Build in release (`cargo build --release`)
16+
17+
If compilation fails because it could not link against binaryninjacore than you should double-check you set `BINARYNINJADIR` correctly.
18+
19+
Once it finishes you now will have a `bntl_cli` binary in `target/release` for use.
20+
21+
### Usage
22+
23+
> Assuming you already have the `bntl_cli` binary and a valid headless compatible Binary Ninja license.
24+
25+
#### Create
26+
27+
Generate a new type library from local files or remote projects.
28+
29+
Examples:
30+
31+
- `./bntl_cli create sqlite3.dll "windows-x86_64" ./headers/ ./output/`
32+
- Places a single `sqlite.dll.bntl` file in the `output` directory, as headers have no dependency names associated they will be named `sqlite.dll`.
33+
- `./bntl_cli create myproject "windows-x86_64" binaryninja://enterprise/https://enterprise.com/23ce5eaa-f532-4a93-80f2-a7d7f0aed040/ ./output/`
34+
- Downloads and processes all files in the project, placing potentially multiple `.bntl` files in the `output` directory.
35+
- `./bntl_cli create sqlite3.dll "windows-x86_64" ./winmd/ ./output/`
36+
- `winmd` files are also supported as input, they will be processed together. You also probably want to provide some apiset schema files as well.
37+
38+
#### Dump
39+
40+
Export a type library back into a C header file for inspection.
41+
42+
Examples:
43+
44+
- `./bntl_cli dump sqlite3.dll.bntl ./output/sqlite.h`
45+
46+
#### Diff
47+
48+
Compare two type libraries and generate a .diff file containing a similarity ratio.
49+
50+
Examples:
51+
52+
- `./bntl_cli diff sqlite3.dll.bntl sqlite3.dll.bntl ./output/sqlite.diff`
53+
54+
#### Validate
55+
56+
Check type libraries for common errors, ensuring all referenced types exist across specified platforms.
57+
58+
Examples:
59+
60+
- `./bntl_cli validate ./typelibs/ ./output/`
61+
- Pass in a directory containing `.bntl` files to validate, outputting a JSON file for each type library containing any errors.

plugins/bntl_utils/cli/build.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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(not(target_os = "windows"))]
9+
{
10+
println!(
11+
"cargo::rustc-link-arg=-Wl,-rpath,{0},-L{0}",
12+
link_path.to_string_lossy()
13+
);
14+
}
15+
}

0 commit comments

Comments
 (0)