Skip to content

Commit bf012e1

Browse files
committed
[Rust] Add string reader helpers and fix analysis_info
- `analysis_info` was causing a double free with function refs, also did not need to be wrapped in a Result
1 parent f371cb9 commit bf012e1

File tree

2 files changed

+63
-20
lines changed

2 files changed

+63
-20
lines changed

rust/src/binary_view.rs

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ use crate::types::{
5959
NamedTypeReference, QualifiedName, QualifiedNameAndType, QualifiedNameTypeAndId, Type,
6060
};
6161
use crate::variable::DataVariable;
62-
use crate::Endianness;
62+
use crate::{Endianness, BN_FULL_CONFIDENCE};
6363
use std::collections::HashMap;
64-
use std::ffi::{c_char, c_void};
64+
use std::ffi::{c_char, c_void, CString};
6565
use std::ops::Range;
6666
use std::path::Path;
6767
use std::ptr::NonNull;
@@ -222,10 +222,8 @@ pub trait BinaryViewExt: BinaryViewBase {
222222
/// Reads up to `len` bytes from address `offset`
223223
fn read_vec(&self, offset: u64, len: usize) -> Vec<u8> {
224224
let mut ret = vec![0; len];
225-
226225
let size = self.read(&mut ret, offset);
227226
ret.truncate(size);
228-
229227
ret
230228
}
231229

@@ -238,6 +236,22 @@ pub trait BinaryViewExt: BinaryViewBase {
238236
read_size
239237
}
240238

239+
/// Reads up to `len` bytes from the address `offset` returning a `CString` if available.
240+
fn read_c_string_at(&self, offset: u64, len: usize) -> Option<CString> {
241+
let mut buf = vec![0; len];
242+
let size = self.read(&mut buf, offset);
243+
let string = CString::new(buf[..size].to_vec()).ok()?;
244+
Some(string)
245+
}
246+
247+
/// Reads up to `len` bytes from the address `offset` returning a `String` if available.
248+
fn read_utf8_string_at(&self, offset: u64, len: usize) -> Option<String> {
249+
let mut buf = vec![0; len];
250+
let size = self.read(&mut buf, offset);
251+
let string = String::from_utf8(buf[..size].to_vec()).ok()?;
252+
Some(string)
253+
}
254+
241255
/// Search the view using the query options.
242256
///
243257
/// In the `on_match` callback return `false` to stop searching.
@@ -562,17 +576,15 @@ pub trait BinaryViewExt: BinaryViewBase {
562576
}
563577
}
564578

