Skip to content

Commit 68fcc03

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. 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 68fcc03

32 files changed

Lines changed: 5514 additions & 21 deletions

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)