summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
authorMason Reed <mason@vector35.com>2024-10-23 20:47:31 -0400
committerMason Reed <mason@vector35.com>2024-10-24 17:03:11 -0400
commite151425087b6c0ee8c4d099bfc2d6d1268201db0 (patch)
tree25160cf81aa6d650cf1497a078acc50f0062757c /plugins
parent0f53c21d6a7abbfd48fd59ec7c15586f02777b7e (diff)
Add WARP integration
https://github.com/Vector35/warp/
Diffstat (limited to 'plugins')
-rw-r--r--plugins/warp/.gitignore2
-rw-r--r--plugins/warp/CMakeLists.txt128
-rw-r--r--plugins/warp/Cargo.toml52
-rw-r--r--plugins/warp/benches/convert.rs39
-rw-r--r--plugins/warp/benches/function.rs42
-rw-r--r--plugins/warp/benches/guid.rs22
-rw-r--r--plugins/warp/build.rs67
-rw-r--r--plugins/warp/fixtures/src/library.c33
-rw-r--r--plugins/warp/fixtures/src/library.h11
-rw-r--r--plugins/warp/fixtures/src/simple.c15
-rw-r--r--plugins/warp/src/bin/sigem.rs176
-rw-r--r--plugins/warp/src/cache.rs270
-rw-r--r--plugins/warp/src/convert.rs688
-rw-r--r--plugins/warp/src/lib.rs161
-rw-r--r--plugins/warp/src/matcher.rs432
-rw-r--r--plugins/warp/src/plugin.rs152
-rw-r--r--plugins/warp/src/plugin/apply.rs82
-rw-r--r--plugins/warp/src/plugin/copy.rs35
-rw-r--r--plugins/warp/src/plugin/create.rs71
-rw-r--r--plugins/warp/src/plugin/find.rs57
-rw-r--r--plugins/warp/src/plugin/types.rs52
-rw-r--r--plugins/warp/src/plugin/workflow.rs38
-rw-r--r--plugins/warp/src/snapshots/warp_ninja__tests__insta_signatures.snap39
23 files changed, 2664 insertions, 0 deletions
diff --git a/plugins/warp/.gitignore b/plugins/warp/.gitignore
new file mode 100644
index 00000000..04d953d3
--- /dev/null
+++ b/plugins/warp/.gitignore
@@ -0,0 +1,2 @@
+Cargo.lock
+!bin/ \ No newline at end of file
diff --git a/plugins/warp/CMakeLists.txt b/plugins/warp/CMakeLists.txt
new file mode 100644
index 00000000..acfbf41e
--- /dev/null
+++ b/plugins/warp/CMakeLists.txt
@@ -0,0 +1,128 @@
+cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
+
+project(warp_ninja)
+
+if(NOT BN_API_BUILD_EXAMPLES AND NOT BN_INTERNAL_BUILD)
+ if(NOT BN_API_PATH)
+ # If we have not already defined the API source directory try and find it.
+ find_path(
+ BN_API_PATH
+ NAMES binaryninjaapi.h
+ # List of paths to search for the clone of the api
+ HINTS ../../.. ../../binaryninja/api/ binaryninjaapi binaryninja-api $ENV{BN_API_PATH}
+ REQUIRED
+ )
+ endif()
+ set(CARGO_STABLE_VERSION 1.77.0)
+ add_subdirectory(${BN_API_PATH} binaryninjaapi)
+endif()
+
+file(GLOB_RECURSE PLUGIN_SOURCES CONFIGURE_DEPENDS
+ ${PROJECT_SOURCE_DIR}/Cargo.toml
+ ${PROJECT_SOURCE_DIR}/src/*.rs)
+
+if(CMAKE_BUILD_TYPE MATCHES Debug)
+ set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/debug)
+ set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target)
+else()
+ set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/release)
+ set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target --release)
+endif()
+
+if(FORCE_COLORED_OUTPUT)
+ set(CARGO_OPTS ${CARGO_OPTS} --color always)
+endif()
+
+set(CARGO_FEATURES "")
+set(OUTPUT_FILE_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}${PROJECT_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX})
+set(OUTPUT_PDB_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}${PROJECT_NAME}.pdb)
+set(OUTPUT_FILE_PATH ${BN_CORE_PLUGIN_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${PROJECT_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX})
+set(OUTPUT_PDB_PATH ${BN_CORE_PLUGIN_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${PROJECT_NAME}.pdb)
+
+add_custom_target(${PROJECT_NAME} ALL DEPENDS ${OUTPUT_FILE_PATH})
+add_dependencies(${PROJECT_NAME} binaryninjaapi)
+get_target_property(BN_API_SOURCE_DIR binaryninjaapi SOURCE_DIR)
+list(APPEND CMAKE_MODULE_PATH "${BN_API_SOURCE_DIR}/cmake")
+find_package(BinaryNinjaCore REQUIRED)
+
+set(BINJA_LIB_DIR ${BN_INSTALL_BIN_DIR})
+
+set_property(TARGET ${PROJECT_NAME} PROPERTY OUTPUT_FILE_PATH ${OUTPUT_FILE_PATH})
+
+# Add the whole api to the depends too
+file(GLOB_RECURSE API_SOURCES CONFIGURE_DEPENDS
+ ${BN_API_SOURCE_DIR}/binaryninjacore.h
+ ${BN_API_SOURCE_DIR}/rust/src/*.rs
+ ${BN_API_SOURCE_DIR}/rust/binaryninjacore-sys/src/*.rs)
+
+find_program(RUSTUP_PATH rustup REQUIRED HINTS ~/.cargo/bin)
+set(RUSTUP_COMMAND ${RUSTUP_PATH} run ${CARGO_STABLE_VERSION} cargo)
+
+if(APPLE)
+ if(UNIVERSAL)
+ if(CMAKE_BUILD_TYPE MATCHES Debug)
+ set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/debug/${OUTPUT_FILE_NAME})
+ set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/debug/${OUTPUT_FILE_NAME})
+ else()
+ set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/release/${OUTPUT_FILE_NAME})
+ set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/release/${OUTPUT_FILE_NAME})
+ endif()
+
+ add_custom_command(
+ OUTPUT ${OUTPUT_FILE_PATH}
+ COMMAND ${CMAKE_COMMAND} -E env
+ MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR}
+ ${RUSTUP_COMMAND} clean --target=aarch64-apple-darwin ${CARGO_OPTS}
+ COMMAND ${CMAKE_COMMAND} -E env
+ MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR}
+ ${RUSTUP_COMMAND} clean --target=x86_64-apple-darwin ${CARGO_OPTS}
+ COMMAND ${CMAKE_COMMAND} -E env
+ MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR}
+ ${RUSTUP_COMMAND} build --target=aarch64-apple-darwin ${CARGO_OPTS} ${CARGO_FEATURES}
+ COMMAND ${CMAKE_COMMAND} -E env
+ MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR}
+ ${RUSTUP_COMMAND} build --target=x86_64-apple-darwin ${CARGO_OPTS} ${CARGO_FEATURES}
+ COMMAND lipo -create ${AARCH64_LIB_PATH} ${X86_64_LIB_PATH} -output ${OUTPUT_FILE_PATH}
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+ DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}
+ )
+ else()
+ if(CMAKE_BUILD_TYPE MATCHES Debug)
+ set(LIB_PATH ${PROJECT_BINARY_DIR}/target/debug/${OUTPUT_FILE_NAME})
+ else()
+ set(LIB_PATH ${PROJECT_BINARY_DIR}/target/release/${OUTPUT_FILE_NAME})
+ endif()
+
+ add_custom_command(
+ OUTPUT ${OUTPUT_FILE_PATH}
+ COMMAND ${CMAKE_COMMAND} -E env
+ MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR}
+ ${RUSTUP_COMMAND} clean ${CARGO_OPTS}
+ COMMAND ${CMAKE_COMMAND} -E env
+ MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR}
+ ${RUSTUP_COMMAND} build ${CARGO_OPTS} ${CARGO_FEATURES}
+ COMMAND ${CMAKE_COMMAND} -E copy ${LIB_PATH} ${OUTPUT_FILE_PATH}
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+ DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}
+ )
+ endif()
+elseif(WIN32)
+ add_custom_command(
+ OUTPUT ${OUTPUT_FILE_PATH}
+ COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} clean ${CARGO_OPTS}
+ COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} build ${CARGO_OPTS} ${CARGO_FEATURES}
+ COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_FILE_NAME} ${OUTPUT_FILE_PATH}
+ COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_PDB_NAME} ${OUTPUT_PDB_PATH}
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+ DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}
+ )
+else()
+ add_custom_command(
+ OUTPUT ${OUTPUT_FILE_PATH}
+ COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} clean ${CARGO_OPTS}
+ COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} build ${CARGO_OPTS} ${CARGO_FEATURES}
+ COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_FILE_NAME} ${OUTPUT_FILE_PATH}
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+ DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}
+ )
+endif()
diff --git a/plugins/warp/Cargo.toml b/plugins/warp/Cargo.toml
new file mode 100644
index 00000000..5527eca3
--- /dev/null
+++ b/plugins/warp/Cargo.toml
@@ -0,0 +1,52 @@
+[package]
+name = "warp_ninja"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["lib", "cdylib"]
+
+[dependencies]
+binaryninja = { path = "../../rust", features = ["rayon"] }
+binaryninjacore-sys = { path = "../../rust/binaryninjacore-sys" }
+warp = { git = "https://github.com/Vector35/warp/", rev = "dc51fd5" }
+log = "0.4"
+arboard = "3.4"
+rayon = "1.10"
+dashmap = "6.1"
+walkdir = "2.5"
+fastbloom = "0.7"
+# For sigem
+env_logger = "0.11.5"
+clap = { version = "4.5.16", features = ["derive"] }
+ar = { git = "https://github.com/mdsteele/rust-ar" }
+tempdir = "0.3.7"
+serde_json = "1.0.132"
+
+[build-dependencies]
+cc = "1.1.28"
+
+[dev-dependencies]
+criterion = "0.5.1"
+insta = { version = "1.38.0", features = ["yaml"] }
+
+[profile.release]
+panic = "abort"
+lto = true
+debug = "full"
+
+[profile.dev.package]
+insta.opt-level = 3
+similar.opt-level = 3
+
+[[bench]]
+name = "guid"
+harness = false
+
+[[bench]]
+name = "convert"
+harness = false
+
+[[bench]]
+name = "function"
+harness = false \ No newline at end of file
diff --git a/plugins/warp/benches/convert.rs b/plugins/warp/benches/convert.rs
new file mode 100644
index 00000000..e916d5df
--- /dev/null
+++ b/plugins/warp/benches/convert.rs
@@ -0,0 +1,39 @@
+use binaryninja::binaryview::BinaryViewExt;
+use binaryninja::headless::Session;
+use binaryninja::types::Conf;
+use criterion::{criterion_group, criterion_main, Criterion};
+use std::path::PathBuf;
+use warp_ninja::convert::from_bn_type;
+
+pub fn type_conversion_benchmark(c: &mut Criterion) {
+ let session = Session::new();
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ for entry in std::fs::read_dir(out_dir).expect("Failed to read OUT_DIR") {
+ let entry = entry.expect("Failed to read directory entry");
+ let path = entry.path();
+ if path.is_file() {
+ if let Some(bv) = session.load(path.to_str().unwrap()) {
+ let functions = bv.functions();
+ c.bench_function("type conversion all functions", |b| {
+ b.iter(|| {
+ for func in &functions {
+ from_bn_type(&bv, func.function_type(), u8::MAX);
+ }
+ })
+ });
+
+ let types = bv.types();
+ c.bench_function("type conversion all types", |b| {
+ b.iter(|| {
+ for ty in &types {
+ from_bn_type(&bv, ty.type_object().clone(), u8::MAX);
+ }
+ })
+ });
+ }
+ }
+ }
+}
+
+criterion_group!(benches, type_conversion_benchmark);
+criterion_main!(benches);
diff --git a/plugins/warp/benches/function.rs b/plugins/warp/benches/function.rs
new file mode 100644
index 00000000..c6f6d70d
--- /dev/null
+++ b/plugins/warp/benches/function.rs
@@ -0,0 +1,42 @@
+use binaryninja::binaryview::BinaryViewExt;
+use binaryninja::headless::Session;
+use criterion::{criterion_group, criterion_main, Criterion};
+use rayon::prelude::*;
+use warp_ninja::build_function;
+use warp_ninja::cache::FunctionCache;
+
+pub fn function_benchmark(c: &mut Criterion) {
+ let session = Session::new();
+ let bv = session.load(env!("TEST_BIN_LIBRARY_OBJ")).unwrap();
+ let functions = bv.functions();
+ assert_eq!(functions.len(), 6);
+ let mut function_iter = functions.into_iter();
+ let first_function = function_iter.next().unwrap();
+
+ c.bench_function("signature first function", |b| {
+ b.iter(|| {
+ let _ = build_function(&first_function, &first_function.low_level_il().unwrap());
+ })
+ });
+
+ c.bench_function("signature all functions", |b| {
+ b.iter(|| {
+ for func in &functions {
+ let _ = build_function(&func, &func.low_level_il().unwrap());
+ }
+ })
+ });
+
+ let cache = FunctionCache::default();
+ c.bench_function("signature all functions rayon", |b| {
+ b.iter(|| {
+ functions
+ .par_iter()
+ .map_with(cache.clone(), |par_cache, func| par_cache.function(&func))
+ .collect::<Vec<_>>()
+ })
+ });
+}
+
+criterion_group!(benches, function_benchmark);
+criterion_main!(benches);
diff --git a/plugins/warp/benches/guid.rs b/plugins/warp/benches/guid.rs
new file mode 100644
index 00000000..577f80b5
--- /dev/null
+++ b/plugins/warp/benches/guid.rs
@@ -0,0 +1,22 @@
+use binaryninja::binaryview::BinaryViewExt;
+use binaryninja::headless::Session;
+use criterion::{criterion_group, criterion_main, Criterion};
+use warp_ninja::function_guid;
+
+pub fn guid_benchmark(c: &mut Criterion) {
+ let session = Session::new();
+ let bv = session.load(env!("TEST_BIN_LIBRARY_OBJ")).unwrap();
+ let functions = bv.functions();
+ assert_eq!(functions.len(), 6);
+ let mut function_iter = functions.into_iter();
+ let first_function = function_iter.next().unwrap();
+
+ c.bench_function("function guid", |b| {
+ b.iter(|| {
+ function_guid(&first_function, &vec![]);
+ })
+ });
+}
+
+criterion_group!(benches, guid_benchmark);
+criterion_main!(benches);
diff --git a/plugins/warp/build.rs b/plugins/warp/build.rs
new file mode 100644
index 00000000..fe4d31f8
--- /dev/null
+++ b/plugins/warp/build.rs
@@ -0,0 +1,67 @@
+use std::path::PathBuf;
+use std::process::Command;
+
+fn compile_rust(file: PathBuf) -> bool {
+ let out_dir = std::env::var_os("OUT_DIR").unwrap();
+ let rustc = std::env::var_os("RUSTC").unwrap();
+ let rustc = rustc.to_str().unwrap();
+ let mut rustc = rustc.split('\x1f');
+ let mut cmd = Command::new(rustc.next().unwrap());
+ cmd.args(rustc)
+ .arg("--crate-type=rlib")
+ .arg("--out-dir")
+ .arg(out_dir)
+ .arg(file);
+ cmd.status().expect("failed to invoke rustc").success()
+}
+
+fn main() {
+ let link_path = std::env::var_os("BINARYNINJADIR").expect("BINARYNINJADIR specified");
+ let out_dir = std::env::var_os("OUT_DIR").expect("OUT_DIR specified");
+ let out_dir_path = PathBuf::from(out_dir);
+
+ println!("cargo::rustc-link-lib=dylib=binaryninjacore");
+ println!("cargo::rustc-link-search={}", link_path.to_str().unwrap());
+
+ #[cfg(not(target_os = "windows"))]
+ {
+ println!(
+ "cargo::rustc-link-arg=-Wl,-rpath,{0},-L{0}",
+ link_path.to_string_lossy()
+ );
+ }
+
+ // Copy all binaries to OUT_DIR for unit tests.
+ let bin_dir: PathBuf = "fixtures/bin".into();
+ if let Ok(entries) = std::fs::read_dir(bin_dir) {
+ for entry in entries {
+ let entry = entry.unwrap();
+ let path = entry.path();
+ if path.is_file() {
+ let file_name = path.file_name().unwrap();
+ let dest_path = out_dir_path.join(file_name);
+ std::fs::copy(&path, &dest_path).expect("failed to copy binary to OUT_DIR");
+ }
+ }
+ }
+
+ // Compile all .c files in fixtures/src directory for unit tests.
+ let src_dir: PathBuf = "fixtures/src".into();
+ if let Ok(entries) = std::fs::read_dir(src_dir) {
+ for entry in entries {
+ let entry = entry.unwrap();
+ let path = entry.path();
+ match path.extension().map(|s| s.to_str().unwrap()) {
+ Some("c") => {
+ cc::Build::new()
+ .file(&path)
+ .compile(path.file_stem().unwrap().to_str().unwrap());
+ }
+ Some("rs") => {
+ compile_rust(path);
+ }
+ _ => {}
+ }
+ }
+ }
+}
diff --git a/plugins/warp/fixtures/src/library.c b/plugins/warp/fixtures/src/library.c
new file mode 100644
index 00000000..66a84ad5
--- /dev/null
+++ b/plugins/warp/fixtures/src/library.c
@@ -0,0 +1,33 @@
+#include <stdio.h>
+
+#include "library.h"
+
+int myFunction(int x)
+{
+ printf("%d\n", x);
+ return x;
+}
+
+int recursiveFunc(int x);
+int otherFunction(int x)
+{
+ x += 5;
+ if (x < 10) return otherFunction(x);
+ return recursiveFunc(x);
+}
+
+int recursiveFunc(int x)
+{
+ if (x <= 0) return 0;
+ return x + otherFunction(x - 1);
+}
+
+struct MyStruct myFunction2(int x)
+{
+ printf("MyStruct %d\n", x);
+ struct MyStruct myStruct;
+ myStruct.a = recursiveFunc(x);
+ myStruct.b = x * 10;
+ myStruct.c = "my struct";
+ return myStruct;
+} \ No newline at end of file
diff --git a/plugins/warp/fixtures/src/library.h b/plugins/warp/fixtures/src/library.h
new file mode 100644
index 00000000..ec7a6e63
--- /dev/null
+++ b/plugins/warp/fixtures/src/library.h
@@ -0,0 +1,11 @@
+#pragma once
+
+struct MyStruct {
+ int a;
+ int b;
+ const char* c;
+ struct MyStruct* d;
+};
+
+int myFunction(int x);
+struct MyStruct myFunction2(int x); \ No newline at end of file
diff --git a/plugins/warp/fixtures/src/simple.c b/plugins/warp/fixtures/src/simple.c
new file mode 100644
index 00000000..6e7fbd43
--- /dev/null
+++ b/plugins/warp/fixtures/src/simple.c
@@ -0,0 +1,15 @@
+#include <stdio.h>
+
+#include "library.h"
+
+int simple() {
+ printf("This is main!\n");
+
+ printf("calling myFunction\n");
+ int returnVal = myFunction(55);
+
+ printf("calling myFunction2\n");
+ struct MyStruct myStruct = myFunction2(returnVal);
+
+ return myStruct.b;
+} \ No newline at end of file
diff --git a/plugins/warp/src/bin/sigem.rs b/plugins/warp/src/bin/sigem.rs
new file mode 100644
index 00000000..f0551ccc
--- /dev/null
+++ b/plugins/warp/src/bin/sigem.rs
@@ -0,0 +1,176 @@
+use std::fs::File;
+use std::io::Read;
+use std::path::{Path, PathBuf};
+
+use ar::Archive;
+use clap::{arg, Parser};
+use rayon::prelude::*;
+
+use binaryninja::binaryview::{BinaryView, BinaryViewExt};
+use serde_json::json;
+use warp::signature::Data;
+
+#[derive(Parser, Debug)]
+#[command(version, about, long_about = None)]
+struct Args {
+ /// Path of the binary/BNDB to generate signatures of
+ #[arg(index = 1)]
+ binary: PathBuf,
+
+ /// The signature output file
+ #[arg(index = 2)]
+ output: Option<PathBuf>,
+
+ /// Should we overwrite output file
+ ///
+ /// NOTE: If the file exists we will exit early to prevent wasted effort.
+ /// NOTE: If the file is created while we are running it will still be overwritten.
+ #[arg(short, long)]
+ overwrite: Option<bool>,
+
+ /// The external debug information file to use
+ #[arg(short, long)]
+ debug_info: Option<PathBuf>,
+}
+
+fn main() {
+ let args = Args::parse();
+ env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
+
+ // If no output file was given, just prepend binary with extension sbin
+ let output_file = args.output.unwrap_or(args.binary.with_extension("sbin"));
+
+ if output_file.exists() && !args.overwrite.unwrap_or(false) {
+ log::info!("Output file already exists, skipping... {:?}", output_file);
+ return;
+ }
+
+ log::debug!("Starting Binary Ninja session...");
+ let _headless_session = binaryninja::headless::Session::new();
+
+ log::info!("Creating functions for {:?}...", args.binary);
+ let start = std::time::Instant::now();
+ let data = data_from_file(&args.binary).expect("Failed to read data");
+ log::info!("Functions created in {:?}", start.elapsed());
+
+ // TODO: Add a way to override the symbol type to make it a different function symbol.
+ // TODO: Right now the consumers must dictate that.
+ // TODO: The binja_warp consumer sets this to library function fwiw
+
+ if !data.functions.is_empty() {
+ std::fs::write(&output_file, data.to_bytes()).expect("Failed to write functions to file");
+ log::info!(
+ "{} functions written to {:?}...",
+ data.functions.len(),
+ output_file
+ );
+ } else {
+ log::warn!("No functions found for binary {:?}...", args.binary);
+ }
+}
+
+fn data_from_view(view: &BinaryView) -> Data {
+ let mut data = Data::default();
+
+ let functions = view
+ .functions()
+ .par_iter()
+ .filter(|f| !f.symbol().short_name().as_str().contains("sub_") || f.has_user_annotations())
+ .filter_map(|f| {
+ let llil = f.low_level_il().ok()?;
+ warp_ninja::cache::cached_function(&f, &llil)
+ })
+ .collect::<Vec<_>>();
+
+ data.functions = functions;
+ data
+}
+
+fn data_from_archive<R: Read>(mut archive: Archive<R>) -> Option<Data> {
+ // TODO: I feel like this is a hack...
+ let temp_dir = tempdir::TempDir::new("tmp_archive").ok()?;
+ // Iterate through the entries in the ar file and make a temp dir with them
+ let mut entry_files = Vec::new();
+ while let Some(entry) = archive.next_entry() {
+ match entry {
+ Ok(mut entry) => {
+ let name = String::from_utf8_lossy(entry.header().identifier()).to_string();
+ // Write entry data to a temp directory
+ let output_path = temp_dir.path().join(name);
+ let mut output_file =
+ File::create(&output_path).expect("Failed to create entry file");
+ std::io::copy(&mut entry, &mut output_file).expect("Failed to read entry data");
+ entry_files.push(output_path);
+ }
+ Err(e) => {
+ log::error!("Failed to read archive entry: {}", e);
+ }
+ }
+ }
+
+ // Create the data.
+ // TODO: into_par_iter will corrupt the heap frequently, you should basically always restrict
+ // TODO: With RAYON_NUM_THREADS (set to like... 1)
+ let entry_data = entry_files
+ .into_par_iter()
+ .filter_map(|path| {
+ log::debug!("Creating data for ENTRY {:?}...", path);
+ data_from_file(&path)
+ })
+ .collect::<Vec<_>>();
+
+ // TODO: Cloning here is unnecessary
+ Some(Data {
+ functions: entry_data
+ .iter()
+ .flat_map(|d| d.functions.to_owned())
+ .collect(),
+ types: entry_data.iter().flat_map(|d| d.types.to_owned()).collect(),
+ })
+}
+
+// TODO: Pass settings.
+fn data_from_file(path: &Path) -> Option<Data> {
+ // TODO: Add external debug info files.
+ // TODO: Support IDB's through debug info
+ let settings_json = json!({
+ "analysis.linearSweep.autorun": true,
+ "analysis.signatureMatcher.autorun": false,
+ "analysis.plugins.WARPMatcher": true,
+ });
+
+ match path.extension() {
+ Some(ext) if ext == "a" || ext == "lib" || ext == "rlib" => {
+ let archive_file = File::open(path).expect("Failed to open archive file");
+ let archive = Archive::new(archive_file);
+ data_from_archive(archive)
+ }
+ _ => {
+ let path_str = path.to_str().unwrap();
+ let view =
+ binaryninja::load_with_options(path_str, true, Some(settings_json.to_string()))?;
+ Some(data_from_view(&view))
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_data_from_file() {
+ env_logger::init();
+ // TODO: Store oracles here to get more out of this test.
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ let _headless_session = binaryninja::headless::Session::new();
+ for entry in std::fs::read_dir(out_dir).expect("Failed to read OUT_DIR") {
+ let entry = entry.expect("Failed to read directory entry");
+ let path = entry.path();
+ if path.is_file() {
+ let result = data_from_file(&path);
+ assert!(result.is_some());
+ }
+ }
+ }
+}
diff --git a/plugins/warp/src/cache.rs b/plugins/warp/src/cache.rs
new file mode 100644
index 00000000..79e974b4
--- /dev/null
+++ b/plugins/warp/src/cache.rs
@@ -0,0 +1,270 @@
+use binaryninja::architecture::Architecture;
+use binaryninja::binaryview::{BinaryView, BinaryViewBase, BinaryViewExt};
+use binaryninja::function::Function as BNFunction;
+use binaryninja::llil;
+use binaryninja::llil::{FunctionMutability, NonSSA, NonSSAVariant};
+use binaryninja::rc::Guard;
+use binaryninja::rc::Ref as BNRef;
+use dashmap::try_result::TryResult;
+use dashmap::DashMap;
+use std::collections::HashSet;
+use std::hash::{DefaultHasher, Hash, Hasher};
+use std::sync::OnceLock;
+use warp::signature::function::constraints::FunctionConstraint;
+use warp::signature::function::{Function, FunctionGUID};
+
+use crate::convert::from_bn_symbol;
+use crate::{build_function, function_guid};
+
+pub static FUNCTION_CACHE: OnceLock<DashMap<ViewID, FunctionCache>> = OnceLock::new();
+pub static GUID_CACHE: OnceLock<DashMap<ViewID, GUIDCache>> = OnceLock::new();
+
+pub fn cached_function<A: Architecture, M: FunctionMutability, V: NonSSAVariant>(
+ function: &BNFunction,
+ llil: &llil::Function<A, M, NonSSA<V>>,
+) -> Option<Function> {
+ let view = function.view();
+ let view_id = ViewID::from(view.as_ref());
+ let function_cache = FUNCTION_CACHE.get_or_init(Default::default);
+ match function_cache.get(&view_id) {
+ Some(cache) => cache.function(function, llil),
+ None => {
+ let cache = FunctionCache::default();
+ let function = cache.function(function, llil);
+ function_cache.insert(view_id, cache);
+ function
+ }
+ }
+}
+
+pub fn cached_call_site_constraints(function: &BNFunction) -> HashSet<FunctionConstraint> {
+ let view = function.view();
+ let view_id = ViewID::from(view);
+ let guid_cache = GUID_CACHE.get_or_init(Default::default);
+ match guid_cache.get(&view_id) {
+ Some(cache) => cache.call_site_constraints(function),
+ None => {
+ let cache = GUIDCache::default();
+ let constraints = cache.call_site_constraints(function);
+ guid_cache.insert(view_id, cache);
+ constraints
+ }
+ }
+}
+
+pub fn cached_adjacency_constraints(function: &BNFunction) -> HashSet<FunctionConstraint> {
+ let view = function.view();
+ let view_id = ViewID::from(view);
+ let guid_cache = GUID_CACHE.get_or_init(Default::default);
+ match guid_cache.get(&view_id) {
+ Some(cache) => cache.adjacency_constraints(function),
+ None => {
+ let cache = GUIDCache::default();
+ let constraints = cache.adjacency_constraints(function);
+ guid_cache.insert(view_id, cache);
+ constraints
+ }
+ }
+}
+
+pub fn cached_function_guid<A: Architecture, M: FunctionMutability, V: NonSSAVariant>(
+ function: &BNFunction,
+ llil: &llil::Function<A, M, NonSSA<V>>,
+) -> Option<FunctionGUID> {
+ let view = function.view();
+ let view_id = ViewID::from(view);
+ let guid_cache = GUID_CACHE.get_or_init(Default::default);
+ match guid_cache.get(&view_id) {
+ Some(cache) => cache.function_guid(function, llil),
+ None => {
+ let cache = GUIDCache::default();
+ let guid = cache.function_guid(function, llil);
+ guid_cache.insert(view_id, cache);
+ guid
+ }
+ }
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct FunctionCache {
+ pub cache: DashMap<FunctionID, Option<Function>>,
+}
+
+impl FunctionCache {
+ pub fn function<A: Architecture, M: FunctionMutability, V: NonSSAVariant>(
+ &self,
+ function: &BNFunction,
+ llil: &llil::Function<A, M, NonSSA<V>>,
+ ) -> Option<Function> {
+ let function_id = FunctionID::from(function);
+ match self.cache.try_get_mut(&function_id) {
+ TryResult::Present(function) => function.value().to_owned(),
+ TryResult::Absent => {
+ let function = build_function(function, llil);
+ self.cache.insert(function_id, function.clone());
+ function
+ }
+ TryResult::Locked => build_function(function, llil),
+ }
+ }
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct GUIDCache {
+ pub cache: DashMap<FunctionID, Option<FunctionGUID>>,
+}
+
+impl GUIDCache {
+ pub fn call_site_constraints(&self, function: &BNFunction) -> HashSet<FunctionConstraint> {
+ let view = function.view();
+ let func_id = FunctionID::from(function);
+ let func_start = function.start();
+ let mut constraints = HashSet::new();
+ for call_site in &function.call_sites() {
+ for cs_ref in &view.get_code_refs(call_site.address) {
+ let cs_ref_func = cs_ref.function();
+ let cs_ref_func_id = FunctionID::from(cs_ref_func);
+ if cs_ref_func_id != func_id {
+ if let Some(cs_ref_func_llil) = cs_ref_func.low_level_il_if_available() {
+ // Function references another function, constrain on the pattern.
+ // TODO: If function is trivial thunk we should _also_ insert the tailcall target as a constraint.
+ let call_site_offset: i64 = func_start as i64 - call_site.address as i64;
+ constraints.insert(self.function_constraint(
+ cs_ref_func,
+ &cs_ref_func_llil,
+ call_site_offset,
+ ));
+ }
+ }
+ }
+ }
+ constraints
+ }
+
+ pub fn adjacency_constraints(&self, function: &BNFunction) -> HashSet<FunctionConstraint> {
+ let view = function.view();
+ let func_id = FunctionID::from(function);
+ let func_start = function.start();
+ let mut constraints = HashSet::new();
+
+ let mut func_addr_constraint = |func_start_addr| {
+ // NOTE: We could potentially have dozens of functions all at the same start address.
+ for curr_func in &view.functions_at(func_start_addr) {
+ let curr_func_id = FunctionID::from(curr_func.as_ref());
+ if curr_func_id != func_id {
+ // NOTE: We have to get the llil here for the function which is problematic for running
+ // NOTE: within a workflow (before analysis has finished)
+ if let Some(curr_func_llil) = curr_func.low_level_il_if_available() {
+ // Function adjacent to another function, constrain on the pattern.
+ let curr_addr_offset = (func_start_addr as i64) - func_start as i64;
+ constraints.insert(self.function_constraint(
+ &curr_func,
+ &curr_func_llil,
+ curr_addr_offset,
+ ));
+ }
+ }
+ }
+ };
+
+ let mut before_func_start = func_start;
+ for _ in 0..2 {
+ before_func_start = view.function_start_before(before_func_start);
+ func_addr_constraint(before_func_start);
+ }
+
+ let mut after_func_start = func_start;
+ for _ in 0..2 {
+ after_func_start = view.function_start_after(after_func_start);
+ func_addr_constraint(after_func_start);
+ }
+
+ constraints
+ }
+
+ /// Construct a function constraint, must pass the offset at which it is located.
+ pub fn function_constraint<A: Architecture, M: FunctionMutability, V: NonSSAVariant>(
+ &self,
+ function: &BNFunction,
+ llil: &llil::Function<A, M, NonSSA<V>>,
+ offset: i64,
+ ) -> FunctionConstraint {
+ let guid = self.function_guid(function, llil);
+ let symbol = from_bn_symbol(&function.symbol());
+ FunctionConstraint {
+ guid,
+ symbol: Some(symbol),
+ offset,
+ }
+ }
+
+ pub fn function_guid<A: Architecture, M: FunctionMutability, V: NonSSAVariant>(
+ &self,
+ function: &BNFunction,
+ llil: &llil::Function<A, M, NonSSA<V>>,
+ ) -> Option<FunctionGUID> {
+ let function_id = FunctionID::from(function);
+ match self.cache.try_get_mut(&function_id) {
+ TryResult::Present(function_guid) => function_guid.value().to_owned(),
+ TryResult::Absent => {
+ let function_guid = function_guid(function, llil);
+ self.cache.insert(function_id, function_guid);
+ function_guid
+ }
+ TryResult::Locked => function_guid(function, llil),
+ }
+ }
+}
+
+/// A unique view ID, used for caching.
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
+pub struct ViewID(u64);
+
+impl From<&BinaryView> for ViewID {
+ fn from(value: &BinaryView) -> Self {
+ let mut hasher = DefaultHasher::new();
+ hasher.write_u64(value.original_image_base());
+ hasher.write(value.view_type().to_bytes());
+ hasher.write_u64(value.entry_point());
+ hasher.write(value.file().filename().to_bytes());
+ Self(hasher.finish())
+ }
+}
+
+impl From<BNRef<BinaryView>> for ViewID {
+ fn from(value: BNRef<BinaryView>) -> Self {
+ Self::from(value.as_ref())
+ }
+}
+
+impl From<Guard<'_, BinaryView>> for ViewID {
+ fn from(value: Guard<'_, BinaryView>) -> Self {
+ Self::from(value.as_ref())
+ }
+}
+
+/// A unique function ID, used for caching.
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
+pub struct FunctionID(u64);
+
+impl From<&BNFunction> for FunctionID {
+ fn from(value: &BNFunction) -> Self {
+ let mut hasher = DefaultHasher::new();
+ hasher.write_u64(value.start());
+ hasher.write_u64(value.lowest_address());
+ hasher.write_u64(value.highest_address());
+ Self(hasher.finish())
+ }
+}
+
+impl From<BNRef<BNFunction>> for FunctionID {
+ fn from(value: BNRef<BNFunction>) -> Self {
+ Self::from(value.as_ref())
+ }
+}
+
+impl From<Guard<'_, BNFunction>> for FunctionID {
+ fn from(value: Guard<'_, BNFunction>) -> Self {
+ Self::from(value.as_ref())
+ }
+}
diff --git a/plugins/warp/src/convert.rs b/plugins/warp/src/convert.rs
new file mode 100644
index 00000000..cbb47858
--- /dev/null
+++ b/plugins/warp/src/convert.rs
@@ -0,0 +1,688 @@
+use std::collections::HashSet;
+
+use binaryninja::architecture::Architecture as BNArchitecture;
+use binaryninja::architecture::ArchitectureExt;
+use binaryninja::binaryview::{BinaryView, BinaryViewExt};
+use binaryninja::callingconvention::CallingConvention as BNCallingConvention;
+use binaryninja::rc::Ref as BNRef;
+use binaryninja::symbol::{Symbol as BNSymbol, SymbolType as BNSymbolType};
+use binaryninja::types::{
+ BaseStructure as BNBaseStructure, Conf as BNConf, EnumerationBuilder as BNEnumerationBuilder,
+ FunctionParameter as BNFunctionParameter, MemberAccess as BNMemberAccess, MemberAccess,
+ MemberScope as BNMemberScope, NamedTypeReference, NamedTypeReference as BNNamedTypeReference,
+ NamedTypeReferenceClass, StructureBuilder as BNStructureBuilder,
+ StructureMember as BNStructureMember,
+};
+use binaryninja::types::{
+ StructureType as BNStructureType, Type as BNType, TypeClass as BNTypeClass,
+};
+
+use warp::r#type::class::array::ArrayModifiers;
+use warp::r#type::class::function::{Location, RegisterLocation};
+use warp::r#type::class::pointer::PointerAddressing;
+use warp::r#type::class::structure::StructureMemberModifiers;
+use warp::r#type::class::{
+ ArrayClass, BooleanClass, CallingConvention, CharacterClass, EnumerationClass,
+ EnumerationMember, FloatClass, FunctionClass, FunctionMember, IntegerClass, PointerClass,
+ ReferrerClass, StructureClass, StructureMember, TypeClass,
+};
+use warp::r#type::guid::TypeGUID;
+use warp::r#type::Type;
+use warp::symbol::class::SymbolClass;
+use warp::symbol::{Symbol, SymbolModifiers};
+
+pub fn from_bn_symbol(raw_symbol: &BNSymbol) -> Symbol {
+ // TODO: Use this?
+ let _is_export = raw_symbol.external();
+ let symbol_name = raw_symbol.raw_name().to_string();
+ match raw_symbol.sym_type() {
+ BNSymbolType::ImportAddress => {
+ todo!()
+ }
+ BNSymbolType::Data => {
+ Symbol::new(
+ symbol_name,
+ // TODO: Data?
+ SymbolClass::Data,
+ SymbolModifiers::default(),
+ )
+ }
+ BNSymbolType::Symbolic => {
+ todo!()
+ }
+ BNSymbolType::LocalLabel => {
+ todo!()
+ }
+ // BN External is our Exported
+ BNSymbolType::External => Symbol::new(
+ symbol_name,
+ // TODO: Data?
+ SymbolClass::Data,
+ SymbolModifiers::Exported,
+ ),
+ BNSymbolType::ImportedData => {
+ Symbol::new(symbol_name, SymbolClass::Data, SymbolModifiers::External)
+ }
+ BNSymbolType::LibraryFunction | BNSymbolType::Function => Symbol::new(
+ symbol_name,
+ SymbolClass::Function,
+ SymbolModifiers::default(),
+ ),
+ // BN Imported is our External
+ BNSymbolType::ImportedFunction => Symbol::new(
+ symbol_name,
+ SymbolClass::Function,
+ SymbolModifiers::External,
+ ),
+ }
+}
+
+pub fn to_bn_symbol_at_address(view: &BinaryView, symbol: &Symbol, addr: u64) -> BNRef<BNSymbol> {
+ let is_external = symbol.modifiers.contains(SymbolModifiers::External);
+ let _is_exported = symbol.modifiers.contains(SymbolModifiers::Exported);
+ let symbol_type = match symbol.class {
+ SymbolClass::Function if is_external => BNSymbolType::ImportedFunction,
+ // TODO: We should instead make it a Function, however due to the nature of the imports we are setting them to library for now.
+ SymbolClass::Function => BNSymbolType::LibraryFunction,
+ SymbolClass::Data if is_external => BNSymbolType::ImportedData,
+ SymbolClass::Data => BNSymbolType::Data,
+ };
+ let raw_name = symbol.name.as_str();
+ let mut symbol_builder = BNSymbol::builder(symbol_type, &symbol.name, addr);
+ // Demangle symbol name (short is with simplifications).
+ if let Some(arch) = view.default_arch() {
+ if let Ok((_, full_name_list)) =
+ binaryninja::demangle::demangle_generic(&arch, raw_name, Some(view), false)
+ {
+ let full_name = full_name_list.join("::");
+ symbol_builder = symbol_builder.full_name(&full_name);
+ }
+ if let Ok((_, short_name_list)) =
+ binaryninja::demangle::demangle_generic(&arch, raw_name, Some(view), false)
+ {
+ let short_name = short_name_list.join("::");
+ symbol_builder = symbol_builder.short_name(&short_name);
+ }
+ }
+ symbol_builder.create()
+}
+
+pub fn from_bn_type(view: &BinaryView, raw_ty: BNRef<BNType>, confidence: u8) -> Type {
+ from_bn_type_internal(view, &mut HashSet::new(), raw_ty, confidence)
+}
+
+fn from_bn_type_internal(
+ view: &BinaryView,
+ visited_refs: &mut HashSet<String>,
+ raw_ty: BNRef<BNType>,
+ confidence: u8,
+) -> Type {
+ let bytes_to_bits = |val| val * 8;
+ let raw_ty_bit_width = bytes_to_bits(raw_ty.width());
+ let type_class = match raw_ty.type_class() {
+ BNTypeClass::VoidTypeClass => TypeClass::Void,
+ BNTypeClass::BoolTypeClass => {
+ let bool_class = BooleanClass { width: None };
+ TypeClass::Boolean(bool_class)
+ }
+ BNTypeClass::IntegerTypeClass => {
+ let signed = raw_ty.is_signed().contents;
+ let width = Some(raw_ty_bit_width as u16);
+ if signed && width == Some(8) {
+ // NOTE: if its an i8, its a char.
+ let char_class = CharacterClass { width: None };
+ TypeClass::Character(char_class)
+ } else {
+ let int_class = IntegerClass { width, signed };
+ TypeClass::Integer(int_class)
+ }
+ }
+ BNTypeClass::FloatTypeClass => {
+ let float_class = FloatClass {
+ width: Some(raw_ty_bit_width as u16),
+ };
+ TypeClass::Float(float_class)
+ }
+ // TODO: Union?????
+ BNTypeClass::StructureTypeClass => {
+ let raw_struct = raw_ty.get_structure().unwrap();
+
+ let mut members = raw_struct
+ .members()
+ .unwrap()
+ .into_iter()
+ .map(|raw_member| {
+ let bit_offset = bytes_to_bits(raw_member.offset);
+ let mut modifiers = StructureMemberModifiers::empty();
+ // If this member is not public mark it as internal.
+ modifiers.set(
+ StructureMemberModifiers::Internal,
+ !matches!(raw_member.access, MemberAccess::PublicAccess),
+ );
+ StructureMember {
+ name: Some(raw_member.name),
+ offset: bit_offset,
+ ty: from_bn_type_internal(
+ view,
+ visited_refs,
+ raw_member.ty.contents,
+ raw_member.ty.confidence,
+ ),
+ modifiers,
+ }
+ })
+ .collect::<Vec<_>>();
+
+ // Add base structures as flattened members
+ if let Ok(base_structs) = raw_struct.base_structures() {
+ let base_to_member_iter = base_structs.iter().map(|base_struct| {
+ let bit_offset = bytes_to_bits(base_struct.offset);
+ let mut modifiers = StructureMemberModifiers::empty();
+ modifiers.set(StructureMemberModifiers::Flattened, true);
+ let base_struct_ty = from_bn_type_internal(
+ view,
+ visited_refs,
+ BNType::named_type(&base_struct.ty),
+ 255,
+ );
+ StructureMember {
+ name: base_struct_ty.name.to_owned(),
+ offset: bit_offset,
+ ty: base_struct_ty,
+ modifiers,
+ }
+ });
+ members.extend(base_to_member_iter);
+ }
+
+ // TODO: Check if union
+ let struct_class = StructureClass::new(members);
+ TypeClass::Structure(struct_class)
+ }
+ BNTypeClass::EnumerationTypeClass => {
+ let raw_enum = raw_ty.get_enumeration().unwrap();
+
+ let enum_ty_signed = raw_ty.is_signed().contents;
+ let enum_ty = Type::builder::<String, _>()
+ .class(TypeClass::Integer(IntegerClass {
+ width: Some(raw_ty_bit_width as u16),
+ signed: enum_ty_signed,
+ }))
+ .build();
+
+ let members = raw_enum
+ .members()
+ .into_iter()
+ .map(|raw_member| EnumerationMember {
+ name: Some(raw_member.name),
+ constant: raw_member.value,
+ })
+ .collect();
+
+ let enum_class = EnumerationClass::new(enum_ty, members);
+ TypeClass::Enumeration(enum_class)
+ }
+ BNTypeClass::PointerTypeClass => {
+ let raw_child_ty = raw_ty.target().unwrap();
+ let ptr_class = PointerClass {
+ width: Some(raw_ty_bit_width as u16),
+ child_type: from_bn_type_internal(
+ view,
+ visited_refs,
+ raw_child_ty.contents,
+ raw_child_ty.confidence,
+ ),
+ // TODO: Handle addressing.
+ addressing: PointerAddressing::Absolute,
+ };
+ TypeClass::Pointer(ptr_class)
+ }
+ BNTypeClass::ArrayTypeClass => {
+ let length = raw_ty.count();
+ let raw_member_ty = raw_ty.element_type().unwrap();
+ let array_class = ArrayClass {
+ length: Some(length),
+ member_type: from_bn_type_internal(
+ view,
+ visited_refs,
+ raw_member_ty.contents,
+ raw_member_ty.confidence,
+ ),
+ modifiers: ArrayModifiers::empty(),
+ };
+ TypeClass::Array(array_class)
+ }
+ BNTypeClass::FunctionTypeClass => {
+ let in_members = raw_ty
+ .parameters()
+ .unwrap()
+ .into_iter()
+ .map(|raw_member| {
+ // TODO: Location...
+ let _location = Location::Register(RegisterLocation);
+ FunctionMember {
+ name: Some(raw_member.name),
+ ty: from_bn_type_internal(
+ view,
+ visited_refs,
+ raw_member.t.contents,
+ raw_member.t.confidence,
+ ),
+ // TODO: Just omit location for now?
+ // TODO: Location should be optional...
+ locations: vec![],
+ }
+ })
+ .collect();
+
+ let mut out_members = Vec::new();
+ if let Ok(return_ty) = raw_ty.return_value() {
+ out_members.push(FunctionMember {
+ name: None,
+ ty: from_bn_type_internal(
+ view,
+ visited_refs,
+ return_ty.contents,
+ return_ty.confidence,
+ ),
+ locations: vec![],
+ });
+ }
+
+ let calling_convention = raw_ty
+ .calling_convention()
+ .map(|bn_cc| from_bn_calling_convention(bn_cc.contents))
+ .ok();
+
+ let func_class = FunctionClass {
+ calling_convention,
+ in_members,
+ out_members,
+ };
+ TypeClass::Function(func_class)
+ }
+ BNTypeClass::VarArgsTypeClass => TypeClass::Void,
+ BNTypeClass::ValueTypeClass => {
+ // What the is this.
+ TypeClass::Void
+ }
+ BNTypeClass::NamedTypeReferenceClass => {
+ let raw_ntr = raw_ty.get_named_type_reference().unwrap();
+ let ref_id_str = raw_ntr.id().to_string();
+ let raw_ntr_ty = raw_ntr.target(view);
+ if raw_ntr_ty.is_none() || !visited_refs.insert(ref_id_str.clone()) {
+ let ref_class = ReferrerClass::new(None, Some(raw_ntr.name().to_string()));
+ TypeClass::Referrer(ref_class)
+ } else {
+ use dashmap::DashMap;
+ use std::sync::Arc;
+ use std::sync::OnceLock;
+ static REF_CACHE: OnceLock<Arc<DashMap<String, TypeClass>>> = OnceLock::new();
+ let ref_cache = REF_CACHE.get_or_init(|| Arc::new(DashMap::new()));
+ // Check the cache first before proceeding
+ if let Some(cached_type) = ref_cache.get(&ref_id_str) {
+ cached_type.value().to_owned()
+ } else {
+ let ntr_ty =
+ from_bn_type_internal(view, visited_refs, raw_ntr_ty.unwrap(), confidence);
+ visited_refs.remove(&ref_id_str);
+ // NOTE: The GUID here must always equal the same for any given type for this to work effectively.
+ let ntr_guid = TypeGUID::from(&ntr_ty);
+ let ref_class = ReferrerClass::new(Some(ntr_guid), ntr_ty.name);
+ let ntr_ty_class = TypeClass::Referrer(ref_class);
+ ref_cache.insert(ref_id_str, ntr_ty_class.clone());
+ ntr_ty_class
+ }
+ }
+ }
+ BNTypeClass::WideCharTypeClass => {
+ let char_class = CharacterClass {
+ width: Some(raw_ty_bit_width as u16),
+ };
+ TypeClass::Character(char_class)
+ }
+ };
+
+ let name = raw_ty.registered_name().map(|n| n.name().to_string()).ok();
+
+ Type {
+ name,
+ class: Box::new(type_class),
+ confidence,
+ // TODO: Fill these out...
+ modifiers: vec![],
+ alignment: Default::default(),
+ // TODO: Filling this out is... weird.
+ // TODO: we _do_ want this for networked types (this is the only way we can update type is if we fill this out)
+ ancestors: vec![],
+ }
+}
+
+pub fn from_bn_calling_convention<A: BNArchitecture>(
+ raw_cc: BNRef<BNCallingConvention<A>>,
+) -> CallingConvention {
+ // NOTE: Currently calling convention just stores the name.
+ CallingConvention::new(raw_cc.name().as_str())
+}
+
+pub fn to_bn_calling_convention<A: BNArchitecture>(
+ arch: &A,
+ calling_convention: &CallingConvention,
+) -> BNRef<BNCallingConvention<A>> {
+ for cc in &arch.calling_conventions() {
+ if cc.name().as_str() == calling_convention.name {
+ return cc.clone();
+ }
+ }
+ arch.get_default_calling_convention().unwrap()
+}
+
+pub fn to_bn_type<A: BNArchitecture>(arch: &A, ty: &Type) -> BNRef<BNType> {
+ let bits_to_bytes = |val: u64| (val / 8);
+ let addr_size = arch.address_size() as u64;
+ match ty.class.as_ref() {
+ TypeClass::Void => BNType::void(),
+ TypeClass::Boolean(_) => BNType::bool(),
+ TypeClass::Integer(c) => {
+ let width = c.width.map(|w| bits_to_bytes(w as _)).unwrap_or(4);
+ BNType::int(width as usize, c.signed)
+ }
+ TypeClass::Character(c) => match c.width {
+ Some(w) => BNType::wide_char(bits_to_bytes(w as _) as usize),
+ None => BNType::char(),
+ },
+ TypeClass::Float(c) => {
+ let width = c.width.map(|w| bits_to_bytes(w as _)).unwrap_or(4);
+ BNType::float(width as usize)
+ }
+ TypeClass::Pointer(ref c) => {
+ let child_type = to_bn_type(arch, &c.child_type);
+ let ptr_width = c.width.map(|w| bits_to_bytes(w as _)).unwrap_or(addr_size);
+ // TODO: Child type confidence
+ let constant = ty.is_const();
+ let volatile = ty.is_volatile();
+ // TODO: If the pointer is to a null terminated array of chars, make it a pointer to char
+ // TODO: Addressing mode
+ BNType::pointer_of_width(&child_type, ptr_width as usize, constant, volatile, None)
+ }
+ TypeClass::Array(c) => {
+ let member_type = to_bn_type(arch, &c.member_type);
+ // TODO: How to handle DST array (length is None)
+ BNType::array(&member_type, c.length.unwrap_or(0))
+ }
+ TypeClass::Structure(c) => {
+ let builder = BNStructureBuilder::new();
+ // TODO: Structure type class?
+ // TODO: Alignment
+ // TODO: Other modifiers?
+ let mut base_structs: Vec<BNBaseStructure> = Vec::new();
+ for member in &c.members {
+ let member_type = BNConf::new(to_bn_type(arch, &member.ty), u8::MAX);
+ let member_name = member.name.to_owned().unwrap_or("field_OFFSET".into());
+ let member_offset = bits_to_bytes(member.offset);
+ let member_access = if member
+ .modifiers
+ .contains(StructureMemberModifiers::Internal)
+ {
+ BNMemberAccess::PrivateAccess
+ } else {
+ BNMemberAccess::PublicAccess
+ };
+ // TODO: Member scope
+ let member_scope = BNMemberScope::NoScope;
+ if member
+ .modifiers
+ .contains(StructureMemberModifiers::Flattened)
+ {
+ // Add member as a base structure to inherit its fields.
+ match member.ty.class.as_ref() {
+ TypeClass::Referrer(c) => {
+ // We only support base structures with a referrer right now.
+ let base_struct_ntr_name =
+ c.name.to_owned().unwrap_or("base_UNKNOWN".into());
+ let base_struct_ntr = match c.guid {
+ Some(guid) => BNNamedTypeReference::new_with_id(
+ NamedTypeReferenceClass::UnknownNamedTypeClass,
+ guid.to_string(),
+ base_struct_ntr_name.into(),
+ ),
+ None => BNNamedTypeReference::new(
+ NamedTypeReferenceClass::UnknownNamedTypeClass,
+ base_struct_ntr_name.into(),
+ ),
+ };
+ base_structs.push(BNBaseStructure::new(
+ base_struct_ntr,
+ member_offset,
+ member.ty.size().unwrap_or(0),
+ ))
+ }
+ _ => {
+ log::error!(
+ "Adding base {:?} with invalid ty: {:?}",
+ ty.name,
+ member.ty
+ );
+ }
+ }
+ } else {
+ builder.insert_member(
+ &BNStructureMember::new(
+ member_type,
+ member_name,
+ member_offset,
+ member_access,
+ member_scope,
+ ),
+ false,
+ );
+ }
+ }
+ builder.set_base_structures(base_structs);
+ BNType::structure(&builder.finalize())
+ }
+ TypeClass::Enumeration(c) => {
+ let builder = BNEnumerationBuilder::new();
+ for member in &c.members {
+ // TODO: Add default name?
+ let member_name = member.name.to_owned().unwrap_or("enum_VAL".into());
+ let member_value = member.constant;
+ builder.insert(member_name, member_value);
+ }
+ // TODO: Warn if enumeration has no size.
+ let width = bits_to_bytes(c.member_type.size().unwrap()) as _;
+ let signed = matches!(*c.member_type.class, TypeClass::Integer(c) if c.signed);
+ BNType::enumeration(&builder.finalize(), width, signed)
+ }
+ TypeClass::Union(c) => {
+ let builder = BNStructureBuilder::new();
+ builder.set_structure_type(BNStructureType::UnionStructureType);
+ for member in &c.members {
+ let member_type = BNConf::new(to_bn_type(arch, &member.ty), u8::MAX);
+ let member_name = member.name.to_owned();
+ // TODO: Member access
+ let member_access = BNMemberAccess::PublicAccess;
+ // TODO: Member scope
+ let member_scope = BNMemberScope::NoScope;
+ let structure_member = BNStructureMember::new(
+ member_type,
+ member_name,
+ 0, // Union members all exist at 0 right?
+ member_access,
+ member_scope,
+ );
+ builder.insert_member(&structure_member, false);
+ }
+ BNType::structure(&builder.finalize())
+ }
+ TypeClass::Function(c) => {
+ let return_type = if !c.out_members.is_empty() {
+ // TODO: WTF
+ to_bn_type(arch, &c.out_members[0].ty)
+ } else {
+ BNType::void()
+ };
+ let params: Vec<_> = c
+ .in_members
+ .iter()
+ .map(|member| {
+ let member_type = to_bn_type(arch, &member.ty);
+ let name = member.name.clone();
+ // TODO: Location AND fix default param name
+ BNFunctionParameter::new(member_type, name.unwrap_or("param_IDK".into()), None)
+ })
+ .collect();
+ // TODO: Variable arguments
+ let variable_args = false;
+ // If we have a calling convention we run the extended function type creation.
+ match c.calling_convention.as_ref() {
+ Some(cc) => {
+ let calling_convention = to_bn_calling_convention(arch, cc);
+ BNType::function_with_options(
+ &return_type,
+ &params,
+ variable_args,
+ &BNConf::new(calling_convention, u8::MAX),
+ BNConf::new(0, 0),
+ )
+ }
+ None => BNType::function(&return_type, &params, variable_args),
+ }
+ }
+ TypeClass::Referrer(c) => {
+ let ntr = match c.guid {
+ Some(guid) => {
+ let guid_str = guid.to_string();
+ let ntr_name = c.name.to_owned().unwrap_or(guid_str.clone());
+ NamedTypeReference::new_with_id(
+ NamedTypeReferenceClass::UnknownNamedTypeClass,
+ guid_str,
+ ntr_name.into(),
+ )
+ }
+ None => match c.name.as_ref() {
+ Some(ntr_name) => NamedTypeReference::new(
+ NamedTypeReferenceClass::UnknownNamedTypeClass,
+ ntr_name.into(),
+ ),
+ None => {
+ log::error!("Referrer with no reference! {:?}", c);
+ NamedTypeReference::new(
+ NamedTypeReferenceClass::UnknownNamedTypeClass,
+ "AHHHHHH".into(),
+ )
+ }
+ },
+ };
+ BNType::named_type(&ntr)
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use binaryninja::binaryview::BinaryViewExt;
+ use binaryninja::headless::Session;
+ use std::path::PathBuf;
+ use std::sync::OnceLock;
+ use warp::r#type::guid::TypeGUID;
+
+ static INIT: OnceLock<Session> = OnceLock::new();
+
+ fn get_session<'a>() -> &'a Session {
+ INIT.get_or_init(|| Session::new())
+ }
+
+ #[test]
+ fn type_conversion() {
+ let session = get_session();
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ for entry in std::fs::read_dir(out_dir).expect("Failed to read OUT_DIR") {
+ let entry = entry.expect("Failed to read directory entry");
+ let path = entry.path();
+ if path.is_file() {
+ if let Some(bv) = session.load(path.to_str().unwrap()) {
+ let types_len = bv.types().len();
+ let converted_types: Vec<_> = bv
+ .types()
+ .iter()
+ .map(|t| {
+ let ty = from_bn_type(&bv, t.type_object().clone(), u8::MAX);
+ (TypeGUID::from(&ty), ty)
+ })
+ .collect();
+ assert_eq!(types_len, converted_types.len());
+ }
+ }
+ }
+ }
+
+ #[test]
+ fn check_for_leaks() {
+ let session = get_session();
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ for entry in std::fs::read_dir(out_dir).expect("Failed to read OUT_DIR") {
+ let entry = entry.expect("Failed to read directory entry");
+ let path = entry.path();
+ if path.is_file() {
+ if let Some(inital_bv) = session.load(path.to_str().unwrap()) {
+ let types_len = inital_bv.types().len();
+ let converted_types: Vec<_> = inital_bv
+ .types()
+ .iter()
+ .map(|t| {
+ let ty = from_bn_type(&inital_bv, t.type_object().clone(), u8::MAX);
+ (TypeGUID::from(&ty), ty)
+ })
+ .collect();
+ assert_eq!(types_len, converted_types.len());
+ // Hold on to a reference to the core to prevent view getting dropped in worker thread.
+ let core_ref = inital_bv
+ .functions()
+ .iter()
+ .next()
+ .map(|f| f.unresolved_stack_adjustment_graph());
+ // Drop the file and view.
+ inital_bv.file().close();
+ std::mem::drop(inital_bv);
+ let initial_memory_info = binaryninja::memory_info();
+ if let Some(second_bv) = session.load(path.to_str().unwrap()) {
+ let types_len = second_bv.types().len();
+ let converted_types: Vec<_> = second_bv
+ .types()
+ .iter()
+ .map(|t| {
+ let ty = from_bn_type(&second_bv, t.type_object().clone(), u8::MAX);
+ (TypeGUID::from(&ty), ty)
+ })
+ .collect();
+ assert_eq!(types_len, converted_types.len());
+ // Hold on to a reference to the core to prevent view getting dropped in worker thread.
+ let core_ref = second_bv
+ .functions()
+ .iter()
+ .next()
+ .map(|f| f.unresolved_stack_adjustment_graph());
+ // Drop the file and view.
+ second_bv.file().close();
+ std::mem::drop(second_bv);
+ let final_memory_info = binaryninja::memory_info();
+ for info in initial_memory_info {
+ let initial_count = info.1;
+ if let Some(&final_count) = final_memory_info.get(&info.0) {
+ assert!(
+ final_count <= initial_count,
+ "{}: final objects {} vs initial objects {}",
+ info.0,
+ final_count,
+ initial_count
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/plugins/warp/src/lib.rs b/plugins/warp/src/lib.rs
new file mode 100644
index 00000000..ae14ae80
--- /dev/null
+++ b/plugins/warp/src/lib.rs
@@ -0,0 +1,161 @@
+use binaryninja::architecture::Architecture;
+use binaryninja::basicblock::BasicBlock as BNBasicBlock;
+use binaryninja::binaryview::BinaryViewExt;
+use binaryninja::function::{Function as BNFunction, NativeBlock};
+use binaryninja::llil;
+use binaryninja::llil::{ExprInfo, FunctionMutability, NonSSA, NonSSAVariant, VisitorAction};
+use binaryninja::rc::Ref as BNRef;
+use warp::signature::basic_block::{BasicBlock, BasicBlockGUID};
+use warp::signature::function::constraints::FunctionConstraints;
+use warp::signature::function::{Function, FunctionGUID};
+
+use crate::cache::{
+ cached_adjacency_constraints, cached_call_site_constraints, cached_function_guid,
+};
+use crate::convert::{from_bn_symbol, from_bn_type};
+
+pub mod cache;
+pub mod convert;
+mod matcher;
+/// Only used when compiled for cdylib target.
+mod plugin;
+
+pub fn build_function<A: Architecture, M: FunctionMutability, V: NonSSAVariant>(
+ func: &BNFunction,
+ llil: &llil::Function<A, M, NonSSA<V>>,
+) -> Option<Function> {
+ let bn_fn_ty = func.function_type();
+ Some(Function {
+ guid: cached_function_guid(func, llil)?,
+ symbol: from_bn_symbol(&func.symbol()),
+ // TODO: Confidence should be derived from function type.
+ ty: from_bn_type(&func.view(), bn_fn_ty, 255),
+ constraints: FunctionConstraints {
+ // NOTE: Adding adjacent only works if analysis is complete.
+ adjacent: cached_adjacency_constraints(func),
+ call_sites: cached_call_site_constraints(func),
+ // TODO: Add caller sites (when adjacent and call sites are minimal)
+ // NOTE: Adding caller sites only works if analysis is complete.
+ caller_sites: Default::default(),
+ },
+ // TODO: We need more than one entry block.
+ entry: entry_basic_block_guid(func, llil).map(BasicBlock::new),
+ })
+}
+
+pub fn entry_basic_block_guid<A: Architecture, M: FunctionMutability, V: NonSSAVariant>(
+ func: &BNFunction,
+ llil: &llil::Function<A, M, NonSSA<V>>,
+) -> Option<BasicBlockGUID> {
+ // NOTE: This is not actually the entry point. This is the highest basic block.
+ let first_basic_block = sorted_basic_blocks(func).into_iter().next()?;
+ basic_block_guid(&first_basic_block, llil)
+}
+
+/// Basic blocks sorted from high to low.
+pub fn sorted_basic_blocks(func: &BNFunction) -> Vec<BNRef<BNBasicBlock<NativeBlock>>> {
+ let mut basic_blocks = func
+ .basic_blocks()
+ .iter()
+ .map(|bb| bb.clone())
+ .collect::<Vec<_>>();
+ basic_blocks.sort_by_key(|f| f.raw_start());
+ basic_blocks
+}
+
+pub fn function_guid<A: Architecture, M: FunctionMutability, V: NonSSAVariant>(
+ func: &BNFunction,
+ llil: &llil::Function<A, M, NonSSA<V>>,
+) -> Option<FunctionGUID> {
+ // TODO: Sort the basic blocks.
+ let basic_blocks = sorted_basic_blocks(func);
+ let basic_block_guids = basic_blocks
+ .iter()
+ .filter_map(|bb| basic_block_guid(bb, llil))
+ .collect::<Vec<_>>();
+ Some(FunctionGUID::from_basic_blocks(&basic_block_guids))
+}
+
+pub fn basic_block_guid<A: Architecture, M: FunctionMutability, V: NonSSAVariant>(
+ basic_block: &BNBasicBlock<NativeBlock>,
+ llil: &llil::Function<A, M, NonSSA<V>>,
+) -> Option<BasicBlockGUID> {
+ let func = basic_block.function();
+ let view = func.view();
+ let arch = func.arch();
+ let max_instr_len = arch.max_instr_len();
+ // TODO: Add all the hacks here to remove stuff like function prolog...
+ // TODO mov edi, edi on windows x86
+ // TODO: Ugh i really dislike the above and REALLY don't wanna do that.
+ // TODO: The above invalidates our "all function bytes" approach.
+ // TODO: Could we keep the bytes and just zero mask them? At least then we don't completely get rid of them.
+
+ let basic_block_range = basic_block.raw_start()..basic_block.raw_end();
+ let mut basic_block_bytes = Vec::with_capacity(basic_block_range.count());
+ for instr_addr in basic_block.into_iter() {
+ let mut instr_bytes = view.read_vec(instr_addr, max_instr_len);
+ if let Some(instr_info) = arch.instruction_info(&instr_bytes, instr_addr) {
+ let instr_len = instr_info.len();
+ instr_bytes.truncate(instr_len);
+ if let Some(instr_llil) = llil.instruction_at(instr_addr) {
+ if instr_llil.visit_tree(&mut |_expr, expr_info| match expr_info {
+ ExprInfo::ConstPtr(_) | ExprInfo::ExternPtr(_) => VisitorAction::Halt,
+ _ => VisitorAction::Descend,
+ }) == VisitorAction::Halt
+ {
+ // Found a variant instruction, mask off entire instruction.
+ instr_bytes.fill(0);
+ }
+ }
+ // Add the instructions bytes to the functions bytes
+ basic_block_bytes.extend(instr_bytes);
+ }
+ }
+
+ Some(BasicBlockGUID::from(basic_block_bytes.as_slice()))
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::cache::cached_function_guid;
+ use crate::convert::from_bn_type;
+ use binaryninja::binaryview::BinaryViewExt;
+ use binaryninja::headless::Session;
+ use std::path::PathBuf;
+ use std::sync::OnceLock;
+ use warp::r#type::guid::TypeGUID;
+
+ static INIT: OnceLock<Session> = OnceLock::new();
+
+ fn get_session<'a>() -> &'a Session {
+ // TODO: This is not shared between other test modules, should still be fine (mutex in core now).
+ INIT.get_or_init(|| Session::new())
+ }
+
+ #[test]
+ fn insta_signatures() {
+ let session = get_session();
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ for entry in std::fs::read_dir(out_dir).expect("Failed to read OUT_DIR") {
+ let entry = entry.expect("Failed to read directory entry");
+ let path = entry.path();
+ if path.is_file() {
+ if let Some(path_str) = path.to_str() {
+ if path_str.ends_with("library.o") {
+ if let Some(inital_bv) = session.load(path_str) {
+ let mut functions = inital_bv
+ .functions()
+ .iter()
+ .map(|f| {
+ cached_function_guid(&f, &f.low_level_il().unwrap()).unwrap()
+ })
+ .collect::<Vec<_>>();
+ functions.sort_by_key(|guid| guid.guid);
+ insta::assert_debug_snapshot!(functions);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/plugins/warp/src/matcher.rs b/plugins/warp/src/matcher.rs
new file mode 100644
index 00000000..ae49f00a
--- /dev/null
+++ b/plugins/warp/src/matcher.rs
@@ -0,0 +1,432 @@
+use binaryninja::architecture::{Architecture as BNArchitecture, Architecture};
+use binaryninja::backgroundtask::BackgroundTask;
+use binaryninja::binaryview::{BinaryView, BinaryViewExt};
+use binaryninja::function::{Function as BNFunction, FunctionUpdateType};
+use binaryninja::llil;
+use binaryninja::llil::{FunctionMutability, NonSSA, NonSSAVariant};
+use binaryninja::platform::Platform;
+use binaryninja::rc::Guard;
+use binaryninja::rc::Ref as BNRef;
+use dashmap::DashMap;
+use fastbloom::BloomFilter;
+use std::cmp::Ordering;
+use std::collections::{HashMap, HashSet};
+use std::hash::{DefaultHasher, Hasher};
+use std::path::PathBuf;
+use std::sync::OnceLock;
+use walkdir::{DirEntry, WalkDir};
+use warp::r#type::class::TypeClass;
+use warp::r#type::guid::TypeGUID;
+use warp::r#type::Type;
+use warp::signature::basic_block::BasicBlock;
+use warp::signature::function::{Function, FunctionGUID};
+use warp::signature::Data;
+
+use crate::cache::{cached_call_site_constraints, cached_function_guid, FunctionID};
+use crate::convert::to_bn_type;
+use crate::entry_basic_block_guid;
+use crate::plugin::on_matched_function;
+
+pub const TRIVIAL_LLIL_THRESHOLD: usize = 8;
+
+pub static PLAT_MATCHER_CACHE: OnceLock<DashMap<PlatformID, Matcher>> = OnceLock::new();
+
+pub fn cached_function_match<A: Architecture, M: FunctionMutability, V: NonSSAVariant>(
+ function: &BNFunction,
+ llil: &llil::Function<A, M, NonSSA<V>>,
+) {
+ let platform = function.platform();
+ let platform_id = PlatformID::from(platform.as_ref());
+ let matcher_cache = PLAT_MATCHER_CACHE.get_or_init(Default::default);
+ match matcher_cache.get(&platform_id) {
+ Some(matcher) => matcher.match_function(function, llil),
+ None => {
+ let matcher = Matcher::from_platform(platform);
+ matcher.match_function(function, llil);
+ matcher_cache.insert(platform_id, matcher);
+ }
+ }
+}
+
+pub struct Matcher {
+ pub matched_functions: DashMap<FunctionID, Function>,
+ pub functions: DashMap<FunctionGUID, Vec<Function>>,
+ pub types: DashMap<TypeGUID, Type>,
+ pub named_types: DashMap<String, Type>,
+ /// This is used to fast-fail on functions not in the dataset.
+ /// NOTE: This can only handle one basic block classification, right now that is the entry block.
+ basic_block_filter: BloomFilter,
+}
+
+impl Matcher {
+ /// Create a matcher from the platforms signature subdirectory.
+ pub fn from_platform(platform: BNRef<Platform>) -> Self {
+ let platform_name = platform.name().to_string();
+ let task = BackgroundTask::new(
+ format!("Getting platform matcher data... {}", platform_name),
+ false,
+ )
+ .unwrap();
+ // Get core signatures for the given platform
+ let core_dir = binaryninja::install_directory().unwrap();
+ let root_core_sig_dir = core_dir.join("signatures");
+ let plat_core_sig_dir = root_core_sig_dir.join(&platform_name);
+ let mut data = get_data_from_dir(&plat_core_sig_dir);
+
+ // Get user signatures for the given platform
+ let user_dir = binaryninja::user_directory().unwrap();
+ let root_user_sig_dir = user_dir.join("signatures");
+ let plat_user_sig_dir = root_user_sig_dir.join(&platform_name);
+ let user_data = get_data_from_dir(&plat_user_sig_dir);
+
+ data.extend(user_data);
+
+ // TODO: If a user signature has the same name as a core signature, remove the core signature.
+
+ task.set_progress_text("Gathering entry blocks for matcher filtering...");
+
+ // Get entry_blocks for filtering.
+ let entry_blocks = data
+ .iter()
+ .flat_map(|(_, data)| data.functions.iter().map(|function| &function.entry))
+ .collect::<Vec<_>>();
+ // TODO: We need to disable this if we get a None basic block, as it will then fail to match all cases.
+ let basic_block_filter = BloomFilter::with_false_pos(0.1).items(entry_blocks);
+
+ task.set_progress_text("Gathering matcher functions...");
+
+ // TODO: Merge like functions, right now we just hope and pray.
+
+ // Get functions for comprehensive matching.
+ let functions = data
+ .iter()
+ .flat_map(|(_, data)| {
+ data.functions.iter().fold(DashMap::new(), |map, func| {
+ #[allow(clippy::unwrap_or_default)]
+ map.entry(func.guid)
+ .or_insert_with(Vec::new)
+ .push(func.clone());
+ map
+ })
+ })
+ .map(|(guid, mut funcs)| {
+ funcs.sort_by_key(|f| f.symbol.name.to_owned());
+ funcs.dedup_by_key(|f| f.symbol.name.to_owned());
+ (guid, funcs)
+ })
+ .collect();
+
+ task.set_progress_text("Gathering matcher types...");
+
+ let types = data
+ .iter()
+ .flat_map(|(_, data)| {
+ data.types.iter().fold(DashMap::new(), |map, comp_ty| {
+ map.insert(comp_ty.guid, comp_ty.ty.clone());
+ map
+ })
+ })
+ .collect();
+
+ task.set_progress_text("Gathering matcher named types...");
+
+ // TODO: We store a duplicate lookup for named references.
+ let named_types = data
+ .iter()
+ .flat_map(|(_, data)| {
+ data.types.iter().fold(DashMap::new(), |map, comp_ty| {
+ if let Some(ty_name) = &comp_ty.ty.name {
+ map.insert(ty_name.to_owned(), comp_ty.ty.clone());
+ }
+ map
+ })
+ })
+ .collect();
+
+ task.finish();
+
+ log::debug!("Loaded signatures: {:?}", data.keys());
+
+ Self {
+ matched_functions: Default::default(),
+ functions,
+ basic_block_filter,
+ types,
+ named_types,
+ }
+ }
+
+ pub fn add_type_to_view<A: BNArchitecture>(&self, view: &BinaryView, arch: &A, ty: &Type) {
+ fn inner_add_type_to_view<A: BNArchitecture>(
+ matcher: &Matcher,
+ view: &BinaryView,
+ arch: &A,
+ visited_refs: &mut HashSet<String>,
+ ty: &Type,
+ ) {
+ let ty_id_str = TypeGUID::from(ty).to_string();
+ if view.get_type_by_id(&ty_id_str).is_some() {
+ // Type already added.
+ return;
+ }
+ // Type not already added to the view.
+ // Verify all nested types are added before adding type.
+ match ty.class.as_ref() {
+ TypeClass::Pointer(c) => {
+ inner_add_type_to_view(matcher, view, arch, visited_refs, &c.child_type)
+ }
+ TypeClass::Array(c) => {
+ inner_add_type_to_view(matcher, view, arch, visited_refs, &c.member_type)
+ }
+ TypeClass::Structure(c) => {
+ for member in &c.members {
+ inner_add_type_to_view(matcher, view, arch, visited_refs, &member.ty)
+ }
+ }
+ TypeClass::Enumeration(c) => {
+ inner_add_type_to_view(matcher, view, arch, visited_refs, &c.member_type)
+ }
+ TypeClass::Union(c) => {
+ for member in &c.members {
+ inner_add_type_to_view(matcher, view, arch, visited_refs, &member.ty)
+ }
+ }
+ TypeClass::Function(c) => {
+ for out_member in &c.out_members {
+ inner_add_type_to_view(matcher, view, arch, visited_refs, &out_member.ty)
+ }
+ for in_member in &c.in_members {
+ inner_add_type_to_view(matcher, view, arch, visited_refs, &in_member.ty)
+ }
+ }
+ TypeClass::Referrer(c) => {
+ // Check to see if the referrer has been added to the view.
+ let mut resolved = false;
+ if let Some(ref_guid) = c.guid {
+ // NOTE: We do not need to check for cyclic reference here because
+ // NOTE: GUID references are unable to be referenced.
+ if view.get_type_by_id(ref_guid.to_string()).is_none() {
+ // Add the ref to the view if it is in the Matcher types
+ if let Some(ref_ty) = matcher.types.get(&ref_guid) {
+ inner_add_type_to_view(matcher, view, arch, visited_refs, &ref_ty);
+ resolved = true;
+ }
+ }
+ }
+
+ if let Some(ref_name) = &c.name {
+ // Only try and resolve by name if not already visiting.
+ if !resolved
+ && visited_refs.insert(ref_name.to_string())
+ && view.get_type_by_name(ref_name).is_none()
+ {
+ // Add the ref to the view if it is in the Matcher types
+ if let Some(ref_ty) = matcher.named_types.get(ref_name) {
+ inner_add_type_to_view(matcher, view, arch, visited_refs, &ref_ty);
+ }
+ // No longer visiting type.
+ visited_refs.remove(ref_name);
+ }
+ }
+ }
+ _ => {}
+ }
+ // All nested types _should_ be added now, we can add this type.
+ let ty_name = ty.name.to_owned().unwrap_or_else(|| ty_id_str.clone());
+ view.define_auto_type_with_id(ty_name, ty_id_str, &to_bn_type(arch, ty));
+ }
+ inner_add_type_to_view(self, view, arch, &mut HashSet::new(), ty)
+ }
+
+ pub fn match_function<A: Architecture, M: FunctionMutability, V: NonSSAVariant>(
+ &self,
+ function: &BNFunction,
+ llil: &llil::Function<A, M, NonSSA<V>>,
+ ) {
+ let function_id = FunctionID::from(function);
+ if let Some(matched_function) = self.matched_functions.get(&function_id) {
+ // Skip computing the match for already matched function.
+ // We do still need to apply the match data through analysis updates.
+ return on_matched_function(function, &matched_function);
+ }
+
+ let on_new_match = |matched: &Function| {
+ // We also want to resolve the types here.
+ if let TypeClass::Function(c) = matched.ty.class.as_ref() {
+ // Recursively go through the function type and resolve the uuids
+ let view = function.view();
+ let arch = function.arch();
+ for out_member in &c.out_members {
+ self.add_type_to_view(&view, &arch, &out_member.ty);
+ }
+ for in_member in &c.in_members {
+ self.add_type_to_view(&view, &arch, &in_member.ty);
+ }
+ } else {
+ // This should never happen.
+ log::error!(
+ "Matched function is not of function type class... 0x{:x}",
+ function.start()
+ );
+ }
+ on_matched_function(function, matched);
+
+ // We matched on the function, great! Now make sure we don't do this again :3
+ self.matched_functions
+ .insert(function_id, matched.to_owned());
+ // Also mark this for updates.
+ // TODO: Does this do anything?
+ function.mark_updates_required(FunctionUpdateType::UserFunctionUpdate);
+ };
+
+ // TODO: Expand this check to be less broad.
+ let is_function_trivial = { llil.instruction_count() < TRIVIAL_LLIL_THRESHOLD };
+
+ // Check to see if the functions entry block is even in the dataset
+ let entry_block = entry_basic_block_guid(function, llil).map(BasicBlock::new);
+ if self.basic_block_filter.contains(&entry_block) {
+ // Build the full function guid now
+ if let Some(warp_func_guid) = cached_function_guid(function, llil) {
+ if let Some(matched) = self.functions.get(&warp_func_guid) {
+ if matched.len() == 1 && !is_function_trivial {
+ on_new_match(&matched[0]);
+ } else if let Some(matched_function) =
+ self.match_function_from_constraints(function, &matched)
+ {
+ log::info!(
+ "Found best matching function `{}`... 0x{:x}",
+ matched_function.symbol.name,
+ function.start()
+ );
+ on_new_match(matched_function);
+ } else {
+ log::error!(
+ "Failed to find matching function `{}`... 0x{:x}",
+ matched.len(),
+ function.start()
+ );
+ }
+ }
+ }
+ }
+ }
+
+ pub fn match_function_from_constraints<'a>(
+ &self,
+ function: &BNFunction,
+ matched_functions: &'a [Function],
+ ) -> Option<&'a Function> {
+ // TODO: To prevent invoking adjacent constraint function analysis, we must call call_site constraints specifically.
+ let call_sites = cached_call_site_constraints(function);
+
+ // NOTE: We are only matching with call_sites for now, as adjacency requires we run after all analysis has completed.
+ if call_sites.is_empty() {
+ return None;
+ }
+
+ // Check call site guids
+ let mut highest_guid_count = 0;
+ let mut matched_guid_func = None;
+ let call_site_guids = call_sites
+ .iter()
+ .filter_map(|c| c.guid)
+ .collect::<HashSet<_>>();
+ for matched in matched_functions {
+ let matched_call_site_guids = matched
+ .constraints
+ .call_sites
+ .iter()
+ .filter_map(|c| c.guid)
+ .collect::<HashSet<_>>();
+ let common_guid_count = call_site_guids
+ .intersection(&matched_call_site_guids)
+ .count();
+ match common_guid_count.cmp(&highest_guid_count) {
+ Ordering::Equal => {
+ // Multiple matches with same count, don't match on ONE of them.
+ matched_guid_func = None;
+ }
+ Ordering::Greater => {
+ highest_guid_count = common_guid_count;
+ matched_guid_func = Some(matched);
+ }
+ Ordering::Less => {}
+ }
+ }
+
+ // Check call site symbol names
+ let mut highest_symbol_count = 0;
+ let mut matched_symbol_func = None;
+ let call_site_symbol_names = call_sites
+ .into_iter()
+ .filter_map(|c| Some(c.symbol?.name))
+ .collect::<HashSet<_>>();
+ for matched in matched_functions {
+ let matched_call_site_symbol_names = matched
+ .constraints
+ .call_sites
+ .iter()
+ .filter_map(|c| Some(c.symbol.to_owned()?.name))
+ .collect::<HashSet<_>>();
+ let common_symbol_count = call_site_symbol_names
+ .intersection(&matched_call_site_symbol_names)
+ .count();
+ match common_symbol_count.cmp(&highest_symbol_count) {
+ Ordering::Equal => {
+ // Multiple matches with same count, don't match on ONE of them.
+ matched_symbol_func = None;
+ }
+ Ordering::Greater => {
+ highest_symbol_count = common_symbol_count;
+ matched_symbol_func = Some(matched);
+ }
+ Ordering::Less => {}
+ }
+ }
+
+ match highest_guid_count.cmp(&highest_symbol_count) {
+ Ordering::Less => matched_symbol_func,
+ Ordering::Greater => matched_guid_func,
+ Ordering::Equal => None,
+ }
+ }
+}
+
+fn get_data_from_dir(dir: &PathBuf) -> HashMap<PathBuf, Data> {
+ let data_from_entry = |entry: DirEntry| {
+ let path = entry.path();
+ let contents = std::fs::read(path).ok()?;
+ Data::from_bytes(&contents)
+ };
+
+ WalkDir::new(dir)
+ .into_iter()
+ .filter_map(|e| e.ok())
+ .filter(|e| e.file_type().is_file())
+ .filter_map(|e| Some((e.clone().into_path(), data_from_entry(e)?)))
+ .collect()
+}
+
+/// A unique platform ID, used for caching.
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
+pub struct PlatformID(u64);
+
+impl From<&Platform> for PlatformID {
+ fn from(value: &Platform) -> Self {
+ let mut hasher = DefaultHasher::new();
+ hasher.write(value.name().to_bytes());
+ Self(hasher.finish())
+ }
+}
+
+impl From<BNRef<Platform>> for PlatformID {
+ fn from(value: BNRef<Platform>) -> Self {
+ Self::from(value.as_ref())
+ }
+}
+
+impl From<Guard<'_, Platform>> for PlatformID {
+ fn from(value: Guard<'_, Platform>) -> Self {
+ Self::from(value.as_ref())
+ }
+}
diff --git a/plugins/warp/src/plugin.rs b/plugins/warp/src/plugin.rs
new file mode 100644
index 00000000..5bca373b
--- /dev/null
+++ b/plugins/warp/src/plugin.rs
@@ -0,0 +1,152 @@
+use log::LevelFilter;
+
+use binaryninja::binaryview::{BinaryView, BinaryViewExt};
+use binaryninja::command::{Command, FunctionCommand};
+use binaryninja::function::Function;
+use binaryninja::rc::Ref;
+use binaryninja::tags::TagType;
+use warp::signature::function::Function as WarpFunction;
+
+use crate::build_function;
+use crate::cache::{ViewID, FUNCTION_CACHE, GUID_CACHE};
+use crate::convert::{to_bn_symbol_at_address, to_bn_type};
+use crate::matcher::{PlatformID, PLAT_MATCHER_CACHE};
+
+mod apply;
+mod copy;
+mod create;
+mod find;
+mod types;
+mod workflow;
+
+// TODO: This icon is a little much
+const TAG_ICON: &str = "🌏";
+const TAG_NAME: &str = "WARP";
+
+fn get_warp_tag_type(view: &BinaryView) -> Ref<TagType> {
+ view.get_tag_type(TAG_NAME)
+ .unwrap_or_else(|| view.create_tag_type(TAG_NAME, TAG_ICON))
+}
+
+// What happens to the function when it is matched.
+// TODO: add user: bool
+// TODO: Rename to markup_function or something.
+pub fn on_matched_function(function: &Function, matched: &WarpFunction) {
+ let view = function.view();
+ view.define_auto_symbol(&to_bn_symbol_at_address(
+ &view,
+ &matched.symbol,
+ function.symbol().address(),
+ ));
+ function.set_auto_type(&to_bn_type(&function.arch(), &matched.ty));
+ // TODO: Add metadata. (both binja metadata and warp metadata)
+ function.add_tag(
+ &get_warp_tag_type(&view),
+ matched.guid.to_string(),
+ None,
+ true,
+ None,
+ );
+}
+
+struct DebugFunction;
+
+impl FunctionCommand for DebugFunction {
+ fn action(&self, _view: &BinaryView, func: &Function) {
+ if let Ok(llil) = func.low_level_il() {
+ if let Some(function) = build_function(func, &llil) {
+ log::info!("{:#?}", function);
+ }
+ }
+ }
+
+ fn valid(&self, _view: &BinaryView, _func: &Function) -> bool {
+ true
+ }
+}
+
+struct DebugCache;
+
+impl Command for DebugCache {
+ fn action(&self, view: &BinaryView) {
+ let function_cache = FUNCTION_CACHE.get_or_init(Default::default);
+ let view_id = ViewID::from(view);
+ if let Some(cache) = function_cache.get(&view_id) {
+ log::info!("View functions: {}", cache.cache.len());
+ }
+
+ let function_guid_cache = GUID_CACHE.get_or_init(Default::default);
+ if let Some(cache) = function_guid_cache.get(&view_id) {
+ log::info!("View function guids: {}", cache.cache.len());
+ }
+
+ let plat_cache = PLAT_MATCHER_CACHE.get_or_init(Default::default);
+ if let Some(plat) = view.default_platform() {
+ let platform_id = PlatformID::from(plat);
+ if let Some(cache) = plat_cache.get(&platform_id) {
+ log::info!("Platform functions: {}", cache.functions.len());
+ log::info!("Platform types: {}", cache.types.len());
+ log::info!(
+ "Platform matched functions: {}",
+ cache.matched_functions.len()
+ );
+ }
+ }
+ }
+
+ fn valid(&self, _view: &BinaryView) -> bool {
+ true
+ }
+}
+
+#[no_mangle]
+#[allow(non_snake_case)]
+pub extern "C" fn CorePluginInit() -> bool {
+ binaryninja::logger::init(LevelFilter::Debug).unwrap();
+
+ workflow::insert_matcher_workflow();
+
+ binaryninja::command::register(
+ "WARP\\Apply Signature File Types",
+ "Load all types from a signature file and ignore functions",
+ types::LoadTypesCommand {},
+ );
+
+ binaryninja::command::register(
+ "WARP\\Debug Cache",
+ "Debug cache sizes... because...",
+ DebugCache {},
+ );
+
+ binaryninja::command::register_for_function(
+ "WARP\\Debug Signature",
+ "Print the entire signature for the function",
+ DebugFunction {},
+ );
+
+ binaryninja::command::register_for_function(
+ "WARP\\Copy Pattern",
+ "Copy the computed pattern for the function",
+ copy::CopyFunctionGUID {},
+ );
+
+ binaryninja::command::register(
+ "WARP\\Find Function From GUID",
+ "Locate the function in the view using a GUID",
+ find::FindFunctionFromGUID {},
+ );
+
+ binaryninja::command::register(
+ "WARP\\Generate Signature File",
+ "Generates a signature file containing all binary view functions",
+ create::CreateSignatureFile {},
+ );
+
+ binaryninja::command::register(
+ "WARP\\Apply Signature File",
+ "Applies a signature file to the current view",
+ apply::ApplySignatureFile {},
+ );
+
+ true
+}
diff --git a/plugins/warp/src/plugin/apply.rs b/plugins/warp/src/plugin/apply.rs
new file mode 100644
index 00000000..56c09d77
--- /dev/null
+++ b/plugins/warp/src/plugin/apply.rs
@@ -0,0 +1,82 @@
+use std::collections::HashMap;
+use std::time::Instant;
+
+use crate::cache::cached_function_guid;
+use crate::plugin::on_matched_function;
+use binaryninja::binaryview::{BinaryView, BinaryViewExt};
+use binaryninja::command::Command;
+use rayon::prelude::*;
+use warp::signature::function::{Function, FunctionGUID};
+
+pub struct ApplySignatureFile;
+
+// TODO: All this should do is insert data into the Matcher. this is leftover code.
+impl Command for ApplySignatureFile {
+ fn action(&self, view: &BinaryView) {
+ // TODO: Start bulk modification
+ // TODO: view.begin_bulk_modify_symbols();
+ let Some(file) =
+ binaryninja::interaction::get_open_filename_input("Apply Signature File", "*.sbin")
+ else {
+ return;
+ };
+
+ // TODO: signature files also need to store type information.
+
+ let Ok(data) = std::fs::read(&file) else {
+ log::error!("Could not read signature file: {:?}", file);
+ return;
+ };
+
+ let Some(data) = warp::signature::Data::from_bytes(&data) else {
+ log::error!("Could not get data from signature file: {:?}", file);
+ return;
+ };
+
+ // TODO: Turn Vec<Function> to HashSet so that functions with the same symbol and type get eliminated.
+ let data_functions: HashMap<FunctionGUID, Vec<Function>> =
+ data.functions
+ .into_iter()
+ .fold(HashMap::new(), |mut acc, func| {
+ #[allow(clippy::unwrap_or_default)]
+ acc.entry(func.guid).or_insert_with(Vec::new).push(func);
+ acc
+ });
+
+ let background_task = binaryninja::backgroundtask::BackgroundTask::new(
+ format!("Applying signatures from {:?}", file),
+ true,
+ )
+ .unwrap();
+
+ let funcs = view.functions();
+ let start = Instant::now();
+
+ background_task
+ .set_progress_text(format!("Building {} patterns to lookup...", funcs.len()));
+
+ // TODO: Redo this.
+ let single_matched = funcs
+ .par_iter()
+ .filter_map(|func| {
+ let llil = func.low_level_il_if_available()?;
+ let pattern = cached_function_guid(&func, &llil)?;
+ Some((func, data_functions.get(&pattern)?))
+ })
+ .filter(|(_, sig)| sig.len() == 1)
+ .collect::<Vec<_>>();
+
+ background_task.set_progress_text(format!("Applying {} matches...", single_matched.len()));
+ for (func, matched) in single_matched {
+ on_matched_function(&func, &matched[0]);
+ }
+
+ log::info!("Signature application took {:?}", start.elapsed());
+
+ background_task.finish();
+ }
+
+ fn valid(&self, _view: &BinaryView) -> bool {
+ true
+ }
+}
diff --git a/plugins/warp/src/plugin/copy.rs b/plugins/warp/src/plugin/copy.rs
new file mode 100644
index 00000000..ee91fb4a
--- /dev/null
+++ b/plugins/warp/src/plugin/copy.rs
@@ -0,0 +1,35 @@
+use binaryninja::binaryview::BinaryView;
+use binaryninja::command::FunctionCommand;
+use binaryninja::function::Function;
+
+use crate::cache::cached_function_guid;
+
+pub struct CopyFunctionGUID;
+
+impl FunctionCommand for CopyFunctionGUID {
+ fn action(&self, _view: &BinaryView, func: &Function) {
+ let Ok(llil) = func.low_level_il() else {
+ log::error!("Could not get low level il for copied function");
+ return;
+ };
+ if let Some(guid) = cached_function_guid(func, &llil) {
+ log::info!(
+ "Function GUID for {}... {}",
+ func.symbol().short_name().to_string(),
+ guid
+ );
+ if let Ok(mut clipboard) = arboard::Clipboard::new() {
+ let _ = clipboard.set_text(guid.to_string());
+ }
+ } else {
+ log::error!(
+ "Failed to create GUID for function... 0x{:0x}",
+ func.start()
+ );
+ }
+ }
+
+ fn valid(&self, _view: &BinaryView, _func: &Function) -> bool {
+ true
+ }
+}
diff --git a/plugins/warp/src/plugin/create.rs b/plugins/warp/src/plugin/create.rs
new file mode 100644
index 00000000..2b1ad60e
--- /dev/null
+++ b/plugins/warp/src/plugin/create.rs
@@ -0,0 +1,71 @@
+use crate::cache::cached_function;
+use crate::convert::from_bn_type;
+use binaryninja::binaryview::{BinaryView, BinaryViewExt};
+use binaryninja::command::Command;
+use rayon::prelude::*;
+use std::io::Write;
+use std::thread;
+use std::time::Instant;
+use warp::r#type::ComputedType;
+
+pub struct CreateSignatureFile;
+
+// TODO: Prompt the user to add the newly created signature file to the signature blacklist (so that it doesn't keep getting applied)
+
+impl Command for CreateSignatureFile {
+ fn action(&self, view: &BinaryView) {
+ let mut signature_dir = binaryninja::user_directory().unwrap().join("signatures/");
+ // TODO: This needs to split out each platform into its own bucket...
+ if let Some(default_plat) = view.default_platform() {
+ // If there is a default platform, put the signature in there.
+ signature_dir.push(default_plat.name().to_string());
+ }
+ let view = view.to_owned();
+ thread::spawn(move || {
+ let background_task = binaryninja::backgroundtask::BackgroundTask::new(
+ format!("Generating {} signatures... ", view.functions().len()),
+ true,
+ )
+ .unwrap();
+
+ let start = Instant::now();
+
+ let mut data = warp::signature::Data::default();
+ data.functions.par_extend(
+ view.functions()
+ .par_iter()
+ .filter_map(|func| cached_function(&func, func.low_level_il().ok()?.as_ref())),
+ );
+ data.types.extend(view.types().iter().map(|ty| {
+ let ref_ty = ty.type_object().to_owned();
+ ComputedType::new(from_bn_type(&view, ref_ty, u8::MAX))
+ }));
+
+ // And type generation :3
+ log::info!("Signature generation took {:?}", start.elapsed());
+
+ if let Some(sig_file_name) = binaryninja::interaction::get_text_line_input(
+ "Signature File",
+ "Create Signature File",
+ ) {
+ let save_file = signature_dir.join(sig_file_name + ".sbin");
+ log::info!("Saving to signatures to {:?}...", &save_file);
+ // TODO: Should we overwrite? Prompt user.
+ if let Ok(mut file) = std::fs::File::create(&save_file) {
+ match file.write_all(&data.to_bytes()) {
+ Ok(_) => log::info!("Signature file saved successfully."),
+ Err(e) => log::error!("Failed to write data to signature file: {:?}", e),
+ }
+ } else {
+ log::error!("Could not create signature file: {:?}", save_file);
+ }
+ }
+
+ background_task.finish();
+ });
+ }
+
+ fn valid(&self, _view: &BinaryView) -> bool {
+ true
+ }
+}
diff --git a/plugins/warp/src/plugin/find.rs b/plugins/warp/src/plugin/find.rs
new file mode 100644
index 00000000..7f089c2f
--- /dev/null
+++ b/plugins/warp/src/plugin/find.rs
@@ -0,0 +1,57 @@
+use binaryninja::binaryview::{BinaryView, BinaryViewExt};
+use binaryninja::command::Command;
+use rayon::prelude::*;
+use std::thread;
+use warp::signature::function::FunctionGUID;
+
+use crate::cache::cached_function_guid;
+
+pub struct FindFunctionFromGUID;
+
+impl Command for FindFunctionFromGUID {
+ fn action(&self, view: &BinaryView) {
+ let Some(guid_str) = binaryninja::interaction::get_text_line_input(
+ "Function GUID",
+ "Find Function from GUID",
+ ) else {
+ return;
+ };
+
+ let Ok(searched_guid) = guid_str.parse::<FunctionGUID>() else {
+ log::error!("Failed to parse function guid... {}", guid_str);
+ return;
+ };
+
+ log::info!("Searching functions for GUID... {}", searched_guid);
+ let funcs = view.functions();
+ thread::spawn(move || {
+ let background_task = binaryninja::backgroundtask::BackgroundTask::new(
+ format!("Searching functions for GUID... {}", searched_guid),
+ true,
+ )
+ .unwrap();
+
+ // TODO: While background_task has not finished.
+ let matched = funcs
+ .par_iter()
+ .filter_map(|func| {
+ Some((
+ func.clone(),
+ cached_function_guid(&func, func.low_level_il_if_available()?.as_ref())?,
+ ))
+ })
+ .filter(|(_func, guid)| guid.eq(&searched_guid))
+ .collect::<Vec<_>>();
+
+ for (func, _) in matched {
+ log::info!("Match found at function... 0x{:0x}", func.start());
+ }
+
+ background_task.finish();
+ });
+ }
+
+ fn valid(&self, _view: &BinaryView) -> bool {
+ true
+ }
+}
diff --git a/plugins/warp/src/plugin/types.rs b/plugins/warp/src/plugin/types.rs
new file mode 100644
index 00000000..b8718a63
--- /dev/null
+++ b/plugins/warp/src/plugin/types.rs
@@ -0,0 +1,52 @@
+use crate::convert::to_bn_type;
+use binaryninja::binaryview::{BinaryView, BinaryViewExt};
+use binaryninja::command::Command;
+use std::time::Instant;
+
+pub struct LoadTypesCommand;
+
+impl Command for LoadTypesCommand {
+ fn action(&self, view: &BinaryView) {
+ let Some(file) = binaryninja::interaction::get_open_filename_input(
+ "Apply Signature File Types",
+ "*.sbin",
+ ) else {
+ return;
+ };
+
+ let Ok(data) = std::fs::read(&file) else {
+ log::error!("Could not read signature file: {:?}", file);
+ return;
+ };
+
+ let Some(data) = warp::signature::Data::from_bytes(&data) else {
+ log::error!("Could not get data from signature file: {:?}", file);
+ return;
+ };
+
+ let view = view.to_owned();
+ std::thread::spawn(move || {
+ let background_task = binaryninja::backgroundtask::BackgroundTask::new(
+ format!("Applying {} types...", data.types.len()),
+ true,
+ )
+ .unwrap();
+
+ let start = Instant::now();
+ for comp_ty in data.types {
+ let ty_id = comp_ty.guid.to_string();
+ let ty_name = comp_ty.ty.name.to_owned().unwrap_or_else(|| ty_id.clone());
+ // TODO: Using arch here is problematic.
+ let arch = view.default_arch().unwrap();
+ view.define_auto_type_with_id(ty_name, ty_id, &to_bn_type(&arch, &comp_ty.ty));
+ }
+
+ log::info!("Type application took {:?}", start.elapsed());
+ background_task.finish();
+ });
+ }
+
+ fn valid(&self, _view: &BinaryView) -> bool {
+ true
+ }
+}
diff --git a/plugins/warp/src/plugin/workflow.rs b/plugins/warp/src/plugin/workflow.rs
new file mode 100644
index 00000000..da8473f0
--- /dev/null
+++ b/plugins/warp/src/plugin/workflow.rs
@@ -0,0 +1,38 @@
+use crate::matcher::cached_function_match;
+use binaryninja::llil;
+use binaryninja::workflow::{Activity, AnalysisContext, Workflow};
+
+const MATCHER_ACTIVITY_NAME: &str = "analysis.plugins.WARPMatcher";
+// NOTE: runOnce is off because previously matched functions need info applied.
+const MATCHER_ACTIVITY_CONFIG: &str = r#"{
+ "name": "analysis.plugins.WARPMatcher",
+ "title" : "WARP Matcher",
+ "description": "This analysis step applies WARP info to matched functions...",
+ "eligibility": {
+ "auto": { "default": true },
+ "runOnce": false
+ }
+}"#;
+
+pub fn insert_matcher_workflow() {
+ let matcher_activity = |ctx: &AnalysisContext| {
+ let function = ctx.function();
+ if function.has_user_annotations() {
+ // User has touched the function, stop trying to match on it!
+ return;
+ }
+
+ if let Some(llil) = unsafe { ctx.llil_function::<llil::NonSSA<llil::RegularNonSSA>>() } {
+ cached_function_match(&function, &llil);
+ }
+ };
+
+ let meta_workflow = Workflow::new_from_copy("core.function.metaAnalysis");
+ let activity = Activity::new_with_action(MATCHER_ACTIVITY_CONFIG, matcher_activity);
+ meta_workflow.register_activity(&activity).unwrap();
+ meta_workflow.insert(
+ "core.function.runFunctionRecognizers",
+ [MATCHER_ACTIVITY_NAME],
+ );
+ meta_workflow.register().unwrap();
+}
diff --git a/plugins/warp/src/snapshots/warp_ninja__tests__insta_signatures.snap b/plugins/warp/src/snapshots/warp_ninja__tests__insta_signatures.snap
new file mode 100644
index 00000000..442fce25
--- /dev/null
+++ b/plugins/warp/src/snapshots/warp_ninja__tests__insta_signatures.snap
@@ -0,0 +1,39 @@
+---
+source: src/lib.rs
+expression: functions
+---
+[
+ FunctionGUID {
+ guid: 623a8338-34d6-5a6e-8c4e-36a1a071117e,
+ },
+ FunctionGUID {
+ guid: 6cd81a21-6967-5c90-b73e-5a810f835a84,
+ },
+ FunctionGUID {
+ guid: 905fa3b0-3571-58ed-b81f-7cf62bdcfe49,
+ },
+ FunctionGUID {
+ guid: 9a3e480c-5ebd-5278-8e33-4a6e982167fb,
+ },
+ FunctionGUID {
+ guid: a25a06fb-fb60-542c-9b11-4c286dbc607b,
+ },
+ FunctionGUID {
+ guid: b1c5fbad-2657-5231-9f7b-b15a0d3b3bb6,
+ },
+ FunctionGUID {
+ guid: bcba7769-cc5a-5cdf-be50-d95b7a74ef60,
+ },
+ FunctionGUID {
+ guid: dd833eb1-f99b-5c58-a289-998dba19e69a,
+ },
+ FunctionGUID {
+ guid: f631b282-0174-5bdd-846d-c0514f2539e1,
+ },
+ FunctionGUID {
+ guid: fa2d7ebf-d187-5592-bfc1-ee41614437b3,
+ },
+ FunctionGUID {
+ guid: fa2d7ebf-d187-5592-bfc1-ee41614437b3,
+ },
+]