diff options
| author | Mason Reed <mason@vector35.com> | 2025-07-13 17:34:02 -0400 |
|---|---|---|
| committer | Mason Reed <mason@vector35.com> | 2025-07-15 12:34:43 -0400 |
| commit | bd1b6b8a1a75df296da5c7afa8107ba301c9cd8e (patch) | |
| tree | b0155db15147c9d91da10519ee72eb87560ba2d4 /rust/src | |
| parent | fa3c81fd9b4287792c3e3d534a7d237d6005cb5f (diff) | |
[Rust] Add `BinaryView::search` and friends
Diffstat (limited to 'rust/src')
| -rw-r--r-- | rust/src/architecture.rs | 5 | ||||
| -rw-r--r-- | rust/src/binary_view.rs | 197 | ||||
| -rw-r--r-- | rust/src/binary_view/search.rs | 109 | ||||
| -rw-r--r-- | rust/src/data_buffer.rs | 21 |
4 files changed, 314 insertions, 18 deletions
diff --git a/rust/src/architecture.rs b/rust/src/architecture.rs index d226230c..6b33b6bb 100644 --- a/rust/src/architecture.rs +++ b/rust/src/architecture.rs @@ -1828,10 +1828,7 @@ impl Architecture for CoreArchitecture { fn assemble(&self, code: &str, addr: u64) -> Result<Vec<u8>, String> { let code = CString::new(code).map_err(|_| "Invalid encoding in code string".to_string())?; - let result = match DataBuffer::new(&[]) { - Ok(result) => result, - Err(_) => return Err("Result buffer allocation failed".to_string()), - }; + let result = DataBuffer::new(&[]); // TODO: This is actually a list of errors. let mut error_raw: *mut c_char = std::ptr::null_mut(); let res = unsafe { diff --git a/rust/src/binary_view.rs b/rust/src/binary_view.rs index 73c3a2ab..2db833c4 100644 --- a/rust/src/binary_view.rs +++ b/rust/src/binary_view.rs @@ -34,7 +34,7 @@ use crate::external_library::{ExternalLibrary, ExternalLocation}; use crate::file_accessor::{Accessor, FileAccessor}; use crate::file_metadata::FileMetadata; use crate::flowgraph::FlowGraph; -use crate::function::{Function, NativeBlock}; +use crate::function::{Function, FunctionViewType, NativeBlock}; use crate::linear_view::{LinearDisassemblyLine, LinearViewCursor}; use crate::metadata::Metadata; use crate::platform::Platform; @@ -66,8 +66,11 @@ use std::{result, slice}; pub mod memory_map; pub mod reader; +pub mod search; pub mod writer; +use crate::binary_view::search::SearchQuery; +use crate::disassembly::DisassemblySettings; use crate::workflow::Workflow; pub use memory_map::MemoryMap; pub use reader::BinaryReader; @@ -78,6 +81,7 @@ pub type BinaryViewEventType = BNBinaryViewEventType; pub type AnalysisState = BNAnalysisState; pub type ModificationStatus = BNModificationStatus; pub type StringType = BNStringType; +pub type FindFlag = BNFindFlag; #[allow(clippy::len_without_is_empty)] pub trait BinaryViewBase: AsRef<BinaryView> { @@ -230,6 +234,197 @@ pub trait BinaryViewExt: BinaryViewBase { read_size } + /// Search the view using the query options. + fn search<C: FnMut(u64, &DataBuffer) -> bool>(&self, query: &SearchQuery, on_match: C) -> bool { + self.search_with_progress(query, on_match, NoProgressCallback) + } + + /// Search the view using the query options. + fn search_with_progress<P: ProgressCallback, C: FnMut(u64, &DataBuffer) -> bool>( + &self, + query: &SearchQuery, + mut on_match: C, + mut progress: P, + ) -> bool { + unsafe extern "C" fn cb_on_match<C: FnMut(u64, &DataBuffer) -> bool>( + ctx: *mut c_void, + offset: u64, + data: *mut BNDataBuffer, + ) -> bool { + let f = ctx as *mut C; + let buffer = DataBuffer::from_raw(data); + (*f)(offset, &buffer) + } + + let query = query.to_json().to_cstr(); + unsafe { + BNSearch( + self.as_ref().handle, + query.as_ptr(), + &mut progress as *mut P as *mut c_void, + Some(P::cb_progress_callback), + &mut on_match as *const C as *mut c_void, + Some(cb_on_match::<C>), + ) + } + } + + fn find_next_data(&self, start: u64, end: u64, data: &DataBuffer) -> Option<u64> { + self.find_next_data_with_opts( + start, + end, + data, + FindFlag::FindCaseInsensitive, + NoProgressCallback, + ) + } + + /// # Warning + /// + /// This function is likely to be changed to take in a "query" structure. Or deprecated entirely. + fn find_next_data_with_opts<P: ProgressCallback>( + &self, + start: u64, + end: u64, + data: &DataBuffer, + flag: FindFlag, + mut progress: P, + ) -> Option<u64> { + let mut result: u64 = 0; + let found = unsafe { + BNFindNextDataWithProgress( + self.as_ref().handle, + start, + end, + data.as_raw(), + &mut result, + flag, + &mut progress as *mut P as *mut c_void, + Some(P::cb_progress_callback), + ) + }; + + if found { + Some(result) + } else { + None + } + } + + fn find_next_constant( + &self, + start: u64, + end: u64, + constant: u64, + view_type: FunctionViewType, + ) -> Option<u64> { + // TODO: What are the best "default" settings? + let settings = DisassemblySettings::new(); + self.find_next_constant_with_opts( + start, + end, + constant, + &settings, + view_type, + NoProgressCallback, + ) + } + + /// # Warning + /// + /// This function is likely to be changed to take in a "query" structure. + fn find_next_constant_with_opts<P: ProgressCallback>( + &self, + start: u64, + end: u64, + constant: u64, + disasm_settings: &DisassemblySettings, + view_type: FunctionViewType, + mut progress: P, + ) -> Option<u64> { + let mut result: u64 = 0; + let raw_view_type = FunctionViewType::into_raw(view_type); + let found = unsafe { + BNFindNextConstantWithProgress( + self.as_ref().handle, + start, + end, + constant, + &mut result, + disasm_settings.handle, + raw_view_type, + &mut progress as *mut P as *mut c_void, + Some(P::cb_progress_callback), + ) + }; + FunctionViewType::free_raw(raw_view_type); + + if found { + Some(result) + } else { + None + } + } + + fn find_next_text( + &self, + start: u64, + end: u64, + text: &str, + view_type: FunctionViewType, + ) -> Option<u64> { + // TODO: What are the best "default" settings? + let settings = DisassemblySettings::new(); + self.find_next_text_with_opts( + start, + end, + text, + &settings, + FindFlag::FindCaseInsensitive, + view_type, + NoProgressCallback, + ) + } + + /// # Warning + /// + /// This function is likely to be changed to take in a "query" structure. + fn find_next_text_with_opts<P: ProgressCallback>( + &self, + start: u64, + end: u64, + text: &str, + disasm_settings: &DisassemblySettings, + flag: FindFlag, + view_type: FunctionViewType, + mut progress: P, + ) -> Option<u64> { + let text = text.to_cstr(); + let raw_view_type = FunctionViewType::into_raw(view_type); + let mut result: u64 = 0; + let found = unsafe { + BNFindNextTextWithProgress( + self.as_ref().handle, + start, + end, + text.as_ptr(), + &mut result, + disasm_settings.handle, + flag, + raw_view_type, + &mut progress as *mut P as *mut c_void, + Some(P::cb_progress_callback), + ) + }; + FunctionViewType::free_raw(raw_view_type); + + if found { + Some(result) + } else { + None + } + } + fn notify_data_written(&self, offset: u64, len: usize) { unsafe { BNNotifyDataWritten(self.as_ref().handle, offset, len); diff --git a/rust/src/binary_view/search.rs b/rust/src/binary_view/search.rs new file mode 100644 index 00000000..cd86603a --- /dev/null +++ b/rust/src/binary_view/search.rs @@ -0,0 +1,109 @@ +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct SearchQuery { + /// ex. "42 2e 64 65 ?? 75 67 24" + pattern: String, + #[serde(skip_serializing_if = "Option::is_none")] + start: Option<u64>, + #[serde(skip_serializing_if = "Option::is_none")] + end: Option<u64>, + #[serde(rename = "ignoreCase")] + ignore_case: bool, + raw: bool, + overlap: bool, + #[serde(skip_serializing_if = "Option::is_none")] + align: Option<u64>, +} + +impl SearchQuery { + pub fn new(pattern: impl Into<String>) -> Self { + Self { + pattern: pattern.into(), + ..Default::default() + } + } + + /// Set the starting address for the search + pub fn start(mut self, addr: u64) -> Self { + self.start = Some(addr); + self + } + + /// Set the ending address for the search (inclusive) + pub fn end(mut self, addr: u64) -> Self { + self.end = Some(addr); + self + } + + /// Set whether to interpret the pattern as a raw string + pub fn raw(mut self, raw: bool) -> Self { + self.raw = raw; + self + } + + /// Set whether to perform case-insensitive matching + pub fn ignore_case(mut self, ignore_case: bool) -> Self { + self.ignore_case = ignore_case; + self + } + + /// Set whether to allow matches to overlap + pub fn overlap(mut self, overlap: bool) -> Self { + self.overlap = overlap; + self + } + + /// Set the alignment of matches (must be a power of 2) + pub fn align(mut self, align: u64) -> Self { + // Validate that align is a power of 2 + if align != 0 && (align & (align - 1)) == 0 { + self.align = Some(align); + } + self + } +} + +impl SearchQuery { + /// Serialize the query to a JSON string + pub fn to_json(&self) -> String { + serde_json::to_string(self).expect("failed to serialize search query") + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_search_query_builder() { + let query = SearchQuery::new("test pattern") + .start(0x1000) + .end(0x2000) + .raw(true) + .ignore_case(true) + .overlap(false) + .align(16); + + assert_eq!(query.pattern, "test pattern"); + assert_eq!(query.start, Some(0x1000)); + assert_eq!(query.end, Some(0x2000)); + assert!(query.raw); + assert!(query.ignore_case); + assert!(!query.overlap); + assert_eq!(query.align, Some(16)); + } + + #[test] + fn test_search_query_json() { + let query = SearchQuery::new("test").start(0x1000).align(8); + + let json = query.to_json().unwrap(); + let parsed: serde_json::Value = serde_json::from_str(&json).unwrap(); + + assert_eq!(parsed["pattern"], "test"); + assert_eq!(parsed["start"], 4096); + assert_eq!(parsed["align"], 8); + assert!(!parsed.as_object().unwrap().contains_key("end")); + } +} diff --git a/rust/src/data_buffer.rs b/rust/src/data_buffer.rs index a3f51d54..5f59393e 100644 --- a/rust/src/data_buffer.rs +++ b/rust/src/data_buffer.rs @@ -32,6 +32,12 @@ impl DataBuffer { self.0 } + pub fn new(data: &[u8]) -> Self { + let buffer = unsafe { BNCreateDataBuffer(data.as_ptr() as *const c_void, data.len()) }; + assert!(!buffer.is_null()); + DataBuffer::from_raw(buffer) + } + pub fn get_data(&self) -> &[u8] { let buffer = unsafe { BNGetDataBufferContents(self.0) }; if buffer.is_null() { @@ -161,15 +167,6 @@ impl DataBuffer { pub fn is_empty(&self) -> bool { self.len() == 0 } - - pub fn new(data: &[u8]) -> Result<Self, ()> { - let buffer = unsafe { BNCreateDataBuffer(data.as_ptr() as *const c_void, data.len()) }; - if buffer.is_null() { - Err(()) - } else { - Ok(DataBuffer::from_raw(buffer)) - } - } } impl Default for DataBuffer { @@ -192,10 +189,8 @@ impl Clone for DataBuffer { } } -impl TryFrom<&[u8]> for DataBuffer { - type Error = (); - - fn try_from(value: &[u8]) -> Result<Self, Self::Error> { +impl From<&[u8]> for DataBuffer { + fn from(value: &[u8]) -> Self { DataBuffer::new(value) } } |
