diff options
| author | Mason Reed <mason@vector35.com> | 2024-10-23 20:47:31 -0400 |
|---|---|---|
| committer | Mason Reed <mason@vector35.com> | 2024-10-24 17:03:11 -0400 |
| commit | e151425087b6c0ee8c4d099bfc2d6d1268201db0 (patch) | |
| tree | 25160cf81aa6d650cf1497a078acc50f0062757c /plugins/warp/src/plugin | |
| parent | 0f53c21d6a7abbfd48fd59ec7c15586f02777b7e (diff) | |
Add WARP integration
https://github.com/Vector35/warp/
Diffstat (limited to 'plugins/warp/src/plugin')
| -rw-r--r-- | plugins/warp/src/plugin/apply.rs | 82 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/copy.rs | 35 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/create.rs | 71 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/find.rs | 57 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/types.rs | 52 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/workflow.rs | 38 |
6 files changed, 335 insertions, 0 deletions
diff --git a/plugins/warp/src/plugin/apply.rs b/plugins/warp/src/plugin/apply.rs new file mode 100644 index 00000000..56c09d77 --- /dev/null +++ b/plugins/warp/src/plugin/apply.rs @@ -0,0 +1,82 @@ +use std::collections::HashMap; +use std::time::Instant; + +use crate::cache::cached_function_guid; +use crate::plugin::on_matched_function; +use binaryninja::binaryview::{BinaryView, BinaryViewExt}; +use binaryninja::command::Command; +use rayon::prelude::*; +use warp::signature::function::{Function, FunctionGUID}; + +pub struct ApplySignatureFile; + +// TODO: All this should do is insert data into the Matcher. this is leftover code. +impl Command for ApplySignatureFile { + fn action(&self, view: &BinaryView) { + // TODO: Start bulk modification + // TODO: view.begin_bulk_modify_symbols(); + let Some(file) = + binaryninja::interaction::get_open_filename_input("Apply Signature File", "*.sbin") + else { + return; + }; + + // TODO: signature files also need to store type information. + + let Ok(data) = std::fs::read(&file) else { + log::error!("Could not read signature file: {:?}", file); + return; + }; + + let Some(data) = warp::signature::Data::from_bytes(&data) else { + log::error!("Could not get data from signature file: {:?}", file); + return; + }; + + // TODO: Turn Vec<Function> to HashSet so that functions with the same symbol and type get eliminated. + let data_functions: HashMap<FunctionGUID, Vec<Function>> = + data.functions + .into_iter() + .fold(HashMap::new(), |mut acc, func| { + #[allow(clippy::unwrap_or_default)] + acc.entry(func.guid).or_insert_with(Vec::new).push(func); + acc + }); + + let background_task = binaryninja::backgroundtask::BackgroundTask::new( + format!("Applying signatures from {:?}", file), + true, + ) + .unwrap(); + + let funcs = view.functions(); + let start = Instant::now(); + + background_task + .set_progress_text(format!("Building {} patterns to lookup...", funcs.len())); + + // TODO: Redo this. + let single_matched = funcs + .par_iter() + .filter_map(|func| { + let llil = func.low_level_il_if_available()?; + let pattern = cached_function_guid(&func, &llil)?; + Some((func, data_functions.get(&pattern)?)) + }) + .filter(|(_, sig)| sig.len() == 1) + .collect::<Vec<_>>(); + + background_task.set_progress_text(format!("Applying {} matches...", single_matched.len())); + for (func, matched) in single_matched { + on_matched_function(&func, &matched[0]); + } + + log::info!("Signature application took {:?}", start.elapsed()); + + background_task.finish(); + } + + fn valid(&self, _view: &BinaryView) -> bool { + true + } +} diff --git a/plugins/warp/src/plugin/copy.rs b/plugins/warp/src/plugin/copy.rs new file mode 100644 index 00000000..ee91fb4a --- /dev/null +++ b/plugins/warp/src/plugin/copy.rs @@ -0,0 +1,35 @@ +use binaryninja::binaryview::BinaryView; +use binaryninja::command::FunctionCommand; +use binaryninja::function::Function; + +use crate::cache::cached_function_guid; + +pub struct CopyFunctionGUID; + +impl FunctionCommand for CopyFunctionGUID { + fn action(&self, _view: &BinaryView, func: &Function) { + let Ok(llil) = func.low_level_il() else { + log::error!("Could not get low level il for copied function"); + return; + }; + if let Some(guid) = cached_function_guid(func, &llil) { + log::info!( + "Function GUID for {}... {}", + func.symbol().short_name().to_string(), + guid + ); + if let Ok(mut clipboard) = arboard::Clipboard::new() { + let _ = clipboard.set_text(guid.to_string()); + } + } else { + log::error!( + "Failed to create GUID for function... 0x{:0x}", + func.start() + ); + } + } + + fn valid(&self, _view: &BinaryView, _func: &Function) -> bool { + true + } +} diff --git a/plugins/warp/src/plugin/create.rs b/plugins/warp/src/plugin/create.rs new file mode 100644 index 00000000..2b1ad60e --- /dev/null +++ b/plugins/warp/src/plugin/create.rs @@ -0,0 +1,71 @@ +use crate::cache::cached_function; +use crate::convert::from_bn_type; +use binaryninja::binaryview::{BinaryView, BinaryViewExt}; +use binaryninja::command::Command; +use rayon::prelude::*; +use std::io::Write; +use std::thread; +use std::time::Instant; +use warp::r#type::ComputedType; + +pub struct CreateSignatureFile; + +// TODO: Prompt the user to add the newly created signature file to the signature blacklist (so that it doesn't keep getting applied) + +impl Command for CreateSignatureFile { + fn action(&self, view: &BinaryView) { + let mut signature_dir = binaryninja::user_directory().unwrap().join("signatures/"); + // TODO: This needs to split out each platform into its own bucket... + if let Some(default_plat) = view.default_platform() { + // If there is a default platform, put the signature in there. + signature_dir.push(default_plat.name().to_string()); + } + let view = view.to_owned(); + thread::spawn(move || { + let background_task = binaryninja::backgroundtask::BackgroundTask::new( + format!("Generating {} signatures... ", view.functions().len()), + true, + ) + .unwrap(); + + let start = Instant::now(); + + let mut data = warp::signature::Data::default(); + data.functions.par_extend( + view.functions() + .par_iter() + .filter_map(|func| cached_function(&func, func.low_level_il().ok()?.as_ref())), + ); + data.types.extend(view.types().iter().map(|ty| { + let ref_ty = ty.type_object().to_owned(); + ComputedType::new(from_bn_type(&view, ref_ty, u8::MAX)) + })); + + // And type generation :3 + log::info!("Signature generation took {:?}", start.elapsed()); + + if let Some(sig_file_name) = binaryninja::interaction::get_text_line_input( + "Signature File", + "Create Signature File", + ) { + let save_file = signature_dir.join(sig_file_name + ".sbin"); + log::info!("Saving to signatures to {:?}...", &save_file); + // TODO: Should we overwrite? Prompt user. + if let Ok(mut file) = std::fs::File::create(&save_file) { + match file.write_all(&data.to_bytes()) { + Ok(_) => log::info!("Signature file saved successfully."), + Err(e) => log::error!("Failed to write data to signature file: {:?}", e), + } + } else { + log::error!("Could not create signature file: {:?}", save_file); + } + } + + background_task.finish(); + }); + } + + fn valid(&self, _view: &BinaryView) -> bool { + true + } +} diff --git a/plugins/warp/src/plugin/find.rs b/plugins/warp/src/plugin/find.rs new file mode 100644 index 00000000..7f089c2f --- /dev/null +++ b/plugins/warp/src/plugin/find.rs @@ -0,0 +1,57 @@ +use binaryninja::binaryview::{BinaryView, BinaryViewExt}; +use binaryninja::command::Command; +use rayon::prelude::*; +use std::thread; +use warp::signature::function::FunctionGUID; + +use crate::cache::cached_function_guid; + +pub struct FindFunctionFromGUID; + +impl Command for FindFunctionFromGUID { + fn action(&self, view: &BinaryView) { + let Some(guid_str) = binaryninja::interaction::get_text_line_input( + "Function GUID", + "Find Function from GUID", + ) else { + return; + }; + + let Ok(searched_guid) = guid_str.parse::<FunctionGUID>() else { + log::error!("Failed to parse function guid... {}", guid_str); + return; + }; + + log::info!("Searching functions for GUID... {}", searched_guid); + let funcs = view.functions(); + thread::spawn(move || { + let background_task = binaryninja::backgroundtask::BackgroundTask::new( + format!("Searching functions for GUID... {}", searched_guid), + true, + ) + .unwrap(); + + // TODO: While background_task has not finished. + let matched = funcs + .par_iter() + .filter_map(|func| { + Some(( + func.clone(), + cached_function_guid(&func, func.low_level_il_if_available()?.as_ref())?, + )) + }) + .filter(|(_func, guid)| guid.eq(&searched_guid)) + .collect::<Vec<_>>(); + + for (func, _) in matched { + log::info!("Match found at function... 0x{:0x}", func.start()); + } + + background_task.finish(); + }); + } + + fn valid(&self, _view: &BinaryView) -> bool { + true + } +} diff --git a/plugins/warp/src/plugin/types.rs b/plugins/warp/src/plugin/types.rs new file mode 100644 index 00000000..b8718a63 --- /dev/null +++ b/plugins/warp/src/plugin/types.rs @@ -0,0 +1,52 @@ +use crate::convert::to_bn_type; +use binaryninja::binaryview::{BinaryView, BinaryViewExt}; +use binaryninja::command::Command; +use std::time::Instant; + +pub struct LoadTypesCommand; + +impl Command for LoadTypesCommand { + fn action(&self, view: &BinaryView) { + let Some(file) = binaryninja::interaction::get_open_filename_input( + "Apply Signature File Types", + "*.sbin", + ) else { + return; + }; + + let Ok(data) = std::fs::read(&file) else { + log::error!("Could not read signature file: {:?}", file); + return; + }; + + let Some(data) = warp::signature::Data::from_bytes(&data) else { + log::error!("Could not get data from signature file: {:?}", file); + return; + }; + + let view = view.to_owned(); + std::thread::spawn(move || { + let background_task = binaryninja::backgroundtask::BackgroundTask::new( + format!("Applying {} types...", data.types.len()), + true, + ) + .unwrap(); + + let start = Instant::now(); + for comp_ty in data.types { + let ty_id = comp_ty.guid.to_string(); + let ty_name = comp_ty.ty.name.to_owned().unwrap_or_else(|| ty_id.clone()); + // TODO: Using arch here is problematic. + let arch = view.default_arch().unwrap(); + view.define_auto_type_with_id(ty_name, ty_id, &to_bn_type(&arch, &comp_ty.ty)); + } + + log::info!("Type application took {:?}", start.elapsed()); + background_task.finish(); + }); + } + + fn valid(&self, _view: &BinaryView) -> bool { + true + } +} diff --git a/plugins/warp/src/plugin/workflow.rs b/plugins/warp/src/plugin/workflow.rs new file mode 100644 index 00000000..da8473f0 --- /dev/null +++ b/plugins/warp/src/plugin/workflow.rs @@ -0,0 +1,38 @@ +use crate::matcher::cached_function_match; +use binaryninja::llil; +use binaryninja::workflow::{Activity, AnalysisContext, Workflow}; + +const MATCHER_ACTIVITY_NAME: &str = "analysis.plugins.WARPMatcher"; +// NOTE: runOnce is off because previously matched functions need info applied. +const MATCHER_ACTIVITY_CONFIG: &str = r#"{ + "name": "analysis.plugins.WARPMatcher", + "title" : "WARP Matcher", + "description": "This analysis step applies WARP info to matched functions...", + "eligibility": { + "auto": { "default": true }, + "runOnce": false + } +}"#; + +pub fn insert_matcher_workflow() { + let matcher_activity = |ctx: &AnalysisContext| { + let function = ctx.function(); + if function.has_user_annotations() { + // User has touched the function, stop trying to match on it! + return; + } + + if let Some(llil) = unsafe { ctx.llil_function::<llil::NonSSA<llil::RegularNonSSA>>() } { + cached_function_match(&function, &llil); + } + }; + + let meta_workflow = Workflow::new_from_copy("core.function.metaAnalysis"); + let activity = Activity::new_with_action(MATCHER_ACTIVITY_CONFIG, matcher_activity); + meta_workflow.register_activity(&activity).unwrap(); + meta_workflow.insert( + "core.function.runFunctionRecognizers", + [MATCHER_ACTIVITY_NAME], + ); + meta_workflow.register().unwrap(); +} |