565-
fn analysis_info(&self) -> Result<AnalysisInfo> {
566-
let info_ref = unsafe { BNGetAnalysisInfo(self.as_ref().handle) };
567-
if info_ref.is_null() {
568-
return Err(());
569-
}
570-
let info = unsafe { *info_ref };
579+
fn analysis_info(&self) -> AnalysisInfo {
580+
let info_ptr = unsafe { BNGetAnalysisInfo(self.as_ref().handle) };
581+
assert!(!info_ptr.is_null());
582+
let info = unsafe { *info_ptr };
571583
let active_infos = unsafe { slice::from_raw_parts(info.activeInfo, info.count) };
572584

573585
let mut active_info_list = vec![];
574586
for active_info in active_infos {
575-
let func = unsafe { Function::ref_from_raw(active_info.func) };
587+
let func = unsafe { Function::from_raw(active_info.func).to_owned() };
576588
active_info_list.push(ActiveAnalysisInfo {
577589
func,
578590
analysis_time: active_info.analysisTime,
@@ -584,11 +596,11 @@ pub trait BinaryViewExt: BinaryViewBase {
584596
let result = AnalysisInfo {
585597
state: info.state,
586598
analysis_time: info.analysisTime,
587-
active_info: vec![],
599+
active_info: active_info_list,
588600
};
589601

590-
unsafe { BNFreeAnalysisInfo(info_ref) };
591-
Ok(result)
602+
unsafe { BNFreeAnalysisInfo(info_ptr) };
603+
result
592604
}
593605

594606
fn analysis_progress(&self) -> AnalysisProgress {
@@ -772,7 +784,7 @@ pub trait BinaryViewExt: BinaryViewBase {
772784
} else {
773785
std::ptr::null_mut()
774786
},
775-
confidence: 255, // BN_FULL_CONFIDENCE
787+
confidence: BN_FULL_CONFIDENCE,
776788
};
777789

778790
unsafe {
@@ -2235,6 +2247,14 @@ pub trait BinaryViewExt: BinaryViewBase {
22352247
/// NOTE: This returns a list of [`StringReference`] as strings may not be representable
22362248
/// as a [`String`] or even a [`BnString`]. It is the caller's responsibility to read the underlying
22372249
/// data and convert it to a representable form.
2250+
///
2251+
/// Some helpers for reading strings are available:
2252+
///
2253+
/// - [`BinaryViewExt::read_c_string_at`]
2254+
/// - [`BinaryViewExt::read_utf8_string_at`]
2255+
///
2256+
/// NOTE: This returns discovered strings and is therefore governed by `analysis.limits.minStringLength`
2257+
/// and other settings.
22382258
fn strings(&self) -> Array<StringReference> {
22392259
unsafe {
22402260
let mut count = 0;
@@ -2245,13 +2265,22 @@ pub trait BinaryViewExt: BinaryViewBase {
22452265

22462266
/// Retrieve the string that falls on a given virtual address.
22472267
///
2248-
/// NOTE: This returns discovered strings and is therefore governed by `analysis.limits.minStringLength` and other settings.
2249-
fn string_at(&self, addr: u64) -> Option<BNStringReference> {
2268+
/// NOTE: This returns a [`StringReference`] and since strings may not be representable as a Rust
2269+
/// [`String`] or even a [`BnString`]. It is the caller's responsibility to read the underlying
2270+
/// data and convert it to a representable form.
2271+
///
2272+
/// Some helpers for reading strings are available:
2273+
///
2274+
/// - [`BinaryViewExt::read_c_string_at`]
2275+
/// - [`BinaryViewExt::read_utf8_string_at`]
2276+
///
2277+
/// NOTE: This returns discovered strings and is therefore governed by `analysis.limits.minStringLength`
2278+
/// and other settings.
2279+
fn string_at(&self, addr: u64) -> Option<StringReference> {
22502280
let mut str_ref = BNStringReference::default();
22512281
let success = unsafe { BNGetStringAtAddress(self.as_ref().handle, addr, &mut str_ref) };
2252-
22532282
if success {
2254-
Some(str_ref)
2283+
Some(str_ref.into())
22552284
} else {
22562285
None
22572286
}
@@ -2262,6 +2291,14 @@ pub trait BinaryViewExt: BinaryViewBase {
22622291
/// NOTE: This returns a list of [`StringReference`] as strings may not be representable
22632292
/// as a [`String`] or even a [`BnString`]. It is the caller's responsibility to read the underlying
22642293
/// data and convert it to a representable form.
2294+
///
2295+
/// Some helpers for reading strings are available:
2296+
///
2297+
/// - [`BinaryViewExt::read_c_string_at`]
2298+
/// - [`BinaryViewExt::read_utf8_string_at`]
2299+
///
2300+
/// NOTE: This returns discovered strings and is therefore governed by `analysis.limits.minStringLength`
2301+
/// and other settings.
22652302
fn strings_in_range(&self, range: Range<u64>) -> Array<StringReference> {
22662303
unsafe {
22672304
let mut count = 0;

rust/tests/binary_view.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use binaryninja::binary_view::search::SearchQuery;
2-
use binaryninja::binary_view::{AnalysisState, BinaryViewBase, BinaryViewExt};
2+
use binaryninja::binary_view::{AnalysisState, BinaryViewBase, BinaryViewExt, StringType};
33
use binaryninja::data_buffer::DataBuffer;
44
use binaryninja::function::{Function, FunctionViewType};
55
use binaryninja::headless::Session;
@@ -99,6 +99,12 @@ fn test_binary_view_strings() {
9999
.expect("Failed to find string 'Microsoft (R) Optimizing Compiler'");
100100
assert_eq!(str_15dc.start, image_base + 0x15dc);
101101
assert_eq!(str_15dc.length, 33);
102+
assert_eq!(str_15dc.ty, StringType::AsciiString);
103+
104+
let string = view
105+
.read_c_string_at(str_15dc.start, str_15dc.length)
106+
.expect("Failed to read string");
107+
assert_eq!(string, c"Microsoft (R) Optimizing Compiler");
102108
}
103109

104110
#[test]

0 commit comments

Comments
 (0)