Skip to content

Commit 01ef88f

Browse files
rbranemesare
authored andcommitted
Implement Rust BaseAddressDetection
1 parent 0577345 commit 01ef88f

File tree

4 files changed

+281
-0
lines changed

4 files changed

+281
-0
lines changed

rust/src/base_detection.rs

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
use binaryninjacore_sys::*;
2+
use std::ffi::CStr;
3+
4+
use crate::architecture::CoreArchitecture;
5+
use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner};
6+
use crate::string::BnString;
7+
use std::num::NonZeroU32;
8+
use std::ptr::NonNull;
9+
10+
pub type BaseAddressDetectionPOISetting = BNBaseAddressDetectionPOISetting;
11+
pub type BaseAddressDetectionConfidence = BNBaseAddressDetectionConfidence;
12+
pub type BaseAddressDetectionPOIType = BNBaseAddressDetectionPOIType;
13+
14+
pub enum BaseAddressDetectionAnalysis {
15+
Basic,
16+
ControlFlow,
17+
Full,
18+
}
19+
20+
impl BaseAddressDetectionAnalysis {
21+
pub fn as_raw(&self) -> &'static CStr {
22+
match self {
23+
BaseAddressDetectionAnalysis::Basic => c"basic",
24+
BaseAddressDetectionAnalysis::ControlFlow => c"controlFlow",
25+
BaseAddressDetectionAnalysis::Full => c"full",
26+
}
27+
}
28+
}
29+
30+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
31+
pub struct BaseAddressDetectionResult {
32+
pub scores: Vec<BaseAddressDetectionScore>,
33+
pub confidence: BaseAddressDetectionConfidence,
34+
pub last_base: u64,
35+
}
36+
37+
#[repr(C)]
38+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
39+
pub struct BaseAddressDetectionScore {
40+
pub score: usize,
41+
pub base_address: u64,
42+
}
43+
44+
#[repr(C)]
45+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
46+
pub struct BaseAddressDetectionReason {
47+
pub pointer: u64,
48+
pub poi_offset: u64,
49+
pub poi_type: BaseAddressDetectionPOIType,
50+
}
51+
52+
impl CoreArrayProvider for BaseAddressDetectionReason {
53+
type Raw = BNBaseAddressDetectionReason;
54+
type Context = ();
55+
type Wrapped<'a> = &'a Self;
56+
}
57+
58+
unsafe impl CoreArrayProviderInner for BaseAddressDetectionReason {
59+
unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) {
60+
BNFreeBaseAddressDetectionReasons(raw)
61+
}
62+
63+
unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> {
64+
// SAFETY BNBaseAddressDetectionReason and BaseAddressDetectionReason
65+
// are transparent
66+
std::mem::transmute::<&BNBaseAddressDetectionReason, &BaseAddressDetectionReason>(raw)
67+
}
68+
}
69+
70+
pub struct BaseAddressDetection {
71+
handle: NonNull<BNBaseAddressDetection>,
72+
}
73+
74+
impl BaseAddressDetection {
75+
pub(crate) unsafe fn from_raw(handle: NonNull<BNBaseAddressDetection>) -> Self {
76+
Self { handle }
77+
}
78+
79+
#[allow(clippy::mut_from_ref)]
80+
pub(crate) unsafe fn as_raw(&self) -> &mut BNBaseAddressDetection {
81+
&mut *self.handle.as_ptr()
82+
}
83+
84+
/// Indicates whether base address detection analysis was aborted early
85+
pub fn aborted(&self) -> bool {
86+
unsafe { BNIsBaseAddressDetectionAborted(self.as_raw()) }
87+
}
88+
89+
/// Aborts base address detection analysis
90+
///
91+
/// NOTE: Does not stop base address detection until after initial analysis has completed, and
92+
/// it is in the base address enumeration phase.
93+
pub fn abort(&self) {
94+
unsafe { BNAbortBaseAddressDetection(self.as_raw()) }
95+
}
96+
97+
/// Returns a list of reasons that can be used to determine why a base
98+
/// address is a candidate
99+
pub fn get_reasons(&self, base_address: u64) -> Array<BaseAddressDetectionReason> {
100+
let mut count = 0;
101+
let reasons =
102+
unsafe { BNGetBaseAddressDetectionReasons(self.as_raw(), base_address, &mut count) };
103+
unsafe { Array::new(reasons, count, ()) }
104+
}
105+
106+
pub fn scores(&self, max_candidates: usize) -> BaseAddressDetectionResult {
107+
let mut scores = vec![BNBaseAddressDetectionScore::default(); max_candidates];
108+
let mut confidence = BNBaseAddressDetectionConfidence::NoConfidence;
109+
let mut last_base = 0;
110+
let num_candidates = unsafe {
111+
BNGetBaseAddressDetectionScores(
112+
self.as_raw(),
113+
scores.as_mut_ptr(),
114+
scores.len(),
115+
&mut confidence,
116+
&mut last_base,
117+
)
118+
};
119+
scores.truncate(num_candidates);
120+
// SAFETY BNBaseAddressDetectionScore and BaseAddressDetectionScore
121+
// are transparent
122+
let scores = unsafe {
123+
std::mem::transmute::<Vec<BNBaseAddressDetectionScore>, Vec<BaseAddressDetectionScore>>(
124+
scores,
125+
)
126+
};
127+
BaseAddressDetectionResult {
128+
scores,
129+
confidence,
130+
last_base,
131+
}
132+
}
133+
134+
/// Initial analysis and attempts to identify candidate base addresses
135+
///
136+
/// NOTE: This operation can take a long time to complete depending on the size and complexity
137+
/// of the binary and the settings used.
138+
pub fn detect(&self, settings: &BaseAddressDetectionSettings) -> bool {
139+
let mut raw_settings = BaseAddressDetectionSettings::into_raw(settings);
140+
unsafe { BNDetectBaseAddress(self.handle.as_ptr(), &mut raw_settings) }
141+
}
142+
}
143+
144+
impl Drop for BaseAddressDetection {
145+
fn drop(&mut self) {
146+
unsafe { BNFreeBaseAddressDetection(self.as_raw()) }
147+
}
148+
}
149+
150+
/// Build the initial analysis.
151+
///
152+
/// * `analysis` - analysis mode
153+
/// * `min_strlen` - minimum length of a string to be considered a point-of-interest
154+
/// * `alignment` - byte boundary to align the base address to while brute-forcing
155+
/// * `low_boundary` - lower boundary of the base address range to test
156+
/// * `high_boundary` - upper boundary of the base address range to test
157+
/// * `poi_analysis` - specifies types of points-of-interest to use for analysis
158+
/// * `max_pointers` - maximum number of candidate pointers to collect per pointer cluster
159+
pub struct BaseAddressDetectionSettings {
160+
arch: Option<CoreArchitecture>,
161+
analysis: BaseAddressDetectionAnalysis,
162+
min_string_len: u32,
163+
alignment: NonZeroU32,
164+
lower_boundary: u64,
165+
upper_boundary: u64,
166+
poi_analysis: BaseAddressDetectionPOISetting,
167+
max_pointers: u32,
168+
}
169+
170+
impl BaseAddressDetectionSettings {
171+
pub(crate) fn into_raw(value: &Self) -> BNBaseAddressDetectionSettings {
172+
let arch_name = value.arch.map(|a| a.name()).unwrap_or(BnString::new(""));
173+
BNBaseAddressDetectionSettings {
174+
Architecture: arch_name.as_ptr(),
175+
Analysis: value.analysis.as_raw().as_ptr(),
176+
MinStrlen: value.min_string_len,
177+
Alignment: value.alignment.get(),
178+
LowerBoundary: value.lower_boundary,
179+
UpperBoundary: value.upper_boundary,
180+
POIAnalysis: value.poi_analysis,
181+
MaxPointersPerCluster: value.max_pointers,
182+
}
183+
}
184+
185+
pub fn arch(mut self, value: CoreArchitecture) -> Self {
186+
self.arch = Some(value);
187+
self
188+
}
189+
190+
pub fn analysis(mut self, value: BaseAddressDetectionAnalysis) -> Self {
191+
self.analysis = value;
192+
self
193+
}
194+
195+
pub fn min_strlen(mut self, value: u32) -> Self {
196+
self.min_string_len = value;
197+
self
198+
}
199+
200+
pub fn alignment(mut self, value: NonZeroU32) -> Self {
201+
self.alignment = value;
202+
self
203+
}
204+
205+
pub fn low_boundary(mut self, value: u64) -> Self {
206+
assert!(
207+
self.upper_boundary >= value,
208+
"upper boundary must be greater than lower boundary"
209+
);
210+
self.lower_boundary = value;
211+
self
212+
}
213+
214+
pub fn high_boundary(mut self, value: u64) -> Self {
215+
assert!(
216+
self.lower_boundary <= value,
217+
"upper boundary must be greater than lower boundary"
218+
);
219+
self.upper_boundary = value;
220+
self
221+
}
222+
223+
pub fn poi_analysis(mut self, value: BaseAddressDetectionPOISetting) -> Self {
224+
self.poi_analysis = value;
225+
self
226+
}
227+
228+
pub fn max_pointers(mut self, value: u32) -> Self {
229+
assert!(value > 2, "max pointers must be at least 2");
230+
self.max_pointers = value;
231+
self
232+
}
233+
}
234+
235+
impl Default for BaseAddressDetectionSettings {
236+
fn default() -> Self {
237+
BaseAddressDetectionSettings {
238+
arch: None,
239+
analysis: BaseAddressDetectionAnalysis::Full,
240+
min_string_len: 10,
241+
alignment: 1024.try_into().unwrap(),
242+
lower_boundary: u64::MIN,
243+
upper_boundary: u64::MAX,
244+
poi_analysis: BaseAddressDetectionPOISetting::POIAnalysisAll,
245+
max_pointers: 128,
246+
}
247+
}
248+
}

