diff options
Diffstat (limited to 'plugins/warp/src/plugin')
| -rw-r--r-- | plugins/warp/src/plugin/add.rs | 72 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/copy.rs | 29 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/create.rs | 284 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/debug.rs | 48 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/ffi.rs | 205 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/ffi/container.rs | 412 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/ffi/function.rs | 228 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/file.rs | 41 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/find.rs | 57 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/function.rs | 133 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/load.rs | 182 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/project.rs | 132 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/render_layer.rs | 100 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/settings.rs | 129 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/types.rs | 57 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/workflow.rs | 188 |
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(¤t_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", |
