Skip to content

Commit bd1b6b8

Browse files
committed
[Rust] Add BinaryView::search and friends
1 parent fa3c81f commit bd1b6b8

File tree

8 files changed

+365
-21
lines changed

8 files changed

+365
-21
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/svd/src/mapper.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ impl DeviceMapper {
167167
if self.settings.add_backing_regions {
168168
// Because adding a memory region will add a possibly large memory buffer in the BNDB, this
169169
// is optional. if a user disables this, they cannot write to the segment until they add a backing memory region.
170-
let data_memory = DataBuffer::new(&vec![0; address_block.size as usize]).unwrap();
170+
let data_memory = DataBuffer::new(&vec![0; address_block.size as usize]);
171171
let added_memory = view.memory_map().add_data_memory_region(
172172
&block_name,
173173
block_addr,

rust/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ log = { version = "0.4", features = ["std"] }
1818
rayon = { version = "1.10", optional = true }
1919
binaryninjacore-sys = { path = "binaryninjacore-sys" }
2020
thiserror = "2.0"
21+
serde = "1.0"
22+
serde_derive = "1.0"
2123
# Parts of the collaboration and workflow APIs consume and produce JSON.
2224
serde_json = "1.0"
2325

rust/src/architecture.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1828,10 +1828,7 @@ impl Architecture for CoreArchitecture {
18281828
fn assemble(&self, code: &str, addr: u64) -> Result<Vec<u8>, String> {
18291829
let code = CString::new(code).map_err(|_| "Invalid encoding in code string".to_string())?;
18301830

1831-
let result = match DataBuffer::new(&[]) {
1832-
Ok(result) => result,
1833-
Err(_) => return Err("Result buffer allocation failed".to_string()),
1834-
};
1831+
let result = DataBuffer::new(&[]);
18351832
// TODO: This is actually a list of errors.
18361833
let mut error_raw: *mut c_char = std::ptr::null_mut();
18371834
let res = unsafe {

rust/src/binary_view.rs

Lines changed: 196 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use crate::external_library::{ExternalLibrary, ExternalLocation};
3434
use crate::file_accessor::{Accessor, FileAccessor};
3535
use crate::file_metadata::FileMetadata;
3636
use crate::flowgraph::FlowGraph;
37-
use crate::function::{Function, NativeBlock};
37+
use crate::function::{Function, FunctionViewType, NativeBlock};
3838
use crate::linear_view::{LinearDisassemblyLine, LinearViewCursor};
3939
use crate::metadata::Metadata;
4040
use crate::platform::Platform;
@@ -66,8 +66,11 @@ use std::{result, slice};
6666

6767
pub mod memory_map;
6868
pub mod reader;
69+
pub mod search;
6970
pub mod writer;
7071

72+
use crate::binary_view::search::SearchQuery;
73+
use crate::disassembly::DisassemblySettings;
7174
use crate::workflow::Workflow;
7275
pub use memory_map::MemoryMap;
7376
pub use reader::BinaryReader;
@@ -78,6 +81,7 @@ pub type BinaryViewEventType = BNBinaryViewEventType;
7881
pub type AnalysisState = BNAnalysisState;
7982
pub type ModificationStatus = BNModificationStatus;
8083
pub type StringType = BNStringType;
84+
pub type FindFlag = BNFindFlag;
8185

8286
#[allow(clippy::len_without_is_empty)]
8387
pub trait BinaryViewBase: AsRef<BinaryView> {
@@ -230,6 +234,197 @@ pub trait BinaryViewExt: BinaryViewBase {
230234
read_size
231235
}
232236

237+
/// Search the view using the query options.
238+
fn search<C: FnMut(u64, &DataBuffer) -> bool>(&self, query: &SearchQuery, on_match: C) -> bool {
239+
self.search_with_progress(query, on_match, NoProgressCallback)
240+
}
241+
242+
/// Search the view using the query options.
243+
fn search_with_progress<P: ProgressCallback, C: FnMut(u64, &DataBuffer) -> bool>(
244+
&self,
245+
query: &SearchQuery,
246+
mut on_match: C,
247+
mut progress: P,
248+
) -> bool {
249+
unsafe extern "C" fn cb_on_match<C: FnMut(u64, &DataBuffer) -> bool>(
250+
ctx: *mut c_void,
251+
offset: u64,
252+
data: *mut BNDataBuffer,
253+
) -> bool {
254+
let f = ctx as *mut C;
255+
let buffer = DataBuffer::from_raw(data);
256+
(*f)(offset, &buffer)
257+
}
258+
259+
let query = query.to_json().to_cstr();
260+
unsafe {
261+
BNSearch(
262+
self.as_ref().handle,
263+
query.as_ptr(),
264+
&mut progress as *mut P as *mut c_void,
265+
Some(P::cb_progress_callback),
266+
&mut on_match as *const C as *mut c_void,
267+
Some(cb_on_match::<C>),
268+
)
269+
}
270+
}
271+
272+
fn find_next_data(&self, start: u64, end: u64, data: &DataBuffer) -> Option<u64> {
273+
self.find_next_data_with_opts(
274+
start,
275+
end,
276+
data,
277+
FindFlag::FindCaseInsensitive,
278+
NoProgressCallback,
279+
)
280+
}
281+
282+
/// # Warning
283+
///
284+
/// This function is likely to be changed to take in a "query" structure. Or deprecated entirely.
285+
fn find_next_data_with_opts<P: ProgressCallback>(
286+
&self,
287+
start: u64,
288+
end: u64,
289+
data: &DataBuffer,
290+
flag: FindFlag,
291+
mut progress: P,
292+
) -> Option<u64> {
293+
let mut result: u64 = 0;
294+
let found = unsafe {
295+
BNFindNextDataWithProgress(
296+
self.as_ref().handle,
297+
start,
298+
end,
299+
data.as_raw(),
300+
&mut result,
301+
flag,
302+
&mut progress as *mut P as *mut c_void,
303+
Some(P::cb_progress_callback),
304+
)
305+
};
306+
307+
if found {
308+
Some(result)
309+
} else {
310+
None
311+
}
312+
}
313+
314+
fn find_next_constant(
315+
&self,
316+
start: u64,
317+
end: u64,
318+
constant: u64,
319+
view_type: FunctionViewType,
320+
) -> Option<u64> {
321+
// TODO: What are the best "default" settings?
322+
let settings = DisassemblySettings::new();
323+
self.find_next_constant_with_opts(
324+
start,
325+
end,
326+
constant,
327+
&settings,
328+
view_type,
329+
NoProgressCallback,
330+
)
331+
}
332+
333+
/// # Warning
334+
///
335+
/// This function is likely to be changed to take in a "query" structure.
336+
fn find_next_constant_with_opts<P: ProgressCallback>(
337+
&self,
338+
start: u64,
339+
end: u64,
340+
constant: u64,
341+
disasm_settings: &DisassemblySettings,
342+
view_type: FunctionViewType,
343+
mut progress: P,
344+
) -> Option<u64> {
345+
let mut result: u64 = 0;
346+
let raw_view_type = FunctionViewType::into_raw(view_type);
347+
let found = unsafe {
348+
BNFindNextConstantWithProgress(
349+
self.as_ref().handle,
350+
start,
351+
end,
352+
constant,
353+
&mut result,
354+
disasm_settings.handle,
355+
raw_view_type,
356+
&mut progress as *mut P as *mut c_void,
357+
Some(P::cb_progress_callback),
358+
)
359+
};
360+
FunctionViewType::free_raw(raw_view_type);
361+
362+
if found {
363+
Some(result)
364+
} else {
365+
None
366+
}
367+
}
368+
369+
fn find_next_text(
370+
&self,
371+
start: u64,
372+
end: u64,
373+
text: &str,
374+
view_type: FunctionViewType,
375+
) -> Option<u64> {
376+
// TODO: What are the best "default" settings?
377+
let settings = DisassemblySettings::new();
378+
self.find_next_text_with_opts(
379+
start,
380+
end,
381+
text,
382+
&settings,
383+
FindFlag::FindCaseInsensitive,
384+
view_type,
385+
NoProgressCallback,
386+
)
387+
}
388+
389+
/// # Warning
390+
///
391+
/// This function is likely to be changed to take in a "query" structure.
392+
fn find_next_text_with_opts<P: ProgressCallback>(
393+
&self,
394+
start: u64,
395+
end: u64,
396+
text: &str,
397+
disasm_settings: &DisassemblySettings,
398+
flag: FindFlag,
399+
view_type: FunctionViewType,
400+
mut progress: P,
401+
) -> Option<u64> {
402+
let text = text.to_cstr();
403+
let raw_view_type = FunctionViewType::into_raw(view_type);
404+
let mut result: u64 = 0;
405+
let found = unsafe {
406+
BNFindNextTextWithProgress(
407+
self.as_ref().handle,
408+
start,
409+
end,
410+
text.as_ptr(),
411+
&mut result,
412+
disasm_settings.handle,
413+
flag,
414+
raw_view_type,
415+
&mut progress as *mut P as *mut c_void,
416+
Some(P::cb_progress_callback),
417+
)
418+
};
419+
FunctionViewType::free_raw(raw_view_type);
420+
421+
if found {
422+
Some(result)
423+
} else {
424+
None
425+
}
426+
}
427+
233428
fn notify_data_written(&self, offset: u64, len: usize) {
234429
unsafe {
235430
BNNotifyDataWritten(self.as_ref().handle, offset, len);

rust/src/binary_view/search.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use serde_derive::{Deserialize, Serialize};
2+
3+
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
4+
pub struct SearchQuery {
5+
/// ex. "42 2e 64 65 ?? 75 67 24"
6+
pattern: String,
7+
#[serde(skip_serializing_if = "Option::is_none")]
8+
start: Option<u64>,
9+
#[serde(skip_serializing_if = "Option::is_none")]
10+
end: Option<u64>,
11+
#[serde(rename = "ignoreCase")]
12+
ignore_case: bool,
13+
raw: bool,
14+
overlap: bool,
15+
#[serde(skip_serializing_if = "Option::is_none")]
16+
align: Option<u64>,
17+
}
18+
19+
impl SearchQuery {
20+
pub fn new(pattern: impl Into<String>) -> Self {
21+
Self {
22+
pattern: pattern.into(),
23+
..Default::default()
24+
}
25+
}
26+
27+
/// Set the starting address for the search
28+
pub fn start(mut self, addr: u64) -> Self {
29+
self.start = Some(addr);
30+
self
31+
}
32+
33+
/// Set the ending address for the search (inclusive)
34+
pub fn end(mut self, addr: u64) -> Self {
35+
self.end = Some(addr);
36+
self
37+
}
38+
39+
/// Set whether to interpret the pattern as a raw string
40+
pub fn raw(mut self, raw: bool) -> Self {
41+
self.raw = raw;
42+
self
43+
}
44+
45+
/// Set whether to perform case-insensitive matching
46+
pub fn ignore_case(mut self, ignore_case: bool) -> Self {
47+
self.ignore_case = ignore_case;
48+
self
49+
}
50+
51+
/// Set whether to allow matches to overlap
52+
pub fn overlap(mut self, overlap: bool) -> Self {
53+
self.overlap = overlap;
54+
self
55+
}
56+
57+
/// Set the alignment of matches (must be a power of 2)
58+
pub fn align(mut self, align: u64) -> Self {
59+
// Validate that align is a power of 2
60+
if align != 0 && (align & (align - 1)) == 0 {
61+
self.align = Some(align);
62+
}
63+
self
64+
}
65+
}
66+
67+
impl SearchQuery {
68+
/// Serialize the query to a JSON string
69+
pub fn to_json(&self) -> String {
70+
serde_json::to_string(self).expect("failed to serialize search query")
71+
}
72+
}
73+
74+
#[cfg(test)]
75+
mod tests {
76+
use super::*;
77+
78+
#[test]
79+
fn test_search_query_builder() {
80+
let query = SearchQuery::new("test pattern")
81+
.start(0x1000)
82+
.end(0x2000)
83+
.raw(true)
84+
.ignore_case(true)
85+
.overlap(false)
86+
.align(16);
87+
88+
assert_eq!(query.pattern, "test pattern");
89+
assert_eq!(query.start, Some(0x1000));
90+
assert_eq!(query.end, Some(0x2000));
91+
assert!(query.raw);
92+
assert!(query.ignore_case);
93+
assert!(!query.overlap);
94+
assert_eq!(query.align, Some(16));
95+
}
96+
97+
#[test]
98+
fn test_search_query_json() {
99+
let query = SearchQuery::new("test").start(0x1000).align(8);
100+
101+
let json = query.to_json().unwrap();
102+
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
103+
104+
assert_eq!(parsed["pattern"], "test");
105+
assert_eq!(parsed["start"], 4096);
106+
assert_eq!(parsed["align"], 8);
107+
assert!(!parsed.as_object().unwrap().contains_key("end"));
108+
}
109+
}

0 commit comments

Comments
 (0)