summaryrefslogtreecommitdiff
path: root/plugins/warp/src/plugin
diff options
context:
space:
mode:
authorMason Reed <mason@vector35.com>2024-10-23 20:47:31 -0400
committerMason Reed <mason@vector35.com>2024-10-24 17:03:11 -0400
commite151425087b6c0ee8c4d099bfc2d6d1268201db0 (patch)
tree25160cf81aa6d650cf1497a078acc50f0062757c /plugins/warp/src/plugin
parent0f53c21d6a7abbfd48fd59ec7c15586f02777b7e (diff)
Add WARP integration
https://github.com/Vector35/warp/
Diffstat (limited to 'plugins/warp/src/plugin')
-rw-r--r--plugins/warp/src/plugin/apply.rs82
-rw-r--r--plugins/warp/src/plugin/copy.rs35
-rw-r--r--plugins/warp/src/plugin/create.rs71
-rw-r--r--plugins/warp/src/plugin/find.rs57
-rw-r--r--plugins/warp/src/plugin/types.rs52
-rw-r--r--plugins/warp/src/plugin/workflow.rs38
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();
+}