summaryrefslogtreecommitdiff
path: root/plugins/warp/src/plugin
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/warp/src/plugin')
-rw-r--r--plugins/warp/src/plugin/add.rs72
-rw-r--r--plugins/warp/src/plugin/copy.rs29
-rw-r--r--plugins/warp/src/plugin/create.rs284
-rw-r--r--plugins/warp/src/plugin/debug.rs48
-rw-r--r--plugins/warp/src/plugin/ffi.rs205
-rw-r--r--plugins/warp/src/plugin/ffi/container.rs412
-rw-r--r--plugins/warp/src/plugin/ffi/function.rs228
-rw-r--r--plugins/warp/src/plugin/file.rs41
-rw-r--r--plugins/warp/src/plugin/find.rs57
-rw-r--r--plugins/warp/src/plugin/function.rs133
-rw-r--r--plugins/warp/src/plugin/load.rs182
-rw-r--r--plugins/warp/src/plugin/project.rs132
-rw-r--r--plugins/warp/src/plugin/render_layer.rs100
-rw-r--r--plugins/warp/src/plugin/settings.rs129
-rw-r--r--plugins/warp/src/plugin/types.rs57
-rw-r--r--plugins/warp/src/plugin/workflow.rs188
16 files changed, 1910 insertions, 387 deletions
diff --git a/plugins/warp/src/plugin/add.rs b/plugins/warp/src/plugin/add.rs
deleted file mode 100644
index 2f76c8b5..00000000
--- a/plugins/warp/src/plugin/add.rs
+++ /dev/null
@@ -1,72 +0,0 @@
-use crate::cache::{cached_function, cached_type_references};
-use crate::matcher::invalidate_function_matcher_cache;
-use crate::user_signature_dir;
-use binaryninja::binary_view::BinaryView;
-use binaryninja::command::FunctionCommand;
-use binaryninja::function::Function;
-use std::thread;
-
-pub struct AddFunctionSignature;
-
-impl FunctionCommand for AddFunctionSignature {
- fn action(&self, view: &BinaryView, func: &Function) {
- let func_plat_name = func.platform().name().to_string();
- let signature_dir = user_signature_dir().join(func_plat_name);
- let view = view.to_owned();
- let func = func.to_owned();
- thread::spawn(move || {
- let Ok(llil) = func.low_level_il() else {
- log::error!("Could not get low level IL for function.");
- return;
- };
-
- // NOTE: Because we only can consume signatures from a specific directory, we don't need to use the interaction API.
- // If we did need to save signature files to a project than this would need to change.
- let Some(save_file) = rfd::FileDialog::new()
- .add_filter("Signature Files", &["sbin"])
- .set_file_name("user.sbin")
- .set_directory(signature_dir)
- .save_file()
- else {
- return;
- };
-
- let mut data = warp::signature::Data::default();
- if let Ok(file_bytes) = std::fs::read(&save_file) {
- // If the file we are adding the function to already has data we should preserve it!
- log::info!("Signature file already exists, preserving data...");
- let Some(file_data) = warp::signature::Data::from_bytes(&file_bytes) else {
- log::error!("Could not get data from signature file: {:?}", save_file);
- return;
- };
- data = file_data;
- };
-
- // Now add our function to the data.
- data.functions.push(cached_function(&func, &llil));
-
- if let Some(ref_ty_cache) = cached_type_references(&view) {
- let referenced_types = ref_ty_cache
- .cache
- .iter()
- .filter_map(|t| t.to_owned())
- .collect::<Vec<_>>();
-
- data.types.extend(referenced_types);
- }
-
- match std::fs::write(&save_file, data.to_bytes()) {
- Ok(_) => {
- log::info!("Signature file saved successfully.");
- // Force rebuild platform matcher.
- invalidate_function_matcher_cache();
- }
- Err(e) => log::error!("Failed to write data to signature file: {:?}", e),
- }
- });
- }
-
- fn valid(&self, _view: &BinaryView, _func: &Function) -> bool {
- true
- }
-}
diff --git a/plugins/warp/src/plugin/copy.rs b/plugins/warp/src/plugin/copy.rs
deleted file mode 100644
index b9b985fc..00000000
--- a/plugins/warp/src/plugin/copy.rs
+++ /dev/null
@@ -1,29 +0,0 @@
-use binaryninja::binary_view::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;
- };
- let guid = cached_function_guid(func, &llil);
- log::info!(
- "Function GUID for {:?}... {}",
- func.symbol().short_name(),
- guid
- );
- if let Ok(mut clipboard) = arboard::Clipboard::new() {
- let _ = clipboard.set_text(guid.to_string());
- }
- }
-
- 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
index 1dd83e6e..9ebcba2d 100644
--- a/plugins/warp/src/plugin/create.rs
+++ b/plugins/warp/src/plugin/create.rs
@@ -1,94 +1,232 @@
-use crate::cache::{cached_function, cached_type_references};
-use crate::matcher::invalidate_function_matcher_cache;
-use crate::user_signature_dir;
+use crate::processor::{
+ new_processing_state_background_thread, CompressionTypeField, FileDataKindField,
+ IncludedFunctionsField, SaveReportToDiskField, WarpFileProcessor,
+};
+use crate::report::{ReportGenerator, ReportKindField};
+use crate::{user_signature_dir, INCLUDE_TAG_NAME};
+use binaryninja::background_task::BackgroundTask;
use binaryninja::binary_view::{BinaryView, BinaryViewExt};
use binaryninja::command::Command;
-use binaryninja::function::Function;
-use binaryninja::rc::Guard;
-use rayon::prelude::*;
-use std::sync::atomic::AtomicUsize;
-use std::sync::atomic::Ordering::Relaxed;
+use binaryninja::interaction::form::{Form, FormInputField};
+use binaryninja::interaction::{MessageBoxButtonResult, MessageBoxButtonSet, MessageBoxIcon};
+use binaryninja::rc::Ref;
+use std::path::PathBuf;
use std::thread;
-use std::time::Instant;
+use warp::chunk::Chunk;
+use warp::WarpFile;
-pub struct CreateSignatureFile;
+pub struct SaveFileField;
-// TODO: Prompt the user to add the newly created signature file to the signature blacklist (so that it doesn't keep getting applied)
+impl SaveFileField {
+ pub fn field(view: &BinaryView) -> FormInputField {
+ let default_name = view
+ .file()
+ .filename()
+ .split('/')
+ .last()
+ .unwrap_or("file")
+ .to_string();
+ let signature_dir = user_signature_dir();
+ let default_file_path = signature_dir.join(&default_name).with_extension("warp");
+ FormInputField::SaveFileName {
+ prompt: "File Path".to_string(),
+ // TODO: This is called extension but is really a filter.
+ extension: Some("*.warp".to_string()),
+ default_name: Some(default_name),
+ default: Some(default_file_path.to_string_lossy().to_string()),
+ value: None,
+ }
+ }
-impl Command for CreateSignatureFile {
- fn action(&self, view: &BinaryView) {
- let is_function_named = |f: &Guard<Function>| {
- !f.symbol().short_name().to_string_lossy().contains("sub_") || f.has_user_annotations()
- };
- let mut signature_dir = user_signature_dir();
- if let Some(default_plat) = view.default_platform() {
- // If there is a default platform, put the signature in there.
- // TODO: We should instead use the platform of the function.
- signature_dir.push(default_plat.name().to_string());
+ pub fn from_form(form: &Form) -> Option<PathBuf> {
+ let field = form.get_field_with_name("File Path")?;
+ let field_value = field.try_value_string()?;
+ Some(PathBuf::from(field_value))
+ }
+}
+
+pub struct OpenFileField;
+
+impl OpenFileField {
+ pub fn field() -> FormInputField {
+ FormInputField::OpenFileName {
+ prompt: "Input File Path".to_string(),
+ extension: None,
+ default: None,
+ value: None,
}
- let view = view.to_owned();
- thread::spawn(move || {
- let total_functions = view.functions().len();
- let done_functions = AtomicUsize::default();
- let background_task = binaryninja::background_task::BackgroundTask::new(
- &format!("Generating signatures... ({}/{})", 0, total_functions),
- true,
- );
+ }
+
+ pub fn from_form(form: &Form) -> Option<PathBuf> {
+ let field = form.get_field_with_name("Input File Path")?;
+ let field_value = field.try_value_string()?;
+ Some(PathBuf::from(field_value))
+ }
+}
+
+pub struct CreateFromCurrentView;
- let start = Instant::now();
+impl CreateFromCurrentView {
+ pub fn execute(view: Ref<BinaryView>, external_file: bool) -> Option<()> {
+ // Prompt the user first so that they can go do other things and not worry about a popup.
+ let mut form = Form::new("Create From View");
- let mut data = warp::signature::Data::default();
- data.functions.par_extend(
- view.functions()
- .par_iter()
- .inspect(|_| {
- done_functions.fetch_add(1, Relaxed);
- background_task.set_progress_text(&format!(
- "Generating signatures... ({}/{})",
- done_functions.load(Relaxed),
- total_functions
- ))
- })
- .filter(is_function_named)
- .filter(|f| !f.analysis_skipped())
- .filter_map(|func| {
- let llil = func.low_level_il().ok()?;
- Some(cached_function(&func, &llil))
- }),
+ if external_file {
+ form.add_field(OpenFileField::field());
+ }
+
+ form.add_field(SaveFileField::field(&view));
+
+ let fd_field = FileDataKindField::default();
+ form.add_field(fd_field.to_field());
+
+ let compression_field = CompressionTypeField::default();
+ form.add_field(compression_field.to_field());
+
+ let mut included_field = IncludedFunctionsField::default();
+ // If the view has the include tag, we better set the default to the selected functions.
+ if view.tag_type_by_name(INCLUDE_TAG_NAME).is_some() {
+ included_field = IncludedFunctionsField::Selected;
+ }
+ form.add_field(included_field.to_field());
+
+ let report_field = ReportKindField::default();
+ form.add_field(report_field.to_field());
+ let report_to_disk_field = SaveReportToDiskField::default();
+ form.add_field(report_to_disk_field.to_field());
+
+ if !form.prompt() {
+ return None;
+ }
+ let compression_type = CompressionTypeField::from_form(&form).unwrap_or_default();
+ let file_path = SaveFileField::from_form(&form)?;
+ let file_data_kind = FileDataKindField::from_form(&form).unwrap_or_default();
+ let file_included_functions = IncludedFunctionsField::from_form(&form).unwrap_or_default();
+ let report_kind = ReportKindField::from_form(&form).unwrap_or_default();
+ let save_report_to_disk = SaveReportToDiskField::from_form(&form).unwrap_or_default();
+ let open_file_path = OpenFileField::from_form(&form);
+
+ // If we already have a file, prompt the user if they want to add the data.
+ let mut existing_chunks = Vec::new();
+ if file_path.exists() {
+ let prompt_result = binaryninja::interaction::show_message_box(
+ "Keep existing file data?",
+ "The file already exists. Do you want to keep the existing data?",
+ MessageBoxButtonSet::YesNoCancelButtonSet,
+ MessageBoxIcon::QuestionIcon,
);
- if let Some(ref_ty_cache) = cached_type_references(&view) {
- let referenced_types = ref_ty_cache
- .cache
- .iter()
- .filter_map(|t| t.to_owned())
- .collect::<Vec<_>>();
+ match prompt_result {
+ MessageBoxButtonResult::NoButton => {
+ // User wants to overwrite the file.
+ }
+ MessageBoxButtonResult::YesButton | MessageBoxButtonResult::OKButton => {
+ // User wants to keep the existing data.
+ let data = std::fs::read(&file_path).ok()?;
+ let existing_file = WarpFile::from_owned_bytes(data)?;
+ existing_chunks.extend(existing_file.chunks);
+ }
+ MessageBoxButtonResult::CancelButton => {
+ log::info!(
+ "User cancelled signature file creation, no operations were performed."
+ );
+ return None;
+ }
+ }
+ }
+
+ let processor = WarpFileProcessor::new()
+ .with_compression_type(compression_type)
+ .with_file_data(file_data_kind)
+ .with_included_functions(file_included_functions);
- data.types.extend(referenced_types);
+ let file = match open_file_path {
+ None => {
+ // We are processing the current view. NOT an external file.
+ // Reference path is just used for the state tracking. Does not need to be readable.
+ let reference_path = file_path.clone();
+ processor.process_view(reference_path, &view)
+ }
+ Some(open_file_path) => {
+ // This thread will show the state in a background task.
+ let background_task = BackgroundTask::new("Processing started...", true);
+ new_processing_state_background_thread(background_task.clone(), processor.state());
+ let file = processor.process(open_file_path);
+ background_task.finish();
+ file
}
+ };
+
+ if let Err(err) = file {
+ binaryninja::interaction::show_message_box(
+ "Error",
+ &format!("Failed to create signature file: {}", err),
+ MessageBoxButtonSet::OKButtonSet,
+ MessageBoxIcon::ErrorIcon,
+ );
+ log::error!("Failed to create signature file: {}", err);
+ return None;
+ }
- log::info!("Signature generation took {:?}", start.elapsed());
- background_task.finish();
+ let mut file = file.unwrap();
+ // Add back the existing chunks if the user selected to keep them.
+ file.chunks.extend(existing_chunks);
+ // TODO: Make merging optional?
+ file.chunks = Chunk::merge(&file.chunks, compression_type.into());
+
+ if std::fs::write(&file_path, file.to_bytes()).is_err() {
+ log::error!("Failed to write data to signature file!");
+ }
- // NOTE: Because we only can consume signatures from a specific directory, we don't need to use the interaction API.
- // If we did need to save signature files to a project than this would need to change.
- let Some(save_file) = rfd::FileDialog::new()
- .add_filter("Signature Files", &["sbin"])
- .set_file_name(format!("{}.sbin", view.file().filename()))
- .set_directory(signature_dir)
- .save_file()
- else {
- return;
- };
+ // Show a report of the generate signatures, if desired.
+ let report_generator = ReportGenerator::new();
+ if let Some(report_string) = report_generator.report(&report_kind, &file) {
+ if save_report_to_disk == SaveReportToDiskField::Yes {
+ let report_ext = report_generator
+ .report_extension(&report_kind)
+ .unwrap_or_default();
+ let report_path = file_path.with_extension(report_ext);
+ let _ = std::fs::write(report_path, &report_string);
+ }
- match std::fs::write(&save_file, data.to_bytes()) {
- Ok(_) => {
- log::info!("Signature file saved successfully.");
- // Force rebuild platform matcher.
- invalidate_function_matcher_cache();
+ match report_kind {
+ ReportKindField::None => {}
+ ReportKindField::Html => {
+ view.show_html_report("Generated WARP File", report_string.as_str(), "");
+ }
+ ReportKindField::Markdown => {
+ view.show_markdown_report("Generated WARP File", report_string.as_str(), "");
+ }
+ ReportKindField::Json => {
+ view.show_plaintext_report("Generated WARP File", report_string.as_str());
}
- Err(e) => log::error!("Failed to write data to signature file: {:?}", e),
}
+ }
+
+ Some(())
+ }
+}
+
+impl Command for CreateFromCurrentView {
+ fn action(&self, view: &BinaryView) {
+ let view = view.to_owned();
+ thread::spawn(move || {
+ CreateFromCurrentView::execute(view, false);
+ });
+ }
+
+ fn valid(&self, _view: &BinaryView) -> bool {
+ true
+ }
+}
+
+pub struct CreateFromFiles;
+
+impl Command for CreateFromFiles {
+ fn action(&self, view: &BinaryView) {
+ let view = view.to_owned();
+ thread::spawn(move || {
+ CreateFromCurrentView::execute(view, true);
});
}
diff --git a/plugins/warp/src/plugin/debug.rs b/plugins/warp/src/plugin/debug.rs
new file mode 100644
index 00000000..fc4e5817
--- /dev/null
+++ b/plugins/warp/src/plugin/debug.rs
@@ -0,0 +1,48 @@
+use crate::cache::container::for_cached_containers;
+use crate::{build_function, cache};
+use binaryninja::binary_view::BinaryView;
+use binaryninja::command::{Command, FunctionCommand};
+use binaryninja::function::Function;
+use binaryninja::ObjectDestructor;
+
+pub struct DebugFunction;
+
+impl FunctionCommand for DebugFunction {
+ fn action(&self, _view: &BinaryView, func: &Function) {
+ if let Ok(lifted_il) = func.lifted_il() {
+ log::info!("{:#?}", build_function(func, &lifted_il));
+ }
+ }
+
+ fn valid(&self, _view: &BinaryView, _func: &Function) -> bool {
+ true
+ }
+}
+
+pub struct DebugCache;
+
+impl Command for DebugCache {
+ fn action(&self, _view: &BinaryView) {
+ for_cached_containers(|c| {
+ log::info!("Container: {:#?}", c);
+ });
+ }
+
+ fn valid(&self, _view: &BinaryView) -> bool {
+ true
+ }
+}
+
+pub struct DebugInvalidateCache;
+
+impl Command for DebugInvalidateCache {
+ fn action(&self, view: &BinaryView) {
+ let destructor = cache::CacheDestructor {};
+ destructor.destruct_view(view);
+ log::info!("Invalidated all WARP caches...");
+ }
+
+ fn valid(&self, _view: &BinaryView) -> bool {
+ true
+ }
+}
diff --git a/plugins/warp/src/plugin/ffi.rs b/plugins/warp/src/plugin/ffi.rs
new file mode 100644
index 00000000..d78ca3c5
--- /dev/null
+++ b/plugins/warp/src/plugin/ffi.rs
@@ -0,0 +1,205 @@
+mod container;
+mod function;
+
+use binaryninjacore_sys::{
+ BNBasicBlock, BNBinaryView, BNFunction, BNLowLevelILFunction, BNPlatform,
+};
+use std::ffi::c_char;
+use std::sync::{Arc, RwLock};
+use uuid::Uuid;
+
+use binaryninja::basic_block::{BasicBlock, BasicBlockType};
+use binaryninja::function::{Function, NativeBlock};
+
+use crate::cache::cached_function_guid;
+use crate::container::{Container, SourceId};
+use crate::convert::platform_to_target;
+use crate::plugin::workflow::run_matcher;
+use crate::{
+ basic_block_guid, is_blacklisted_instruction, is_computed_variant_instruction,
+ is_variant_instruction, relocatable_regions,
+};
+use binaryninja::binary_view::BinaryView;
+use binaryninja::low_level_il::function::{LowLevelILFunction, Mutable, NonSSA};
+use binaryninja::low_level_il::instruction::LowLevelInstructionIndex;
+use binaryninja::platform::Platform;
+use binaryninja::string::BnString;
+use warp::r#type::guid::TypeGUID;
+use warp::signature::basic_block::BasicBlockGUID;
+use warp::signature::constraint::{Constraint, ConstraintGUID, UNRELATED_OFFSET};
+use warp::signature::function::FunctionGUID;
+
+/// [`SourceId`] is marked transparent to the underlying `[u8; 16]`, safe to use directly in FFI.
+pub type BNWARPSource = SourceId;
+
+/// [`BasicBlockGUID`] is marked transparent to the underlying `[u8; 16]`, safe to use directly in FFI.
+pub type BNWARPBasicBlockGUID = BasicBlockGUID;
+
+/// [`ConstraintGUID`] is marked transparent to the underlying `[u8; 16]`, safe to use directly in FFI.
+pub type BNWARPConstraintGUID = ConstraintGUID;
+
+/// [`FunctionGUID`] is marked transparent to the underlying `[u8; 16]`, safe to use directly in FFI.
+pub type BNWARPFunctionGUID = FunctionGUID;
+
+/// [`TypeGUID`] is marked transparent to the underlying `[u8; 16]`, safe to use directly in FFI.
+pub type BNWARPTypeGUID = TypeGUID;
+
+pub type BNWARPTarget = warp::target::Target;
+pub type BNWARPFunction = warp::signature::function::Function;
+pub type BNWARPContainer = RwLock<Box<dyn Container>>;
+
+// TODO: Some sort of callback for loading functions
+// TODO: Be able to run matcher for a specific file
+// TODO: Generate signatures for a file, return what?
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub struct BNWARPConstraint {
+ guid: BNWARPConstraintGUID,
+ offset: i64,
+}
+
+impl From<BNWARPConstraint> for Constraint {
+ fn from(constraint: BNWARPConstraint) -> Self {
+ Constraint {
+ guid: constraint.guid,
+ offset: match constraint.offset {
+ UNRELATED_OFFSET => None,
+ _ => Some(constraint.offset),
+ },
+ }
+ }
+}
+
+impl From<Constraint> for BNWARPConstraint {
+ fn from(constraint: Constraint) -> Self {
+ BNWARPConstraint {
+ guid: constraint.guid,
+ offset: constraint.offset.unwrap_or(UNRELATED_OFFSET),
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPUUIDGetString(uuid: *const Uuid) -> *mut c_char {
+ let uuid_str = (*uuid).to_string();
+ // NOTE: Leak the uuid string to be freed by BNFreeString
+ BnString::into_raw(uuid_str.into())
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPUUIDEqual(a: *const Uuid, b: *const Uuid) -> bool {
+ (*a) == (*b)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPRunMatcher(view: *mut BNBinaryView) {
+ let view = BinaryView::from_raw(view);
+ run_matcher(&view)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPGetBasicBlockGUID(
+ basic_block: *mut BNBasicBlock,
+ result: *mut BNWARPBasicBlockGUID,
+) -> bool {
+ let basic_block = unsafe { BasicBlock::from_raw(basic_block, NativeBlock::new()) };
+ if basic_block.block_type() != BasicBlockType::Native {
+ return false;
+ }
+ let function = basic_block.function();
+ match function.lifted_il() {
+ Ok(lifted_il) => {
+ let relocatable_regions = relocatable_regions(&function.view());
+ *result = basic_block_guid(&relocatable_regions, &basic_block, &lifted_il);
+ true
+ }
+ Err(_) => false,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPGetAnalysisFunctionGUID(
+ analysis_function: *mut BNFunction,
+ result: *mut BNWARPFunctionGUID,
+) -> bool {
+ let function = unsafe { Function::from_raw(analysis_function) };
+ match function.lifted_il() {
+ Ok(lifted_il) => {
+ *result = cached_function_guid(&function, &lifted_il);
+ true
+ }
+ Err(_) => false,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPIsLiftedInstructionVariant(
+ analysis_function: *mut BNLowLevelILFunction,
+ index: LowLevelInstructionIndex,
+) -> bool {
+ let lifted_il: LowLevelILFunction<Mutable, NonSSA> =
+ unsafe { LowLevelILFunction::from_raw(analysis_function) };
+ match lifted_il.instruction_from_index(index) {
+ Some(instr) => {
+ let relocatable_regions = relocatable_regions(&lifted_il.function().view());
+ is_variant_instruction(&relocatable_regions, &instr)
+ }
+ None => false,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPIsLowLevelInstructionComputedVariant(
+ analysis_function: *mut BNLowLevelILFunction,
+ index: LowLevelInstructionIndex,
+) -> bool {
+ let llil: LowLevelILFunction<Mutable, NonSSA> =
+ unsafe { LowLevelILFunction::from_raw(analysis_function) };
+ match llil.instruction_from_index(index) {
+ Some(instr) => {
+ let relocatable_regions = relocatable_regions(&llil.function().view());
+ is_computed_variant_instruction(&relocatable_regions, &instr)
+ }
+ None => false,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPIsLiftedInstructionBlacklisted(
+ analysis_function: *mut BNLowLevelILFunction,
+ index: LowLevelInstructionIndex,
+) -> bool {
+ let lifted_il: LowLevelILFunction<Mutable, NonSSA> =
+ unsafe { LowLevelILFunction::from_raw(analysis_function) };
+ match lifted_il.instruction_from_index(index) {
+ Some(instr) => is_blacklisted_instruction(&instr),
+ None => false,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeUUIDList(uuids: *mut Uuid, count: usize) {
+ let sources_ptr = std::ptr::slice_from_raw_parts_mut(uuids, count);
+ let _ = unsafe { Box::from_raw(sources_ptr) };
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPGetTarget(platform: *mut BNPlatform) -> *mut BNWARPTarget {
+ let platform = Platform::from_raw(platform);
+ Arc::into_raw(Arc::new(platform_to_target(&platform))) as *mut BNWARPTarget
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPNewTargetReference(target: *mut BNWARPTarget) -> *mut BNWARPTarget {
+ Arc::increment_strong_count(target);
+ target
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeTargetReference(target: *mut BNWARPTarget) {
+ if target.is_null() {
+ return;
+ }
+ Arc::decrement_strong_count(target);
+}
diff --git a/plugins/warp/src/plugin/ffi/container.rs b/plugins/warp/src/plugin/ffi/container.rs
new file mode 100644
index 00000000..21ad4dc0
--- /dev/null
+++ b/plugins/warp/src/plugin/ffi/container.rs
@@ -0,0 +1,412 @@
+use crate::cache::container::cached_containers;
+use crate::container::SourcePath;
+use crate::convert::{from_bn_type, to_bn_type};
+use crate::plugin::ffi::{
+ BNWARPContainer, BNWARPFunction, BNWARPFunctionGUID, BNWARPSource, BNWARPTarget, BNWARPTypeGUID,
+};
+use binaryninja::architecture::CoreArchitecture;
+use binaryninja::binary_view::BinaryView;
+use binaryninja::rc::Ref;
+use binaryninja::string::BnString;
+use binaryninja::types::Type;
+use binaryninjacore_sys::{BNArchitecture, BNBinaryView, BNType};
+use std::ffi::{c_char, CStr};
+use std::mem::ManuallyDrop;
+use std::sync::Arc;
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPGetContainers(count: *mut usize) -> *mut *mut BNWARPContainer {
+ // NOTE: Leak the arc pointers to be freed by BNWARPFreeContainerList
+ let boxed_raw_containers: Box<[_]> =
+ cached_containers().into_iter().map(Arc::into_raw).collect();
+ *count = boxed_raw_containers.len();
+ let leaked_raw_containers = Box::into_raw(boxed_raw_containers);
+ leaked_raw_containers as *mut *mut BNWARPContainer
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerGetName(container: *mut BNWARPContainer) -> *const c_char {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(container) = arc_container.read() else {
+ return std::ptr::null();
+ };
+ let name = container.to_string();
+ // NOTE: Leak the container name to be freed by BNFreeString
+ BnString::into_raw(name.into())
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerGetSources(
+ container: *mut BNWARPContainer,
+ count: *mut usize,
+) -> *mut BNWARPSource {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(container) = arc_container.write() else {
+ return std::ptr::null_mut();
+ };
+
+ // NOTE: Leak the sources to be freed by BNWARPFreeSourceList
+ let boxed_sources: Box<[_]> = container.sources().unwrap_or_default().into_boxed_slice();
+ *count = boxed_sources.len();
+ Box::into_raw(boxed_sources) as *mut BNWARPSource
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerAddSource(
+ container: *mut BNWARPContainer,
+ source_path: *const c_char,
+ result: *mut BNWARPSource,
+) -> bool {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(mut container) = arc_container.write() else {
+ return false;
+ };
+
+ let source_path_cstr = unsafe { CStr::from_ptr(source_path) };
+ let source_path_str = source_path_cstr.to_str().unwrap();
+ let source_path = SourcePath::new_with_str(source_path_str);
+
+ match container.add_source(source_path) {
+ Ok(source) => {
+ // NOTE: Leak the source to be freed by BNFreeString
+ *result = source;
+ true
+ }
+ Err(_) => false,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerCommitSource(
+ container: *mut BNWARPContainer,
+ source: *const BNWARPSource,
+) -> bool {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(mut container) = arc_container.write() else {
+ return false;
+ };
+
+ let source = unsafe { *source };
+
+ container
+ .commit_source(&source)
+ .is_ok_and(|committed| committed)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerIsSourceUncommitted(
+ container: *mut BNWARPContainer,
+ source: *const BNWARPSource,
+) -> bool {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(container) = arc_container.read() else {
+ return false;
+ };
+
+ let source = unsafe { *source };
+
+ container
+ .is_source_uncommitted(&source)
+ .is_ok_and(|uncommitted| uncommitted)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerIsSourceWritable(
+ container: *mut BNWARPContainer,
+ source: *const BNWARPSource,
+) -> bool {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(container) = arc_container.read() else {
+ return false;
+ };
+
+ let source = unsafe { *source };
+
+ container
+ .is_source_writable(&source)
+ .is_ok_and(|writable| writable)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerGetSourcePath(
+ container: *mut BNWARPContainer,
+ source: *const BNWARPSource,
+) -> *const c_char {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(container) = arc_container.read() else {
+ return std::ptr::null();
+ };
+
+ let source = unsafe { *source };
+
+ match container.source_path(&source) {
+ Ok(path) => {
+ let path = path.to_string();
+ // NOTE: Leak the source path to be freed by BNFreeString
+ BnString::into_raw(path.into())
+ }
+ Err(_) => std::ptr::null(),
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerAddFunctions(
+ container: *mut BNWARPContainer,
+ target: *mut BNWARPTarget,
+ source: *const BNWARPSource,
+ functions: *mut *mut BNWARPFunction,
+ count: usize,
+) -> bool {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(mut container) = arc_container.write() else {
+ return false;
+ };
+
+ let target = unsafe { ManuallyDrop::new(Arc::from_raw(target)) };
+
+ let source = unsafe { *source };
+
+ let functions_ptr = std::slice::from_raw_parts(functions, count);
+ // TODO: We have to clone the objects here to make the type checker happy.
+ // TODO: See about avoiding this later.
+ let functions: Vec<_> = functions_ptr
+ .iter()
+ .map(|&f| unsafe { ManuallyDrop::new(Arc::from_raw(f)).as_ref().clone() })
+ .collect();
+ container
+ .add_functions(&target, &source, &functions)
+ .is_ok()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerAddTypes(
+ view: *mut BNBinaryView,
+ container: *mut BNWARPContainer,
+ source: *const BNWARPSource,
+ types: *mut *mut BNType,
+ count: usize,
+) -> bool {
+ let view = unsafe { BinaryView::from_raw(view) };
+
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(mut container) = arc_container.write() else {
+ return false;
+ };
+
+ let source = unsafe { *source };
+
+ let types_ptr = std::slice::from_raw_parts(types, count);
+ let types: Vec<_> = types_ptr
+ .iter()
+ .map(|&t| Type::from_raw(t))
+ .map(|ty| from_bn_type(&view, &ty, 255))
+ .collect();
+ container.add_types(&source, &types).is_ok()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerRemoveFunctions(
+ container: *mut BNWARPContainer,
+ target: *mut BNWARPTarget,
+ source: *const BNWARPSource,
+ functions: *mut *mut BNWARPFunction,
+ count: usize,
+) -> bool {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(mut container) = arc_container.write() else {
+ return false;
+ };
+
+ let target = unsafe { ManuallyDrop::new(Arc::from_raw(target)) };
+
+ let source = unsafe { *source };
+
+ let functions_ptr = std::slice::from_raw_parts(functions, count);
+ // TODO: We have to clone the objects here to make the type checker happy.
+ // TODO: See about avoiding this later.
+ let functions: Vec<_> = functions_ptr
+ .iter()
+ .map(|&f| unsafe { ManuallyDrop::new(Arc::from_raw(f)).as_ref().clone() })
+ .collect();
+ container
+ .remove_functions(&target, &source, &functions)
+ .is_ok()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerRemoveTypes(
+ container: *mut BNWARPContainer,
+ source: *const BNWARPSource,
+ guids: *mut BNWARPTypeGUID,
+ count: usize,
+) -> bool {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(mut container) = arc_container.write() else {
+ return false;
+ };
+
+ let source = unsafe { *source };
+
+ let guids = std::slice::from_raw_parts(guids, count);
+ container.remove_types(&source, &guids).is_ok()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerGetSourcesWithFunctionGUID(
+ container: *mut BNWARPContainer,
+ target: *mut BNWARPTarget,
+ guid: *const BNWARPFunctionGUID,
+ count: *mut usize,
+) -> *mut BNWARPSource {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(container) = arc_container.read() else {
+ return std::ptr::null_mut();
+ };
+
+ let target = unsafe { ManuallyDrop::new(Arc::from_raw(target)) };
+
+ let guid = unsafe { *guid };
+
+ // NOTE: Leak the sources to be freed by BNWARPFreeSourceList
+ let boxed_sources: Box<[_]> = container
+ .sources_with_function_guid(&target, &guid)
+ .unwrap_or_default()
+ .into_boxed_slice();
+ *count = boxed_sources.len();
+ Box::into_raw(boxed_sources) as *mut BNWARPSource
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerGetSourcesWithTypeGUID(
+ container: *mut BNWARPContainer,
+ guid: *const BNWARPTypeGUID,
+ count: *mut usize,
+) -> *mut BNWARPSource {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(container) = arc_container.read() else {
+ return std::ptr::null_mut();
+ };
+
+ let guid = unsafe { *guid };
+
+ // NOTE: Leak the sources to be freed by BNWARPFreeSourceList
+ let boxed_sources: Box<[_]> = container
+ .sources_with_type_guid(&guid)
+ .unwrap_or_default()
+ .into_boxed_slice();
+ *count = boxed_sources.len();
+ Box::into_raw(boxed_sources) as *mut BNWARPSource
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerGetFunctionsWithGUID(
+ container: *mut BNWARPContainer,
+ target: *mut BNWARPTarget,
+ source: *const BNWARPSource,
+ guid: *const BNWARPFunctionGUID,
+ count: *mut usize,
+) -> *mut *mut BNWARPFunction {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(container) = arc_container.read() else {
+ return std::ptr::null_mut();
+ };
+
+ let source = unsafe { *source };
+
+ let target = unsafe { ManuallyDrop::new(Arc::from_raw(target)) };
+
+ let guid = unsafe { *guid };
+
+ // NOTE: Leak the functions to be freed by BNWARPFreeFunctionList
+ let raw_boxed_functions: Box<[_]> = container
+ .functions_with_guid(&target, &source, &guid)
+ .unwrap_or_default()
+ .into_iter()
+ .map(Arc::new)
+ .map(Arc::into_raw)
+ .collect();
+ *count = raw_boxed_functions.len();
+ Box::into_raw(raw_boxed_functions) as *mut *mut BNWARPFunction
+}
+
+// TODO: Swap arch to Target?
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerGetTypeWithGUID(
+ arch: *mut BNArchitecture,
+ container: *mut BNWARPContainer,
+ source: *const BNWARPSource,
+ guid: *const BNWARPTypeGUID,
+) -> *mut BNType {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(container) = arc_container.read() else {
+ return std::ptr::null_mut();
+ };
+
+ // NOTE: to convert the type, we must have an architecture.
+ let arch = CoreArchitecture::from_raw(arch);
+
+ let source = unsafe { *source };
+
+ let guid = unsafe { *guid };
+
+ let Some(ty) = container.type_with_guid(&source, &guid).unwrap_or_default() else {
+ return std::ptr::null_mut();
+ };
+ let function_type = to_bn_type(&arch, &ty);
+ // NOTE: The type ref has been pre-incremented for the caller.
+ unsafe { Ref::into_raw(function_type) }.handle
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerGetTypeGUIDsWithName(
+ container: *mut BNWARPContainer,
+ source: *const BNWARPSource,
+ name: *const c_char,
+ count: *mut usize,
+) -> *mut BNWARPTypeGUID {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(container) = arc_container.read() else {
+ return std::ptr::null_mut();
+ };
+
+ let source = unsafe { *source };
+
+ let name_cstr = unsafe { CStr::from_ptr(name) };
+ let name = name_cstr.to_str().unwrap();
+
+ // NOTE: Leak the guids to be freed by BNWARPFreeTypeGUIDList
+ let boxed_guids = container
+ .type_guids_with_name(&source, name)
+ .unwrap_or_default()
+ .into_boxed_slice();
+ *count = boxed_guids.len();
+ Box::into_raw(boxed_guids) as *mut BNWARPTypeGUID
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPNewContainerReference(
+ container: *mut BNWARPContainer,
+) -> *mut BNWARPContainer {
+ Arc::increment_strong_count(container);
+ container
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeContainerReference(container: *mut BNWARPContainer) {
+ if container.is_null() {
+ return;
+ }
+ Arc::decrement_strong_count(container);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeContainerList(
+ containers: *mut *mut BNWARPContainer,
+ count: usize,
+) {
+ let containers_ptr = std::ptr::slice_from_raw_parts_mut(containers, count);
+ let containers = unsafe { Box::from_raw(containers_ptr) };
+ for container in containers {
+ BNWARPFreeContainerReference(container);
+ }
+}
diff --git a/plugins/warp/src/plugin/ffi/function.rs b/plugins/warp/src/plugin/ffi/function.rs
new file mode 100644
index 00000000..3db8f307
--- /dev/null
+++ b/plugins/warp/src/plugin/ffi/function.rs
@@ -0,0 +1,228 @@
+use crate::build_function;
+use crate::cache::{insert_cached_function_match, try_cached_function_match};
+use crate::convert::{to_bn_symbol_at_address, to_bn_type};
+use crate::plugin::ffi::{BNWARPConstraint, BNWARPFunction, BNWARPFunctionGUID};
+use binaryninja::function::Function;
+use binaryninja::rc::Ref;
+use binaryninja::string::BnString;
+use binaryninjacore_sys::{BNFunction, BNSymbol, BNType};
+use std::ffi::c_char;
+use std::mem::ManuallyDrop;
+use std::sync::Arc;
+use warp::signature::comment::FunctionComment;
+
+#[repr(C)]
+pub struct BNWarpFunctionComment {
+ pub text: *mut c_char,
+ pub offset: i64,
+}
+
+impl BNWarpFunctionComment {
+ /// Leaks the text string to be freed with BNWARPFreeFunctionComment
+ pub fn from_owned(value: &FunctionComment) -> Self {
+ let text = BnString::into_raw(BnString::new(&value.text));
+ Self {
+ text,
+ offset: value.offset,
+ }
+ }
+
+ pub unsafe fn free_raw(value: &Self) {
+ unsafe { BnString::free_raw(value.text) }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPGetFunction(
+ analysis_function: *mut BNFunction,
+) -> *mut BNWARPFunction {
+ let function = Function::from_raw(analysis_function);
+ let Ok(lifted_il) = function.lifted_il() else {
+ return std::ptr::null_mut();
+ };
+ let function = build_function(&function, &lifted_il);
+ Arc::into_raw(Arc::new(function)) as *mut BNWARPFunction
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPGetMatchedFunction(
+ analysis_function: *mut BNFunction,
+) -> *mut BNWARPFunction {
+ let function = Function::from_raw(analysis_function);
+ match try_cached_function_match(&function) {
+ Some(matched_function) => {
+ let arc_matched_function = Arc::new(matched_function);
+ // NOTE: Freed by BNWARPFreeFunctionReference
+ Arc::into_raw(arc_matched_function) as *mut BNWARPFunction
+ }
+ None => std::ptr::null_mut(),
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFunctionApply(
+ function: *mut BNWARPFunction,
+ analysis_function: *mut BNFunction,
+) {
+ let analysis_function = Function::from_raw(analysis_function);
+ match function.is_null() {
+ false => {
+ // Set the matched function to `function` and return previous.
+ let matched_function = ManuallyDrop::new(Arc::from_raw(function));
+ insert_cached_function_match(
+ &analysis_function,
+ Some(matched_function.as_ref().clone()),
+ )
+ }
+ true => {
+ // We are removing the previous match and returning it.
+ insert_cached_function_match(&analysis_function, None)
+ }
+ };
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFunctionGetGUID(
+ function: *mut BNWARPFunction,
+) -> BNWARPFunctionGUID {
+ // We do not own function so we should not drop.
+ let function = ManuallyDrop::new(Arc::from_raw(function));
+ function.guid
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFunctionGetSymbol(
+ function: *mut BNWARPFunction,
+ analysis_function: *mut BNFunction,
+) -> *mut BNSymbol {
+ let analysis_function = Function::from_raw(analysis_function);
+ // We do not own function so we should not drop.
+ let function = ManuallyDrop::new(Arc::from_raw(function));
+ let view = analysis_function.view();
+ let address = analysis_function.symbol().address();
+ let function_symbol = to_bn_symbol_at_address(&view, &function.symbol, address);
+ // NOTE: The symbol ref has been pre-incremented for the caller.
+ Ref::into_raw(function_symbol).handle
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFunctionGetSymbolName(function: *mut BNWARPFunction) -> *mut c_char {
+ // We do not own function so we should not drop.
+ let function = ManuallyDrop::new(Arc::from_raw(function));
+ let bn_name = BnString::new(&function.symbol.name);
+ // NOTE: The symbol name string to be freed by BNFreeString
+ BnString::into_raw(bn_name)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFunctionGetType(
+ function: *mut BNWARPFunction,
+ analysis_function: *mut BNFunction,
+) -> *mut BNType {
+ let analysis_function = Function::from_raw(analysis_function);
+ // We do not own function so we should not drop.
+ let function = ManuallyDrop::new(Arc::from_raw(function));
+ match &function.ty {
+ Some(func_ty) => {
+ let arch = analysis_function.arch();
+ let function_type = to_bn_type(&arch, func_ty);
+ // NOTE: The type ref has been pre-incremented for the caller.
+ unsafe { Ref::into_raw(function_type) }.handle
+ }
+ None => std::ptr::null_mut(),
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFunctionGetConstraints(
+ function: *mut BNWARPFunction,
+ count: *mut usize,
+) -> *mut BNWARPConstraint {
+ // We do not own function so we should not drop.
+ let function = ManuallyDrop::new(Arc::from_raw(function));
+ let raw_constraints: Box<[BNWARPConstraint]> = function
+ .constraints
+ .clone()
+ .into_iter()
+ .map(Into::into)
+ .collect();
+ *count = raw_constraints.len();
+ let raw_constraints_ptr = Box::into_raw(raw_constraints);
+ raw_constraints_ptr as *mut BNWARPConstraint
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFunctionGetComments(
+ function: *mut BNWARPFunction,
+ count: *mut usize,
+) -> *mut BNWarpFunctionComment {
+ // We do not own function so we should not drop.
+ let function = ManuallyDrop::new(Arc::from_raw(function));
+ let raw_comments: Box<[_]> = function
+ .comments
+ .iter()
+ .map(BNWarpFunctionComment::from_owned)
+ .collect();
+ *count = raw_comments.len();
+ let raw_comments_ptr = Box::into_raw(raw_comments);
+ raw_comments_ptr as *mut BNWarpFunctionComment
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFunctionsEqual(
+ function_a: *mut BNWARPFunction,
+ function_b: *mut BNWARPFunction,
+) -> bool {
+ // We do not own function so we should not drop.
+ let function_a = ManuallyDrop::new(Arc::from_raw(function_a));
+ // We do not own function so we should not drop.
+ let function_b = ManuallyDrop::new(Arc::from_raw(function_b));
+ function_a.eq(&function_b)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPNewFunctionReference(
+ function: *mut BNWARPFunction,
+) -> *mut BNWARPFunction {
+ Arc::increment_strong_count(function);
+ function
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeFunctionReference(function: *mut BNWARPFunction) {
+ if function.is_null() {
+ return;
+ }
+ Arc::decrement_strong_count(function);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeFunctionList(functions: *mut *mut BNWARPFunction, count: usize) {
+ let functions_ptr = std::ptr::slice_from_raw_parts_mut(functions, count);
+ let functions = Box::from_raw(functions_ptr);
+ for function in functions {
+ // NOTE: The functions themselves should also be arc.
+ BNWARPFreeFunctionReference(function);
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeFunctionCommentList(
+ comments: *mut BNWarpFunctionComment,
+ count: usize,
+) {
+ let comments_ptr = std::ptr::slice_from_raw_parts_mut(comments, count);
+ let comments = Box::from_raw(comments_ptr);
+ for comment in &comments {
+ BNWarpFunctionComment::free_raw(comment)
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeConstraintList(
+ constraints: *mut BNWARPConstraint,
+ count: usize,
+) {
+ let constraints_ptr = std::ptr::slice_from_raw_parts_mut(constraints, count);
+ let _constraints = unsafe { Box::from_raw(constraints_ptr) };
+}
diff --git a/plugins/warp/src/plugin/file.rs b/plugins/warp/src/plugin/file.rs
new file mode 100644
index 00000000..ec61bd78
--- /dev/null
+++ b/plugins/warp/src/plugin/file.rs
@@ -0,0 +1,41 @@
+use crate::report::ReportGenerator;
+use binaryninja::binary_view::{BinaryView, BinaryViewExt};
+use binaryninja::command::Command;
+
+pub struct ShowFileReport;
+
+impl Command for ShowFileReport {
+ fn action(&self, view: &BinaryView) {
+ let view = view.to_owned();
+ std::thread::spawn(move || {
+ let Some(path) =
+ binaryninja::interaction::get_open_filename_input("Select file to show", "*.warp")
+ else {
+ return;
+ };
+
+ let Ok(bytes) = std::fs::read(&path) else {
+ log::error!("Failed to read file: {:?}", path);
+ return;
+ };
+
+ let Some(file) = warp::WarpFile::from_bytes(&bytes) else {
+ log::error!("Failed to parse file: {:?}", path);
+ return;
+ };
+
+ let report_generator = ReportGenerator::new();
+ if let Some(html_string) = report_generator.html_report(&file) {
+ view.show_html_report(
+ &format!("WARP File: {}", path.to_string_lossy()),
+ html_string.as_str(),
+ "",
+ );
+ }
+ });
+ }
+
+ fn valid(&self, _view: &BinaryView) -> bool {
+ true
+ }
+}
diff --git a/plugins/warp/src/plugin/find.rs b/plugins/warp/src/plugin/find.rs
deleted file mode 100644
index accc015d..00000000
--- a/plugins/warp/src/plugin/find.rs
+++ /dev/null
@@ -1,57 +0,0 @@
-use crate::cache::try_cached_function_guid;
-use binaryninja::binary_view::{BinaryView, BinaryViewExt};
-use binaryninja::command::Command;
-use binaryninja::function::Function as BNFunction;
-use binaryninja::rc::Guard as BNGuard;
-use rayon::prelude::*;
-use std::thread;
-use warp::signature::function::FunctionGUID;
-
-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::background_task::BackgroundTask::new(
- &format!("Searching functions for GUID... {}", searched_guid),
- false,
- );
-
- // Only run this for functions which have already generated a GUID.
- let matched = funcs
- .par_iter()
- .filter(|func| {
- try_cached_function_guid(func).is_some_and(|guid| guid == searched_guid)
- })
- .collect::<Vec<BNGuard<BNFunction>>>();
-
- if matched.is_empty() {
- log::info!("No matches found for GUID... {}", searched_guid);
- } else {
- 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/function.rs b/plugins/warp/src/plugin/function.rs
new file mode 100644
index 00000000..130e8582
--- /dev/null
+++ b/plugins/warp/src/plugin/function.rs
@@ -0,0 +1,133 @@
+use crate::cache::{cached_function_guid, try_cached_function_guid};
+use crate::{get_warp_include_tag_type, INCLUDE_TAG_NAME};
+use binaryninja::background_task::BackgroundTask;
+use binaryninja::binary_view::{BinaryView, BinaryViewExt};
+use binaryninja::command::{Command, FunctionCommand};
+use binaryninja::function::Function;
+use binaryninja::rc::Guard;
+use rayon::iter::ParallelIterator;
+use std::thread;
+use warp::signature::function::FunctionGUID;
+
+pub struct IncludeFunction;
+
+impl FunctionCommand for IncludeFunction {
+ fn action(&self, view: &BinaryView, func: &Function) {
+ let sym_name = func.symbol().short_name();
+ let sym_name_str = sym_name.to_string_lossy();
+ let should_add_tag = func.function_tags(None, Some(INCLUDE_TAG_NAME)).is_empty();
+ let insert_tag_type = get_warp_include_tag_type(view);
+ match should_add_tag {
+ true => {
+ log::info!(
+ "Including selected function '{}' at 0x{:x}",
+ sym_name_str,
+ func.start()
+ );
+ func.add_tag(&insert_tag_type, "", None, false, None);
+ }
+ false => {
+ log::info!(
+ "Removing included function '{}' at 0x{:x}",
+ sym_name_str,
+ func.start()
+ );
+ func.remove_tags_of_type(&insert_tag_type, None, false, None);
+ }
+ }
+ }
+
+ fn valid(&self, _view: &BinaryView, _func: &Function) -> bool {
+ // TODO: Only allow if the function is named?
+ true
+ }
+}
+
+pub struct CopyFunctionGUID;
+
+impl FunctionCommand for CopyFunctionGUID {
+ fn action(&self, _view: &BinaryView, func: &Function) {
+ let Ok(lifted_il) = func.lifted_il() else {
+ log::error!("Could not get lifted il for copied function");
+ return;
+ };
+ let guid = cached_function_guid(func, &lifted_il);
+ log::info!(
+ "Function GUID for {:?}... {}",
+ func.symbol().short_name(),
+ guid
+ );
+ if let Ok(mut clipboard) = arboard::Clipboard::new() {
+ let _ = clipboard.set_text(guid.to_string());
+ }
+ }
+
+ fn valid(&self, _view: &BinaryView, _func: &Function) -> bool {
+ true
+ }
+}
+
+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();
+ let view = view.to_owned();
+ thread::spawn(move || {
+ let background_task = BackgroundTask::new(
+ &format!("Searching functions for GUID... {}", searched_guid),
+ false,
+ );
+
+ // Only run this for functions which have already generated a GUID.
+ let matched: Vec<Guard<Function>> = funcs
+ .par_iter()
+ .filter(|func| {
+ try_cached_function_guid(func).is_some_and(|guid| guid == searched_guid)
+ })
+ .collect();
+
+ if matched.is_empty() {
+ log::info!("No matches found for GUID... {}", searched_guid);
+ } else {
+ for func in &matched {
+ // Also navigate the user, as that is probably what they want.
+ if matched.len() == 1 {
+ let current_view = view.file().current_view();
+ if view
+ .file()
+ .navigate_to(&current_view, func.start())
+ .is_err()
+ {
+ log::error!(
+ "Failed to navigate to found function 0x{:0x} in view {}",
+ func.start(),
+ current_view
+ );
+ }
+ }
+ 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/load.rs b/plugins/warp/src/plugin/load.rs
index c86a16cb..c0319ea5 100644
--- a/plugins/warp/src/plugin/load.rs
+++ b/plugins/warp/src/plugin/load.rs
@@ -1,54 +1,162 @@
-use crate::matcher::{Matcher, PlatformID, PLAT_MATCHER_CACHE};
+use crate::cache::container::add_cached_container;
+use crate::container::disk::{DiskContainer, DiskContainerSource};
+use crate::container::{ContainerError, SourcePath};
+use crate::convert::platform_to_target;
+use crate::plugin::workflow::run_matcher;
use binaryninja::binary_view::{BinaryView, BinaryViewExt};
use binaryninja::command::Command;
+use binaryninja::interaction::{
+ show_message_box, Form, FormInputField, MessageBoxButtonResult, MessageBoxButtonSet,
+ MessageBoxIcon,
+};
+use binaryninja::rc::Ref;
+use std::collections::HashMap;
+use std::path::PathBuf;
+use std::thread;
+use warp::WarpFile;
+
+pub struct LoadFileField;
+
+impl LoadFileField {
+ pub fn field() -> FormInputField {
+ FormInputField::OpenFileName {
+ prompt: "File Path".to_string(),
+ // TODO: This is called extension but is really a filter.
+ extension: Some("*.warp".to_string()),
+ default: None,
+ value: None,
+ }
+ }
+
+ pub fn from_form(form: &Form) -> Option<PathBuf> {
+ let field = form.get_field_with_name("File Path")?;
+ let field_value = field.try_value_string()?;
+ Some(PathBuf::from(field_value))
+ }
+}
+
+pub struct RunMatcherField;
+
+impl RunMatcherField {
+ pub fn field() -> FormInputField {
+ FormInputField::Choice {
+ prompt: "Rerun Initial Matcher".to_string(),
+ choices: vec!["No".to_string(), "Yes".to_string()],
+ default: Some(1),
+ value: 0,
+ }
+ }
+
+ pub fn from_form(form: &Form) -> Option<bool> {
+ let field = form.get_field_with_name("Rerun Initial Matcher")?;
+ let field_value = field.try_value_index()?;
+ match field_value {
+ 1 => Some(true),
+ _ => Some(false),
+ }
+ }
+}
+
pub struct LoadSignatureFile;
-impl Command for LoadSignatureFile {
- fn action(&self, view: &BinaryView) {
- let Some(platform) = view.default_platform() else {
- log::error!("Default platform must be set to load signature!");
- return;
- };
+impl LoadSignatureFile {
+ pub fn read_file(
+ view: &BinaryView,
+ path: SourcePath,
+ ) -> Result<WarpFile<'static>, ContainerError> {
+ let contents = std::fs::read(&path).map_err(|e| ContainerError::FailedIO(e.kind()))?;
+ let mut file = WarpFile::from_owned_bytes(contents).ok_or(
+ ContainerError::CorruptedData("file data failed to validate"),
+ )?;
- // NOTE: Because we only can consume signatures from a specific directory, we don't need to use the interaction API.
- // If we did need to load signature files from a project than this would need to change.
- let Some(file) = rfd::FileDialog::new()
- .add_filter("Signature Files", &["sbin"])
- .set_file_name(format!("{}.sbin", view.file().filename()))
- .pick_file()
- else {
- return;
- };
+ let view_target = view
+ .default_platform()
+ .map(|p| platform_to_target(&p))
+ .unwrap_or_default();
+ let file_has_target = file
+ .chunks
+ .iter()
+ .find(|c| c.header.target == view_target)
+ .is_some();
+
+ if !file_has_target {
+ // File does not contain a view target, alert user if they would like to override the file chunks to the view target.
+ let text = format!(
+ "Attempting to load WARP file with no target `{:?}`, continue loading anyways?",
+ &view_target
+ );
+ let res = show_message_box(
+ "Override file target?",
+ &text,
+ MessageBoxButtonSet::YesNoButtonSet,
+ MessageBoxIcon::WarningIcon,
+ );
+ if res != MessageBoxButtonResult::YesButton {
+ return Err(ContainerError::CorruptedData(
+ "User does not want to load file",
+ ));
+ }
+
+ // Take all the chunks and convert them to the target, so we load them.
+ // If we do not do this, the user will be surprised when they get no new matches.
+ for chunk in &mut file.chunks {
+ chunk.header.target = view_target.clone();
+ }
+ }
- let Ok(data) = std::fs::read(&file) else {
- log::error!("Could not read signature file: {:?}", file);
+ Ok(file)
+ }
+
+ pub fn execute(view: Ref<BinaryView>) {
+ let mut form = Form::new("Load Signature File");
+ form.add_field(LoadFileField::field());
+ // let fd_field = FileDataKindField::default();
+ // form.add_field(fd_field.to_field());
+ form.add_field(RunMatcherField::field());
+ if !form.prompt() {
return;
- };
+ }
- let Some(data) = warp::signature::Data::from_bytes(&data) else {
- log::error!("Could not get data from signature file: {:?}", file);
+ let Some(file_path) = LoadFileField::from_form(&form) else {
return;
};
+ // TODO: Decide what to pull using the file data kind.
+ // let _file_data_kind = FileDataKindField::from_form(&form).unwrap_or_default();
+ let rerun_matcher = RunMatcherField::from_form(&form).unwrap_or(false);
+
+ let source_file_path = SourcePath::new(file_path.clone());
- let new_matcher = Matcher::from_data(data);
- log::info!(
- "Loading signature file with {} functions and {} types...",
- new_matcher.functions.len(),
- new_matcher.types.len()
- );
- let platform_id = PlatformID::from(platform.as_ref());
- let matcher_cache = PLAT_MATCHER_CACHE.get_or_init(Default::default);
- match matcher_cache.get_mut(&platform_id) {
- Some(mut matcher) => matcher.extend_with_matcher(new_matcher),
- None => {
- // We still must uphold `from_platform` in case we are running this before the matcher workflow
- // is kicked off. Other-wise we only will have the `new_matcher` data.
- let mut matcher = Matcher::from_platform(platform);
- matcher.extend_with_matcher(new_matcher);
- matcher_cache.insert(platform_id, matcher);
+ let file = match LoadSignatureFile::read_file(&view, source_file_path.clone()) {
+ Ok(file) => file,
+ Err(e) => {
+ log::error!("Failed to read signature file: {}", e);
+ return;
}
+ };
+
+ let container_source = DiskContainerSource::new(source_file_path.clone(), file);
+ log::info!("Loading container source: '{}'", container_source.path);
+ let mut map = HashMap::new();
+ map.insert(source_file_path.to_source_id(), container_source);
+ let container = DiskContainer::new("Loaded signatures".to_string(), map);
+ // TODO: See notes in the matcher about doing this, we really need to load it into an existing container.
+ add_cached_container(container);
+
+ if rerun_matcher {
+ thread::spawn(move || {
+ run_matcher(&view);
+ });
}
}
+}
+
+impl Command for LoadSignatureFile {
+ fn action(&self, view: &BinaryView) {
+ let view = view.to_owned();
+ thread::spawn(move || {
+ LoadSignatureFile::execute(view);
+ });
+ }
fn valid(&self, _view: &BinaryView) -> bool {
true
diff --git a/plugins/warp/src/plugin/project.rs b/plugins/warp/src/plugin/project.rs
new file mode 100644
index 00000000..ea5a5600
--- /dev/null
+++ b/plugins/warp/src/plugin/project.rs
@@ -0,0 +1,132 @@
+use binaryninja::background_task::BackgroundTask;
+use binaryninja::command::ProjectCommand;
+use binaryninja::interaction::{Form, FormInputField};
+use binaryninja::project::Project;
+use binaryninja::rc::Ref;
+use regex::Regex;
+use std::thread;
+use std::time::Instant;
+
+use crate::processor::{
+ new_processing_state_background_thread, FileDataKindField, FileFilterField, WarpFileProcessor,
+};
+use crate::report::{ReportGenerator, ReportKindField};
+
+pub struct CreateSignaturesForm {
+ form: Form,
+}
+
+impl CreateSignaturesForm {
+ pub fn new(_project: &Project) -> CreateSignaturesForm {
+ let mut form = Form::new("Create Signature File");
+ form.add_field(Self::file_data_field());
+ form.add_field(Self::file_filter_field());
+ form.add_field(Self::generated_report_field());
+ // TODO: Threads (we run the analysis in the background)
+ Self { form }
+ }
+
+ pub fn file_data_field() -> FormInputField {
+ FileDataKindField::default().to_field()
+ }
+
+ pub fn file_data_kind(&self) -> FileDataKindField {
+ FileDataKindField::from_form(&self.form).unwrap_or_default()
+ }
+
+ pub fn file_filter_field() -> FormInputField {
+ FileFilterField::to_field()
+ }
+
+ pub fn file_filter(&self) -> Option<Regex> {
+ FileFilterField::from_form(&self.form)
+ }
+
+ pub fn generated_report_field() -> FormInputField {
+ ReportKindField::default().to_field()
+ }
+
+ pub fn generated_report_kind(&self) -> ReportKindField {
+ ReportKindField::from_form(&self.form).unwrap_or_default()
+ }
+
+ pub fn prompt(&mut self) -> bool {
+ self.form.prompt()
+ }
+}
+
+pub struct CreateSignatures;
+
+impl CreateSignatures {
+ pub fn execute(project: Ref<Project>) {
+ let mut form = CreateSignaturesForm::new(&project);
+ if !form.prompt() {
+ return;
+ }
+ let file_data_kind = form.file_data_kind();
+ let report_kind = form.generated_report_kind();
+
+ let mut processor = WarpFileProcessor::new().with_file_data(file_data_kind);
+
+ // This thread will show the state in a background task.
+ let background_task = BackgroundTask::new("Processing started...", true);
+ new_processing_state_background_thread(background_task.clone(), processor.state());
+
+ if let Some(filter) = form.file_filter() {
+ processor = processor.with_file_filter(filter);
+ }
+
+ let start = Instant::now();
+ match processor.process_project(&project) {
+ Ok(warp_file) => {
+ // Print the processor string into the description of the file, so we know how it was generated.
+ let processor_str = format!("{:#?}", &processor);
+
+ // TODO: File name needs to be configurable.
+ if project
+ .create_file(
+ &warp_file.to_bytes(),
+ None,
+ "generated.warp",
+ &processor_str,
+ )
+ .is_err()
+ {
+ log::error!("Failed to create project file!");
+ }
+
+ let report = ReportGenerator::new();
+ if let Some(generated) = report.report(&report_kind, &warp_file) {
+ let ext = report.report_extension(&report_kind).unwrap_or_default();
+ let file_name = format!("report.{}", ext);
+ if project
+ .create_file(&generated.into_bytes(), None, &file_name, "Warp file")
+ .is_err()
+ {
+ log::error!("Failed to create project file!");
+ }
+ }
+ }
+ Err(e) => {
+ log::error!("Failed to process project: {}", e);
+ }
+ }
+ log::info!("Processing project files took: {:?}", start.elapsed());
+
+ // Tells the processing state thread to finish.
+ background_task.finish();
+ }
+}
+
+impl ProjectCommand for CreateSignatures {
+ fn action(&self, project: &Project) {
+ let project = project.to_owned();
+ thread::spawn(move || {
+ CreateSignatures::execute(project);
+ });
+ }
+
+ fn valid(&self, _view: &Project) -> bool {
+ true
+ }
+}
diff --git a/plugins/warp/src/plugin/render_layer.rs b/plugins/warp/src/plugin/render_layer.rs
index cdac71f2..d7984303 100644
--- a/plugins/warp/src/plugin/render_layer.rs
+++ b/plugins/warp/src/plugin/render_layer.rs
@@ -1,56 +1,94 @@
-use crate::{is_blacklisted_instruction, is_variant_instruction, relocatable_regions};
+use crate::{
+ is_blacklisted_instruction, is_computed_variant_instruction, is_variant_instruction,
+ relocatable_regions,
+};
use binaryninja::basic_block::BasicBlock;
use binaryninja::disassembly::DisassemblyTextLine;
+use binaryninja::flowgraph::FlowGraph;
use binaryninja::function::{HighlightColor, HighlightStandardColor, NativeBlock};
-use binaryninja::low_level_il::instruction::LowLevelInstructionIndex;
+use binaryninja::low_level_il::LowLevelILRegularFunction;
use binaryninja::render_layer::{register_render_layer, RenderLayer};
-pub struct HighlightRenderLayer {}
+// TODO: Add a render layer to show basic block GUID's?
+// TODO: Add a render layer to show constraints for current function?
+
+pub struct HighlightRenderLayer {
+ blacklist: HighlightColor,
+ variant: HighlightColor,
+ computed_variant: HighlightColor,
+}
impl HighlightRenderLayer {
pub fn register() {
register_render_layer(
"WARP Highlight Layer",
- HighlightRenderLayer {},
+ // TODO: Make the highlight colors configurable.
+ HighlightRenderLayer {
+ blacklist: HighlightColor::StandardHighlightColor {
+ color: HighlightStandardColor::BlackHighlightColor,
+ alpha: 155,
+ },
+ variant: HighlightColor::StandardHighlightColor {
+ color: HighlightStandardColor::RedHighlightColor,
+ alpha: 155,
+ },
+ computed_variant: HighlightColor::StandardHighlightColor {
+ color: HighlightStandardColor::OrangeHighlightColor,
+ alpha: 155,
+ },
+ },
Default::default(),
);
}
+
+ /// Highlights the lines that are variant or blacklisted.
+ pub fn highlight_lines(
+ &self,
+ lifted_il: &LowLevelILRegularFunction,
+ llil: &LowLevelILRegularFunction,
+ lines: &mut [DisassemblyTextLine],
+ ) {
+ let relocatable_regions = relocatable_regions(&lifted_il.function().view());
+ for line in lines {
+ // We use address here instead of index since it's more reliable for other IL's.
+ if let Some(lifted_il_instr) = lifted_il.instruction_at(line.address) {
+ if is_blacklisted_instruction(&lifted_il_instr) {
+ line.highlight = self.blacklist;
+ } else if is_variant_instruction(&relocatable_regions, &lifted_il_instr) {
+ line.highlight = self.variant;
+ }
+ }
+
+ if let Some(llil_instr) = llil.instruction_at(line.address) {
+ if is_computed_variant_instruction(&relocatable_regions, &llil_instr) {
+ line.highlight = self.computed_variant;
+ }
+ }
+ }
+ }
}
impl RenderLayer for HighlightRenderLayer {
- fn apply_to_llil_block(
+ fn apply_to_flow_graph(&self, graph: &mut FlowGraph) {
+ if let (Some(lifted_il), Some(llil)) = (graph.lifted_il(), graph.low_level_il()) {
+ for node in &graph.nodes() {
+ let mut new_lines = node.lines().to_vec();
+ self.highlight_lines(&lifted_il, &llil, &mut new_lines);
+ node.set_lines(new_lines);
+ }
+ }
+ }
+
+ fn apply_to_block(
&self,
block: &BasicBlock<NativeBlock>,
mut lines: Vec<DisassemblyTextLine>,
) -> Vec<DisassemblyTextLine> {
- // Highlight any LLIL instruction that will be masked by WARP.
+ // Highlight any instruction that WARP will mask.
let function = block.function();
- // TODO: We might need to make relocatable regions configurable.
- let relocatable_regions = relocatable_regions(&function.view());
- let Ok(llil) = function.low_level_il() else {
- // Don't even think this is possible but _shrug_.
- return lines;
- };
-
- for line in &mut lines {
- let llil_instr_idx = LowLevelInstructionIndex(line.instruction_index);
- if let Some(llil_instr) = llil.instruction_from_index(llil_instr_idx) {
- if is_blacklisted_instruction(&llil_instr) {
- // We have a blacklisted instruction, highlight it as orange!
- line.highlight = HighlightColor::StandardHighlightColor {
- color: HighlightStandardColor::OrangeHighlightColor,
- alpha: 155,
- };
- } else if is_variant_instruction(&relocatable_regions, &llil_instr) {
- // We have a variant instruction, highlight it as red!
- line.highlight = HighlightColor::StandardHighlightColor {
- color: HighlightStandardColor::RedHighlightColor,
- alpha: 155,
- };
- }
- }
+ if let (Ok(lifted_il), Ok(llil)) = (function.lifted_il(), function.low_level_il()) {
+ self.highlight_lines(&lifted_il, &llil, &mut lines);
}
-
lines
}
}
diff --git a/plugins/warp/src/plugin/settings.rs b/plugins/warp/src/plugin/settings.rs
new file mode 100644
index 00000000..6cd2e0cc
--- /dev/null
+++ b/plugins/warp/src/plugin/settings.rs
@@ -0,0 +1,129 @@
+use binaryninja::settings::Settings as BNSettings;
+use serde_json::json;
+use std::string::ToString;
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct PluginSettings {
+ /// Whether to load bundled WARP files on startup. Turn this off if you want to manually load them.
+ ///
+ /// This is set to [PluginSettings::LOAD_BUNDLED_FILES_DEFAULT] by default.
+ pub load_bundled_files: bool,
+ /// Whether to load user WARP files on startup. Turn this off if you want to manually load them.
+ ///
+ /// This is set to [PluginSettings::LOAD_USER_FILES_DEFAULT] by default.
+ pub load_user_files: bool,
+ /// The WARP server to use.
+ ///
+ /// This is set to [PluginSettings::SERVER_URL_DEFAULT] by default.
+ pub server_url: String,
+ /// The API key to use for the selected WARP server, if not specified, you will be unable to push data and may be rate-limited.
+ ///
+ /// This is set to [PluginSettings::SERVER_API_KEY_DEFAULT] by default.
+ pub server_api_key: String,
+ /// Whether to allow networked WARP requests. Turning this off will not disable local WARP functionality.
+ ///
+ /// This is set to [PluginSettings::ENABLE_SERVER_DEFAULT] by default.
+ pub enable_server: bool,
+}
+
+impl PluginSettings {
+ pub const LOAD_BUNDLED_FILES_DEFAULT: bool = true;
+ pub const LOAD_BUNDLED_FILES_SETTING: &'static str = "analysis.warp.loadBundledFiles";
+ pub const LOAD_USER_FILES_DEFAULT: bool = true;
+ pub const LOAD_USER_FILES_SETTING: &'static str = "analysis.warp.loadUserFiles";
+ pub const SERVER_URL_DEFAULT: &'static str = "https://warp.binary.ninja";
+ pub const SERVER_URL_SETTING: &'static str = "analysis.warp.serverUrl";
+ pub const SERVER_API_KEY_DEFAULT: &'static str = "";
+ pub const SERVER_API_KEY_SETTING: &'static str = "analysis.warp.serverApiKey";
+ pub const ENABLE_SERVER_DEFAULT: bool = true;
+ pub const ENABLE_SERVER_SETTING: &'static str = "network.enableWARP";
+
+ pub fn register(bn_settings: &mut BNSettings) {
+ let load_bundled_files_prop = json!({
+ "title" : "Load Bundled Files",
+ "type" : "boolean",
+ "default" : Self::LOAD_BUNDLED_FILES_DEFAULT,
+ "description" : "Whether to load bundled WARP files on startup. Turn this off if you want to manually load them.",
+ "ignore" : ["SettingsProjectScope", "SettingsResourceScope"]
+ });
+ bn_settings.register_setting_json(
+ Self::LOAD_BUNDLED_FILES_SETTING,
+ &load_bundled_files_prop.to_string(),
+ );
+ let load_user_files_prop = json!({
+ "title" : "Load User Files",
+ "type" : "boolean",
+ "default" : Self::LOAD_USER_FILES_DEFAULT,
+ "description" : "Whether to load user WARP files on startup. Turn this off if you want to manually load them.",
+ "ignore" : ["SettingsProjectScope", "SettingsResourceScope"]
+ });
+ bn_settings.register_setting_json(
+ Self::LOAD_USER_FILES_SETTING,
+ &load_user_files_prop.to_string(),
+ );
+ let server_url_prop = json!({
+ "title" : "Server URL",
+ "type" : "string",
+ "default" : Self::SERVER_URL_DEFAULT,
+ "description" : "The WARP server to use.",
+ "ignore" : ["SettingsProjectScope", "SettingsResourceScope"]
+ });
+ bn_settings.register_setting_json(Self::SERVER_URL_SETTING, &server_url_prop.to_string());
+ let server_api_key_prop = json!({
+ "title" : "Server API Key",
+ "type" : "string",
+ "default" : Self::SERVER_API_KEY_DEFAULT,
+ "description" : "The API key to use for the selected WARP server, if not specified you will be unable to push data, and may be rate limited.",
+ "ignore" : ["SettingsProjectScope", "SettingsResourceScope"],
+ "hidden": true
+ });
+ bn_settings.register_setting_json(
+ Self::SERVER_API_KEY_SETTING,
+ &server_api_key_prop.to_string(),
+ );
+ let server_enabled_prop = json!({
+ "title" : "Enable WARP",
+ "type" : "boolean",
+ "default" : Self::ENABLE_SERVER_DEFAULT,
+ "description" : "Whether or not to allow networked WARP requests. Turning this off will not disable local WARP functionality.",
+ "ignore" : ["SettingsProjectScope", "SettingsResourceScope"]
+ });
+ bn_settings.register_setting_json(
+ Self::ENABLE_SERVER_SETTING,
+ &server_enabled_prop.to_string(),
+ );
+ }
+
+ /// Retrieve plugin settings from [`BNSettings`].
+ pub fn from_settings(bn_settings: &BNSettings) -> Self {
+ let mut settings = PluginSettings::default();
+ if bn_settings.contains(Self::LOAD_BUNDLED_FILES_SETTING) {
+ settings.load_bundled_files = bn_settings.get_bool(Self::LOAD_BUNDLED_FILES_SETTING);
+ }
+ if bn_settings.contains(Self::LOAD_USER_FILES_SETTING) {
+ settings.load_user_files = bn_settings.get_bool(Self::LOAD_USER_FILES_SETTING);
+ }
+ if bn_settings.contains(Self::SERVER_URL_SETTING) {
+ settings.server_url = bn_settings.get_string(Self::SERVER_URL_SETTING);
+ }
+ if bn_settings.contains(Self::SERVER_API_KEY_SETTING) {
+ settings.server_url = bn_settings.get_string(Self::SERVER_API_KEY_SETTING);
+ }
+ if bn_settings.contains(Self::ENABLE_SERVER_SETTING) {
+ settings.enable_server = bn_settings.get_bool(Self::ENABLE_SERVER_SETTING);
+ }
+ settings
+ }
+}
+
+impl Default for PluginSettings {
+ fn default() -> Self {
+ Self {
+ load_bundled_files: PluginSettings::LOAD_BUNDLED_FILES_DEFAULT,
+ load_user_files: PluginSettings::LOAD_USER_FILES_DEFAULT,
+ server_url: PluginSettings::SERVER_URL_DEFAULT.to_string(),
+ server_api_key: PluginSettings::SERVER_API_KEY_DEFAULT.to_string(),
+ enable_server: PluginSettings::ENABLE_SERVER_DEFAULT,
+ }
+ }
+}
diff --git a/plugins/warp/src/plugin/types.rs b/plugins/warp/src/plugin/types.rs
deleted file mode 100644
index 41e03cd3..00000000
--- a/plugins/warp/src/plugin/types.rs
+++ /dev/null
@@ -1,57 +0,0 @@
-use crate::convert::to_bn_type;
-use binaryninja::binary_view::{BinaryView, BinaryViewExt};
-use binaryninja::command::Command;
-use std::time::Instant;
-
-pub struct LoadTypes;
-
-impl Command for LoadTypes {
- fn action(&self, view: &BinaryView) {
- // NOTE: Because we only can consume signatures from a specific directory, we don't need to use the interaction API.
- // If we did need to load signature files from a project than this would need to change.
- let Some(file) = rfd::FileDialog::new()
- .add_filter("Signature Files", &["sbin"])
- .set_file_name(format!("{}.sbin", view.file().filename()))
- .pick_file()
- 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 Some(arch) = view.default_arch() else {
- log::error!("Could not get default architecture");
- return;
- };
-
- let view = view.to_owned();
- std::thread::spawn(move || {
- let background_task = binaryninja::background_task::BackgroundTask::new(
- &format!("Applying {} types...", data.types.len()),
- true,
- );
-
- 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());
- 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
index f5763116..487fccfe 100644
--- a/plugins/warp/src/plugin/workflow.rs
+++ b/plugins/warp/src/plugin/workflow.rs
@@ -1,16 +1,40 @@
-use crate::cache::cached_function_guid;
-use crate::matcher::cached_function_matcher;
+use crate::cache::container::for_cached_containers;
+use crate::cache::{
+ cached_function_guid, insert_cached_function_match, try_cached_function_guid,
+ try_cached_function_match,
+};
+use crate::convert::{
+ comment_to_bn_comment, platform_to_target, to_bn_symbol_at_address, to_bn_type,
+};
+use crate::matcher::{Matcher, MatcherSettings};
+use crate::{get_warp_tag_type, relocatable_regions};
use binaryninja::background_task::BackgroundTask;
use binaryninja::binary_view::{BinaryView, BinaryViewExt};
use binaryninja::command::Command;
+use binaryninja::settings::{QueryOptions, Settings};
use binaryninja::workflow::{Activity, AnalysisContext, Workflow};
+use itertools::Itertools;
+use std::collections::HashMap;
use std::time::Instant;
+use warp::signature::function::{Function, FunctionGUID};
+use warp::target::Target;
+
+pub const APPLY_ACTIVITY_NAME: &str = "analysis.warp.apply";
+const APPLY_ACTIVITY_CONFIG: &str = r#"{
+ "name": "analysis.warp.apply",
+ "title" : "WARP Apply Matched",
+ "description": "This analysis step applies WARP info to matched functions...",
+ "eligibility": {
+ "auto": {},
+ "runOnce": false
+ }
+}"#;
pub const MATCHER_ACTIVITY_NAME: &str = "analysis.warp.matcher";
const MATCHER_ACTIVITY_CONFIG: &str = r#"{
"name": "analysis.warp.matcher",
"title" : "WARP Matcher",
- "description": "This analysis step applies WARP info to matched functions...",
+ "description": "This analysis step attempts to find matching WARP functions after the initial analysis is complete...",
"eligibility": {
"auto": {},
"runOnce": true
@@ -24,7 +48,7 @@ const GUID_ACTIVITY_CONFIG: &str = r#"{
"description": "This analysis step generates the GUID for all analyzed functions...",
"eligibility": {
"auto": {},
- "runOnce": true
+ "runOnce": false
}
}"#;
@@ -33,19 +57,8 @@ pub struct RunMatcher;
impl Command for RunMatcher {
fn action(&self, view: &BinaryView) {
let view = view.to_owned();
- // TODO: Check to see if the GUID cache is empty and ask the user if they want to regenerate the guids.
std::thread::spawn(move || {
- let undo_id = view.file().begin_undo_actions(true);
- let background_task = BackgroundTask::new("Matching on functions...", false);
- let start = Instant::now();
- view.functions()
- .iter()
- .for_each(|function| cached_function_matcher(&function));
- log::info!("Function matching took {:?}", start.elapsed());
- background_task.finish();
- view.file().commit_undo_actions(&undo_id);
- // Now we want to trigger re-analysis.
- view.update_analysis();
+ run_matcher(&view);
});
}
@@ -54,44 +67,157 @@ impl Command for RunMatcher {
}
}
+pub fn run_matcher(view: &BinaryView) {
+ // Alert the user if we have no actual regions (one comes from the synthetic section).
+ let regions = relocatable_regions(view);
+ if regions.len() <= 1 {
+ log::warn!(
+ "No relocatable regions found, for best results please define sections for the binary!"
+ );
+ }
+
+ // Then we want to actually find matching functions.
+ let background_task = BackgroundTask::new("Matching on WARP functions...", true);
+ let start = Instant::now();
+
+ // Build matcher
+ let view_settings = Settings::new();
+ let mut query_opts = QueryOptions::new_with_view(view);
+ let matcher_settings = MatcherSettings::from_settings(&view_settings, &mut query_opts);
+ let matcher = Matcher::new(matcher_settings);
+
+ // TODO: Par iter this? Using dashmap
+ let functions_by_target_and_guid: HashMap<(FunctionGUID, Target), Vec<_>> = view
+ .functions()
+ .iter()
+ .filter_map(|f| {
+ let guid = try_cached_function_guid(&f)?;
+ let target = platform_to_target(&f.platform());
+ Some(((guid, target), f.to_owned()))
+ })
+ .into_group_map();
+
+ // TODO: Par iter this? Using dashmap
+ let guids_by_target: HashMap<Target, Vec<FunctionGUID>> = functions_by_target_and_guid
+ .keys()
+ .map(|(guid, target)| (target.clone(), *guid))
+ .into_group_map();
+
+ // TODO: Target gets cloned a lot.
+ // TODO: Containers might both match on the same function. What should we do?
+ for_cached_containers(|container| {
+ if background_task.is_cancelled() {
+ return;
+ }
+
+ for (target, guids) in &guids_by_target {
+ let function_guid_with_sources = container
+ .sources_with_function_guids(target, guids)
+ .unwrap_or_default();
+
+ for (guid, sources) in &function_guid_with_sources {
+ let matched_functions: Vec<Function> = sources
+ .iter()
+ .flat_map(|source| {
+ container
+ .functions_with_guid(target, source, guid)
+ .unwrap_or_default()
+ })
+ .collect();
+
+ let functions = functions_by_target_and_guid
+ .get(&(*guid, target.clone()))
+ .expect("Function guid not found");
+
+ for function in functions {
+ // Match on all the possible functions
+ if let Some(matched_function) =
+ matcher.match_function_from_constraints(function, &matched_functions)
+ {
+ // We were able to find a match, add it to the match cache and then mark the function
+ // as requiring updates; this is so that we know about it in the applier activity.
+ insert_cached_function_match(function, Some(matched_function.clone()));
+ }
+ }
+ }
+ }
+ });
+
+ if background_task.is_cancelled() {
+ log::info!("Matcher was cancelled by user, you may run it again by running the 'Run Matcher' command.");
+ }
+
+ log::info!("Function matching took {:?}", start.elapsed());
+ background_task.finish();
+
+ // Now we want to trigger re-analysis.
+ view.update_analysis();
+}
+
pub fn insert_workflow() {
+ // "Hey look, it's a plier" ~ Josh 2025
+ let apply_activity = |ctx: &AnalysisContext| {
+ let view = ctx.view();
+ let function = ctx.function();
+ if let Some(matched_function) = try_cached_function_match(&function) {
+ view.define_auto_symbol(&to_bn_symbol_at_address(
+ &view,
+ &matched_function.symbol,
+ function.symbol().address(),
+ ));
+ if let Some(func_ty) = &matched_function.ty {
+ function.set_auto_type(&to_bn_type(&function.arch(), func_ty));
+ }
+ // TODO: How to clear the comments? They are just persisted.
+ // TODO: Also they generate an undo action, i hate implicit undo actions so much.
+ for comment in matched_function.comments {
+ let bn_comment = comment_to_bn_comment(&function, comment);
+ function.set_comment_at(bn_comment.addr, &bn_comment.comment);
+ }
+ function.add_tag(
+ &get_warp_tag_type(&view),
+ &matched_function.guid.to_string(),
+ None,
+ false,
+ None,
+ );
+ }
+ };
+
let matcher_activity = |ctx: &AnalysisContext| {
let view = ctx.view();
- let undo_id = view.file().begin_undo_actions(true);
- let background_task = BackgroundTask::new("Matching on functions...", false);
- let start = Instant::now();
- view.functions()
- .iter()
- .for_each(|function| cached_function_matcher(&function));
- log::info!("Function matching took {:?}", start.elapsed());
- background_task.finish();
- view.file().commit_undo_actions(&undo_id);
- // Now we want to trigger re-analysis.
- view.update_analysis();
+ run_matcher(&view);
};
let guid_activity = |ctx: &AnalysisContext| {
let function = ctx.function();
- // TODO: Returning RegularNonSSA means we cant modify the il (the lifting code was written just for lifted il, that needs to be fixed)
- if let Some(llil) = unsafe { ctx.llil_function() } {
- cached_function_guid(&function, &llil);
+ if let Some(lifted_il) = unsafe { ctx.lifted_il_function() } {
+ cached_function_guid(&function, &lifted_il);
}
};
let old_function_meta_workflow = Workflow::instance("core.function.metaAnalysis");
let function_meta_workflow = old_function_meta_workflow.clone_to("core.function.metaAnalysis");
let guid_activity = Activity::new_with_action(GUID_ACTIVITY_CONFIG, guid_activity);
+ let apply_activity = Activity::new_with_action(APPLY_ACTIVITY_CONFIG, apply_activity);
function_meta_workflow
.register_activity(&guid_activity)
.unwrap();
+ // Because we are going to impact analysis with application we must make sure the function update is triggered to continue to update analysis.
+ // TODO: need to ask why i cant do core.function.update like in the rtti plugin.
+ function_meta_workflow
+ .register_activity_with_subactivities::<Vec<String>>(&apply_activity, vec![])
+ .unwrap();
function_meta_workflow.insert("core.function.runFunctionRecognizers", [GUID_ACTIVITY_NAME]);
+ function_meta_workflow.insert("core.function.generateMediumLevelIL", [APPLY_ACTIVITY_NAME]);
function_meta_workflow.register().unwrap();
let old_module_meta_workflow = Workflow::instance("core.module.metaAnalysis");
let module_meta_workflow = old_module_meta_workflow.clone_to("core.module.metaAnalysis");
let matcher_activity = Activity::new_with_action(MATCHER_ACTIVITY_CONFIG, matcher_activity);
+ // Matcher activity must have core.module.update as subactivity otherwise analysis will sometimes never retrigger.
module_meta_workflow
- .register_activity(&matcher_activity)
+ .register_activity_with_subactivities(&matcher_activity, vec!["core.module.update"])
.unwrap();
module_meta_workflow.insert(
"core.module.deleteUnusedAutoFunctions",