summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock2
-rw-r--r--plugins/svd/src/mapper.rs2
-rw-r--r--rust/Cargo.toml2
-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
-rw-r--r--rust/tests/binary_view.rs48
8 files changed, 365 insertions, 21 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 60964008..c3d7bb56 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -179,6 +179,8 @@ dependencies = [
"log",
"rayon",
"rstest",
+ "serde",
+ "serde_derive",
"serde_json",
"serial_test",
"tempfile",
diff --git a/plugins/svd/src/mapper.rs b/plugins/svd/src/mapper.rs
index 314fba76..8846c858 100644
--- a/plugins/svd/src/mapper.rs
+++ b/plugins/svd/src/mapper.rs
@@ -167,7 +167,7 @@ impl DeviceMapper {
if self.settings.add_backing_regions {
// Because adding a memory region will add a possibly large memory buffer in the BNDB, this
// is optional. if a user disables this, they cannot write to the segment until they add a backing memory region.
- let data_memory = DataBuffer::new(&vec![0; address_block.size as usize]).unwrap();
+ let data_memory = DataBuffer::new(&vec![0; address_block.size as usize]);
let added_memory = view.memory_map().add_data_memory_region(
&block_name,
block_addr,
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 9b29b283..0b9ac4f3 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -18,6 +18,8 @@ log = { version = "0.4", features = ["std"] }
rayon = { version = "1.10", optional = true }
binaryninjacore-sys = { path = "binaryninjacore-sys" }
thiserror = "2.0"
+serde = "1.0"
+serde_derive = "1.0"
# Parts of the collaboration and workflow APIs consume and produce JSON.
serde_json = "1.0"
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)
}
}
diff --git a/rust/tests/binary_view.rs b/rust/tests/binary_view.rs
index 5e8206e0..c3dfc1b8 100644
--- a/rust/tests/binary_view.rs
+++ b/rust/tests/binary_view.rs
@@ -1,11 +1,13 @@
+use binaryninja::binary_view::search::SearchQuery;
use binaryninja::binary_view::{AnalysisState, BinaryViewBase, BinaryViewExt};
-use binaryninja::function::Function;
+use binaryninja::data_buffer::DataBuffer;
+use binaryninja::function::{Function, FunctionViewType};
use binaryninja::headless::Session;
use binaryninja::main_thread::execute_on_main_thread_and_wait;
use binaryninja::platform::Platform;
use binaryninja::rc::Ref;
use binaryninja::symbol::{Symbol, SymbolBuilder, SymbolType};
-use std::collections::BTreeMap;
+use std::collections::{BTreeMap, HashSet};
use std::path::PathBuf;
#[test]
@@ -100,6 +102,48 @@ fn test_binary_view_strings() {
}
#[test]
+fn test_binary_view_search() {
+ let _session = Session::new().expect("Failed to initialize session");
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view");
+ let image_base = view.original_image_base();
+
+ // Test text search.
+ let txt_1580 = view
+ .find_next_text(0, view.end(), "minkernel", FunctionViewType::MediumLevelIL)
+ .expect("Failed to find text 'minkernel'");
+ assert_eq!(txt_1580, image_base + 0x1580);
+
+ // Test data search.
+ // 65 5c 6d 69 6e 6b 65 72 6e 65 6c (prepend bytes + minkernel)
+ let data = DataBuffer::new(&[
+ 0x65, 0x5c, 0x6d, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c,
+ ]);
+ let data_1580 = view
+ .find_next_data(0, view.end(), &data)
+ .expect("Failed to find data");
+ assert_eq!(data_1580, image_base + 0x1580);
+
+ // Test constant search.
+ let constant = 0x80000000;
+ let const_2607b = view
+ .find_next_constant(0, view.end(), constant, FunctionViewType::MediumLevelIL)
+ .expect("Failed to find constant");
+ assert_eq!(const_2607b, image_base + 0x2607b);
+
+ // Test binary search.
+ let query = SearchQuery::new("42 2e 64 65 ?? 75 67 24");
+ let mut found: HashSet<u64> = HashSet::new();
+ let found_any = view.search(&query, |offset, _data| {
+ found.insert(offset);
+ true
+ });
+ assert!(found_any);
+ assert_eq!(found.len(), 1);
+ assert_eq!(found.contains(&(&image_base + 0x63)), true);
+}
+
+#[test]
fn test_binary_tags() {
let _session = Session::new().expect("Failed to initialize session");
let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();