rust/src/binary_view.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use binaryninjacore_sys::*;
2525

2626
use crate::architecture::{Architecture, CoreArchitecture};
27+
use crate::base_detection::BaseAddressDetection;
2728
use crate::basic_block::BasicBlock;
2829
use crate::component::{Component, IntoComponentGuid};
2930
use crate::confidence::Conf;
@@ -369,6 +370,13 @@ pub trait BinaryViewExt: BinaryViewBase {
369370
}
370371
}
371372

373+
fn base_address_detection(&self) -> Option<BaseAddressDetection> {
374+
unsafe {
375+
let handle = BNCreateBaseAddressDetection(self.as_ref().handle);
376+
NonNull::new(handle).map(|base| BaseAddressDetection::from_raw(base))
377+
}
378+
}
379+
372380
fn instruction_len<A: Architecture>(&self, arch: &A, addr: u64) -> Option<usize> {
373381
unsafe {
374382
let size = BNGetInstructionLength(self.as_ref().handle, arch.as_ref().handle, addr);

rust/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ mod operand_iter;
3030

3131
pub mod architecture;
3232
pub mod background_task;
33+
pub mod base_detection;
3334
pub mod basic_block;
3435
pub mod binary_reader;
3536
pub mod binary_view;

rust/tests/base_detection.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use binaryninja::base_detection::BaseAddressDetectionSettings;
2+
use binaryninja::binary_view::BinaryViewExt;
3+
use binaryninja::headless::Session;
4+
use rstest::{fixture, rstest};
5+
use std::path::PathBuf;
6+
7+
#[fixture]
8+
#[once]
9+
fn session() -> Session {
10+
Session::new().expect("Failed to initialize session")
11+
}
12+
13+
#[rstest]
14+
fn test_failed_base_detection(_session: &Session) {
15+
let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
16+
let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view");
17+
let bad = view
18+
.base_address_detection()
19+
.expect("Failed to create base address detection");
20+
assert!(
21+
!bad.detect(&BaseAddressDetectionSettings::default()),
22+
"Detection should fail on this view"
23+
);
24+
}

0 commit comments

Comments
 (0)