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 attempts to find matching WARP functions after the initial analysis is complete...", "eligibility": { "auto": {}, "runOnce": true } }"#; pub const GUID_ACTIVITY_NAME: &str = "analysis.warp.guid"; const GUID_ACTIVITY_CONFIG: &str = r#"{ "name": "analysis.warp.guid", "title" : "WARP GUID Generator", "description": "This analysis step generates the GUID for all analyzed functions...", "eligibility": { "auto": {}, "runOnce": false } }"#; pub struct RunMatcher; impl Command for RunMatcher { fn action(&self, view: &BinaryView) { let view = view.to_owned(); std::thread::spawn(move || { run_matcher(&view); }); } fn valid(&self, _view: &BinaryView) -> bool { true } } 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> = 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 = 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); } // TODO: Fix this before release. // TODO: Any attempt to add a tag type will create a undo action // TODO: Those are currently not thread safe when running in headless python. // TODO: See Mason for more lore. // 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(); run_matcher(&view); }; let guid_activity = |ctx: &AnalysisContext| { let function = ctx.function(); 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::>(&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_with_subactivities(&matcher_activity, vec!["core.module.update"]) .unwrap(); module_meta_workflow.insert( "core.module.deleteUnusedAutoFunctions", [MATCHER_ACTIVITY_NAME], ); module_meta_workflow.register().unwrap(); }