summaryrefslogtreecommitdiff
path: root/rust/src
diff options
context:
space:
mode:
authorMason Reed <mason@vector35.com>2025-07-13 17:34:02 -0400
committerMason Reed <mason@vector35.com>2025-07-15 12:34:43 -0400
commitbd1b6b8a1a75df296da5c7afa8107ba301c9cd8e (patch)
treeb0155db15147c9d91da10519ee72eb87560ba2d4 /rust/src
parentfa3c81fd9b4287792c3e3d534a7d237d6005cb5f (diff)
[Rust] Add `BinaryView::search` and friends
Diffstat (limited to 'rust/src')
-rw-r--r--rust/src/architecture.rs5
-rw-r--r--rust/src/binary_view.rs197
-rw-r--r--rust/src/binary_view/search.rs109
-rw-r--r--rust/src/data_buffer.rs21
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)
}
}