summaryrefslogtreecommitdiff
path: root/plugins/warp/src/plugin
diff options
context:
space:
mode:
authorMason Reed <mason@vector35.com>2025-08-26 23:59:38 -0400
committerMason Reed <mason@vector35.com>2025-10-01 21:38:39 -0400
commitede39aee7e00c40a43b67ca18dd8ab80ee863d85 (patch)
tree67c5eda347ece2282e3c888f38066b496e06dec8 /plugins/warp/src/plugin
parenta1c46813e7f279aa4cfdb9dbb91c45b559ebeacd (diff)
[WARP] Enhanced network support
Diffstat (limited to 'plugins/warp/src/plugin')
-rw-r--r--plugins/warp/src/plugin/commit.rs150
-rw-r--r--plugins/warp/src/plugin/ffi.rs12
-rw-r--r--plugins/warp/src/plugin/ffi/container.rs271
-rw-r--r--plugins/warp/src/plugin/ffi/file.rs38
-rw-r--r--plugins/warp/src/plugin/ffi/function.rs2
-rw-r--r--plugins/warp/src/plugin/load.rs11
-rw-r--r--plugins/warp/src/plugin/settings.rs76
-rw-r--r--plugins/warp/src/plugin/workflow.rs10
8 files changed, 551 insertions, 19 deletions
diff --git a/plugins/warp/src/plugin/commit.rs b/plugins/warp/src/plugin/commit.rs
new file mode 100644
index 00000000..2af5fc3e
--- /dev/null
+++ b/plugins/warp/src/plugin/commit.rs
@@ -0,0 +1,150 @@
+//! Commit file to a source.
+
+use crate::cache::container::cached_containers;
+use crate::container::{SourceId, SourcePath};
+use crate::plugin::create::OpenFileField;
+use binaryninja::binary_view::BinaryView;
+use binaryninja::command::Command;
+use binaryninja::interaction::{Form, FormInputField};
+use warp::chunk::ChunkKind;
+use warp::WarpFile;
+
+pub struct SelectedSourceField {
+ sources: Vec<(SourceId, SourcePath)>,
+}
+
+impl SelectedSourceField {
+ pub fn field(&self) -> FormInputField {
+ FormInputField::Choice {
+ prompt: "Selected Source".to_string(),
+ choices: self
+ .sources
+ .iter()
+ .map(|(id, path)| {
+ // For display purposes we only want to show the last path item.
+ let path_name = path
+ .to_string()
+ .rsplit_once('/')
+ .map_or(path.to_string(), |(_, last_path_item)| {
+ last_path_item.to_string()
+ });
+ // TODO: Probably have a truncation limit here, this is just for display after all.
+ format!("{} ({})", path_name, id)
+ })
+ .collect(),
+ default: None,
+ value: 0,
+ }
+ }
+
+ pub fn from_form(&self, form: &Form) -> Option<SourceId> {
+ let field = form.get_field_with_name("Selected Source")?;
+ let field_value = field.try_value_index()?;
+ self.sources.get(field_value).map(|(id, _)| *id)
+ }
+}
+
+pub struct CommitFile;
+
+impl CommitFile {
+ pub fn selected_source_field() -> SelectedSourceField {
+ let mut writable_sources = Vec::new();
+ for container in cached_containers() {
+ if let Ok(container) = container.read() {
+ for source in container.sources().unwrap_or_default() {
+ if let Ok(true) = container.is_source_writable(&source) {
+ if let Ok(source_path) = container.source_path(&source) {
+ writable_sources.push((source, source_path));
+ }
+ }
+ }
+ }
+ }
+ SelectedSourceField {
+ sources: writable_sources,
+ }
+ }
+
+ pub fn execute() -> Option<()> {
+ let mut form = Form::new("Commit File");
+
+ // Users are going to get confused between this and adding functions to a source then commiting.
+ // So we should make it clear with a label, and also probably deprecate this command and replace it with "add functions to source" and "commit source".
+ form.add_field(FormInputField::Label {
+ prompt: "Commits a WARP file to an existing source, this is primarily used for committing to network containers".to_string()
+ });
+
+ form.add_field(OpenFileField::field());
+ let source_field = Self::selected_source_field();
+ form.add_field(source_field.field());
+
+ if !form.prompt() {
+ return None;
+ }
+
+ let open_file_path = OpenFileField::from_form(&form)?;
+ let source_id = source_field.from_form(&form)?;
+ log::info!("Committing file to source: {}", source_id);
+
+ let bytes = std::fs::read(open_file_path).ok()?;
+ let Some(warp_file) = WarpFile::from_bytes(&bytes) else {
+ log::error!("Failed to parse warp file!");
+ return None;
+ };
+
+ for container in cached_containers() {
+ let Ok(mut container) = container.write() else {
+ continue;
+ };
+
+ if let Ok(true) = container.is_source_writable(&source_id) {
+ // TODO: We need to find a sane way to do this procedure through the FFI.
+ for chunk in &warp_file.chunks {
+ match &chunk.kind {
+ ChunkKind::Signature(sc) => {
+ let functions: Vec<_> = sc.functions().collect();
+ log::info!(
+ "Adding {} functions to source: {}",
+ functions.len(),
+ source_id
+ );
+ if let Err(e) = container.add_functions(
+ &chunk.header.target,
+ &source_id,
+ &functions,
+ ) {
+ log::error!("Failed to add functions to source: {}", e);
+ }
+ }
+ ChunkKind::Type(sc) => {
+ let types: Vec<_> = sc.types().collect();
+ log::info!("Adding {} types to source: {}", types.len(), source_id);
+ if let Err(e) = container.add_computed_types(&source_id, &types) {
+ log::error!("Failed to add types to source: {}", e);
+ }
+ }
+ }
+ }
+ if let Err(e) = container.commit_source(&source_id) {
+ log::error!("Failed to commit source: {}", e);
+ }
+ log::info!("Committed file to source: {}", source_id);
+ return Some(());
+ }
+ }
+
+ Some(())
+ }
+}
+
+impl Command for CommitFile {
+ fn action(&self, _view: &BinaryView) {
+ std::thread::spawn(move || {
+ Self::execute();
+ });
+ }
+
+ fn valid(&self, _view: &BinaryView) -> bool {
+ true
+ }
+}
diff --git a/plugins/warp/src/plugin/ffi.rs b/plugins/warp/src/plugin/ffi.rs
index 2826ed34..c1f5acb8 100644
--- a/plugins/warp/src/plugin/ffi.rs
+++ b/plugins/warp/src/plugin/ffi.rs
@@ -1,4 +1,5 @@
mod container;
+mod file;
mod function;
use binaryninjacore_sys::{
@@ -88,6 +89,17 @@ pub unsafe extern "C" fn BNWARPUUIDGetString(uuid: *const Uuid) -> *mut c_char {
}
#[no_mangle]
+pub unsafe extern "C" fn BNWARPUUIDFromString(uuid_str: *mut c_char, uuid: *mut Uuid) -> bool {
+ if let Ok(uuid_str) = std::ffi::CStr::from_ptr(uuid_str).to_str() {
+ if let Some(parsed_uuid) = Uuid::parse_str(uuid_str).ok() {
+ *uuid = parsed_uuid;
+ return true;
+ }
+ }
+ false
+}
+
+#[no_mangle]
pub unsafe extern "C" fn BNWARPUUIDEqual(a: *const Uuid, b: *const Uuid) -> bool {
(*a) == (*b)
}
diff --git a/plugins/warp/src/plugin/ffi/container.rs b/plugins/warp/src/plugin/ffi/container.rs
index 4de6bab3..72c85366 100644
--- a/plugins/warp/src/plugin/ffi/container.rs
+++ b/plugins/warp/src/plugin/ffi/container.rs
@@ -1,5 +1,7 @@
use crate::cache::container::cached_containers;
-use crate::container::SourcePath;
+use crate::container::{
+ ContainerSearchItem, ContainerSearchItemKind, ContainerSearchQuery, SourcePath, SourceTag,
+};
use crate::convert::{from_bn_type, to_bn_type};
use crate::plugin::ffi::{
BNWARPContainer, BNWARPFunction, BNWARPFunctionGUID, BNWARPSource, BNWARPTarget, BNWARPTypeGUID,
@@ -12,7 +14,163 @@ use binaryninja::types::Type;
use binaryninjacore_sys::{BNArchitecture, BNBinaryView, BNType};
use std::ffi::{c_char, CStr};
use std::mem::ManuallyDrop;
+use std::ops::Deref;
use std::sync::Arc;
+use warp::r#type::guid::TypeGUID;
+
+pub type BNWARPContainerSearchQuery = ContainerSearchQuery;
+pub type BNWARPContainerSearchItem = ContainerSearchItem;
+
+#[repr(C)]
+pub enum BNWARPContainerSearchItemKind {
+ Source = 0,
+ Function = 1,
+ Type = 2,
+ Symbol = 3,
+}
+
+#[repr(C)]
+pub struct BNWARPContainerSearchResponse {
+ pub count: usize,
+ pub items: *mut *mut BNWARPContainerSearchItem,
+ pub offset: usize,
+ pub total: usize,
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPNewContainerSearchQuery(
+ query: *mut c_char,
+ offset: *const usize,
+ limit: *const usize,
+ source: *const BNWARPSource,
+ source_tags: *mut *mut c_char,
+ source_tags_count: usize,
+) -> *mut BNWARPContainerSearchQuery {
+ let query_cstr = unsafe { CStr::from_ptr(query) };
+ let Ok(query) = query_cstr.to_str() else {
+ return std::ptr::null_mut();
+ };
+ let mut search_query = ContainerSearchQuery::new(query.to_string());
+ if !offset.is_null() {
+ search_query.offset = Some(*offset);
+ }
+ if !limit.is_null() {
+ search_query.limit = Some(*limit);
+ }
+ if !source.is_null() {
+ search_query.source = Some(*source);
+ }
+ if !source_tags.is_null() {
+ let source_tags_raw = unsafe { std::slice::from_raw_parts(source_tags, source_tags_count) };
+ let source_tags: Vec<SourceTag> = source_tags_raw
+ .iter()
+ .filter_map(|&ptr| CStr::from_ptr(ptr).to_str().ok())
+ .map(|s| s.into())
+ .collect();
+ search_query.tags = source_tags;
+ }
+ Box::into_raw(Box::new(search_query))
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerSearchItemGetKind(
+ item: *mut BNWARPContainerSearchItem,
+) -> BNWARPContainerSearchItemKind {
+ let item = ManuallyDrop::new(Arc::from_raw(item));
+ match &item.kind {
+ ContainerSearchItemKind::Source { .. } => BNWARPContainerSearchItemKind::Source,
+ ContainerSearchItemKind::Function(_) => BNWARPContainerSearchItemKind::Function,
+ ContainerSearchItemKind::Type(_) => BNWARPContainerSearchItemKind::Type,
+ ContainerSearchItemKind::Symbol(_) => BNWARPContainerSearchItemKind::Symbol,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerSearchItemGetSource(
+ item: *mut BNWARPContainerSearchItem,
+) -> BNWARPSource {
+ let item = ManuallyDrop::new(Arc::from_raw(item));
+ item.source
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerSearchItemGetType(
+ arch: *mut BNArchitecture,
+ item: *mut BNWARPContainerSearchItem,
+) -> *mut BNType {
+ // NOTE: to convert the type, we must have an architecture.
+ let arch = match !arch.is_null() {
+ true => Some(CoreArchitecture::from_raw(arch)),
+ false => None,
+ };
+
+ let item = ManuallyDrop::new(Arc::from_raw(item));
+ match &item.kind {
+ ContainerSearchItemKind::Source { .. } => std::ptr::null_mut(),
+ ContainerSearchItemKind::Function(func) => {
+ match &func.ty {
+ None => std::ptr::null_mut(),
+ Some(ty) => {
+ let bn_ty = to_bn_type(arch, &ty);
+ // NOTE: The type ref has been pre-incremented for the caller.
+ unsafe { Ref::into_raw(bn_ty) }.handle
+ }
+ }
+ }
+ ContainerSearchItemKind::Type(ty) => {
+ let bn_ty = to_bn_type(arch, &ty);
+ // NOTE: The type ref has been pre-incremented for the caller.
+ unsafe { Ref::into_raw(bn_ty) }.handle
+ }
+ ContainerSearchItemKind::Symbol(_) => std::ptr::null_mut(),
+ }
+}
+
+// NOTE: In the future we should allow for the possibility of this returning a null pointer.
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerSearchItemGetName(
+ item: *mut BNWARPContainerSearchItem,
+) -> *mut c_char {
+ let item = ManuallyDrop::new(Arc::from_raw(item));
+ match &item.kind {
+ ContainerSearchItemKind::Source { path, .. } => {
+ let bn_name = BnString::new(path.to_string());
+ BnString::into_raw(bn_name)
+ }
+ ContainerSearchItemKind::Function(func) => {
+ let bn_name = BnString::new(func.symbol.name.clone());
+ BnString::into_raw(bn_name)
+ }
+ ContainerSearchItemKind::Type(ty) => {
+ // TODO: Maybe un-named types should return std::ptr::null_mut()?
+ let ty_name = ty
+ .name
+ .clone()
+ .unwrap_or_else(|| TypeGUID::from(ty).to_string());
+ let bn_name = BnString::new(ty_name);
+ BnString::into_raw(bn_name)
+ }
+ ContainerSearchItemKind::Symbol(sym) => {
+ let bn_name = BnString::new(sym.name.clone());
+ BnString::into_raw(bn_name)
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerSearchItemGetFunction(
+ item: *mut BNWARPContainerSearchItem,
+) -> *mut BNWARPFunction {
+ let item = ManuallyDrop::new(Arc::from_raw(item));
+ match &item.kind {
+ ContainerSearchItemKind::Source { .. } => std::ptr::null_mut(),
+ ContainerSearchItemKind::Function(func) => {
+ Arc::into_raw(Arc::new(func.clone())) as *mut BNWARPFunction
+ }
+ ContainerSearchItemKind::Type(_) => std::ptr::null_mut(),
+ ContainerSearchItemKind::Symbol(_) => std::ptr::null_mut(),
+ }
+}
#[no_mangle]
pub unsafe extern "C" fn BNWARPGetContainers(count: *mut usize) -> *mut *mut BNWARPContainer {
@@ -39,6 +197,8 @@ pub unsafe extern "C" fn BNWARPContainerGetName(container: *mut BNWARPContainer)
pub unsafe extern "C" fn BNWARPContainerFetchFunctions(
container: *mut BNWARPContainer,
target: *mut BNWARPTarget,
+ source_tags: *mut *mut c_char,
+ source_tags_count: usize,
guids: *const BNWARPFunctionGUID,
count: usize,
) {
@@ -49,9 +209,16 @@ pub unsafe extern "C" fn BNWARPContainerFetchFunctions(
let target = unsafe { ManuallyDrop::new(Arc::from_raw(target)) };
+ let source_tags_raw = unsafe { std::slice::from_raw_parts(source_tags, source_tags_count) };
+ let source_tags: Vec<SourceTag> = source_tags_raw
+ .iter()
+ .filter_map(|&ptr| CStr::from_ptr(ptr).to_str().ok())
+ .map(|s| s.into())
+ .collect();
+
let guids = unsafe { std::slice::from_raw_parts(guids, count) };
- if let Err(e) = container.fetch_functions(&target, guids) {
+ if let Err(e) = container.fetch_functions(&target, &source_tags, guids) {
log::error!("Failed to fetch functions: {}", e);
}
}
@@ -373,7 +540,7 @@ pub unsafe extern "C" fn BNWARPContainerGetTypeWithGUID(
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);
+ let function_type = to_bn_type(Some(arch), &ty);
// NOTE: The type ref has been pre-incremented for the caller.
unsafe { Ref::into_raw(function_type) }.handle
}
@@ -405,6 +572,45 @@ pub unsafe extern "C" fn BNWARPContainerGetTypeGUIDsWithName(
}
#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerSearch(
+ container: *mut BNWARPContainer,
+ query: *mut BNWARPContainerSearchQuery,
+) -> *mut BNWARPContainerSearchResponse {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(container) = arc_container.read() else {
+ return std::ptr::null_mut();
+ };
+
+ let query = unsafe { ManuallyDrop::new(Arc::from_raw(query)) };
+
+ let result = match container.search(&query) {
+ Ok(result) => result,
+ Err(err) => {
+ log::error!("Failed to search container {:?}: {}", query.deref(), err);
+ return std::ptr::null_mut();
+ }
+ };
+
+ let boxed_raw_items: Box<[_]> = result
+ .items
+ .into_iter()
+ .map(Arc::new)
+ .map(Arc::into_raw)
+ .collect();
+ let count = boxed_raw_items.len();
+ // NOTE: Leak the functions to be freed by BNWARPFreeContainerSearchItemList
+ let leaked_raw_items = Box::into_raw(boxed_raw_items) as *mut *mut BNWARPContainerSearchItem;
+ let raw_result = BNWARPContainerSearchResponse {
+ count,
+ items: leaked_raw_items,
+ total: result.total,
+ offset: result.offset,
+ };
+ // NOTE: Leak the result to be freed by BNWARPFreeContainerSearchResult
+ Box::into_raw(Box::new(raw_result))
+}
+
+#[no_mangle]
pub unsafe extern "C" fn BNWARPNewContainerReference(
container: *mut BNWARPContainer,
) -> *mut BNWARPContainer {
@@ -431,3 +637,62 @@ pub unsafe extern "C" fn BNWARPFreeContainerList(
BNWARPFreeContainerReference(container);
}
}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPNewContainerSearchQueryReference(
+ query: *mut BNWARPContainerSearchQuery,
+) -> *mut BNWARPContainerSearchQuery {
+ Arc::increment_strong_count(query);
+ query
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeContainerSearchQueryReference(
+ query: *mut BNWARPContainerSearchQuery,
+) {
+ if query.is_null() {
+ return;
+ }
+ Arc::decrement_strong_count(query);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPNewContainerSearchItemReference(
+ item: *mut BNWARPContainerSearchItem,
+) -> *mut BNWARPContainerSearchItem {
+ Arc::increment_strong_count(item);
+ item
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeContainerSearchItemReference(
+ item: *mut BNWARPContainerSearchItem,
+) {
+ if item.is_null() {
+ return;
+ }
+ Arc::decrement_strong_count(item);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeContainerSearchItemList(
+ items: *mut *mut BNWARPContainerSearchItem,
+ count: usize,
+) {
+ let items_ptr = std::ptr::slice_from_raw_parts_mut(items, count);
+ let items = unsafe { Box::from_raw(items_ptr) };
+ for item in items {
+ BNWARPFreeContainerSearchItemReference(item);
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeContainerSearchResponse(
+ response: *mut BNWARPContainerSearchResponse,
+) {
+ if response.is_null() {
+ return;
+ }
+ let response = unsafe { Box::from_raw(response) };
+ BNWARPFreeContainerSearchItemList(response.items, response.count);
+}
diff --git a/plugins/warp/src/plugin/ffi/file.rs b/plugins/warp/src/plugin/ffi/file.rs
new file mode 100644
index 00000000..951b5eb2
--- /dev/null
+++ b/plugins/warp/src/plugin/ffi/file.rs
@@ -0,0 +1,38 @@
+use std::ffi::c_char;
+use std::sync::Arc;
+use warp::WarpFile;
+
+pub type BNWARPFile = WarpFile<'static>;
+
+// TODO: At some point we may want to expose chunks directly. For now we will just enumerate all of them.
+// pub type BNWARPChunk = warp::chunk::Chunk<'static>;
+
+// TODO: From bytes as well.
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPNewFileFromPath(path: *mut c_char) -> *mut BNWARPFile {
+ let path_cstr = unsafe { std::ffi::CStr::from_ptr(path) };
+ let Ok(path) = path_cstr.to_str() else {
+ return std::ptr::null_mut();
+ };
+ let Ok(bytes) = std::fs::read(path) else {
+ return std::ptr::null_mut();
+ };
+ let Some(file) = WarpFile::from_owned_bytes(bytes) else {
+ return std::ptr::null_mut();
+ };
+ Arc::into_raw(Arc::new(file)) as *mut BNWARPFile
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPNewFileReference(file: *mut BNWARPFile) -> *mut BNWARPFile {
+ Arc::increment_strong_count(file);
+ file
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeFileReference(file: *mut BNWARPFile) {
+ if file.is_null() {
+ return;
+ }
+ Arc::decrement_strong_count(file);
+}
diff --git a/plugins/warp/src/plugin/ffi/function.rs b/plugins/warp/src/plugin/ffi/function.rs
index b37d613b..e3f6a079 100644
--- a/plugins/warp/src/plugin/ffi/function.rs
+++ b/plugins/warp/src/plugin/ffi/function.rs
@@ -124,7 +124,7 @@ pub unsafe extern "C" fn BNWARPFunctionGetType(
match &function.ty {
Some(func_ty) => {
let arch = analysis_function.arch();
- let function_type = to_bn_type(&arch, func_ty);
+ let function_type = to_bn_type(Some(arch), func_ty);
// NOTE: The type ref has been pre-incremented for the caller.
unsafe { Ref::into_raw(function_type) }.handle
}
diff --git a/plugins/warp/src/plugin/load.rs b/plugins/warp/src/plugin/load.rs
index cef6ea11..6f8b562e 100644
--- a/plugins/warp/src/plugin/load.rs
+++ b/plugins/warp/src/plugin/load.rs
@@ -40,16 +40,15 @@ 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,
+ FormInputField::Checkbox {
+ prompt: "Rerun Matcher".to_string(),
+ default: Some(true),
+ value: false,
}
}
pub fn from_form(form: &Form) -> Option<bool> {
- let field = form.get_field_with_name("Rerun Initial Matcher")?;
+ let field = form.get_field_with_name("Rerun Matcher")?;
let field_value = field.try_value_index()?;
match field_value {
1 => Some(true),
diff --git a/plugins/warp/src/plugin/settings.rs b/plugins/warp/src/plugin/settings.rs
index 489deb23..39092177 100644
--- a/plugins/warp/src/plugin/settings.rs
+++ b/plugins/warp/src/plugin/settings.rs
@@ -1,4 +1,4 @@
-use binaryninja::settings::Settings as BNSettings;
+use binaryninja::settings::{QueryOptions, Settings as BNSettings};
use serde_json::json;
use std::string::ToString;
@@ -20,6 +20,12 @@ pub struct PluginSettings {
///
/// This is set to [PluginSettings::SERVER_API_KEY_DEFAULT] by default.
pub server_api_key: Option<String>,
+ pub second_server_url: Option<String>,
+ pub second_server_api_key: Option<String>,
+ /// A source must have at least one of these tags to be considered a valid source.
+ ///
+ /// This is set to [PluginSettings::SOURCE_TAGS_DEFAULT] by default.
+ pub whitelisted_source_tags: Vec<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.
@@ -27,6 +33,8 @@ pub struct PluginSettings {
}
impl PluginSettings {
+ pub const WHITELISTED_SOURCE_TAGS_DEFAULT: Vec<String> = vec![];
+ pub const WHITELISTED_SOURCE_TAGS_SETTING: &'static str = "analysis.warp.whitelistedSourceTags";
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;
@@ -35,10 +43,25 @@ impl PluginSettings {
pub const SERVER_URL_SETTING: &'static str = "analysis.warp.serverUrl";
pub const SERVER_API_KEY_DEFAULT: Option<String> = None;
pub const SERVER_API_KEY_SETTING: &'static str = "analysis.warp.serverApiKey";
+ pub const SECONDARY_SERVER_URL_DEFAULT: Option<String> = None;
+ pub const SECONDARY_SERVER_URL_SETTING: &'static str = "analysis.warp.secondServerUrl";
+ pub const SECONDARY_SERVER_API_KEY_DEFAULT: Option<String> = None;
+ pub const SECONDARY_SERVER_API_KEY_SETTING: &'static str = "analysis.warp.secondServerApiKey";
pub const ENABLE_SERVER_DEFAULT: bool = false;
pub const ENABLE_SERVER_SETTING: &'static str = "network.enableWARP";
pub fn register(bn_settings: &mut BNSettings) {
+ let whitelisted_source_tags_prop = json!({
+ "title" : "Blacklisted Sources",
+ "type" : "array",
+ "default" : Self::WHITELISTED_SOURCE_TAGS_DEFAULT,
+ "description" : "Add a sources UUID to this list to blacklist it from being considered a valid source. This is useful for sources that are known to be false positives.",
+ "ignore" : [],
+ });
+ bn_settings.register_setting_json(
+ Self::WHITELISTED_SOURCE_TAGS_SETTING,
+ &whitelisted_source_tags_prop.to_string(),
+ );
let load_bundled_files_prop = json!({
"title" : "Load Bundled Files",
"type" : "boolean",
@@ -85,6 +108,31 @@ impl PluginSettings {
Self::SERVER_API_KEY_SETTING,
&server_api_key_prop.to_string(),
);
+ let second_server_url_prop = json!({
+ "title" : "Secondary Server URL",
+ "type" : "string",
+ "default" : Self::SECONDARY_SERVER_URL_DEFAULT,
+ "description" : "",
+ "ignore" : ["SettingsProjectScope", "SettingsResourceScope"],
+ "requiresRestart" : true
+ });
+ bn_settings.register_setting_json(
+ Self::SECONDARY_SERVER_URL_SETTING,
+ &second_server_url_prop.to_string(),
+ );
+ let second_server_api_key_prop = json!({
+ "title" : "Secondary Server API Key",
+ "type" : "string",
+ "default" : Self::SECONDARY_SERVER_API_KEY_DEFAULT,
+ "description" : "",
+ "ignore" : ["SettingsProjectScope", "SettingsResourceScope"],
+ "hidden": true,
+ "requiresRestart" : true
+ });
+ bn_settings.register_setting_json(
+ Self::SECONDARY_SERVER_API_KEY_SETTING,
+ &second_server_api_key_prop.to_string(),
+ );
let server_enabled_prop = json!({
"title" : "Enable WARP",
"type" : "boolean",
@@ -100,7 +148,7 @@ impl PluginSettings {
}
/// Retrieve plugin settings from [`BNSettings`].
- pub fn from_settings(bn_settings: &BNSettings) -> Self {
+ pub fn from_settings(bn_settings: &BNSettings, query_opts: &mut QueryOptions) -> 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);
@@ -117,9 +165,30 @@ impl PluginSettings {
settings.server_api_key = Some(server_api_key_str);
}
}
+ if bn_settings.contains(Self::SECONDARY_SERVER_URL_SETTING) {
+ let server_api_key_str = bn_settings.get_string(Self::SECONDARY_SERVER_URL_SETTING);
+ if !server_api_key_str.is_empty() {
+ settings.second_server_url = Some(server_api_key_str);
+ }
+ }
+ if bn_settings.contains(Self::SECONDARY_SERVER_API_KEY_SETTING) {
+ let server_api_key_str = bn_settings.get_string(Self::SECONDARY_SERVER_API_KEY_SETTING);
+ if !server_api_key_str.is_empty() {
+ settings.second_server_api_key = Some(server_api_key_str);
+ }
+ }
if bn_settings.contains(Self::ENABLE_SERVER_SETTING) {
settings.enable_server = bn_settings.get_bool(Self::ENABLE_SERVER_SETTING);
}
+
+ if bn_settings.contains(Self::WHITELISTED_SOURCE_TAGS_SETTING) {
+ let whitelisted_source_tags_str = bn_settings
+ .get_string_list_with_opts(Self::WHITELISTED_SOURCE_TAGS_SETTING, query_opts);
+ settings.whitelisted_source_tags = whitelisted_source_tags_str
+ .iter()
+ .map(|s| s.to_string())
+ .collect();
+ }
settings
}
}
@@ -127,10 +196,13 @@ impl PluginSettings {
impl Default for PluginSettings {
fn default() -> Self {
Self {
+ whitelisted_source_tags: PluginSettings::WHITELISTED_SOURCE_TAGS_DEFAULT,
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,
+ second_server_url: PluginSettings::SECONDARY_SERVER_URL_DEFAULT,
+ second_server_api_key: PluginSettings::SECONDARY_SERVER_API_KEY_DEFAULT,
enable_server: PluginSettings::ENABLE_SERVER_DEFAULT,
}
}
diff --git a/plugins/warp/src/plugin/workflow.rs b/plugins/warp/src/plugin/workflow.rs
index 9871f5d2..08d07c52 100644
--- a/plugins/warp/src/plugin/workflow.rs
+++ b/plugins/warp/src/plugin/workflow.rs
@@ -162,11 +162,7 @@ pub fn run_matcher(view: &BinaryView) {
log::info!("Matcher was cancelled by user, you may run it again by running the 'Run Matcher' command.");
}
- // It is noisy to show this every time, so we only show it in cases where a user can reasonably perceive.
- let elapsed = start.elapsed();
- if elapsed > std::time::Duration::from_secs(1) {
- log::info!("Function matching took {:?}", elapsed);
- }
+ log::info!("Function matching took {:?}", start.elapsed());
background_task.finish();
// Now we want to trigger re-analysis.
@@ -185,7 +181,7 @@ pub fn insert_workflow() -> Result<(), ()> {
// otherwise we will wipe over user type info.
if !function.has_user_type() {
if let Some(func_ty) = &matched_function.ty {
- function.set_auto_type(&to_bn_type(&function.arch(), func_ty));
+ function.set_auto_type(&to_bn_type(Some(function.arch()), func_ty));
}
}
if let Some(mlil) = ctx.mlil_function() {
@@ -206,7 +202,7 @@ pub fn insert_workflow() -> Result<(), ()> {
continue;
}
let decl_ty = match variable.ty {
- Some(decl_ty) => to_bn_type(&function.arch(), &decl_ty),
+ Some(decl_ty) => to_bn_type(Some(function.arch()), &decl_ty),
None => {
let Some(existing_var) = function.variable_type(&decl_var) else {
continue;