From 37b51462dcf26dc40ac5d7e82acdfd4cbe754157 Mon Sep 17 00:00:00 2001 From: Rubens Brandao Date: Wed, 10 Apr 2024 17:58:05 -0300 Subject: [PATCH 01/50] fix the array implementation using GAT --- rust/src/backgroundtask.rs | 8 ++-- rust/src/basicblock.rs | 12 +++--- rust/src/callingconvention.rs | 6 +-- rust/src/custombinaryview.rs | 8 ++-- rust/src/downloadprovider.rs | 8 ++-- rust/src/function.rs | 13 +++--- rust/src/linearview.rs | 6 +-- rust/src/metadata.rs | 6 +-- rust/src/platform.rs | 6 +-- rust/src/rc.rs | 76 +++++++++++++++++------------------ rust/src/references.rs | 14 ++++--- rust/src/relocation.rs | 8 ++-- rust/src/section.rs | 6 +-- rust/src/segment.rs | 6 +-- rust/src/string.rs | 6 +-- rust/src/symbol.rs | 6 +-- rust/src/types.rs | 38 +++++++++--------- 17 files changed, 120 insertions(+), 113 deletions(-) diff --git a/rust/src/backgroundtask.rs b/rust/src/backgroundtask.rs index 1eb090d7c..e62cfbcb0 100644 --- a/rust/src/backgroundtask.rs +++ b/rust/src/backgroundtask.rs @@ -112,13 +112,13 @@ unsafe impl CoreOwnedArrayProvider for BackgroundTask { } } -unsafe impl<'a> CoreArrayWrapper<'a> for BackgroundTask { - type Wrapped = Guard<'a, BackgroundTask>; +unsafe impl CoreArrayWrapper for BackgroundTask { + type Wrapped<'a> = Guard<'a, BackgroundTask>; - unsafe fn wrap_raw( + unsafe fn wrap_raw<'a>( raw: &'a *mut BNBackgroundTask, context: &'a (), - ) -> Guard<'a, BackgroundTask> { + ) -> Self::Wrapped<'a> { Guard::new(BackgroundTask::from_raw(*raw), context) } } diff --git a/rust/src/basicblock.rs b/rust/src/basicblock.rs index 73ad9362b..f28e596f1 100644 --- a/rust/src/basicblock.rs +++ b/rust/src/basicblock.rs @@ -76,10 +76,10 @@ unsafe impl<'a, C: 'a + BlockContext> CoreOwnedArrayProvider for Edge<'a, C> { } } -unsafe impl<'a, C: 'a + BlockContext> CoreArrayWrapper<'a> for Edge<'a, C> { - type Wrapped = Edge<'a, C>; +unsafe impl<'a, C: BlockContext> CoreArrayWrapper for Edge<'a, C> { + type Wrapped<'b> = Edge<'b, C> where 'a: 'b; - unsafe fn wrap_raw(raw: &'a Self::Raw, context: &'a Self::Context) -> Edge<'a, C> { + unsafe fn wrap_raw<'b>(raw: &'b Self::Raw, context: &'b Self::Context) -> Self::Wrapped<'b> { let edge_target = Guard::new( BasicBlock::from_raw(raw.target, context.orig_block.context.clone()), raw, @@ -309,10 +309,10 @@ unsafe impl CoreOwnedArrayProvider for BasicBlock { } } -unsafe impl<'a, C: 'a + BlockContext> CoreArrayWrapper<'a> for BasicBlock { - type Wrapped = Guard<'a, BasicBlock>; +unsafe impl CoreArrayWrapper for BasicBlock { + type Wrapped<'a> = Guard<'a, BasicBlock> where C: 'a; - unsafe fn wrap_raw(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { Guard::new(BasicBlock::from_raw(*raw, context.clone()), context) } } diff --git a/rust/src/callingconvention.rs b/rust/src/callingconvention.rs index 815f4d42a..991b46a94 100644 --- a/rust/src/callingconvention.rs +++ b/rust/src/callingconvention.rs @@ -662,10 +662,10 @@ unsafe impl CoreOwnedArrayProvider for CallingConvention { } } -unsafe impl<'a, A: Architecture> CoreArrayWrapper<'a> for CallingConvention { - type Wrapped = Guard<'a, CallingConvention>; +unsafe impl CoreArrayWrapper for CallingConvention { + type Wrapped<'a> = Guard<'a, CallingConvention>; - unsafe fn wrap_raw(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { Guard::new( CallingConvention { handle: *raw, diff --git a/rust/src/custombinaryview.rs b/rust/src/custombinaryview.rs index 956be9bdc..c69bad393 100644 --- a/rust/src/custombinaryview.rs +++ b/rust/src/custombinaryview.rs @@ -297,10 +297,12 @@ unsafe impl CoreOwnedArrayProvider for BinaryViewType { } } -unsafe impl<'a> CoreArrayWrapper<'a> for BinaryViewType { - type Wrapped = BinaryViewType; +unsafe impl CoreArrayWrapper for BinaryViewType { + // TODO there is nothing blocking the returned value from out-living the + // array, change it to &_ or Guard? + type Wrapped<'a> = BinaryViewType; - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { BinaryViewType(*raw) } } diff --git a/rust/src/downloadprovider.rs b/rust/src/downloadprovider.rs index 97ebbdbcb..cc2437221 100644 --- a/rust/src/downloadprovider.rs +++ b/rust/src/downloadprovider.rs @@ -71,10 +71,12 @@ unsafe impl CoreOwnedArrayProvider for DownloadProvider { } } -unsafe impl<'a> CoreArrayWrapper<'a> for DownloadProvider { - type Wrapped = DownloadProvider; +unsafe impl CoreArrayWrapper for DownloadProvider { + // TODO there is nothing blocking the returned value from out-living the + // array, change it to &_ or Guard? + type Wrapped<'a> = DownloadProvider; - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { DownloadProvider::from_raw(*raw) } } diff --git a/rust/src/function.rs b/rust/src/function.rs index 273a08612..d2f685dfb 100644 --- a/rust/src/function.rs +++ b/rust/src/function.rs @@ -30,7 +30,6 @@ pub use binaryninjacore_sys::BNAnalysisSkipReason as AnalysisSkipReason; pub use binaryninjacore_sys::BNFunctionAnalysisSkipOverride as FunctionAnalysisSkipOverride; pub use binaryninjacore_sys::BNFunctionUpdateType as FunctionUpdateType; - use std::hash::Hash; use std::{fmt, mem}; @@ -407,10 +406,10 @@ unsafe impl CoreOwnedArrayProvider for Function { } } -unsafe impl<'a> CoreArrayWrapper<'a> for Function { - type Wrapped = Guard<'a, Function>; +unsafe impl CoreArrayWrapper for Function { + type Wrapped<'a> = Guard<'a, Function>; - unsafe fn wrap_raw(raw: &'a *mut BNFunction, context: &'a ()) -> Guard<'a, Function> { + unsafe fn wrap_raw<'a>(raw: &'a *mut BNFunction, context: &'a ()) -> Self::Wrapped<'a> { Guard::new(Function { handle: *raw }, context) } } @@ -461,10 +460,10 @@ unsafe impl CoreOwnedArrayProvider for AddressRange { } } -unsafe impl<'a> CoreArrayWrapper<'a> for AddressRange { - type Wrapped = &'a AddressRange; +unsafe impl CoreArrayWrapper for AddressRange { + type Wrapped<'a> = &'a AddressRange; - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { mem::transmute(raw) } } diff --git a/rust/src/linearview.rs b/rust/src/linearview.rs index 09968fc68..31b05ad27 100644 --- a/rust/src/linearview.rs +++ b/rust/src/linearview.rs @@ -423,10 +423,10 @@ unsafe impl CoreOwnedArrayProvider for LinearDisassemblyLine { } } -unsafe impl<'a> CoreArrayWrapper<'a> for LinearDisassemblyLine { - type Wrapped = Guard<'a, LinearDisassemblyLine>; +unsafe impl CoreArrayWrapper for LinearDisassemblyLine { + type Wrapped<'a> = Guard<'a, LinearDisassemblyLine>; - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { Guard::new(LinearDisassemblyLine::from_raw(raw), _context) } } diff --git a/rust/src/metadata.rs b/rust/src/metadata.rs index e29789e52..35f3b8124 100644 --- a/rust/src/metadata.rs +++ b/rust/src/metadata.rs @@ -343,10 +343,10 @@ unsafe impl CoreOwnedArrayProvider for Metadata { } } -unsafe impl<'a> CoreArrayWrapper<'a> for Metadata { - type Wrapped = Guard<'a, Metadata>; +unsafe impl CoreArrayWrapper for Metadata { + type Wrapped<'a> = Guard<'a, Metadata>; - unsafe fn wrap_raw(raw: &'a *mut BNMetadata, context: &'a ()) -> Guard<'a, Metadata> { + unsafe fn wrap_raw<'a>(raw: &'a *mut BNMetadata, context: &'a ()) -> Self::Wrapped<'a> { Guard::new(Metadata::from_raw(*raw), context) } } diff --git a/rust/src/platform.rs b/rust/src/platform.rs index 3df5e7c47..42e2f80cc 100644 --- a/rust/src/platform.rs +++ b/rust/src/platform.rs @@ -373,10 +373,10 @@ unsafe impl CoreOwnedArrayProvider for Platform { } } -unsafe impl<'a> CoreArrayWrapper<'a> for Platform { - type Wrapped = Guard<'a, Platform>; +unsafe impl CoreArrayWrapper for Platform { + type Wrapped<'a> = Guard<'a, Platform>; - unsafe fn wrap_raw(raw: &'a *mut BNPlatform, context: &'a ()) -> Guard<'a, Platform> { + unsafe fn wrap_raw<'a>(raw: &'a *mut BNPlatform, context: &'a ()) -> Self::Wrapped<'a> { debug_assert!(!raw.is_null()); Guard::new(Platform { handle: *raw }, context) } diff --git a/rust/src/rc.rs b/rust/src/rc.rs index cdcae1792..eaf8e5d26 100644 --- a/rust/src/rc.rs +++ b/rust/src/rc.rs @@ -196,14 +196,12 @@ pub unsafe trait CoreOwnedArrayProvider: CoreArrayProvider { unsafe fn free(raw: *mut Self::Raw, count: usize, context: &Self::Context); } -pub unsafe trait CoreArrayWrapper<'a>: CoreArrayProvider -where - Self::Raw: 'a, - Self::Context: 'a, -{ - type Wrapped: 'a; +pub unsafe trait CoreArrayWrapper: CoreArrayProvider { + type Wrapped<'a> + where + Self: 'a; - unsafe fn wrap_raw(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped; + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a>; } pub struct Array { @@ -250,16 +248,16 @@ impl Array

{ } } -impl<'a, P: 'a + CoreArrayWrapper<'a> + CoreOwnedArrayProvider> Array

{ +impl Array

{ #[inline] - pub fn get(&'a self, index: usize) -> P::Wrapped { + pub fn get(&self, index: usize) -> P::Wrapped<'_> { unsafe { let backing = slice::from_raw_parts(self.contents, self.count); P::wrap_raw(&backing[index], &self.context) } } - pub fn iter(&'a self) -> ArrayIter<'a, P> { + pub fn iter(&self) -> ArrayIter

{ ArrayIter { it: unsafe { slice::from_raw_parts(self.contents, self.count).iter() }, context: &self.context, @@ -267,8 +265,8 @@ impl<'a, P: 'a + CoreArrayWrapper<'a> + CoreOwnedArrayProvider> Array

{ } } -impl<'a, P: 'a + CoreArrayWrapper<'a> + CoreOwnedArrayProvider> IntoIterator for &'a Array

{ - type Item = P::Wrapped; +impl<'a, P: CoreArrayWrapper + CoreOwnedArrayProvider> IntoIterator for &'a Array

{ + type Item = P::Wrapped<'a>; type IntoIter = ArrayIter<'a, P>; fn into_iter(self) -> Self::IntoIter { @@ -323,16 +321,16 @@ impl ArrayGuard

{ } } -impl<'a, P: 'a + CoreArrayWrapper<'a> + CoreArrayProvider> ArrayGuard

{ +impl ArrayGuard

{ #[inline] - pub fn get(&'a self, index: usize) -> P::Wrapped { + pub fn get(&self, index: usize) -> P::Wrapped<'_> { unsafe { let backing = slice::from_raw_parts(self.contents, self.count); P::wrap_raw(&backing[index], &self.context) } } - pub fn iter(&'a self) -> ArrayIter<'a, P> { + pub fn iter(&self) -> ArrayIter

{ ArrayIter { it: unsafe { slice::from_raw_parts(self.contents, self.count).iter() }, context: &self.context, @@ -340,8 +338,8 @@ impl<'a, P: 'a + CoreArrayWrapper<'a> + CoreArrayProvider> ArrayGuard

{ } } -impl<'a, P: 'a + CoreArrayWrapper<'a> + CoreArrayProvider> IntoIterator for &'a ArrayGuard

{ - type Item = P::Wrapped; +impl<'a, P: CoreArrayWrapper + CoreArrayProvider> IntoIterator for &'a ArrayGuard

{ + type Item = P::Wrapped<'a>; type IntoIter = ArrayIter<'a, P>; fn into_iter(self) -> Self::IntoIter { @@ -351,27 +349,27 @@ impl<'a, P: 'a + CoreArrayWrapper<'a> + CoreArrayProvider> IntoIterator for &'a pub struct ArrayIter<'a, P> where - P: 'a + CoreArrayWrapper<'a>, + P: CoreArrayWrapper, { it: slice::Iter<'a, P::Raw>, context: &'a P::Context, } -unsafe impl<'a, P> Send for ArrayIter<'a, P> +unsafe impl

Send for ArrayIter<'_, P> where - P: CoreArrayWrapper<'a>, + P: CoreArrayWrapper, P::Context: Sync, { } impl<'a, P> Iterator for ArrayIter<'a, P> where - P: 'a + CoreArrayWrapper<'a>, + P: 'a + CoreArrayWrapper, { - type Item = P::Wrapped; + type Item = P::Wrapped<'a>; #[inline] - fn next(&mut self) -> Option { + fn next(&mut self) -> Option { self.it .next() .map(|r| unsafe { P::wrap_raw(r, self.context) }) @@ -385,7 +383,7 @@ where impl<'a, P> ExactSizeIterator for ArrayIter<'a, P> where - P: 'a + CoreArrayWrapper<'a>, + P: 'a + CoreArrayWrapper, { #[inline] fn len(&self) -> usize { @@ -395,10 +393,10 @@ where impl<'a, P> DoubleEndedIterator for ArrayIter<'a, P> where - P: 'a + CoreArrayWrapper<'a>, + P: 'a + CoreArrayWrapper, { #[inline] - fn next_back(&mut self) -> Option { + fn next_back(&mut self) -> Option> { self.it .next_back() .map(|r| unsafe { P::wrap_raw(r, self.context) }) @@ -412,20 +410,20 @@ use rayon::prelude::*; use rayon::iter::plumbing::*; #[cfg(feature = "rayon")] -impl<'a, P> Array

+impl

Array

where - P: 'a + CoreArrayWrapper<'a> + CoreOwnedArrayProvider, + P: CoreArrayWrapper + CoreOwnedArrayProvider, P::Context: Sync, - P::Wrapped: Send, + for<'a> P::Wrapped<'a>: Send, { - pub fn par_iter(&'a self) -> ParArrayIter<'a, P> { + pub fn par_iter(&self) -> ParArrayIter<'_, P> { ParArrayIter { it: self.iter() } } } #[cfg(feature = "rayon")] pub struct ParArrayIter<'a, P> where - P: 'a + CoreArrayWrapper<'a>, + P: 'a + CoreArrayWrapper, ArrayIter<'a, P>: Send, { it: ArrayIter<'a, P>, @@ -434,11 +432,11 @@ where #[cfg(feature = "rayon")] impl<'a, P> ParallelIterator for ParArrayIter<'a, P> where - P: 'a + CoreArrayWrapper<'a>, - P::Wrapped: Send, + P: 'a + CoreArrayWrapper, + P::Wrapped<'a>: Send, ArrayIter<'a, P>: Send, { - type Item = P::Wrapped; + type Item = P::Wrapped<'a>; fn drive_unindexed(self, consumer: C) -> C::Result where @@ -455,8 +453,8 @@ where #[cfg(feature = "rayon")] impl<'a, P> IndexedParallelIterator for ParArrayIter<'a, P> where - P: 'a + CoreArrayWrapper<'a>, - P::Wrapped: Send, + P: 'a + CoreArrayWrapper, + P::Wrapped<'a>: Send, ArrayIter<'a, P>: Send, { fn drive(self, consumer: C) -> C::Result @@ -481,7 +479,7 @@ where #[cfg(feature = "rayon")] struct ArrayIterProducer<'a, P> where - P: 'a + CoreArrayWrapper<'a>, + P: 'a + CoreArrayWrapper, ArrayIter<'a, P>: Send, { it: ArrayIter<'a, P>, @@ -490,10 +488,10 @@ where #[cfg(feature = "rayon")] impl<'a, P> Producer for ArrayIterProducer<'a, P> where - P: 'a + CoreArrayWrapper<'a>, + P: 'a + CoreArrayWrapper, ArrayIter<'a, P>: Send, { - type Item = P::Wrapped; + type Item = P::Wrapped<'a>; type IntoIter = ArrayIter<'a, P>; fn into_iter(self) -> ArrayIter<'a, P> { diff --git a/rust/src/references.rs b/rust/src/references.rs index 76ac44934..a30f3f5e2 100644 --- a/rust/src/references.rs +++ b/rust/src/references.rs @@ -64,10 +64,12 @@ unsafe impl CoreOwnedArrayProvider for CodeReference { } } -unsafe impl<'a> CoreArrayWrapper<'a> for CodeReference { - type Wrapped = CodeReference; +unsafe impl CoreArrayWrapper for CodeReference { + // TODO there is nothing blocking the returned value from out-living the + // array, change it to Guard? + type Wrapped<'a> = CodeReference; - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { CodeReference::new(raw) } } @@ -85,10 +87,10 @@ unsafe impl CoreOwnedArrayProvider for DataReference { } } -unsafe impl<'a> CoreArrayWrapper<'a> for DataReference { - type Wrapped = DataReference; +unsafe impl CoreArrayWrapper for DataReference { + type Wrapped<'a> = DataReference; - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { DataReference { address: *raw } } } diff --git a/rust/src/relocation.rs b/rust/src/relocation.rs index f9cbb3c52..47ec2ac0f 100644 --- a/rust/src/relocation.rs +++ b/rust/src/relocation.rs @@ -227,9 +227,11 @@ unsafe impl CoreOwnedArrayProvider for Relocation { } } -unsafe impl<'a> CoreArrayWrapper<'a> for Relocation { - type Wrapped = Relocation; - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { +unsafe impl CoreArrayWrapper for Relocation { + // TODO there is nothing blocking the returned value from out-living the + // array, change it to &_ or Guard? + type Wrapped<'a> = Relocation; + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { Relocation(*raw) } } diff --git a/rust/src/section.rs b/rust/src/section.rs index 25e8ea5d0..06d09fcaa 100644 --- a/rust/src/section.rs +++ b/rust/src/section.rs @@ -179,10 +179,10 @@ unsafe impl CoreOwnedArrayProvider for Section { } } -unsafe impl<'a> CoreArrayWrapper<'a> for Section { - type Wrapped = Guard<'a, Section>; +unsafe impl CoreArrayWrapper for Section { + type Wrapped<'a> = Guard<'a, Section>; - unsafe fn wrap_raw(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { Guard::new(Section::from_raw(*raw), context) } } diff --git a/rust/src/segment.rs b/rust/src/segment.rs index 2de785c5f..32f9db3ab 100644 --- a/rust/src/segment.rs +++ b/rust/src/segment.rs @@ -209,10 +209,10 @@ unsafe impl CoreOwnedArrayProvider for Segment { } } -unsafe impl<'a> CoreArrayWrapper<'a> for Segment { - type Wrapped = Guard<'a, Segment>; +unsafe impl CoreArrayWrapper for Segment { + type Wrapped<'a> = Guard<'a, Segment>; - unsafe fn wrap_raw(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { Guard::new(Segment::from_raw(*raw), context) } } diff --git a/rust/src/string.rs b/rust/src/string.rs index 1011ca498..75942da91 100644 --- a/rust/src/string.rs +++ b/rust/src/string.rs @@ -169,10 +169,10 @@ unsafe impl CoreOwnedArrayProvider for BnString { } } -unsafe impl<'a> CoreArrayWrapper<'a> for BnString { - type Wrapped = &'a str; +unsafe impl CoreArrayWrapper for BnString { + type Wrapped<'a> = &'a str; - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { CStr::from_ptr(*raw).to_str().unwrap() } } diff --git a/rust/src/symbol.rs b/rust/src/symbol.rs index cf4c3b107..bdf0d8552 100644 --- a/rust/src/symbol.rs +++ b/rust/src/symbol.rs @@ -335,10 +335,10 @@ unsafe impl CoreOwnedArrayProvider for Symbol { } } -unsafe impl<'a> CoreArrayWrapper<'a> for Symbol { - type Wrapped = Guard<'a, Symbol>; +unsafe impl CoreArrayWrapper for Symbol { + type Wrapped<'a> = Guard<'a, Symbol>; - unsafe fn wrap_raw(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { Guard::new(Symbol::from_raw(*raw), context) } } diff --git a/rust/src/types.rs b/rust/src/types.rs index 2a2a50d42..43d48b3fd 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -1453,10 +1453,10 @@ unsafe impl CoreOwnedArrayProvider for NamedTypedVariable { } } -unsafe impl<'a> CoreArrayWrapper<'a> for NamedTypedVariable { - type Wrapped = ManuallyDrop; +unsafe impl CoreArrayWrapper for NamedTypedVariable { + type Wrapped<'a> = ManuallyDrop; - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { ManuallyDrop::new(NamedTypedVariable { var: raw.var, ty: raw.type_, @@ -2348,10 +2348,10 @@ unsafe impl CoreOwnedArrayProvider for QualifiedName { } } -unsafe impl<'a> CoreArrayWrapper<'a> for QualifiedName { - type Wrapped = &'a QualifiedName; +unsafe impl CoreArrayWrapper for QualifiedName { + type Wrapped<'a> = &'a QualifiedName; - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { mem::transmute(raw) } } @@ -2390,10 +2390,10 @@ unsafe impl CoreOwnedArrayProvider for QualifiedNameAndType { } } -unsafe impl<'a> CoreArrayWrapper<'a> for QualifiedNameAndType { - type Wrapped = &'a QualifiedNameAndType; +unsafe impl CoreArrayWrapper for QualifiedNameAndType { + type Wrapped<'a> = &'a QualifiedNameAndType; - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { mem::transmute(raw) } } @@ -2436,10 +2436,10 @@ unsafe impl CoreOwnedArrayProvider for QualifiedNameTypeAndId { } } -unsafe impl<'a> CoreArrayWrapper<'a> for QualifiedNameTypeAndId { - type Wrapped = &'a QualifiedNameTypeAndId; +unsafe impl CoreArrayWrapper for QualifiedNameTypeAndId { + type Wrapped<'a> = &'a QualifiedNameTypeAndId; - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { mem::transmute(raw) } } @@ -2497,10 +2497,11 @@ unsafe impl CoreOwnedArrayProvider for NameAndType { } } -unsafe impl<'a, S: 'a + BnStrCompatible> CoreArrayWrapper<'a> for NameAndType { - type Wrapped = &'a NameAndType; +unsafe impl CoreArrayWrapper for NameAndType { + type Wrapped<'a> = &'a NameAndType where S: 'a; - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + // TODO this is not always valid, because the type is not transparent mem::transmute(raw) } } @@ -2544,10 +2545,11 @@ unsafe impl CoreOwnedArrayProvider for DataVariable { } } -unsafe impl<'a> CoreArrayWrapper<'a> for DataVariable { - type Wrapped = &'a DataVariable; +unsafe impl CoreArrayWrapper for DataVariable { + type Wrapped<'a> = &'a DataVariable; - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + // TODO this is not always valid, because the type is not transparent mem::transmute(raw) } } From 9b0175bb0c116e22f00d6a3137c52b136be04bf7 Mon Sep 17 00:00:00 2001 From: Rubens Brandao Date: Thu, 11 Apr 2024 06:29:03 -0300 Subject: [PATCH 02/50] replace Vec with Box<[T]> where capacity eq to len --- rust/src/disassembly.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/rust/src/disassembly.rs b/rust/src/disassembly.rs index f213fa0eb..65606ef63 100644 --- a/rust/src/disassembly.rs +++ b/rust/src/disassembly.rs @@ -307,10 +307,9 @@ impl std::fmt::Display for DisassemblyTextLine { } impl From> for DisassemblyTextLine { - fn from(mut tokens: Vec) -> Self { - tokens.shrink_to_fit(); + fn from(tokens: Vec) -> Self { + let mut tokens: Box<[_]> = tokens.into(); - assert!(tokens.len() == tokens.capacity()); // TODO: let (tokens_pointer, tokens_len, _) = unsafe { tokens.into_raw_parts() }; // Can't use for now...still a rust nightly feature let tokens_pointer = tokens.as_mut_ptr(); let tokens_len = tokens.len(); @@ -345,14 +344,11 @@ impl From> for DisassemblyTextLine { impl From<&Vec<&str>> for DisassemblyTextLine { fn from(string_tokens: &Vec<&str>) -> Self { - let mut tokens: Vec = Vec::with_capacity(string_tokens.len()); - tokens.extend( - string_tokens.iter().map(|&token| { - InstructionTextToken::new(token, InstructionTextTokenContents::Text).0 - }), - ); - - assert!(tokens.len() == tokens.capacity()); + let mut tokens: Box<[BNInstructionTextToken]> = string_tokens + .iter() + .map(|&token| InstructionTextToken::new(token, InstructionTextTokenContents::Text).0) + .collect(); + // let (tokens_pointer, tokens_len, _) = unsafe { tokens.into_raw_parts() }; // Can't use for now...still a rust nighly feature let tokens_pointer = tokens.as_mut_ptr(); let tokens_len = tokens.len(); From 38c68b59ea6c22d7d71cd79df3f3bb123f75bbce Mon Sep 17 00:00:00 2001 From: Rubens Brandao Date: Thu, 11 Apr 2024 08:07:55 -0300 Subject: [PATCH 03/50] update DisassemblyTextLine Drop impl to use Box --- rust/src/disassembly.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rust/src/disassembly.rs b/rust/src/disassembly.rs index 65606ef63..b9cb05ccb 100644 --- a/rust/src/disassembly.rs +++ b/rust/src/disassembly.rs @@ -412,9 +412,8 @@ impl Default for DisassemblyTextLine { impl Drop for DisassemblyTextLine { fn drop(&mut self) { - unsafe { - Vec::from_raw_parts(self.0.tokens, self.0.count, self.0.count); - } + let ptr = core::ptr::slice_from_raw_parts_mut(self.0.tokens, self.0.count); + let _ = unsafe { Box::from_raw(ptr) }; } } From c54ccd257a0c517b7ba429aa155f600878a6067a Mon Sep 17 00:00:00 2001 From: Rubens Brandao Date: Thu, 11 Apr 2024 08:12:05 -0300 Subject: [PATCH 04/50] guard DisassemblyTextLine Drop against null ptr --- rust/src/disassembly.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rust/src/disassembly.rs b/rust/src/disassembly.rs index b9cb05ccb..8b3a4cb5a 100644 --- a/rust/src/disassembly.rs +++ b/rust/src/disassembly.rs @@ -412,8 +412,10 @@ impl Default for DisassemblyTextLine { impl Drop for DisassemblyTextLine { fn drop(&mut self) { - let ptr = core::ptr::slice_from_raw_parts_mut(self.0.tokens, self.0.count); - let _ = unsafe { Box::from_raw(ptr) }; + if !self.0.tokens.is_null() { + let ptr = core::ptr::slice_from_raw_parts_mut(self.0.tokens, self.0.count); + let _ = unsafe { Box::from_raw(ptr) }; + } } } From 9fdd5d0bd053e274a5ae76ab22b32b61c2b80c87 Mon Sep 17 00:00:00 2001 From: Rubens Brandao Date: Sun, 14 Apr 2024 09:38:13 -0300 Subject: [PATCH 05/50] fix metadata impl From lists --- rust/src/metadata.rs | 61 +++++++++++++++----------------------------- 1 file changed, 21 insertions(+), 40 deletions(-) diff --git a/rust/src/metadata.rs b/rust/src/metadata.rs index e29789e52..e9c83dd53 100644 --- a/rust/src/metadata.rs +++ b/rust/src/metadata.rs @@ -441,16 +441,15 @@ impl From<&Array> for Ref { impl From>> for Ref { fn from(value: HashMap>) -> Self { - let mut key_refs: Vec = vec![]; - let mut keys: Vec<*const c_char> = vec![]; - let mut values: Vec<*mut BNMetadata> = vec![]; - for (k, v) in value.into_iter() { - key_refs.push(k.into_bytes_with_nul()); - values.push(v.as_ref().handle); - } - for k in &key_refs { - keys.push(k.as_ref().as_ptr() as *const c_char); - } + let data: Vec<(S::Result, Ref)> = value + .into_iter() + .map(|(k, v)| (k.into_bytes_with_nul(), v)) + .collect(); + let mut keys: Vec<*const c_char> = data + .iter() + .map(|(k, _)| k.as_ref().as_ptr() as *const c_char) + .collect(); + let mut values: Vec<*mut BNMetadata> = data.iter().map(|(_, v)| v.handle).collect(); unsafe { Metadata::ref_from_raw(BNCreateMetadataValueStore( @@ -464,17 +463,15 @@ impl From>> for Ref { impl>> From<&[(S, T)]> for Ref { fn from(value: &[(S, T)]) -> Self { - let mut key_refs: Vec = vec![]; - let mut keys: Vec<*const c_char> = vec![]; - let mut values: Vec<*mut BNMetadata> = vec![]; - for (k, v) in value.iter() { - key_refs.push(k.into_bytes_with_nul()); - let value_metadata: Ref = v.into(); - values.push(value_metadata.handle); - } - for k in &key_refs { - keys.push(k.as_ref().as_ptr() as *const c_char); - } + let data: Vec<(S::Result, Ref)> = value + .into_iter() + .map(|(k, v)| (k.into_bytes_with_nul(), v.into())) + .collect(); + let mut keys: Vec<*const c_char> = data + .iter() + .map(|(k, _)| k.as_ref().as_ptr() as *const c_char) + .collect(); + let mut values: Vec<*mut BNMetadata> = data.iter().map(|(_, v)| v.handle).collect(); unsafe { Metadata::ref_from_raw(BNCreateMetadataValueStore( @@ -490,25 +487,9 @@ impl>, const N: usize> From<[(S for Ref { fn from(value: [(S, T); N]) -> Self { - let mut key_refs: Vec = vec![]; - let mut keys: Vec<*const c_char> = vec![]; - let mut values: Vec<*mut BNMetadata> = vec![]; - for (k, v) in value.into_iter() { - key_refs.push(k.into_bytes_with_nul()); - let value_metadata: Ref = v.into(); - values.push(value_metadata.handle); - } - for k in &key_refs { - keys.push(k.as_ref().as_ptr() as *const c_char); - } - - unsafe { - Metadata::ref_from_raw(BNCreateMetadataValueStore( - keys.as_mut_ptr(), - values.as_mut_ptr(), - keys.len(), - )) - } + let slice = &value[..]; + // use the `impl From<&[(S, T)]>` + slice.into() } } From 152219cd95f9dafb2acfe4aab7baa17204e02b4e Mon Sep 17 00:00:00 2001 From: Rubens Brandao Date: Tue, 16 Apr 2024 16:54:30 -0300 Subject: [PATCH 06/50] remove recursive conversion for Metadata --- rust/src/metadata.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/rust/src/metadata.rs b/rust/src/metadata.rs index e9c83dd53..d92878b9c 100644 --- a/rust/src/metadata.rs +++ b/rust/src/metadata.rs @@ -403,12 +403,6 @@ impl From<&str> for Ref { } } -impl>> From<&T> for Ref { - fn from(value: &T) -> Self { - value.into() - } -} - impl From<&Vec> for Ref { fn from(value: &Vec) -> Self { unsafe { Metadata::ref_from_raw(BNCreateMetadataRawData(value.as_ptr(), value.len())) } @@ -461,7 +455,11 @@ impl From>> for Ref { } } -impl>> From<&[(S, T)]> for Ref { +impl From<&[(S, T)]> for Ref +where + S: BnStrCompatible + Copy, + for<'a> &'a T: Into>, +{ fn from(value: &[(S, T)]) -> Self { let data: Vec<(S::Result, Ref)> = value .into_iter() @@ -483,8 +481,10 @@ impl>> From<&[(S, T)]> for Ref< } } -impl>, const N: usize> From<[(S, T); N]> - for Ref +impl From<[(S, T); N]> for Ref +where + S: BnStrCompatible + Copy, + for<'a> &'a T: Into>, { fn from(value: [(S, T); N]) -> Self { let slice = &value[..]; From f3c1a3cb3cbdd25d5f556d1ab770f8cbc29fd567 Mon Sep 17 00:00:00 2001 From: Rubens Brandao Date: Tue, 16 Apr 2024 17:03:20 -0300 Subject: [PATCH 07/50] add guard to array with unbound return types --- rust/src/custombinaryview.rs | 6 ++---- rust/src/downloadprovider.rs | 8 +++----- rust/src/references.rs | 8 +++----- rust/src/relocation.rs | 7 +++---- rust/src/types.rs | 2 -- 5 files changed, 11 insertions(+), 20 deletions(-) diff --git a/rust/src/custombinaryview.rs b/rust/src/custombinaryview.rs index c69bad393..898102d65 100644 --- a/rust/src/custombinaryview.rs +++ b/rust/src/custombinaryview.rs @@ -298,12 +298,10 @@ unsafe impl CoreOwnedArrayProvider for BinaryViewType { } unsafe impl CoreArrayWrapper for BinaryViewType { - // TODO there is nothing blocking the returned value from out-living the - // array, change it to &_ or Guard? - type Wrapped<'a> = BinaryViewType; + type Wrapped<'a> = Guard<'a, BinaryViewType>; unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { - BinaryViewType(*raw) + Guard::new(BinaryViewType(*raw), &()) } } diff --git a/rust/src/downloadprovider.rs b/rust/src/downloadprovider.rs index cc2437221..8334e0cea 100644 --- a/rust/src/downloadprovider.rs +++ b/rust/src/downloadprovider.rs @@ -1,5 +1,5 @@ use crate::rc::{ - Array, CoreArrayProvider, CoreArrayWrapper, CoreOwnedArrayProvider, Ref, RefCountable, + Array, CoreArrayProvider, CoreArrayWrapper, CoreOwnedArrayProvider, Guard, Ref, RefCountable, }; use crate::settings::Settings; use crate::string::{BnStrCompatible, BnString}; @@ -72,12 +72,10 @@ unsafe impl CoreOwnedArrayProvider for DownloadProvider { } unsafe impl CoreArrayWrapper for DownloadProvider { - // TODO there is nothing blocking the returned value from out-living the - // array, change it to &_ or Guard? - type Wrapped<'a> = DownloadProvider; + type Wrapped<'a> = Guard<'a, DownloadProvider>; unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { - DownloadProvider::from_raw(*raw) + Guard::new(DownloadProvider::from_raw(*raw), &()) } } diff --git a/rust/src/references.rs b/rust/src/references.rs index a30f3f5e2..8b4ebb874 100644 --- a/rust/src/references.rs +++ b/rust/src/references.rs @@ -1,6 +1,6 @@ use crate::architecture::CoreArchitecture; use crate::function::Function; -use crate::rc::{CoreArrayProvider, CoreArrayWrapper, CoreOwnedArrayProvider, Ref}; +use crate::rc::{CoreArrayProvider, CoreArrayWrapper, CoreOwnedArrayProvider, Guard, Ref}; use binaryninjacore_sys::{BNFreeCodeReferences, BNFreeDataReferences, BNReferenceSource}; use std::mem::ManuallyDrop; @@ -65,12 +65,10 @@ unsafe impl CoreOwnedArrayProvider for CodeReference { } unsafe impl CoreArrayWrapper for CodeReference { - // TODO there is nothing blocking the returned value from out-living the - // array, change it to Guard? - type Wrapped<'a> = CodeReference; + type Wrapped<'a> = Guard<'a, CodeReference>; unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { - CodeReference::new(raw) + Guard::new(CodeReference::new(raw), &()) } } diff --git a/rust/src/relocation.rs b/rust/src/relocation.rs index 47ec2ac0f..455f5a5a3 100644 --- a/rust/src/relocation.rs +++ b/rust/src/relocation.rs @@ -1,3 +1,4 @@ +use crate::rc::Guard; use crate::string::BnStrCompatible; use crate::{ architecture::{Architecture, CoreArchitecture}, @@ -228,11 +229,9 @@ unsafe impl CoreOwnedArrayProvider for Relocation { } unsafe impl CoreArrayWrapper for Relocation { - // TODO there is nothing blocking the returned value from out-living the - // array, change it to &_ or Guard? - type Wrapped<'a> = Relocation; + type Wrapped<'a> = Guard<'a, Relocation>; unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { - Relocation(*raw) + Guard::new(Relocation(*raw), &()) } } diff --git a/rust/src/types.rs b/rust/src/types.rs index 43d48b3fd..280d50fd3 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -2501,7 +2501,6 @@ unsafe impl CoreArrayWrapper for NameAndType { type Wrapped<'a> = &'a NameAndType where S: 'a; unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { - // TODO this is not always valid, because the type is not transparent mem::transmute(raw) } } @@ -2549,7 +2548,6 @@ unsafe impl CoreArrayWrapper for DataVariable { type Wrapped<'a> = &'a DataVariable; unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { - // TODO this is not always valid, because the type is not transparent mem::transmute(raw) } } From 90040508478f4551eb303a5056026fd59bb0cdc6 Mon Sep 17 00:00:00 2001 From: Rubens Brandao Date: Wed, 17 Apr 2024 07:23:15 -0300 Subject: [PATCH 08/50] allow DataVariable and NameAndType to be transmutable --- rust/examples/dwarf/dwarf_export/src/lib.rs | 8 +- rust/examples/dwarf/shared/src/lib.rs | 6 +- rust/src/architecture.rs | 6 +- rust/src/binaryview.rs | 12 ++- rust/src/debuginfo.rs | 12 +-- rust/src/rc.rs | 1 + rust/src/types.rs | 89 +++++++++++++-------- 7 files changed, 84 insertions(+), 50 deletions(-) diff --git a/rust/examples/dwarf/dwarf_export/src/lib.rs b/rust/examples/dwarf/dwarf_export/src/lib.rs index 7143f6dd9..ef71f1ae7 100644 --- a/rust/examples/dwarf/dwarf_export/src/lib.rs +++ b/rust/examples/dwarf/dwarf_export/src/lib.rs @@ -551,7 +551,7 @@ fn export_data_vars( dwarf.unit.get_mut(var_die_uid).set( gimli::DW_AT_name, AttributeValue::String( - format!("data_{:x}", data_variable.address) + format!("data_{:x}", data_variable.address()) .as_bytes() .to_vec(), ), @@ -559,15 +559,15 @@ fn export_data_vars( } let mut variable_location = Expression::new(); - variable_location.op_addr(Address::Constant(data_variable.address)); + variable_location.op_addr(Address::Constant(data_variable.address())); dwarf.unit.get_mut(var_die_uid).set( gimli::DW_AT_location, AttributeValue::Exprloc(variable_location), ); if let Some(target_die_uid) = export_type( - format!("{}", data_variable.t.contents), - data_variable.t.contents.as_ref(), + format!("{}", data_variable.t()), + data_variable.t(), bv, defined_types, dwarf, diff --git a/rust/examples/dwarf/shared/src/lib.rs b/rust/examples/dwarf/shared/src/lib.rs index 718dcb8cd..7712ff3b3 100644 --- a/rust/examples/dwarf/shared/src/lib.rs +++ b/rust/examples/dwarf/shared/src/lib.rs @@ -88,11 +88,11 @@ pub fn create_section_reader<'a, Endian: 'a + Endianity>( if let Some(data_var) = view .data_variables() .iter() - .find(|var| var.address == symbol.address()) + .find(|var| var.address() == symbol.address()) { // TODO : This should eventually be wrapped by some DataView sorta thingy thing, like how python does it - let data_type = data_var.type_with_confidence().contents; - let data = view.read_vec(data_var.address, data_type.width() as usize); + let data_type = data_var.t(); + let data = view.read_vec(data_var.address(), data_type.width() as usize); let element_type = data_type.element_type().unwrap().contents; if let Some(current_section_header) = data diff --git a/rust/src/architecture.rs b/rust/src/architecture.rs index 9a7b3e723..82df7441c 100644 --- a/rust/src/architecture.rs +++ b/rust/src/architecture.rs @@ -313,7 +313,7 @@ pub trait Intrinsic: Sized + Clone + Copy { fn id(&self) -> u32; /// Reeturns the list of the input names and types for this intrinsic. - fn inputs(&self) -> Vec>; + fn inputs(&self) -> Vec; /// Returns the list of the output types for this intrinsic. fn outputs(&self) -> Vec>>; @@ -650,7 +650,7 @@ impl Intrinsic for UnusedIntrinsic { fn id(&self) -> u32 { unreachable!() } - fn inputs(&self) -> Vec> { + fn inputs(&self) -> Vec { unreachable!() } fn outputs(&self) -> Vec>> { @@ -992,7 +992,7 @@ impl Intrinsic for crate::architecture::CoreIntrinsic { self.1 } - fn inputs(&self) -> Vec> { + fn inputs(&self) -> Vec { let mut count: usize = 0; unsafe { diff --git a/rust/src/binaryview.rs b/rust/src/binaryview.rs index 52d688b84..bcb3f57fe 100644 --- a/rust/src/binaryview.rs +++ b/rust/src/binaryview.rs @@ -576,14 +576,22 @@ pub trait BinaryViewExt: BinaryViewBase { fn define_auto_data_var(&self, dv: DataVariable) { unsafe { - BNDefineDataVariable(self.as_ref().handle, dv.address, &mut dv.t.into()); + BNDefineDataVariable( + self.as_ref().handle, + dv.address(), + &mut dv.type_with_confidence().into(), + ); } } /// You likely would also like to call [`Self::define_user_symbol`] to bind this data variable with a name fn define_user_data_var(&self, dv: DataVariable) { unsafe { - BNDefineUserDataVariable(self.as_ref().handle, dv.address, &mut dv.t.into()); + BNDefineUserDataVariable( + self.as_ref().handle, + dv.address(), + &mut dv.type_with_confidence().into(), + ); } } diff --git a/rust/src/debuginfo.rs b/rust/src/debuginfo.rs index ab4f8f6b3..7bb5e36ff 100644 --- a/rust/src/debuginfo.rs +++ b/rust/src/debuginfo.rs @@ -376,7 +376,7 @@ impl DebugInfo { } /// Returns a generator of all types provided by a named DebugInfoParser - pub fn types_by_name(&self, parser_name: S) -> Vec> { + pub fn types_by_name(&self, parser_name: S) -> Vec { let parser_name = parser_name.into_bytes_with_nul(); let mut count: usize = 0; @@ -387,10 +387,10 @@ impl DebugInfo { &mut count, ) }; - let result: Vec> = unsafe { + let result: Vec = unsafe { slice::from_raw_parts_mut(debug_types_ptr, count) .iter() - .map(NameAndType::::from_raw) + .map(NameAndType::from_raw) .collect() }; @@ -399,13 +399,13 @@ impl DebugInfo { } /// A generator of all types provided by DebugInfoParsers - pub fn types(&self) -> Vec> { + pub fn types(&self) -> Vec { let mut count: usize = 0; let debug_types_ptr = unsafe { BNGetDebugTypes(self.handle, ptr::null_mut(), &mut count) }; - let result: Vec> = unsafe { + let result: Vec = unsafe { slice::from_raw_parts_mut(debug_types_ptr, count) .iter() - .map(NameAndType::::from_raw) + .map(NameAndType::from_raw) .collect() }; diff --git a/rust/src/rc.rs b/rust/src/rc.rs index cdcae1792..91e515657 100644 --- a/rust/src/rc.rs +++ b/rust/src/rc.rs @@ -43,6 +43,7 @@ pub unsafe trait RefCountable: ToOwned> + Sized { // Represents an 'owned' reference tracked by the core // that we are responsible for cleaning up once we're // done with the encapsulated value. +#[repr(transparent)] pub struct Ref { contents: T, } diff --git a/rust/src/types.rs b/rust/src/types.rs index 8f14cf00e..eb629503b 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -56,6 +56,8 @@ pub type MemberScope = BNMemberScope; //////////////// // Confidence +/// Compatible with the `BNType*WithConfidence` types +#[repr(C)] pub struct Conf { pub contents: T, pub confidence: u8, @@ -698,6 +700,7 @@ impl Drop for TypeBuilder { ////////// // Type +#[repr(transparent)] pub struct Type { pub(crate) handle: *mut BNType, } @@ -2447,12 +2450,10 @@ unsafe impl<'a> CoreArrayWrapper<'a> for QualifiedNameTypeAndId { ////////////////////////// // NameAndType -pub struct NameAndType { - pub name: S, - pub t: Conf>, -} +#[repr(transparent)] +pub struct NameAndType(pub(crate) BNNameAndType); -impl NameAndType { +impl NameAndType { pub(crate) fn from_raw(raw: &BNNameAndType) -> Self { Self::new( raw_to_string(raw.name).unwrap(), @@ -2462,43 +2463,56 @@ impl NameAndType { } } -impl NameAndType { - pub fn new(name: S, t: &Ref, confidence: u8) -> Self { - Self { - name, - t: Conf::new(t.clone(), confidence), - } +impl NameAndType { + pub fn new(name: S, t: &Ref, confidence: u8) -> Self { + Self(BNNameAndType { + name: unsafe { BNAllocString(name.into_bytes_with_nul().as_ref().as_ptr() as *mut _) }, + type_: unsafe { Ref::into_raw(t.to_owned()).handle }, + typeConfidence: confidence, + }) } pub(crate) fn into_raw(self) -> BNNameAndType { - let t = self.t.clone(); - let res = BNNameAndType { - name: BnString::new(self.name).into_raw(), - type_: t.contents.handle, - typeConfidence: self.t.confidence, - }; - mem::forget(t); - res + self.0 } - pub fn type_with_confidence(&self) -> Conf> { - self.t.clone() + pub fn name(&self) -> &str { + let c_str = unsafe { CStr::from_ptr(self.0.name) }; + c_str.to_str().unwrap() + } + + pub fn t(&self) -> &Type { + unsafe { mem::transmute::<_, &Type>(&self.0.type_) } + } + + pub fn type_with_confidence(&self) -> &Conf { + // the struct BNNameAndType contains a Conf inside of it, so this is safe + unsafe { mem::transmute::<_, &Conf>(&self.0.type_) } + } +} + +impl Drop for NameAndType { + fn drop(&mut self) { + unsafe { + BNFreeString(self.0.name); + BNFreeType(self.0.type_); + } } } -impl CoreArrayProvider for NameAndType { +impl CoreArrayProvider for NameAndType { type Raw = BNNameAndType; type Context = (); } -unsafe impl CoreOwnedArrayProvider for NameAndType { +unsafe impl CoreOwnedArrayProvider for NameAndType { unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { BNFreeNameAndTypeList(raw, count); } } -unsafe impl<'a, S: 'a + BnStrCompatible> CoreArrayWrapper<'a> for NameAndType { - type Wrapped = &'a NameAndType; +unsafe impl<'a> CoreArrayWrapper<'a> for NameAndType { + type Wrapped = &'a NameAndType; unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { mem::transmute(raw) @@ -2508,11 +2522,8 @@ unsafe impl<'a, S: 'a + BnStrCompatible> CoreArrayWrapper<'a> for NameAndType ////////////////// // DataVariable -pub struct DataVariable { - pub address: u64, - pub t: Conf>, - pub auto_discovered: bool, -} +#[repr(transparent)] +pub struct DataVariable(pub(crate) BNDataVariable); // impl DataVariable { // pub(crate) fn from_raw(var: &BNDataVariable) -> Self { @@ -2525,12 +2536,26 @@ pub struct DataVariable { // } impl DataVariable { + pub fn address(&self) -> u64 { + self.0.address + } + + pub fn auto_discovered(&self) -> &bool { + unsafe { mem::transmute(&self.0.autoDiscovered) } + } + + pub fn t(&self) -> &Type { + unsafe { mem::transmute(&self.0.type_) } + } + pub fn type_with_confidence(&self) -> Conf> { - Conf::new(self.t.contents.clone(), self.t.confidence) + // if it was not for the `autoDiscovered: bool` between `type_` and + // `typeConfidence` this could have being a reference, like NameAndType + Conf::new(self.t().to_owned(), self.0.typeConfidence) } pub fn symbol(&self, bv: &BinaryView) -> Option> { - bv.symbol_by_address(self.address).ok() + bv.symbol_by_address(self.0.address).ok() } } From 58673b20ef858ae5d4d7b1ff0dec1460a0f36e5f Mon Sep 17 00:00:00 2001 From: Rubens Brandao Date: Wed, 17 Apr 2024 08:14:00 -0300 Subject: [PATCH 09/50] use Ref to own types --- rust/src/architecture.rs | 10 +++---- rust/src/binaryview.rs | 4 +-- rust/src/debuginfo.rs | 8 ++--- rust/src/types.rs | 63 +++++++++++++++++++++++++++++++++------- 4 files changed, 63 insertions(+), 22 deletions(-) diff --git a/rust/src/architecture.rs b/rust/src/architecture.rs index 82df7441c..9b5b36b48 100644 --- a/rust/src/architecture.rs +++ b/rust/src/architecture.rs @@ -313,7 +313,7 @@ pub trait Intrinsic: Sized + Clone + Copy { fn id(&self) -> u32; /// Reeturns the list of the input names and types for this intrinsic. - fn inputs(&self) -> Vec; + fn inputs(&self) -> Vec>; /// Returns the list of the output types for this intrinsic. fn outputs(&self) -> Vec>>; @@ -650,7 +650,7 @@ impl Intrinsic for UnusedIntrinsic { fn id(&self) -> u32 { unreachable!() } - fn inputs(&self) -> Vec { + fn inputs(&self) -> Vec> { unreachable!() } fn outputs(&self) -> Vec>> { @@ -992,7 +992,7 @@ impl Intrinsic for crate::architecture::CoreIntrinsic { self.1 } - fn inputs(&self) -> Vec { + fn inputs(&self) -> Vec> { let mut count: usize = 0; unsafe { @@ -1172,7 +1172,7 @@ impl Architecture for CoreArchitecture { } } } - + fn instruction_llil( &self, data: &[u8], @@ -2424,7 +2424,7 @@ where let inputs = intrinsic.inputs(); let mut res = Vec::with_capacity(inputs.len()); for input in inputs { - res.push(input.into_raw()); + res.push(unsafe { Ref::into_raw(input) }.into_raw()); } unsafe { diff --git a/rust/src/binaryview.rs b/rust/src/binaryview.rs index bcb3f57fe..ecf753841 100644 --- a/rust/src/binaryview.rs +++ b/rust/src/binaryview.rs @@ -574,7 +574,7 @@ pub trait BinaryViewExt: BinaryViewBase { } } - fn define_auto_data_var(&self, dv: DataVariable) { + fn define_auto_data_var(&self, dv: Ref) { unsafe { BNDefineDataVariable( self.as_ref().handle, @@ -585,7 +585,7 @@ pub trait BinaryViewExt: BinaryViewBase { } /// You likely would also like to call [`Self::define_user_symbol`] to bind this data variable with a name - fn define_user_data_var(&self, dv: DataVariable) { + fn define_user_data_var(&self, dv: Ref) { unsafe { BNDefineUserDataVariable( self.as_ref().handle, diff --git a/rust/src/debuginfo.rs b/rust/src/debuginfo.rs index 7bb5e36ff..32db7ebb3 100644 --- a/rust/src/debuginfo.rs +++ b/rust/src/debuginfo.rs @@ -376,7 +376,7 @@ impl DebugInfo { } /// Returns a generator of all types provided by a named DebugInfoParser - pub fn types_by_name(&self, parser_name: S) -> Vec { + pub fn types_by_name(&self, parser_name: S) -> Vec> { let parser_name = parser_name.into_bytes_with_nul(); let mut count: usize = 0; @@ -387,7 +387,7 @@ impl DebugInfo { &mut count, ) }; - let result: Vec = unsafe { + let result: Vec> = unsafe { slice::from_raw_parts_mut(debug_types_ptr, count) .iter() .map(NameAndType::from_raw) @@ -399,10 +399,10 @@ impl DebugInfo { } /// A generator of all types provided by DebugInfoParsers - pub fn types(&self) -> Vec { + pub fn types(&self) -> Vec> { let mut count: usize = 0; let debug_types_ptr = unsafe { BNGetDebugTypes(self.handle, ptr::null_mut(), &mut count) }; - let result: Vec = unsafe { + let result: Vec> = unsafe { slice::from_raw_parts_mut(debug_types_ptr, count) .iter() .map(NameAndType::from_raw) diff --git a/rust/src/types.rs b/rust/src/types.rs index eb629503b..05cad7e61 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -2454,7 +2454,7 @@ unsafe impl<'a> CoreArrayWrapper<'a> for QualifiedNameTypeAndId { pub struct NameAndType(pub(crate) BNNameAndType); impl NameAndType { - pub(crate) fn from_raw(raw: &BNNameAndType) -> Self { + pub(crate) fn from_raw(raw: &BNNameAndType) -> Ref { Self::new( raw_to_string(raw.name).unwrap(), unsafe { &Type::ref_from_raw(raw.type_) }, @@ -2464,12 +2464,14 @@ impl NameAndType { } impl NameAndType { - pub fn new(name: S, t: &Ref, confidence: u8) -> Self { - Self(BNNameAndType { - name: unsafe { BNAllocString(name.into_bytes_with_nul().as_ref().as_ptr() as *mut _) }, - type_: unsafe { Ref::into_raw(t.to_owned()).handle }, - typeConfidence: confidence, - }) + pub fn new(name: S, t: &Type, confidence: u8) -> Ref { + unsafe { + Ref::new(Self(BNNameAndType { + name: BNAllocString(name.into_bytes_with_nul().as_ref().as_ptr() as *mut _), + type_: Ref::into_raw(t.to_owned()).handle, + typeConfidence: confidence, + })) + } } pub(crate) fn into_raw(self) -> BNNameAndType { @@ -2491,11 +2493,27 @@ impl NameAndType { } } -impl Drop for NameAndType { - fn drop(&mut self) { +impl ToOwned for NameAndType { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for NameAndType { + unsafe fn inc_ref(handle: &Self) -> Ref { + Self::new( + CStr::from_ptr(handle.0.name), + handle.t(), + handle.type_with_confidence().confidence, + ) + } + + unsafe fn dec_ref(handle: &Self) { unsafe { - BNFreeString(self.0.name); - BNFreeType(self.0.type_); + BNFreeString(handle.0.name); + RefCountable::dec_ref(handle.t()); } } } @@ -2559,6 +2577,29 @@ impl DataVariable { } } +impl ToOwned for DataVariable { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for DataVariable { + unsafe fn inc_ref(handle: &Self) -> Ref { + unsafe { + Ref::new(Self(BNDataVariable { + type_: Ref::into_raw(handle.t().to_owned()).handle, + ..handle.0 + })) + } + } + + unsafe fn dec_ref(handle: &Self) { + unsafe { BNFreeType(handle.0.type_) } + } +} + impl CoreArrayProvider for DataVariable { type Raw = BNDataVariable; type Context = (); From 9717866665a42356881b062fcd80bc09492ee8b8 Mon Sep 17 00:00:00 2001 From: Rubens Brandao Date: Wed, 17 Apr 2024 09:35:12 -0300 Subject: [PATCH 10/50] remove unecessary and crash causing zeroed call inits --- rust/src/architecture.rs | 25 ++++++++++++------------- rust/src/custombinaryview.rs | 15 ++++++++------- rust/src/debuginfo.rs | 23 +++++++---------------- rust/src/demangle.rs | 8 ++++---- rust/src/relocation.rs | 17 +++++++---------- rust/src/types.rs | 10 +++++----- 6 files changed, 43 insertions(+), 55 deletions(-) diff --git a/rust/src/architecture.rs b/rust/src/architecture.rs index 9a7b3e723..a03f28d0f 100644 --- a/rust/src/architecture.rs +++ b/rust/src/architecture.rs @@ -23,7 +23,7 @@ use std::{ collections::HashMap, ffi::{c_char, c_int, CStr, CString}, hash::Hash, - mem::zeroed, + mem::{zeroed, MaybeUninit}, ops, ptr, slice, }; @@ -1172,7 +1172,7 @@ impl Architecture for CoreArchitecture { } } } - + fn instruction_llil( &self, data: &[u8], @@ -1689,8 +1689,8 @@ where A: 'static + Architecture> + Send + Sync, F: FnOnce(CustomArchitectureHandle, CoreArchitecture) -> A, { - arch: A, - func: F, + arch: MaybeUninit, + func: Option, } extern "C" fn cb_init(ctxt: *mut c_void, obj: *mut BNArchitecture) @@ -1704,11 +1704,10 @@ where handle: ctxt as *mut A, }; - let create = ptr::read(&custom_arch.func); - ptr::write( - &mut custom_arch.arch, - create(custom_arch_handle, CoreArchitecture(obj)), - ); + let create = custom_arch.func.take().unwrap(); + custom_arch + .arch + .write(create(custom_arch_handle, CoreArchitecture(obj))); } } @@ -2685,13 +2684,13 @@ where let name = name.into_bytes_with_nul(); let uninit_arch = ArchitectureBuilder { - arch: unsafe { zeroed() }, - func, + arch: MaybeUninit::zeroed(), + func: Some(func), }; let raw = Box::into_raw(Box::new(uninit_arch)); let mut custom_arch = BNCustomArchitecture { - context: raw as *mut _, + context: raw as *mut ArchitectureBuilder<_, _> as *mut _, init: Some(cb_init::), getEndianness: Some(cb_endianness::), getAddressSize: Some(cb_address_size::), @@ -2776,7 +2775,7 @@ where assert!(!res.is_null()); - &(*raw).arch + (*raw).arch.assume_init_mut() } } diff --git a/rust/src/custombinaryview.rs b/rust/src/custombinaryview.rs index 956be9bdc..14fefdf77 100644 --- a/rust/src/custombinaryview.rs +++ b/rust/src/custombinaryview.rs @@ -20,6 +20,7 @@ pub use binaryninjacore_sys::BNModificationStatus as ModificationStatus; use std::marker::PhantomData; use std::mem; +use std::mem::MaybeUninit; use std::os::raw::c_void; use std::ptr; use std::slice; @@ -122,11 +123,10 @@ where let long_name = long_name.into_bytes_with_nul(); let long_name_ptr = long_name.as_ref().as_ptr() as *mut _; - let ctxt = Box::new(unsafe { mem::zeroed() }); - let ctxt = Box::into_raw(ctxt); + let ctxt = Box::leak(Box::new(MaybeUninit::zeroed())); let mut bn_obj = BNCustomBinaryViewType { - context: ctxt as *mut _, + context: ctxt.as_mut_ptr() as *mut _, create: Some(cb_create::), parse: Some(cb_parse::), isValidForData: Some(cb_valid::), @@ -140,15 +140,16 @@ where if res.is_null() { // avoid leaking the space allocated for the type, but also // avoid running its Drop impl (if any -- not that there should - // be one since view types live for the life of the process) - mem::forget(*Box::from_raw(ctxt)); + // be one since view types live for the life of the process) as + // MaybeUninit suppress the Drop implementation of it's inner type + drop(Box::from_raw(ctxt)); panic!("bvt registration failed"); } - ptr::write(ctxt, constructor(BinaryViewType(res))); + ctxt.write(constructor(BinaryViewType(res))); - &*ctxt + ctxt.assume_init_mut() } } diff --git a/rust/src/debuginfo.rs b/rust/src/debuginfo.rs index ab4f8f6b3..08ba25176 100644 --- a/rust/src/debuginfo.rs +++ b/rust/src/debuginfo.rs @@ -74,7 +74,7 @@ use crate::{ types::{DataVariableAndName, NameAndType, Type}, }; -use std::{hash::Hash, mem, os::raw::c_void, ptr, slice}; +use std::{hash::Hash, os::raw::c_void, ptr, slice}; struct ProgressContext(Option Result<(), ()>>>); @@ -109,14 +109,14 @@ impl DebugInfoParser { /// List all debug-info parsers pub fn list() -> Array { - let mut count: usize = unsafe { mem::zeroed() }; + let mut count = 0; let raw_parsers = unsafe { BNGetDebugInfoParsers(&mut count as *mut _) }; unsafe { Array::new(raw_parsers, count, ()) } } /// Returns a list of debug-info parsers that are valid for the provided binary view pub fn parsers_for_view(bv: &BinaryView) -> Array { - let mut count: usize = unsafe { mem::zeroed() }; + let mut count = 0; let raw_parsers = unsafe { BNGetDebugInfoParsersForView(bv.handle, &mut count as *mut _) }; unsafe { Array::new(raw_parsers, count, ()) } } @@ -414,10 +414,7 @@ impl DebugInfo { } /// Returns a generator of all functions provided by a named DebugInfoParser - pub fn functions_by_name( - &self, - parser_name: S, - ) -> Vec { + pub fn functions_by_name(&self, parser_name: S) -> Vec { let parser_name = parser_name.into_bytes_with_nul(); let mut count: usize = 0; @@ -758,21 +755,15 @@ impl DebugInfo { let short_name_bytes = new_func.short_name.map(|name| name.into_bytes_with_nul()); let short_name = short_name_bytes .as_ref() - .map_or(ptr::null_mut() as *mut _, |name| { - name.as_ptr() as _ - }); + .map_or(ptr::null_mut() as *mut _, |name| name.as_ptr() as _); let full_name_bytes = new_func.full_name.map(|name| name.into_bytes_with_nul()); let full_name = full_name_bytes .as_ref() - .map_or(ptr::null_mut() as *mut _, |name| { - name.as_ptr() as _ - }); + .map_or(ptr::null_mut() as *mut _, |name| name.as_ptr() as _); let raw_name_bytes = new_func.raw_name.map(|name| name.into_bytes_with_nul()); let raw_name = raw_name_bytes .as_ref() - .map_or(ptr::null_mut() as *mut _, |name| { - name.as_ptr() as _ - }); + .map_or(ptr::null_mut() as *mut _, |name| name.as_ptr() as _); let mut components_array: Vec<*const ::std::os::raw::c_char> = Vec::with_capacity(new_func.components.len()); diff --git a/rust/src/demangle.rs b/rust/src/demangle.rs index 19eb085c9..3756ea068 100644 --- a/rust/src/demangle.rs +++ b/rust/src/demangle.rs @@ -33,8 +33,8 @@ pub fn demangle_gnu3( ) -> Result<(Option>, Vec)> { let mangled_name_bwn = mangled_name.into_bytes_with_nul(); let mangled_name_ptr = mangled_name_bwn.as_ref(); - let mut out_type: *mut BNType = unsafe { std::mem::zeroed() }; - let mut out_name: *mut *mut std::os::raw::c_char = unsafe { std::mem::zeroed() }; + let mut out_type: *mut BNType = std::ptr::null_mut(); + let mut out_name: *mut *mut std::os::raw::c_char = std::ptr::null_mut(); let mut out_size: usize = 0; let res = unsafe { BNDemangleGNU3( @@ -89,8 +89,8 @@ pub fn demangle_ms( let mangled_name_bwn = mangled_name.into_bytes_with_nul(); let mangled_name_ptr = mangled_name_bwn.as_ref(); - let mut out_type: *mut BNType = unsafe { std::mem::zeroed() }; - let mut out_name: *mut *mut std::os::raw::c_char = unsafe { std::mem::zeroed() }; + let mut out_type: *mut BNType = std::ptr::null_mut(); + let mut out_name: *mut *mut std::os::raw::c_char = std::ptr::null_mut(); let mut out_size: usize = 0; let res = unsafe { BNDemangleMS( diff --git a/rust/src/relocation.rs b/rust/src/relocation.rs index f9cbb3c52..c56b2a4eb 100644 --- a/rust/src/relocation.rs +++ b/rust/src/relocation.rs @@ -8,6 +8,7 @@ use crate::{ }; use binaryninjacore_sys::*; use std::borrow::Borrow; +use std::mem::MaybeUninit; use std::os::raw::c_void; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -501,12 +502,9 @@ where let name = name.into_bytes_with_nul(); - let uninit_handler = RelocationHandlerBuilder { - handler: unsafe { std::mem::zeroed() }, - }; - let raw = Box::into_raw(Box::new(uninit_handler)); + let raw = Box::leak(Box::new(MaybeUninit::>::zeroed())); let mut custom_handler = BNCustomRelocationHandler { - context: raw as *mut _, + context: raw.as_mut_ptr() as *mut _, freeObject: Some(cb_free::), getRelocationInfo: Some(cb_get_relocation_info::), applyRelocation: Some(cb_apply_relocation::), @@ -517,13 +515,12 @@ where assert!(!handle_raw.is_null()); let handle = CoreRelocationHandler(handle_raw); let custom_handle = CustomRelocationHandlerHandle { - handle: raw as *mut R, + handle: raw.as_mut_ptr() as *mut R, }; unsafe { - core::ptr::write( - &mut raw.as_mut().unwrap().handler, - func(custom_handle, CoreRelocationHandler(handle.0)), - ); + raw.write(RelocationHandlerBuilder { + handler: func(custom_handle, CoreRelocationHandler(handle.0)), + }); BNArchitectureRegisterRelocationHandler( arch.handle().as_ref().0, diff --git a/rust/src/types.rs b/rust/src/types.rs index 8f14cf00e..5f1874bce 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -422,7 +422,7 @@ impl TypeBuilder { pub fn parameters(&self) -> Result> { unsafe { - let mut count: usize = mem::zeroed(); + let mut count = 0; let parameters_raw = BNGetTypeBuilderParameters(self.handle, &mut count); if parameters_raw.is_null() { Err(()) @@ -793,7 +793,7 @@ impl Type { pub fn parameters(&self) -> Result> { unsafe { - let mut count: usize = mem::zeroed(); + let mut count = 0; let parameters_raw: *mut BNFunctionParameter = BNGetTypeParameters(self.handle, &mut count); if parameters_raw.is_null() { @@ -1549,7 +1549,7 @@ impl EnumerationBuilder { pub fn members(&self) -> Vec { unsafe { - let mut count: usize = mem::zeroed(); + let mut count = 0; let members_raw = BNGetEnumerationBuilderMembers(self.handle, &mut count); let members: &[BNEnumerationMember] = slice::from_raw_parts(members_raw, count); @@ -1606,7 +1606,7 @@ impl Enumeration { pub fn members(&self) -> Vec { unsafe { - let mut count: usize = mem::zeroed(); + let mut count = 0; let members_raw = BNGetEnumerationMembers(self.handle, &mut count); let members: &[BNEnumerationMember] = slice::from_raw_parts(members_raw, count); @@ -1937,7 +1937,7 @@ impl Structure { pub fn members(&self) -> Result> { unsafe { - let mut count: usize = mem::zeroed(); + let mut count = 0; let members_raw: *mut BNStructureMember = BNGetStructureMembers(self.handle, &mut count); if members_raw.is_null() { From 3e444f221b4dfc37497342686732ba10dbcfa567 Mon Sep 17 00:00:00 2001 From: Rubens Brandao Date: Wed, 17 Apr 2024 09:53:45 -0300 Subject: [PATCH 11/50] fix leak creation of invalid BinaryView handle --- rust/src/interaction.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rust/src/interaction.rs b/rust/src/interaction.rs index ccb5c81a4..76fc92752 100644 --- a/rust/src/interaction.rs +++ b/rust/src/interaction.rs @@ -296,7 +296,9 @@ impl FormInputBuilder { result.type_ = BNFormInputFieldType::AddressFormField; result.prompt = prompt.as_ref().as_ptr() as *const c_char; if let Some(view) = view { - result.view = view.handle; + // the view is being moved into result, there is no need to clone + // and drop is intentionally being avoided with `Ref::into_raw` + result.view = unsafe { Ref::into_raw(view) }.handle; } result.currentAddress = current_address.unwrap_or(0); result.hasDefault = default.is_some(); From 562fbfae8aa17a2f3758710b23ec25e828d16cd6 Mon Sep 17 00:00:00 2001 From: Rubens Brandao Date: Wed, 17 Apr 2024 12:49:43 -0300 Subject: [PATCH 12/50] remove unnecessary transmutions --- rust/src/binaryview.rs | 4 ++-- rust/src/rc.rs | 1 - rust/src/types.rs | 26 +++++++++++--------------- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/rust/src/binaryview.rs b/rust/src/binaryview.rs index ecf753841..33fd98813 100644 --- a/rust/src/binaryview.rs +++ b/rust/src/binaryview.rs @@ -574,7 +574,7 @@ pub trait BinaryViewExt: BinaryViewBase { } } - fn define_auto_data_var(&self, dv: Ref) { + fn define_auto_data_var(&self, dv: &DataVariable) { unsafe { BNDefineDataVariable( self.as_ref().handle, @@ -585,7 +585,7 @@ pub trait BinaryViewExt: BinaryViewBase { } /// You likely would also like to call [`Self::define_user_symbol`] to bind this data variable with a name - fn define_user_data_var(&self, dv: Ref) { + fn define_user_data_var(&self, dv: &DataVariable) { unsafe { BNDefineUserDataVariable( self.as_ref().handle, diff --git a/rust/src/rc.rs b/rust/src/rc.rs index 91e515657..cdcae1792 100644 --- a/rust/src/rc.rs +++ b/rust/src/rc.rs @@ -43,7 +43,6 @@ pub unsafe trait RefCountable: ToOwned> + Sized { // Represents an 'owned' reference tracked by the core // that we are responsible for cleaning up once we're // done with the encapsulated value. -#[repr(transparent)] pub struct Ref { contents: T, } diff --git a/rust/src/types.rs b/rust/src/types.rs index 05cad7e61..9fcd9d45f 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -57,7 +57,6 @@ pub type MemberScope = BNMemberScope; // Confidence /// Compatible with the `BNType*WithConfidence` types -#[repr(C)] pub struct Conf { pub contents: T, pub confidence: u8, @@ -2487,9 +2486,8 @@ impl NameAndType { unsafe { mem::transmute::<_, &Type>(&self.0.type_) } } - pub fn type_with_confidence(&self) -> &Conf { - // the struct BNNameAndType contains a Conf inside of it, so this is safe - unsafe { mem::transmute::<_, &Conf>(&self.0.type_) } + pub fn type_with_confidence(&self) -> Conf<&Type> { + Conf::new(self.t(), self.0.typeConfidence) } } @@ -2545,11 +2543,11 @@ pub struct DataVariable(pub(crate) BNDataVariable); // impl DataVariable { // pub(crate) fn from_raw(var: &BNDataVariable) -> Self { -// Self { -// address: var.address, -// t: Conf::new(unsafe { Type::ref_from_raw(var.type_) }, var.typeConfidence), -// auto_discovered: var.autoDiscovered, -// } +// let var = DataVariable(*var); +// Self(BNDataVariable { +// type_: unsafe { Ref::into_raw(var.t().to_owned()).handle }, +// ..var.0 +// }) // } // } @@ -2558,18 +2556,16 @@ impl DataVariable { self.0.address } - pub fn auto_discovered(&self) -> &bool { - unsafe { mem::transmute(&self.0.autoDiscovered) } + pub fn auto_discovered(&self) -> bool { + self.0.autoDiscovered } pub fn t(&self) -> &Type { unsafe { mem::transmute(&self.0.type_) } } - pub fn type_with_confidence(&self) -> Conf> { - // if it was not for the `autoDiscovered: bool` between `type_` and - // `typeConfidence` this could have being a reference, like NameAndType - Conf::new(self.t().to_owned(), self.0.typeConfidence) + pub fn type_with_confidence(&self) -> Conf<&Type> { + Conf::new(self.t(), self.0.typeConfidence) } pub fn symbol(&self, bv: &BinaryView) -> Option> { From 859bd3e53b39c207eb90fecbc11a4cba188aedb3 Mon Sep 17 00:00:00 2001 From: Jordan Wiens Date: Wed, 24 Apr 2024 11:52:16 -0400 Subject: [PATCH 13/50] add missing documentation for interaction APIs and some normalization of phrasing. Resolves #5317 --- python/binaryview.py | 4 ++-- python/interaction.py | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/python/binaryview.py b/python/binaryview.py index 9ef7a9132..43eda87f9 100644 --- a/python/binaryview.py +++ b/python/binaryview.py @@ -8833,7 +8833,7 @@ def show_graph_report(self, title: str, graph: flowgraph.FlowGraph) -> None: ``show_graph_report`` displays a :py:class:`FlowGraph` object `graph` in a new tab with ``title``. :param title: Title of the graph - :type title: Plain text string title + :type title: Text string title of the tab :param graph: The graph you wish to display :type graph: :py:class:`FlowGraph` object """ @@ -8844,7 +8844,7 @@ def get_address_input(self, prompt: str, title: str, current_address: Optional[i ``get_address_input`` Gets a virtual address via a prompt displayed to the user :param prompt: Prompt for the dialog - :param title: Display title, if displayed via the UI + :param title: Window title, if used in the UI :param current_address: Optional current address, for relative inputs :return: The value entered by the user, if one was entered """ diff --git a/python/interaction.py b/python/interaction.py index c767af7ce..c3bfb3a28 100644 --- a/python/interaction.py +++ b/python/interaction.py @@ -1062,8 +1062,8 @@ def show_plain_text_report(title, contents): .. note:: This API functions differently on the command-line vs the UI. In the UI, a pop-up is used. On the command-line, \ a simple text prompt is used. - :param str title: title to display in the UI pop-up - :param str contents: plaintext contents to display + :param str title: Title to display in the tab + :param str contents: Plaintext contents to display :rtype: None :Example: >>> show_plain_text_report("title", "contents") @@ -1081,6 +1081,7 @@ def show_markdown_report(title, contents, plaintext=""): .. note:: This API function differently on the command-line vs the UI. In the UI a pop-up is used. On the command-line \ a simple text prompt is used. + :param str title: title to display in the tab :param str contents: markdown contents to display :param str plaintext: Plain text version to display (used on the command-line) :rtype: None @@ -1097,6 +1098,7 @@ def show_html_report(title, contents, plaintext=""): applications. This API doesn't support hyperlinking into the BinaryView, use the :py:meth:`BinaryView.show_html_report` \ API if hyperlinking is needed. + :param str title: Title to display in the tab :param str contents: HTML contents to display :param str plaintext: Plain text version to display (used on the command-line) :rtype: None @@ -1115,6 +1117,7 @@ def show_graph_report(title, graph): .. note:: This API function will have no effect outside the UI. + :param str title: Title to display in the tab :param FlowGraph graph: Flow graph to display :rtype: None """ @@ -1144,9 +1147,9 @@ def get_text_line_input(prompt, title): .. note:: This API function differently on the command-line vs the UI. In the UI a pop-up is used. On the command-line \ a simple text prompt is used. - :param str prompt: String to prompt with. - :param str title: Title of the window when executed in the UI. - :rtype: str containing the input without trailing newline character. + :param str prompt: String to prompt with + :param str title: Title of the window when executed in the UI + :rtype: str containing the input without trailing newline character :Example: >>> get_text_line_input("PROMPT>", "getinfo") PROMPT> Input! @@ -1167,9 +1170,9 @@ def get_int_input(prompt, title): .. note:: This API function differently on the command-line vs the UI. In the UI a pop-up is used. On the command-line \ a simple text prompt is used. - :param str prompt: String to prompt with. - :param str title: Title of the window when executed in the UI. - :rtype: integer value input by the user. + :param str prompt: String to prompt with + :param str title: Title of the window when executed in the UI + :rtype: integer value input by the user :Example: >>> get_int_input("PROMPT>", "getinfo") PROMPT> 10 From fd4f45273ace0a0bf45c087c01f0b3b498bce71f Mon Sep 17 00:00:00 2001 From: Josh Ferrell Date: Wed, 24 Apr 2024 13:20:11 -0400 Subject: [PATCH 14/50] Do not define symbols from mapping symbols --- view/elf/elfview.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/view/elf/elfview.cpp b/view/elf/elfview.cpp index 84ca68f54..96f741b09 100644 --- a/view/elf/elfview.cpp +++ b/view/elf/elfview.cpp @@ -1283,6 +1283,9 @@ bool ElfView::Init() // handle long form symbols if (auto pos = entryName.find(".", 2); (pos != std::string::npos)) { + // These mapping symbols do not define actual names + if (entryName[0] == '$' && (entryName[1] == 'x' || entryName[1] == 'a' || entryName[1] == 'd')) + continue; entryName = entryName.substr(pos + 1); if (entryName.size()) DefineElfSymbol(isMappingFunctionSymbol ? FunctionSymbol : DataSymbol, entryName, entry->value, false, entry->binding, entry->size); From b91f466835221bdee911fdf0479ae7995c3affe3 Mon Sep 17 00:00:00 2001 From: Josh Ferrell Date: Wed, 24 Apr 2024 14:11:57 -0400 Subject: [PATCH 15/50] Define symtab in elf raw view --- view/elf/elfview.cpp | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/view/elf/elfview.cpp b/view/elf/elfview.cpp index 96f741b09..5d1b5cf29 100644 --- a/view/elf/elfview.cpp +++ b/view/elf/elfview.cpp @@ -2170,8 +2170,7 @@ bool ElfView::Init() DefineAutoSymbol(new Symbol(DataSymbol, "__elf_dynamic_table", adjustedVirtualAddr, NoBinding)); } - // Add types for the dynamic symbol table - if (m_auxSymbolTable.size) + if (m_auxSymbolTable.size || m_symbolTableSection.offset) { StructureBuilder symTableBuilder; if (m_elf32) @@ -2196,9 +2195,25 @@ bool ElfView::Init() Ref symTableType = Type::StructureType(symTableStruct); QualifiedName symTableName = m_elf32 ? string("Elf32_Sym") : string("Elf64_Sym"); const string symTableTypeId = Type::GenerateAutoTypeId("elf", symTableName); - QualifiedName symTableTypeName = DefineType(symTableTypeId, symTableName, symTableType); - DefineDataVariable(m_auxSymbolTable.offset, Type::ArrayType(Type::NamedType(this, symTableTypeName), m_auxSymbolTable.size / m_auxSymbolTableEntrySize)); - DefineAutoSymbol(new Symbol(DataSymbol, "__elf_symbol_table", m_auxSymbolTable.offset, NoBinding)); + + // Add types for the dynamic symbol table + if (m_auxSymbolTable.size) + { + auto defineAuxSymTableForView = [&](Ref view) { + QualifiedName symTableTypeName = view->DefineType(symTableTypeId, symTableName, symTableType); + view->DefineDataVariable(m_auxSymbolTable.offset, Type::ArrayType(Type::NamedType(this, symTableTypeName), m_auxSymbolTable.size / m_auxSymbolTableEntrySize)); + view->DefineAutoSymbol(new Symbol(DataSymbol, "__elf_symbol_table", m_auxSymbolTable.offset, NoBinding)); + }; + defineAuxSymTableForView(this); + defineAuxSymTableForView(GetParentView()); + } + + if (m_symbolTableSection.offset) + { + QualifiedName symTableTypeName = GetParentView()->DefineType(symTableTypeId, symTableName, symTableType); + GetParentView()->DefineDataVariable(m_symbolTableSection.offset, Type::ArrayType(Type::NamedType(this, symTableTypeName), m_symbolTableSection.size / m_auxSymbolTableEntrySize)); + GetParentView()->DefineAutoSymbol(new Symbol(DataSymbol, "__elf_symbol_table", m_symbolTableSection.offset, NoBinding)); + } } // In 32-bit mips with .got, add .extern symbol "RTL_Resolve" From 903b227d547a593d95baf1ed87ce5bbe46330f0b Mon Sep 17 00:00:00 2001 From: Jordan Wiens Date: Wed, 24 Apr 2024 17:38:53 -0400 Subject: [PATCH 16/50] add binaryview to important concepts --- docs/dev/concepts.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/dev/concepts.md b/docs/dev/concepts.md index 25dd5af0a..fad8c769f 100644 --- a/docs/dev/concepts.md +++ b/docs/dev/concepts.md @@ -1,5 +1,26 @@ # Important Concepts +## Binary Views + +The highest level analysis object in Binary Ninja is a [BinaryView](https://api.binary.ninja/binaryninja.binaryview-module.html#binaryninja.binaryview.BinaryView) (or `bv` for short). You can think of a `bv` as the Binary Ninja equivalent of what an operating system does when loading an executable binary. These `bv`'s are the top-level analysis object representing how a file is loaded into memory as well as debug information, tables of function pointers, and many other structures. + +When you are interacting in the UI with an executable file, you can access `bv` in the python scripting console to see the representation of the current file's BinaryView: + +```python +>>> bv + +>>> len(bv.functions) +140 +``` + +???+ Info "Tip" + Note the use of `bv` here as a shortcut to the currently open BinaryView. For other "magic" variables, see the [user guide](../guide/index.md#magic-console-variables) + +If you want to start writing a plugin, most top-level methods will exist off of the BinaryView. Conceptually, you can think about the organization as a hierarchy starting with a BinaryView, then functions, then basic blocks, then instructions. There are of course lots of other ways to access parts of the binary but this is the most common organization. Check out the tab completion in the scripting console for `bv.get` for example (a common prefix for many APIs): + +![Tab Completion ><](../img/getcompletion.png "Tab Completion") + +Some BinaryViews have parent views. The view used for decompilation includes memory mappings through segments and sections for example, but the "parent_view" property is a view of the original file on-disk. ## REPL versus Scripts From ff771fabf3211fedc31349efdeef624765b1013e Mon Sep 17 00:00:00 2001 From: Jordan Wiens Date: Wed, 24 Apr 2024 17:39:20 -0400 Subject: [PATCH 17/50] improve traverse concepts examples --- docs/dev/concepts.md | 22 +++++++++++++++++++--- docs/img/getcompletion.png | Bin 0 -> 164464 bytes 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 docs/img/getcompletion.png diff --git a/docs/dev/concepts.md b/docs/dev/concepts.md index fad8c769f..454f0fbfd 100644 --- a/docs/dev/concepts.md +++ b/docs/dev/concepts.md @@ -67,7 +67,13 @@ t = [ bv.get_symbol_by_raw_name('__builtin_strncpy').address ] -list(current_hlil.traverse(find_strcpy, t)) +# Find the first call to a builtin: +for result in current_hlil.traverse(find_strcpy, t): + # Any logic should live here, not inside the callable which is just for + # matching. Because this is a generator, it can fail fast when used for + # search! + print(result) + break def get_memcpy_data(i, t) -> bytes: @@ -77,7 +83,8 @@ def get_memcpy_data(i, t) -> bytes: # Iterate through all instructions in the HLIL t = bv.get_symbol_by_raw_name('__builtin_memcpy').address -list(current_hlil.traverse(get_memcpy_data, t)) +for i in current_hlil.traverse(get_memcpy_data, t): + print(f"Found some memcpy data: {repr(i)}") # find all the calls to __builtin_strcpy and get their values @@ -90,13 +97,20 @@ t = [ bv.get_symbol_by_raw_name('__builtin_strcpy').address, bv.get_symbol_by_raw_name('__builtin_strncpy').address ] -list(current_hlil.traverse(find_strcpy, t)) + +for i in current_hlil.traverse(find_strcpy, t): + print(i) # collect the number of parameters for each function call def param_counter(i) -> int: match i: case HighLevelILCall(): return len(i.params) + +# Note that the results are a generator and usually anything that is found +# should have processing done outside the callback, but you can always +# convert it to a list like this: + list(current_hlil.traverse(param_counter)) @@ -105,6 +119,7 @@ def collect_call_target(i) -> None: match i: case HighLevelILCall(dest=HighLevelILConstPtr(constant=c)): return c + set([hex(a) for a in current_hlil.traverse(collect_call_target)]) @@ -113,6 +128,7 @@ def collect_this_vars(i) -> Variable: match i: case HighLevelILVar(var=v) if v.name == 'this': return v + list(v for v in current_hlil.traverse(collect_this_vars)) ``` diff --git a/docs/img/getcompletion.png b/docs/img/getcompletion.png new file mode 100644 index 0000000000000000000000000000000000000000..15aaf6a8a77a94993ace801e808fb8ed98519bc4 GIT binary patch literal 164464 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

xCrnnmzG3A;SK*x{<#z+WXTia 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

@;TdW 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 F{{v3{kjnr7 literal 0 HcmV?d00001 From 96436fa42aa0c0aa329ea1f2be4469cafa55270d Mon Sep 17 00:00:00 2001 From: Jordan Wiens Date: Wed, 24 Apr 2024 17:45:02 -0400 Subject: [PATCH 18/50] improve traverse examples --- python/highlevelil.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/python/highlevelil.py b/python/highlevelil.py index 79ac73668..afdc2b2ec 100644 --- a/python/highlevelil.py +++ b/python/highlevelil.py @@ -798,10 +798,11 @@ def traverse(self, cb: Callable[['HighLevelILInstruction', Any], Any], *args: An :Example: >>> def get_constant_less_than_value(inst: HighLevelILInstruction, value: int) -> int: - >>> if isinstance(inst, Constant) and inst.constant < value: - >>> return inst.constant + ... if isinstance(inst, Constant) and inst.constant < value: + ... return inst.constant >>> - >>> list(inst.traverse(get_constant_less_than_value, 10)) + >>> for result in inst.traverse(get_constant_less_than_value, 10): + ... print(f"Found a constant {result} < 10 in {repr(inst)}") """ if (result := cb(self, *args, **kwargs)) is not None: yield result @@ -2556,7 +2557,7 @@ def get_basic_block_at(self, index: int) -> Optional['basicblock.BasicBlock']: def traverse(self, cb: Callable[['HighLevelILInstruction', Any], Any], *args: Any, **kwargs: Any) -> Iterator[Any]: """ - ``traverse`` iterates through all the instructions in the HighLevelILInstruction and calls the callback function for + ``traverse`` iterates through all the instructions in the HighLevelILFunction and calls the callback function for each instruction and sub-instruction. See the `Developer Docs `_ for more examples. :param Callable[[HighLevelILInstruction, Any], Any] cb: The callback function to call for each node in the HighLevelILInstruction @@ -2572,7 +2573,8 @@ def traverse(self, cb: Callable[['HighLevelILInstruction', Any], Any], *args: An ... case Localcall(dest=Constant(constant=c), params=[_, _, p]) if c == target and not isinstance(p, Constant): ... return i >>> target_address = bv.get_symbol_by_raw_name('_memcpy').address - >>> list(current_il_function.traverse(find_non_constant_memcpy, target_address)) + >>> for result in current_il_function.traverse(find_non_constant_memcpy, target_address): + ... print(f"Found suspicious memcpy: {repr(i)}") """ root = self.root if root is None: From 803ac2fca1f9bc4b30806615e3927fcd9b3edc69 Mon Sep 17 00:00:00 2001 From: Jordan Wiens Date: Wed, 24 Apr 2024 18:39:45 -0400 Subject: [PATCH 19/50] improve guide description of views --- docs/guide/index.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/guide/index.md b/docs/guide/index.md index 1af0012c8..e4cff8e9f 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -132,7 +132,7 @@ When you create a new file, you're given the [hex view](index.md#hex-view) of an To paste, right click anywhere in the view, select "Paste From," and choose whichever option matches the data you copied. For example, the string `\x01\x02\x03\x04` can be pasted as an Escape String, while `01020304` is Raw Hex. -From here, you can save the contents of your new binary to disk and reopen it for auto-analysis. Of course, you could also switch out of hex view and start creating functions yourself. +From here, you can save the contents of your new binary to disk and reopen it for auto-analysis. Of course, you could also switch out of hex view into linear view and start creating functions directly. ## New Tab @@ -210,6 +210,9 @@ There's also [many](#using-the-keyboard) keyboard-based navigation options. Switching views happens multiple ways. In some instances, it is automatic, such as clicking a data reference from graph view. This will navigate to linear view as data is not shown in the graph view. While navigating, you can use the [view hotkeys](#default-hotkeys) to switch to a specific view at the same location as the current selection. Next you can use the [command palette](#command-palette). Additionally, the view menu in the header at the top of each pane can be used to change views without navigating to any given location. Finally, you can also use the `View` application menu. +???+ Tip "Tip" + Any loaded BinaryView will show up in the upper-left of the main pane. You can switch between (for example), `ELF` and `Raw` to switch between multiple loaded [BinaryViews](../dev/concepts.md#Binary-Views). + ## The Sidebar ![the sidebar ><](../img/sidebars.png "The Sidebar"){ width = "800" } @@ -574,9 +577,10 @@ The hexadecimal view is useful for viewing raw binary files that may or may not The hex view is particularly good for transforming data in various ways via the `Copy as`, `Transform`, and `Paste from` menus. Note that like any other edits, `Transform` menu options will transform the data in-place, but unlike other means of editing the binary, the transformation dialog will work even when the lock button is toggled on (🔒). -???+ Tip "Tip" - Any changes made in the Hex view will take effect immediately in any other views open into the same file (new views can be created via the `Split to new tab`, or `Split to new window` options under `View`, or via [splitting panes](#tiling-panes)). This can, however, cause large amounts of re-analysis so be warned before making large edits or transformations in a large binary file. +If you're using the hex view for a Binary View like ELF, Mach-O or PE, you probably want to make sure you're also in the `Raw` view if you want to see the file as it exists on disk in hex view. +### Live Preview + Any changes made in the Hex view will take effect immediately in any other views open into the same file (new views can be created via the `Split to new tab`, or `Split to new window` options under `View`, or via [splitting panes](#tiling-panes)). This can, however, cause large amounts of re-analysis so be warned before making large edits or transformations in a large binary file. ## Linear View From 6ba7605eafb5419b82e2721026e9dc922de95347 Mon Sep 17 00:00:00 2001 From: Xusheng Date: Mon, 22 Apr 2024 15:43:09 +0800 Subject: [PATCH 20/50] Automatically update the element count when the type is changed in the create array dialog. Fix https://github.com/Vector35/binaryninja-api/issues/5284 --- ui/createarraydialog.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ui/createarraydialog.h b/ui/createarraydialog.h index 1f1114d7b..61c896247 100644 --- a/ui/createarraydialog.h +++ b/ui/createarraydialog.h @@ -12,8 +12,9 @@ class CreateArrayDialog : public QDialog BinaryViewRef m_data; uint64_t m_startAddress; + size_t m_size; TypeRef m_elementType; - uint64_t m_elementCount; + uint64_t m_elementCount{}; QLineEdit* m_startField; QComboBox* m_typeDropdown; @@ -22,15 +23,16 @@ class CreateArrayDialog : public QDialog QPushButton* m_cancelButton; QPushButton* m_createButton; - void validate(); + void update(); + size_t guessElementCount(size_t elementWidth); public: - explicit CreateArrayDialog(BinaryViewRef data, QWidget* parent = nullptr); + explicit CreateArrayDialog(BinaryViewRef data, BNAddressRange selection, QWidget* parent = nullptr); /// Set the initial start address, element type, and element count for /// the dialog. The element type may be null if no default is desired; a /// default will be chosen by the dialog. - void setInitialState(uint64_t start, const TypeRef& elementType, uint64_t count); + void setInitialState(); /// Get the desired start address from the accepted dialog. [[nodiscard]] uint64_t startAddress() const; From ceaa36acd5edd5209175856db4384100c4f9c46c Mon Sep 17 00:00:00 2001 From: Rubens Brandao Date: Thu, 25 Apr 2024 09:51:03 -0300 Subject: [PATCH 21/50] Fix non-escaped CStr --- rust/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/src/types.rs b/rust/src/types.rs index 8f14cf00e..523f31920 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -1234,7 +1234,7 @@ impl fmt::Debug for Type { BNGetTypeLines( self.handle, container, - "".as_ptr() as *const c_char, + "\x00".as_ptr() as *const c_char, 64, false, BNTokenEscapingType::NoTokenEscapingType, From 4b2b3d561b92a01f4d29b86d5510d39e4d1accf3 Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Thu, 21 Mar 2024 10:52:21 -0400 Subject: [PATCH 22/50] Base address detection widget in Triage view Initial implementation of base address detection UI widget in triage summary --- basedetection.cpp | 77 +++++++ basedetection.h | 0 binaryninjaapi.h | 55 +++++ binaryninjacore.h | 66 ++++++ examples/triage/baseaddress.cpp | 355 ++++++++++++++++++++++++++++++++ examples/triage/baseaddress.h | 81 ++++++++ examples/triage/view.cpp | 13 ++ 7 files changed, 647 insertions(+) create mode 100644 basedetection.cpp create mode 100644 basedetection.h create mode 100644 examples/triage/baseaddress.cpp create mode 100644 examples/triage/baseaddress.h diff --git a/basedetection.cpp b/basedetection.cpp new file mode 100644 index 000000000..4389f1b7b --- /dev/null +++ b/basedetection.cpp @@ -0,0 +1,77 @@ +// Copyright (c) 2015-2024 Vector 35 Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "binaryninjaapi.h" + +using namespace BinaryNinja; +using namespace std; + + +BaseAddressDetection::BaseAddressDetection(Ref bv) +{ + m_object = BNCreateBaseAddressDetection(bv->GetObject()); +} + + +BaseAddressDetection::~BaseAddressDetection() +{ + BNFreeBaseAddressDetection(m_object); +} + + +bool BaseAddressDetection::DetectBaseAddress(BaseAddressDetectionSettings& settings) +{ + BNBaseAddressDetectionSettings bnSettings = { + settings.Architecture.c_str(), + settings.Analysis.c_str(), + settings.MinStrlen, + settings.Alignment, + settings.LowerBoundary, + settings.UpperBoundary, + settings.POIAnalysis, + settings.MaxPointersPerCluster, + }; + + return BNDetectBaseAddress(m_object, bnSettings); +} + + +void BaseAddressDetection::Abort() +{ + return BNAbortBaseAddressDetection(m_object); +} + + +bool BaseAddressDetection::IsAborted() +{ + return BNIsBaseAddressDetectionAborted(m_object); +} + + +std::set> BaseAddressDetection::GetScores(BaseAddressDetectionConfidence* confidence) +{ + std::set> result; + BNBaseAddressDetectionScore scores[10]; + size_t numCandidates = BNGetBaseAddressDetectionScores(m_object, scores, 10, + (BNBaseAddressDetectionConfidence *)confidence); + for (size_t i = 0; i < numCandidates; i++) + result.insert(std::make_pair(scores[i].Score, scores[i].BaseAddress)); + return result; +} diff --git a/basedetection.h b/basedetection.h new file mode 100644 index 000000000..e69de29bb diff --git a/binaryninjaapi.h b/binaryninjaapi.h index 6bd63a4b0..6bd33c809 100644 --- a/binaryninjaapi.h +++ b/binaryninjaapi.h @@ -17378,6 +17378,61 @@ namespace BinaryNinja { const std::function& add); void Process(); }; + + struct BaseAddressDetectionSettings + { + std::string Architecture; + std::string Analysis; + uint32_t MinStrlen; + uint32_t Alignment; + uint64_t LowerBoundary; + uint64_t UpperBoundary; + BNBaseAddressDetectionPOISetting POIAnalysis; + uint32_t MaxPointersPerCluster; + }; + + enum BaseAddressDetectionConfidence + { + NoConfidence = 0, + LowConfidence = 1, + HighConfidence = 2, + }; + + /*! + \ingroup baseaddressdetection + */ + class BaseAddressDetection + { + BNBaseAddressDetection* m_object; + + public: + BaseAddressDetection(Ref view); + ~BaseAddressDetection(); + + /*! Analyze program, identify pointers and points-of-interest, and detect candidate base addresses + + \param settings Base address detection settings + \return true on success, false otherwise + */ + bool DetectBaseAddress(BaseAddressDetectionSettings& settings); + + /*! Get the top 10 candidate base addresses and thier scores + + \param confidence Confidence level that the top base address candidate is correct + \return Set of pairs containing candidate base addresses and their scores + */ + std::set> GetScores(BaseAddressDetectionConfidence* confidence); + + /*! Abort base address detection + */ + void Abort(); + + /*! Determine if base address detection is aborted + + \return true if aborted by user, false otherwise + */ + bool IsAborted(); + }; } // namespace BinaryNinja diff --git a/binaryninjacore.h b/binaryninjacore.h index 68828ce8c..25d7618e5 100644 --- a/binaryninjacore.h +++ b/binaryninjacore.h @@ -279,6 +279,7 @@ extern "C" typedef struct BNExternalLibrary BNExternalLibrary; typedef struct BNExternalLocation BNExternalLocation; typedef struct BNProjectFolder BNProjectFolder; + typedef struct BNBaseAddressDetection BNBaseAddressDetection; //! Console log levels typedef enum BNLogLevel @@ -3157,6 +3158,63 @@ extern "C" ConflictSyncStatus } BNSyncStatus; + typedef enum BNBaseAddressDetectionPOISetting + { + POI_ANALYSIS_STRINGS_ONLY, + POI_ANALYSIS_FUNCTIONS_ONLY, + POI_ANALYSIS_ALL, + } BNBaseAddressDetectionPOISetting; + + typedef enum BNBaseAddressDetectionPOIType + { + POI_STRING, + POI_FUNCTION, + POI_DATA_VARIABLE, + POI_FILE_START, + POI_FILE_END, + } BNBaseAddressDetectionPOIType; + + typedef enum BNBaseAddressDetectionConfidence + { + CONFIDENCE_UNASSIGNED, + CONFIDENCE_LOW, + CONFIDENCE_HIGH, + } BNBaseAddressDetectionConfidence; + + typedef struct BNBaseAddressDetectionSettings + { + const char* Architecture; + const char* Analysis; + uint32_t MinStrlen; + uint32_t Alignment; + uint64_t LowerBoundary; + uint64_t UpperBoundary; + BNBaseAddressDetectionPOISetting POIAnalysis; + uint32_t MaxPointersPerCluster; + } BNBaseAddressDetectionSettings; + + typedef struct BNBaseAddressDetectionReason + { + uint64_t Pointer; + uint64_t POIOffset; + BNBaseAddressDetectionPOIType BaseAddressDetectionPOIType; + } BNBaseAddressDetectionReason; + + typedef struct BNBaseAddressDetectionScore + { + size_t Score; + uint64_t BaseAddress; + } BNBaseAddressDetectionScore; + + typedef struct BNBaseAddressDetectionResults + { + BNBaseAddressDetectionConfidence Confidence; + BNBaseAddressDetectionScore** Scores; + BNBaseAddressDetectionReason** Reasons; + char* ErrorStr; + uint64_t LastTestedBaseAddress; + } BNBaseAddressDetectionResults; + BINARYNINJACOREAPI char* BNAllocString(const char* contents); BINARYNINJACOREAPI void BNFreeString(char* str); BINARYNINJACOREAPI char** BNAllocStringList(const char** contents, size_t size); @@ -6988,6 +7046,14 @@ extern "C" BINARYNINJACOREAPI bool BNBinaryViewPullTypeArchiveTypes(BNBinaryView* view, const char* archiveId, const char* const* archiveTypeIds, size_t archiveTypeIdCount, char*** updatedArchiveTypeIds, char*** updatedAnalysisTypeIds, size_t* updatedTypeCount); BINARYNINJACOREAPI bool BNBinaryViewPushTypeArchiveTypes(BNBinaryView* view, const char* archiveId, const char* const* typeIds, size_t typeIdCount, char*** updatedAnalysisTypeIds, char*** updatedArchiveTypeIds, size_t* updatedTypeCount); + // Base Address Detection + BINARYNINJACOREAPI BNBaseAddressDetection* BNCreateBaseAddressDetection(BNBinaryView *view); + BINARYNINJACOREAPI bool BNDetectBaseAddress(BNBaseAddressDetection* bad, BNBaseAddressDetectionSettings& settings); + BINARYNINJACOREAPI size_t BNGetBaseAddressDetectionScores(BNBaseAddressDetection* bad, + BNBaseAddressDetectionScore* scores, size_t count, BNBaseAddressDetectionConfidence* confidence); + BINARYNINJACOREAPI void BNAbortBaseAddressDetection(BNBaseAddressDetection* bad); + BINARYNINJACOREAPI bool BNIsBaseAddressDetectionAborted(BNBaseAddressDetection* bad); + BINARYNINJACOREAPI void BNFreeBaseAddressDetection(BNBaseAddressDetection* bad); #ifdef __cplusplus } #endif diff --git a/examples/triage/baseaddress.cpp b/examples/triage/baseaddress.cpp new file mode 100644 index 000000000..41068cfb3 --- /dev/null +++ b/examples/triage/baseaddress.cpp @@ -0,0 +1,355 @@ +#include "baseaddress.h" + +using namespace std; + + +BNBaseAddressDetectionPOISetting BaseAddressDetectionPOISettingFromString(const std::string& setting) +{ + if (setting == "Strings only") + return POI_ANALYSIS_STRINGS_ONLY; + if (setting == "Functions only") + return POI_ANALYSIS_FUNCTIONS_ONLY; + return POI_ANALYSIS_ALL; // Default to All +} + + +std::string BaseAddressDetectionPOITypeToString(BNBaseAddressDetectionPOIType type) +{ + switch (type) + { + case POI_STRING: + return "String"; + case POI_FUNCTION: + return "Function"; + case POI_DATA_VARIABLE: + return "Data variable"; + case POI_FILE_END: + return "File end"; + case POI_FILE_START: + return "File start"; + default: + return "Unknown"; + } +} + + +std::string BaseAddressDetectionConfidenceToString(BinaryNinja::BaseAddressDetectionConfidence level) +{ + switch (level) + { + case BinaryNinja::NoConfidence: + return "Unassigned"; + case BinaryNinja::HighConfidence: + return "High"; + case BinaryNinja::LowConfidence: + return "Low"; + default: + return "Unknown"; + } +} + + +uint32_t HexOrDecimalQStringToUint32(const QString& str) +{ + if (str.startsWith("0x")) + return str.mid(2).toUInt(nullptr, 16); + return str.toUInt(); +} + + +uint64_t HexOrDecimalQStringToUint64(const QString& str) +{ + if (str.startsWith("0x")) + return str.mid(2).toULongLong(nullptr, 16); + return str.toULongLong(); +} + + +void BaseAddressDetectionThread::run() +{ + BaseAddressDetectionQtResults results; + uint32_t alignment = HexOrDecimalQStringToUint32(m_inputs->AlignmentLineEdit->text()); + if (alignment == 0) + { + results.Status = "Invalid alignment value"; + emit ResultReady(results); + return; + } + + uint32_t minStrlen = HexOrDecimalQStringToUint32(m_inputs->StrlenLineEdit->text()); + if (minStrlen == 0) + { + results.Status = "Invalid minimum string length"; + emit ResultReady(results); + return; + } + + uint64_t upperBoundary = HexOrDecimalQStringToUint64(m_inputs->UpperBoundary->text()); + if (upperBoundary == 0) + { + results.Status = "Invalid upper boundary address"; + emit ResultReady(results); + return; + } + + uint64_t lowerBoundary = HexOrDecimalQStringToUint64(m_inputs->LowerBoundary->text()); + if (lowerBoundary >= upperBoundary) + { + results.Status = "Upper boundary address is less than lower"; + emit ResultReady(results); + return; + } + + uint32_t maxPointersPerCluster = HexOrDecimalQStringToUint32(m_inputs->MaxPointersPerCluster->text()); + if (maxPointersPerCluster < 2) + { + results.Status = "Invalid max pointers (must be >= 2)"; + emit ResultReady(results); + return; + } + + BNBaseAddressDetectionPOISetting poiSetting = BaseAddressDetectionPOISettingFromString( + m_inputs->POIBox->currentText().toStdString()); + BinaryNinja::BaseAddressDetectionSettings settings = { + m_inputs->ArchitectureBox->currentText().toStdString(), + m_inputs->AnalysisBox->currentText().toStdString(), + minStrlen, + alignment, + lowerBoundary, + upperBoundary, + poiSetting, + maxPointersPerCluster, + }; + + if (!m_baseDetection->DetectBaseAddress(settings)) + emit ResultReady(results); + + auto scores = m_baseDetection->GetScores(&results.Confidence); + results.Scores = scores; + emit ResultReady(results); +} + + +void BaseAddressDetectionWidget::HandleResults(const BaseAddressDetectionQtResults& results) +{ + if (!results.Status.empty()) + m_status->setText(QString::fromStdString(results.Status)); + + /* TODO + if (results.Status.empty() && m_worker->IsAborted()) + m_status->setText("Aborted by user (Last Base: 0x" + QString::number(results.Results.LastTestedBaseAddress, 16) + ")"); + */ + + if (results.Scores.empty()) + { + if (!m_worker->IsAborted()) + m_status->setText("Completed with no results"); + m_preferredBase->setText("Not available"); + m_confidence->setText("Not available"); + } + else + { + m_rebaseButton->setEnabled(true); + if (results.Status.empty() && !m_worker->IsAborted()) + m_status->setText("Completed with results"); + m_preferredBase->setText("0x" + QString::number(results.Scores.rbegin()->second, 16)); + m_confidence->setText(QString::fromStdString(BaseAddressDetectionConfidenceToString(results.Confidence)) + + " (Score: " + QString::number(results.Scores.rbegin()->first) + ")"); + m_reloadBase->setText("0x" + QString::number(results.Scores.rbegin()->second, 16)); + } + + m_resultsTableWidget->clearContents(); + /* TODO + size_t numRows = 0; + for (auto rit = results.Results.Scores.rbegin(); rit != results.Results.Scores.rend(); rit++) + numRows += results.Results.Reasons.at(rit->second).size(); + + m_resultsTableWidget->setRowCount(numRows); + size_t row = 0; + for (auto rit = results.Results.Scores.rbegin(); rit != results.Results.Scores.rend(); rit++) + { + auto [score, baseaddr] = *rit; + for (const auto& reason : results.Results.Reasons.at(baseaddr)) + { + m_resultsTableWidget->setItem(row, 0, new QTableWidgetItem("0x" + QString::number(baseaddr, 16))); + m_resultsTableWidget->setItem(row, 1, new QTableWidgetItem("0x" + QString::number(reason.Pointer, 16))); + m_resultsTableWidget->setItem(row, 2, new QTableWidgetItem("0x" + QString::number(reason.POIOffset, 16))); + m_resultsTableWidget->setItem(row, 3, new QTableWidgetItem( + QString::fromStdString(BaseAddressDetectionPOITypeToString(reason.BaseAddressDetectionPOIType)))); + row++; + } + } + */ + + m_detectBaseAddressButton->setEnabled(true); + m_abortButton->setHidden(true); +} + + +void BaseAddressDetectionWidget::DetectBaseAddress() +{ + m_status->setText("Running..."); + m_resultsTableWidget->clearContents(); + m_preferredBase->setText("Not available"); + m_confidence->setText("Not available"); + m_detectBaseAddressButton->setEnabled(false); + m_worker = new BaseAddressDetectionThread(&m_inputs, m_view); + connect(m_worker, &BaseAddressDetectionThread::ResultReady, this, &BaseAddressDetectionWidget::HandleResults); + connect(m_worker, &BaseAddressDetectionThread::finished, m_worker, &QObject::deleteLater); + m_worker->start(); + m_abortButton->setHidden(false); +} + + +void BaseAddressDetectionWidget::Abort() +{ + m_worker->Abort(); + m_abortButton->setHidden(true); +} + + +void BaseAddressDetectionWidget::RebaseWithFullAnalysis() +{ + auto mappedView = m_view->GetFile()->GetViewOfType("Mapped"); + if (!mappedView) + return; + + auto fileMetadata = m_view->GetFile(); + if (!fileMetadata) + return; + + uint64_t address = HexOrDecimalQStringToUint64(m_reloadBase->text()); + if (!fileMetadata->Rebase(mappedView, address)) + return; + + BinaryNinja::Settings::Instance()->Set("analysis.mode", "full", mappedView); + mappedView->Reanalyze(); + + auto frame = ViewFrame::viewFrameForWidget(this); + if (!frame) + return; + + auto fileContext = frame->getFileContext(); + if (!fileContext) + return; + + auto uiContext = UIContext::contextForWidget(this); + if (!uiContext) + return; + + uiContext->recreateViewFrames(fileContext); + fileContext->refreshDataViewCache(); + auto view = frame->getCurrentViewInterface(); + if (!view) + return; + + if (!view->navigate(address)) + m_view->Navigate(std::string("Linear:" + frame->getCurrentDataType().toStdString()), address); +} + + +BaseAddressDetectionWidget::BaseAddressDetectionWidget(QWidget* parent, BinaryNinja::Ref bv) +{ + m_view = bv->GetParentView() ? bv->GetParentView() : bv; + m_layout = new QGridLayout(); + int32_t row = 0; + int32_t column = 0; + + m_layout->addWidget(new QLabel("Architecture:"), row, column, Qt::AlignLeft); + m_inputs.ArchitectureBox = new QComboBox(this); + auto architectures = BinaryNinja::Architecture::GetList(); + auto archItemList = QStringList(); + archItemList << "auto detect"; + for (const auto& arch : architectures) + archItemList << QString::fromStdString(arch->GetName()); + m_inputs.ArchitectureBox->addItems(archItemList); + m_layout->addWidget(m_inputs.ArchitectureBox, row, column + 1, Qt::AlignLeft); + + m_layout->addWidget(new QLabel("Analysis Level:"), row, column + 2, Qt::AlignLeft); + m_inputs.AnalysisBox = new QComboBox(this); + auto analysisItemList = QStringList() << "basic" << "controlFlow" << "full"; + m_inputs.AnalysisBox->addItems(analysisItemList); + m_layout->addWidget(m_inputs.AnalysisBox, row++, column + 3, Qt::AlignLeft); + + m_layout->addWidget(new QLabel("Min. String Length:"), row, column, Qt::AlignLeft); + m_inputs.StrlenLineEdit = new QLineEdit("10"); + m_layout->addWidget(m_inputs.StrlenLineEdit, row, column + 1, Qt::AlignLeft); + + m_layout->addWidget(new QLabel("Alignment:"), row, column + 2, Qt::AlignLeft); + m_inputs.AlignmentLineEdit = new QLineEdit("1024"); + m_layout->addWidget(m_inputs.AlignmentLineEdit, row++, column + 3, Qt::AlignLeft); + + m_layout->addWidget(new QLabel("Lower Boundary:"), row, column, Qt::AlignLeft); + m_inputs.LowerBoundary = new QLineEdit("0x0"); + m_layout->addWidget(m_inputs.LowerBoundary, row, column + 1, Qt::AlignLeft); + + m_layout->addWidget(new QLabel("Upper Boundary:"), row, column + 2, Qt::AlignLeft); + m_inputs.UpperBoundary = new QLineEdit("0xffffffffffffffff"); + m_layout->addWidget(m_inputs.UpperBoundary, row++, column + 3, Qt::AlignLeft); + + m_layout->addWidget(new QLabel("Points Of Interest:"), row, column, Qt::AlignLeft); + auto poiList = QStringList() << "All" << "Strings only" << "Functions only"; + m_inputs.POIBox = new QComboBox(this); + m_inputs.POIBox->addItems(poiList); + m_layout->addWidget(m_inputs.POIBox, row, column + 1, Qt::AlignLeft); + + m_layout->addWidget(new QLabel("Max Pointers:"), row, column + 2, Qt::AlignLeft); + m_inputs.MaxPointersPerCluster = new QLineEdit("128"); + m_layout->addWidget(m_inputs.MaxPointersPerCluster, row++, column + 3, Qt::AlignLeft); + + m_detectBaseAddressButton = new QPushButton("Start"); + connect(m_detectBaseAddressButton, &QPushButton::clicked, this, &BaseAddressDetectionWidget::DetectBaseAddress); + m_layout->addWidget(m_detectBaseAddressButton, row, column, Qt::AlignLeft); + + m_abortButton = new QPushButton("Abort"); + connect(m_abortButton, &QPushButton::clicked, this, &BaseAddressDetectionWidget::Abort); + m_abortButton->setHidden(true); + m_layout->addWidget(m_abortButton, row++, column + 1, Qt::AlignLeft); + + m_layout->addWidget(new QLabel("Status:"), row, column, Qt::AlignLeft); + m_status = new QLabel("Not running"); + auto palette = m_status->palette(); + palette.setColor(QPalette::WindowText, getThemeColor(AlphanumericHighlightColor)); + m_status->setPalette(palette); + m_status->setFont(getMonospaceFont(this)); + m_layout->addWidget(m_status, row++, column + 1, 1, 2, Qt::AlignLeft); + + m_layout->addWidget(new QLabel("Preferred Base:"), row, column, Qt::AlignLeft); + m_preferredBase = new QLabel("Not available"); + m_preferredBase->setTextInteractionFlags(Qt::TextSelectableByMouse); + m_preferredBase->setFont(getMonospaceFont(this)); + m_preferredBase->setPalette(palette); + m_layout->addWidget(m_preferredBase, row, column + 1, Qt::AlignLeft); + + m_layout->addWidget(new QLabel("Confidence:"), row, column + 2, Qt::AlignLeft); + m_confidence = new QLabel("Not available"); + m_confidence->setFont(getMonospaceFont(this)); + m_confidence->setPalette(palette); + m_layout->addWidget(m_confidence, row++, column + 3, Qt::AlignLeft); + + m_resultsTableWidget = new QTableWidget(this); + m_resultsTableWidget->setColumnCount(4); + QStringList header; + header << "Base Address" << "Pointer" << "POI Offset" << "POI Type"; + m_resultsTableWidget->setHorizontalHeaderLabels(header); + m_resultsTableWidget->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); + m_resultsTableWidget->horizontalHeader()->setStretchLastSection(true); + m_resultsTableWidget->verticalHeader()->setVisible(false); + m_resultsTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); + m_resultsTableWidget->setSelectionBehavior(QAbstractItemView::SelectItems); + m_resultsTableWidget->setSelectionMode(QAbstractItemView::SingleSelection); + m_resultsTableWidget->setMinimumHeight(150); + m_layout->addWidget(m_resultsTableWidget, row++, column, 1, 4); + + m_layout->addWidget(new QLabel("Rebase At:"), row, column, Qt::AlignLeft); + m_reloadBase = new QLineEdit("0x0"); + m_layout->addWidget(m_reloadBase, row++, column + 1, Qt::AlignLeft); + + m_rebaseButton = new QPushButton("Start Full Analysis"); + m_rebaseButton->setEnabled(false); + connect(m_rebaseButton, &QPushButton::clicked, this, &BaseAddressDetectionWidget::RebaseWithFullAnalysis); + m_layout->addWidget(m_rebaseButton, row, column, Qt::AlignLeft); + + m_layout->setColumnStretch(3, 1); + setLayout(m_layout); +} \ No newline at end of file diff --git a/examples/triage/baseaddress.h b/examples/triage/baseaddress.h new file mode 100644 index 000000000..aa5e70abc --- /dev/null +++ b/examples/triage/baseaddress.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "theme.h" +#include "fontsettings.h" +#include "viewframe.h" +#include "binaryninjaapi.h" +#include "binaryninjacore.h" + +struct BaseAddressDetectionQtInputs +{ + QComboBox* ArchitectureBox; + QComboBox* AnalysisBox; + QLineEdit* StrlenLineEdit; + QLineEdit* AlignmentLineEdit; + QLineEdit* LowerBoundary; + QLineEdit* UpperBoundary; + QComboBox* POIBox; + QLineEdit* MaxPointersPerCluster; +}; + +struct BaseAddressDetectionQtResults +{ + std::string Status; + std::set> Scores; + BinaryNinja::BaseAddressDetectionConfidence Confidence; +}; + +class BaseAddressDetectionThread : public QThread +{ + Q_OBJECT + BinaryNinja::Ref m_view; + BinaryNinja::BaseAddressDetection* m_baseDetection; + BaseAddressDetectionQtInputs* m_inputs {}; + void run() override; + +public: + BaseAddressDetectionThread(BaseAddressDetectionQtInputs* widgetInputs, BinaryNinja::Ref bv) + { + m_inputs = widgetInputs; + m_view = bv; + m_baseDetection = new BinaryNinja::BaseAddressDetection(m_view); + } + + void Abort() { m_baseDetection->Abort(); } + bool IsAborted() { return m_baseDetection->IsAborted(); } + +signals: + void ResultReady(const BaseAddressDetectionQtResults& result); +}; + +class BaseAddressDetectionWidget : public QWidget +{ + BaseAddressDetectionThread* m_worker; + BinaryNinja::Ref m_view; + QGridLayout* m_layout {}; + + QPushButton* m_detectBaseAddressButton = nullptr; + QPushButton* m_abortButton = nullptr; + + BaseAddressDetectionQtInputs m_inputs; + QLabel* m_preferredBase; + QLabel* m_confidence; + QLabel* m_status; + QLineEdit* m_reloadBase; + QPushButton* m_rebaseButton; + QTableWidget* m_resultsTableWidget; + + void DetectBaseAddress(); + void RebaseWithFullAnalysis(); + void Abort(); + void HandleResults(const BaseAddressDetectionQtResults& results); + +public: + BaseAddressDetectionWidget(QWidget* parent, BinaryNinja::Ref bv); +}; \ No newline at end of file diff --git a/examples/triage/view.cpp b/examples/triage/view.cpp index 87cdb61ae..579f8917d 100644 --- a/examples/triage/view.cpp +++ b/examples/triage/view.cpp @@ -10,6 +10,7 @@ #include "librariesinfo.h" #include "headers.h" #include "strings.h" +#include "baseaddress.h" #include "fontsettings.h" #include @@ -52,6 +53,18 @@ TriageView::TriageView(QWidget* parent, BinaryViewRef data) : QScrollArea(parent delete hdr; } + auto fileMetadata = m_data->GetFile(); + auto existingViews = fileMetadata->GetExistingViews(); + if ((existingViews.size() == 2 && fileMetadata->GetViewOfType("Mapped")) || existingViews.size() == 1) + { + // Binary either only has raw view (Open for triage mode) or raw and mapped view + QGroupBox* baseDetectionGroup = new QGroupBox("Base Address Detection", container); + QVBoxLayout* baseDetectionLayout = new QVBoxLayout(); + baseDetectionLayout->addWidget(new BaseAddressDetectionWidget(this, data)); + baseDetectionGroup->setLayout(baseDetectionLayout); + layout->addWidget(baseDetectionGroup); + } + QGroupBox* librariesGroup = new QGroupBox("Libraries", container); QVBoxLayout* librariesLayout = new QVBoxLayout(); librariesLayout->addWidget(new LibrariesWidget(this, data)); From 465a8d5d7afd5a5648338009e0fb8dbe4f199347 Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Thu, 11 Apr 2024 10:40:09 -0400 Subject: [PATCH 23/50] Querying candidate base address reasons Querying vector of reason information for a candidate base address using the new core API --- basedetection.cpp | 23 +++++++++-- binaryninjaapi.h | 19 +++++---- binaryninjacore.h | 39 ++++++++---------- examples/triage/baseaddress.cpp | 73 +++++++++++++++++---------------- examples/triage/baseaddress.h | 4 +- 5 files changed, 85 insertions(+), 73 deletions(-) diff --git a/basedetection.cpp b/basedetection.cpp index 4389f1b7b..e994ede59 100644 --- a/basedetection.cpp +++ b/basedetection.cpp @@ -21,7 +21,6 @@ #include "binaryninjaapi.h" using namespace BinaryNinja; -using namespace std; BaseAddressDetection::BaseAddressDetection(Ref bv) @@ -65,13 +64,29 @@ bool BaseAddressDetection::IsAborted() } -std::set> BaseAddressDetection::GetScores(BaseAddressDetectionConfidence* confidence) +std::set> BaseAddressDetection::GetScores(BNBaseAddressDetectionConfidence* confidence, + uint64_t *lastTestedBaseAddress) { std::set> result; BNBaseAddressDetectionScore scores[10]; - size_t numCandidates = BNGetBaseAddressDetectionScores(m_object, scores, 10, - (BNBaseAddressDetectionConfidence *)confidence); + size_t numCandidates = BNGetBaseAddressDetectionScores(m_object, scores, 10, confidence, lastTestedBaseAddress); for (size_t i = 0; i < numCandidates; i++) result.insert(std::make_pair(scores[i].Score, scores[i].BaseAddress)); return result; } + + +std::vector BaseAddressDetection::GetReasonsForBaseAddress(uint64_t baseAddress) +{ + std::vector result; + size_t count; + BNBaseAddressDetectionReason *reasons = BNGetBaseAddressDetectionReasons(m_object, baseAddress, &count); + if (!reasons) + return result; + + for (size_t i = 0; i < count; i++) + result.push_back(reasons[i]); + + free(reasons); + return result; +} diff --git a/binaryninjaapi.h b/binaryninjaapi.h index 6bd33c809..803ebf769 100644 --- a/binaryninjaapi.h +++ b/binaryninjaapi.h @@ -17391,13 +17391,6 @@ namespace BinaryNinja { uint32_t MaxPointersPerCluster; }; - enum BaseAddressDetectionConfidence - { - NoConfidence = 0, - LowConfidence = 1, - HighConfidence = 2, - }; - /*! \ingroup baseaddressdetection */ @@ -17418,10 +17411,18 @@ namespace BinaryNinja { /*! Get the top 10 candidate base addresses and thier scores - \param confidence Confidence level that the top base address candidate is correct + \param confidence Confidence level that indicates the likelihood the top base address candidate is correct + \param lastTestedBaseAddress Last base address tested before analysis was aborted or completed \return Set of pairs containing candidate base addresses and their scores */ - std::set> GetScores(BaseAddressDetectionConfidence* confidence); + std::set> GetScores(BNBaseAddressDetectionConfidence* confidence, uint64_t *lastTestedBaseAddress); + + /*! Get a vector of BNBaseAddressDetectionReasons containing information that indicates why a base address was reported as a candidate + + \param baseAddress Base address to query reasons for + \return Vector of reason structures containing information about why a base address was reported as a candidate + */ + std::vector GetReasonsForBaseAddress(uint64_t baseAddress); /*! Abort base address detection */ diff --git a/binaryninjacore.h b/binaryninjacore.h index 25d7618e5..ffe17dbac 100644 --- a/binaryninjacore.h +++ b/binaryninjacore.h @@ -3160,25 +3160,25 @@ extern "C" typedef enum BNBaseAddressDetectionPOISetting { - POI_ANALYSIS_STRINGS_ONLY, - POI_ANALYSIS_FUNCTIONS_ONLY, - POI_ANALYSIS_ALL, + POIAnalysisStringsOnly, + POIAnalysisFunctionsOnly, + POIAnalysisAll, } BNBaseAddressDetectionPOISetting; typedef enum BNBaseAddressDetectionPOIType { - POI_STRING, - POI_FUNCTION, - POI_DATA_VARIABLE, - POI_FILE_START, - POI_FILE_END, + POIString, + POIFunction, + POIDataVariable, + POIFileStart, + POIFileEnd, } BNBaseAddressDetectionPOIType; typedef enum BNBaseAddressDetectionConfidence { - CONFIDENCE_UNASSIGNED, - CONFIDENCE_LOW, - CONFIDENCE_HIGH, + NoConfidence, + LowConfidence, + HighConfidence, } BNBaseAddressDetectionConfidence; typedef struct BNBaseAddressDetectionSettings @@ -3197,7 +3197,7 @@ extern "C" { uint64_t Pointer; uint64_t POIOffset; - BNBaseAddressDetectionPOIType BaseAddressDetectionPOIType; + BNBaseAddressDetectionPOIType POIType; } BNBaseAddressDetectionReason; typedef struct BNBaseAddressDetectionScore @@ -3206,15 +3206,6 @@ extern "C" uint64_t BaseAddress; } BNBaseAddressDetectionScore; - typedef struct BNBaseAddressDetectionResults - { - BNBaseAddressDetectionConfidence Confidence; - BNBaseAddressDetectionScore** Scores; - BNBaseAddressDetectionReason** Reasons; - char* ErrorStr; - uint64_t LastTestedBaseAddress; - } BNBaseAddressDetectionResults; - BINARYNINJACOREAPI char* BNAllocString(const char* contents); BINARYNINJACOREAPI void BNFreeString(char* str); BINARYNINJACOREAPI char** BNAllocStringList(const char** contents, size_t size); @@ -7049,8 +7040,10 @@ extern "C" // Base Address Detection BINARYNINJACOREAPI BNBaseAddressDetection* BNCreateBaseAddressDetection(BNBinaryView *view); BINARYNINJACOREAPI bool BNDetectBaseAddress(BNBaseAddressDetection* bad, BNBaseAddressDetectionSettings& settings); - BINARYNINJACOREAPI size_t BNGetBaseAddressDetectionScores(BNBaseAddressDetection* bad, - BNBaseAddressDetectionScore* scores, size_t count, BNBaseAddressDetectionConfidence* confidence); + BINARYNINJACOREAPI size_t BNGetBaseAddressDetectionScores(BNBaseAddressDetection* bad, BNBaseAddressDetectionScore* scores, size_t count, + BNBaseAddressDetectionConfidence* confidence, uint64_t* lastTestedBaseAddress); + BINARYNINJACOREAPI BNBaseAddressDetectionReason* BNGetBaseAddressDetectionReasons(BNBaseAddressDetection* bad, + uint64_t baseAddress, size_t* count); BINARYNINJACOREAPI void BNAbortBaseAddressDetection(BNBaseAddressDetection* bad); BINARYNINJACOREAPI bool BNIsBaseAddressDetectionAborted(BNBaseAddressDetection* bad); BINARYNINJACOREAPI void BNFreeBaseAddressDetection(BNBaseAddressDetection* bad); diff --git a/examples/triage/baseaddress.cpp b/examples/triage/baseaddress.cpp index 41068cfb3..542a1f941 100644 --- a/examples/triage/baseaddress.cpp +++ b/examples/triage/baseaddress.cpp @@ -6,10 +6,10 @@ using namespace std; BNBaseAddressDetectionPOISetting BaseAddressDetectionPOISettingFromString(const std::string& setting) { if (setting == "Strings only") - return POI_ANALYSIS_STRINGS_ONLY; + return POIAnalysisStringsOnly; if (setting == "Functions only") - return POI_ANALYSIS_FUNCTIONS_ONLY; - return POI_ANALYSIS_ALL; // Default to All + return POIAnalysisFunctionsOnly; + return POIAnalysisAll; // Default to All } @@ -17,34 +17,34 @@ std::string BaseAddressDetectionPOITypeToString(BNBaseAddressDetectionPOIType ty { switch (type) { - case POI_STRING: - return "String"; - case POI_FUNCTION: - return "Function"; - case POI_DATA_VARIABLE: - return "Data variable"; - case POI_FILE_END: - return "File end"; - case POI_FILE_START: - return "File start"; - default: - return "Unknown"; + case POIString: + return "String"; + case POIFunction: + return "Function"; + case POIDataVariable: + return "Data variable"; + case POIFileEnd: + return "File end"; + case POIFileStart: + return "File start"; + default: + return "Unknown"; } } -std::string BaseAddressDetectionConfidenceToString(BinaryNinja::BaseAddressDetectionConfidence level) +std::string BaseAddressDetectionConfidenceToString(BNBaseAddressDetectionConfidence level) { switch (level) { - case BinaryNinja::NoConfidence: - return "Unassigned"; - case BinaryNinja::HighConfidence: - return "High"; - case BinaryNinja::LowConfidence: - return "Low"; - default: - return "Unknown"; + case NoConfidence: + return "Unassigned"; + case HighConfidence: + return "High"; + case LowConfidence: + return "Low"; + default: + return "Unknown"; } } @@ -124,21 +124,24 @@ void BaseAddressDetectionThread::run() if (!m_baseDetection->DetectBaseAddress(settings)) emit ResultReady(results); - auto scores = m_baseDetection->GetScores(&results.Confidence); + auto scores = m_baseDetection->GetScores(&results.Confidence, &results.LastTestedBaseAddress); results.Scores = scores; + for (const auto& score : scores) + { + auto reasons = m_baseDetection->GetReasonsForBaseAddress(score.second); + results.Reasons[score.second] = reasons; + } + emit ResultReady(results); } - void BaseAddressDetectionWidget::HandleResults(const BaseAddressDetectionQtResults& results) { if (!results.Status.empty()) m_status->setText(QString::fromStdString(results.Status)); - /* TODO if (results.Status.empty() && m_worker->IsAborted()) - m_status->setText("Aborted by user (Last Base: 0x" + QString::number(results.Results.LastTestedBaseAddress, 16) + ")"); - */ + m_status->setText("Aborted by user (Last Base: 0x" + QString::number(results.LastTestedBaseAddress, 16) + ")"); if (results.Scores.empty()) { @@ -159,27 +162,25 @@ void BaseAddressDetectionWidget::HandleResults(const BaseAddressDetectionQtResul } m_resultsTableWidget->clearContents(); - /* TODO size_t numRows = 0; - for (auto rit = results.Results.Scores.rbegin(); rit != results.Results.Scores.rend(); rit++) - numRows += results.Results.Reasons.at(rit->second).size(); + for (auto rit = results.Scores.rbegin(); rit != results.Scores.rend(); rit++) + numRows += results.Reasons.at(rit->second).size(); m_resultsTableWidget->setRowCount(numRows); size_t row = 0; - for (auto rit = results.Results.Scores.rbegin(); rit != results.Results.Scores.rend(); rit++) + for (auto rit = results.Scores.rbegin(); rit != results.Scores.rend(); rit++) { auto [score, baseaddr] = *rit; - for (const auto& reason : results.Results.Reasons.at(baseaddr)) + for (const auto& reason : results.Reasons.at(baseaddr)) { m_resultsTableWidget->setItem(row, 0, new QTableWidgetItem("0x" + QString::number(baseaddr, 16))); m_resultsTableWidget->setItem(row, 1, new QTableWidgetItem("0x" + QString::number(reason.Pointer, 16))); m_resultsTableWidget->setItem(row, 2, new QTableWidgetItem("0x" + QString::number(reason.POIOffset, 16))); m_resultsTableWidget->setItem(row, 3, new QTableWidgetItem( - QString::fromStdString(BaseAddressDetectionPOITypeToString(reason.BaseAddressDetectionPOIType)))); + QString::fromStdString(BaseAddressDetectionPOITypeToString(reason.POIType)))); row++; } } - */ m_detectBaseAddressButton->setEnabled(true); m_abortButton->setHidden(true); diff --git a/examples/triage/baseaddress.h b/examples/triage/baseaddress.h index aa5e70abc..f6dc79f15 100644 --- a/examples/triage/baseaddress.h +++ b/examples/triage/baseaddress.h @@ -28,7 +28,9 @@ struct BaseAddressDetectionQtResults { std::string Status; std::set> Scores; - BinaryNinja::BaseAddressDetectionConfidence Confidence; + BNBaseAddressDetectionConfidence Confidence; + std::map> Reasons; + uint64_t LastTestedBaseAddress; }; class BaseAddressDetectionThread : public QThread From 6d11f801e2dd6db6ccee618ec07d4a764878d950 Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Tue, 16 Apr 2024 09:47:52 -0400 Subject: [PATCH 24/50] Hiding base detection results widgets before run --- basedetection.cpp | 2 +- binaryninjacore.h | 1 + examples/triage/baseaddress.cpp | 60 ++++++++++++++++++++++++--------- examples/triage/baseaddress.h | 5 ++- 4 files changed, 51 insertions(+), 17 deletions(-) diff --git a/basedetection.cpp b/basedetection.cpp index e994ede59..5cc7c39fa 100644 --- a/basedetection.cpp +++ b/basedetection.cpp @@ -87,6 +87,6 @@ std::vector BaseAddressDetection::GetReasonsForBas for (size_t i = 0; i < count; i++) result.push_back(reasons[i]); - free(reasons); + BNFreeBaseAddressDetectionReasons(reasons); return result; } diff --git a/binaryninjacore.h b/binaryninjacore.h index ffe17dbac..35bfbe687 100644 --- a/binaryninjacore.h +++ b/binaryninjacore.h @@ -7044,6 +7044,7 @@ extern "C" BNBaseAddressDetectionConfidence* confidence, uint64_t* lastTestedBaseAddress); BINARYNINJACOREAPI BNBaseAddressDetectionReason* BNGetBaseAddressDetectionReasons(BNBaseAddressDetection* bad, uint64_t baseAddress, size_t* count); + BINARYNINJACOREAPI void BNFreeBaseAddressDetectionReasons(BNBaseAddressDetectionReason* reasons); BINARYNINJACOREAPI void BNAbortBaseAddressDetection(BNBaseAddressDetection* bad); BINARYNINJACOREAPI bool BNIsBaseAddressDetectionAborted(BNBaseAddressDetection* bad); BINARYNINJACOREAPI void BNFreeBaseAddressDetection(BNBaseAddressDetection* bad); diff --git a/examples/triage/baseaddress.cpp b/examples/triage/baseaddress.cpp index 542a1f941..0acf66f6b 100644 --- a/examples/triage/baseaddress.cpp +++ b/examples/triage/baseaddress.cpp @@ -135,6 +135,32 @@ void BaseAddressDetectionThread::run() emit ResultReady(results); } + +void BaseAddressDetectionWidget::HideResultsWidgets(bool hide) +{ + if (hide) + { + m_preferredBaseLabel->setHidden(true); + m_preferredBase->setHidden(true); + m_confidenceLabel->setHidden(true); + m_confidence->setHidden(true); + m_resultsTableWidget->setHidden(true); + m_reloadBase->setHidden(true); + m_rebaseButton->setHidden(true); + } + else + { + m_preferredBaseLabel->setHidden(false); + m_preferredBase->setHidden(false); + m_confidenceLabel->setHidden(false); + m_confidence->setHidden(false); + m_resultsTableWidget->setHidden(false); + m_reloadBase->setHidden(false); + m_rebaseButton->setHidden(false); + } +} + + void BaseAddressDetectionWidget::HandleResults(const BaseAddressDetectionQtResults& results) { if (!results.Status.empty()) @@ -152,7 +178,7 @@ void BaseAddressDetectionWidget::HandleResults(const BaseAddressDetectionQtResul } else { - m_rebaseButton->setEnabled(true); + HideResultsWidgets(false); if (results.Status.empty() && !m_worker->IsAborted()) m_status->setText("Completed with results"); m_preferredBase->setText("0x" + QString::number(results.Scores.rbegin()->second, 16)); @@ -182,18 +208,20 @@ void BaseAddressDetectionWidget::HandleResults(const BaseAddressDetectionQtResul } } - m_detectBaseAddressButton->setEnabled(true); m_abortButton->setHidden(true); + m_startButton->setHidden(false); + m_startButton->setEnabled(true); } void BaseAddressDetectionWidget::DetectBaseAddress() { + HideResultsWidgets(true); m_status->setText("Running..."); m_resultsTableWidget->clearContents(); m_preferredBase->setText("Not available"); m_confidence->setText("Not available"); - m_detectBaseAddressButton->setEnabled(false); + m_startButton->setHidden(true); m_worker = new BaseAddressDetectionThread(&m_inputs, m_view); connect(m_worker, &BaseAddressDetectionThread::ResultReady, this, &BaseAddressDetectionWidget::HandleResults); connect(m_worker, &BaseAddressDetectionThread::finished, m_worker, &QObject::deleteLater); @@ -206,6 +234,8 @@ void BaseAddressDetectionWidget::Abort() { m_worker->Abort(); m_abortButton->setHidden(true); + m_startButton->setHidden(false); + m_startButton->setEnabled(false); } @@ -298,31 +328,32 @@ BaseAddressDetectionWidget::BaseAddressDetectionWidget(QWidget* parent, BinaryNi m_inputs.MaxPointersPerCluster = new QLineEdit("128"); m_layout->addWidget(m_inputs.MaxPointersPerCluster, row++, column + 3, Qt::AlignLeft); - m_detectBaseAddressButton = new QPushButton("Start"); - connect(m_detectBaseAddressButton, &QPushButton::clicked, this, &BaseAddressDetectionWidget::DetectBaseAddress); - m_layout->addWidget(m_detectBaseAddressButton, row, column, Qt::AlignLeft); + m_startButton = new QPushButton("Start"); + connect(m_startButton, &QPushButton::clicked, this, &BaseAddressDetectionWidget::DetectBaseAddress); + m_layout->addWidget(m_startButton, row, column, Qt::AlignLeft); m_abortButton = new QPushButton("Abort"); connect(m_abortButton, &QPushButton::clicked, this, &BaseAddressDetectionWidget::Abort); m_abortButton->setHidden(true); - m_layout->addWidget(m_abortButton, row++, column + 1, Qt::AlignLeft); + m_layout->addWidget(m_abortButton, row, column, Qt::AlignLeft); - m_layout->addWidget(new QLabel("Status:"), row, column, Qt::AlignLeft); m_status = new QLabel("Not running"); auto palette = m_status->palette(); palette.setColor(QPalette::WindowText, getThemeColor(AlphanumericHighlightColor)); m_status->setPalette(palette); m_status->setFont(getMonospaceFont(this)); - m_layout->addWidget(m_status, row++, column + 1, 1, 2, Qt::AlignLeft); + m_layout->addWidget(m_status, row++, column + 1, 1, 2, Qt::AlignLeft); - m_layout->addWidget(new QLabel("Preferred Base:"), row, column, Qt::AlignLeft); + m_preferredBaseLabel = new QLabel("Preferred Base:"); + m_layout->addWidget(m_preferredBaseLabel, row, column, Qt::AlignLeft); m_preferredBase = new QLabel("Not available"); m_preferredBase->setTextInteractionFlags(Qt::TextSelectableByMouse); m_preferredBase->setFont(getMonospaceFont(this)); m_preferredBase->setPalette(palette); m_layout->addWidget(m_preferredBase, row, column + 1, Qt::AlignLeft); - m_layout->addWidget(new QLabel("Confidence:"), row, column + 2, Qt::AlignLeft); + m_confidenceLabel = new QLabel("Confidence:"); + m_layout->addWidget(m_confidenceLabel, row, column + 2, Qt::AlignLeft); m_confidence = new QLabel("Not available"); m_confidence->setFont(getMonospaceFont(this)); m_confidence->setPalette(palette); @@ -342,15 +373,14 @@ BaseAddressDetectionWidget::BaseAddressDetectionWidget(QWidget* parent, BinaryNi m_resultsTableWidget->setMinimumHeight(150); m_layout->addWidget(m_resultsTableWidget, row++, column, 1, 4); - m_layout->addWidget(new QLabel("Rebase At:"), row, column, Qt::AlignLeft); m_reloadBase = new QLineEdit("0x0"); - m_layout->addWidget(m_reloadBase, row++, column + 1, Qt::AlignLeft); + m_layout->addWidget(m_reloadBase, row, column, Qt::AlignLeft); m_rebaseButton = new QPushButton("Start Full Analysis"); - m_rebaseButton->setEnabled(false); connect(m_rebaseButton, &QPushButton::clicked, this, &BaseAddressDetectionWidget::RebaseWithFullAnalysis); - m_layout->addWidget(m_rebaseButton, row, column, Qt::AlignLeft); + m_layout->addWidget(m_rebaseButton, row, column + 1, Qt::AlignLeft); + HideResultsWidgets(true); m_layout->setColumnStretch(3, 1); setLayout(m_layout); } \ No newline at end of file diff --git a/examples/triage/baseaddress.h b/examples/triage/baseaddress.h index f6dc79f15..257019476 100644 --- a/examples/triage/baseaddress.h +++ b/examples/triage/baseaddress.h @@ -62,11 +62,13 @@ class BaseAddressDetectionWidget : public QWidget BinaryNinja::Ref m_view; QGridLayout* m_layout {}; - QPushButton* m_detectBaseAddressButton = nullptr; + QPushButton* m_startButton = nullptr; QPushButton* m_abortButton = nullptr; BaseAddressDetectionQtInputs m_inputs; + QLabel* m_preferredBaseLabel; QLabel* m_preferredBase; + QLabel* m_confidenceLabel; QLabel* m_confidence; QLabel* m_status; QLineEdit* m_reloadBase; @@ -77,6 +79,7 @@ class BaseAddressDetectionWidget : public QWidget void RebaseWithFullAnalysis(); void Abort(); void HandleResults(const BaseAddressDetectionQtResults& results); + void HideResultsWidgets(bool hide); public: BaseAddressDetectionWidget(QWidget* parent, BinaryNinja::Ref bv); From 2aa2be5c713a76c86118a225c5cae6ef3585412d Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Wed, 17 Apr 2024 12:29:09 -0400 Subject: [PATCH 25/50] Simplified base address detection UI Moved advanced settings to expandable group and no longer display results items until analysis completes --- basedetection.h | 0 binaryninjacore.h | 2 +- examples/triage/baseaddress.cpp | 101 +++++++++++++++++++------------- examples/triage/baseaddress.h | 8 ++- 4 files changed, 66 insertions(+), 45 deletions(-) delete mode 100644 basedetection.h diff --git a/basedetection.h b/basedetection.h deleted file mode 100644 index e69de29bb..000000000 diff --git a/binaryninjacore.h b/binaryninjacore.h index 35bfbe687..083533735 100644 --- a/binaryninjacore.h +++ b/binaryninjacore.h @@ -37,7 +37,7 @@ // Current ABI version for linking to the core. This is incremented any time // there are changes to the API that affect linking, including new functions, // new types, or modifications to existing functions or types. -#define BN_CURRENT_CORE_ABI_VERSION 59 +#define BN_CURRENT_CORE_ABI_VERSION 60 // Minimum ABI version that is supported for loading of plugins. Plugins that // are linked to an ABI version less than this will not be able to load and diff --git a/examples/triage/baseaddress.cpp b/examples/triage/baseaddress.cpp index 0acf66f6b..663b1ff84 100644 --- a/examples/triage/baseaddress.cpp +++ b/examples/triage/baseaddress.cpp @@ -3,7 +3,7 @@ using namespace std; -BNBaseAddressDetectionPOISetting BaseAddressDetectionPOISettingFromString(const std::string& setting) +BNBaseAddressDetectionPOISetting BaseAddressDetectionPOISettingFromString(const string& setting) { if (setting == "Strings only") return POIAnalysisStringsOnly; @@ -13,7 +13,7 @@ BNBaseAddressDetectionPOISetting BaseAddressDetectionPOISettingFromString(const } -std::string BaseAddressDetectionPOITypeToString(BNBaseAddressDetectionPOIType type) +string BaseAddressDetectionPOITypeToString(BNBaseAddressDetectionPOIType type) { switch (type) { @@ -33,7 +33,7 @@ std::string BaseAddressDetectionPOITypeToString(BNBaseAddressDetectionPOIType ty } -std::string BaseAddressDetectionConfidenceToString(BNBaseAddressDetectionConfidence level) +string BaseAddressDetectionConfidenceToString(BNBaseAddressDetectionConfidence level) { switch (level) { @@ -167,11 +167,12 @@ void BaseAddressDetectionWidget::HandleResults(const BaseAddressDetectionQtResul m_status->setText(QString::fromStdString(results.Status)); if (results.Status.empty() && m_worker->IsAborted()) - m_status->setText("Aborted by user (Last Base: 0x" + QString::number(results.LastTestedBaseAddress, 16) + ")"); + m_status->setText(QString("Aborted by user (Last Base: 0x%1)").arg( + QString::number(results.LastTestedBaseAddress, 16))); if (results.Scores.empty()) { - if (!m_worker->IsAborted()) + if (!m_worker->IsAborted() && results.Status.empty()) m_status->setText("Completed with no results"); m_preferredBase->setText("Not available"); m_confidence->setText("Not available"); @@ -181,10 +182,11 @@ void BaseAddressDetectionWidget::HandleResults(const BaseAddressDetectionQtResul HideResultsWidgets(false); if (results.Status.empty() && !m_worker->IsAborted()) m_status->setText("Completed with results"); - m_preferredBase->setText("0x" + QString::number(results.Scores.rbegin()->second, 16)); - m_confidence->setText(QString::fromStdString(BaseAddressDetectionConfidenceToString(results.Confidence)) + - " (Score: " + QString::number(results.Scores.rbegin()->first) + ")"); - m_reloadBase->setText("0x" + QString::number(results.Scores.rbegin()->second, 16)); + m_preferredBase->setText(QString("0x%1").arg(QString::number(results.Scores.rbegin()->second, 16))); + m_confidence->setText(QString("%1 (Score: %2)").arg( + QString::fromStdString(BaseAddressDetectionConfidenceToString(results.Confidence)), + QString::number(results.Scores.rbegin()->first))); + m_reloadBase->setText(QString("0x%1").arg(QString::number(results.Scores.rbegin()->second, 16))); } m_resultsTableWidget->clearContents(); @@ -199,9 +201,12 @@ void BaseAddressDetectionWidget::HandleResults(const BaseAddressDetectionQtResul auto [score, baseaddr] = *rit; for (const auto& reason : results.Reasons.at(baseaddr)) { - m_resultsTableWidget->setItem(row, 0, new QTableWidgetItem("0x" + QString::number(baseaddr, 16))); - m_resultsTableWidget->setItem(row, 1, new QTableWidgetItem("0x" + QString::number(reason.Pointer, 16))); - m_resultsTableWidget->setItem(row, 2, new QTableWidgetItem("0x" + QString::number(reason.POIOffset, 16))); + m_resultsTableWidget->setItem(row, 0, + new QTableWidgetItem(QString("0x%1").arg(QString::number(baseaddr, 16)))); + m_resultsTableWidget->setItem(row, 1, new QTableWidgetItem( + QString("0x%1").arg(QString::number(reason.Pointer, 16)))); + m_resultsTableWidget->setItem(row, 2, new QTableWidgetItem( + QString("0x%1").arg(QString::number(reason.POIOffset, 16)))); m_resultsTableWidget->setItem(row, 3, new QTableWidgetItem( QString::fromStdString(BaseAddressDetectionPOITypeToString(reason.POIType)))); row++; @@ -275,7 +280,44 @@ void BaseAddressDetectionWidget::RebaseWithFullAnalysis() return; if (!view->navigate(address)) - m_view->Navigate(std::string("Linear:" + frame->getCurrentDataType().toStdString()), address); + m_view->Navigate(string("Linear:" + frame->getCurrentDataType().toStdString()), address); +} + + +void BaseAddressDetectionWidget::CreateAdvancedSettingsGroup() +{ + int32_t row = 0; + int32_t column = 0; + auto grid = new QGridLayout(); + + grid->addWidget(new QLabel("Min. String Length:"), row, column, Qt::AlignLeft); + m_inputs.StrlenLineEdit = new QLineEdit("10"); + grid->addWidget(m_inputs.StrlenLineEdit, row, column + 1, Qt::AlignLeft); + + grid->addWidget(new QLabel("Alignment:"), row, column + 2, Qt::AlignLeft); + m_inputs.AlignmentLineEdit = new QLineEdit("1024"); + grid->addWidget(m_inputs.AlignmentLineEdit, row++, column + 3, Qt::AlignLeft); + + grid->addWidget(new QLabel("Lower Boundary:"), row, column, Qt::AlignLeft); + m_inputs.LowerBoundary = new QLineEdit("0x0"); + grid->addWidget(m_inputs.LowerBoundary, row, column + 1, Qt::AlignLeft); + + grid->addWidget(new QLabel("Upper Boundary:"), row, column + 2, Qt::AlignLeft); + m_inputs.UpperBoundary = new QLineEdit("0xffffffffffffffff"); + grid->addWidget(m_inputs.UpperBoundary, row++, column + 3, Qt::AlignLeft); + + grid->addWidget(new QLabel("Points Of Interest:"), row, column, Qt::AlignLeft); + auto poiList = QStringList() << "All" << "Strings only" << "Functions only"; + m_inputs.POIBox = new QComboBox(this); + m_inputs.POIBox->addItems(poiList); + grid->addWidget(m_inputs.POIBox, row, column + 1, Qt::AlignLeft); + + grid->addWidget(new QLabel("Max Pointers:"), row, column + 2, Qt::AlignLeft); + m_inputs.MaxPointersPerCluster = new QLineEdit("128"); + grid->addWidget(m_inputs.MaxPointersPerCluster, row++, column + 3, Qt::AlignLeft); + + m_advancedSettingsGroup = new ExpandableGroup(grid); + m_advancedSettingsGroup->setTitle("Advanced Settings"); } @@ -294,39 +336,16 @@ BaseAddressDetectionWidget::BaseAddressDetectionWidget(QWidget* parent, BinaryNi for (const auto& arch : architectures) archItemList << QString::fromStdString(arch->GetName()); m_inputs.ArchitectureBox->addItems(archItemList); - m_layout->addWidget(m_inputs.ArchitectureBox, row, column + 1, Qt::AlignLeft); + m_layout->addWidget(m_inputs.ArchitectureBox, row++, column + 1, Qt::AlignLeft); - m_layout->addWidget(new QLabel("Analysis Level:"), row, column + 2, Qt::AlignLeft); + m_layout->addWidget(new QLabel("Analysis Level:"), row, column, Qt::AlignLeft); m_inputs.AnalysisBox = new QComboBox(this); auto analysisItemList = QStringList() << "basic" << "controlFlow" << "full"; m_inputs.AnalysisBox->addItems(analysisItemList); - m_layout->addWidget(m_inputs.AnalysisBox, row++, column + 3, Qt::AlignLeft); + m_layout->addWidget(m_inputs.AnalysisBox, row++, column + 1, Qt::AlignLeft); - m_layout->addWidget(new QLabel("Min. String Length:"), row, column, Qt::AlignLeft); - m_inputs.StrlenLineEdit = new QLineEdit("10"); - m_layout->addWidget(m_inputs.StrlenLineEdit, row, column + 1, Qt::AlignLeft); - - m_layout->addWidget(new QLabel("Alignment:"), row, column + 2, Qt::AlignLeft); - m_inputs.AlignmentLineEdit = new QLineEdit("1024"); - m_layout->addWidget(m_inputs.AlignmentLineEdit, row++, column + 3, Qt::AlignLeft); - - m_layout->addWidget(new QLabel("Lower Boundary:"), row, column, Qt::AlignLeft); - m_inputs.LowerBoundary = new QLineEdit("0x0"); - m_layout->addWidget(m_inputs.LowerBoundary, row, column + 1, Qt::AlignLeft); - - m_layout->addWidget(new QLabel("Upper Boundary:"), row, column + 2, Qt::AlignLeft); - m_inputs.UpperBoundary = new QLineEdit("0xffffffffffffffff"); - m_layout->addWidget(m_inputs.UpperBoundary, row++, column + 3, Qt::AlignLeft); - - m_layout->addWidget(new QLabel("Points Of Interest:"), row, column, Qt::AlignLeft); - auto poiList = QStringList() << "All" << "Strings only" << "Functions only"; - m_inputs.POIBox = new QComboBox(this); - m_inputs.POIBox->addItems(poiList); - m_layout->addWidget(m_inputs.POIBox, row, column + 1, Qt::AlignLeft); - - m_layout->addWidget(new QLabel("Max Pointers:"), row, column + 2, Qt::AlignLeft); - m_inputs.MaxPointersPerCluster = new QLineEdit("128"); - m_layout->addWidget(m_inputs.MaxPointersPerCluster, row++, column + 3, Qt::AlignLeft); + CreateAdvancedSettingsGroup(); + m_layout->addWidget(m_advancedSettingsGroup, row++, column, 1, 4); m_startButton = new QPushButton("Start"); connect(m_startButton, &QPushButton::clicked, this, &BaseAddressDetectionWidget::DetectBaseAddress); diff --git a/examples/triage/baseaddress.h b/examples/triage/baseaddress.h index 257019476..9a282f3e4 100644 --- a/examples/triage/baseaddress.h +++ b/examples/triage/baseaddress.h @@ -8,6 +8,7 @@ #include #include "theme.h" #include "fontsettings.h" +#include "expandablegroup.h" #include "viewframe.h" #include "binaryninjaapi.h" #include "binaryninjacore.h" @@ -60,12 +61,11 @@ class BaseAddressDetectionWidget : public QWidget { BaseAddressDetectionThread* m_worker; BinaryNinja::Ref m_view; - QGridLayout* m_layout {}; + BaseAddressDetectionQtInputs m_inputs; + QGridLayout* m_layout {}; QPushButton* m_startButton = nullptr; QPushButton* m_abortButton = nullptr; - - BaseAddressDetectionQtInputs m_inputs; QLabel* m_preferredBaseLabel; QLabel* m_preferredBase; QLabel* m_confidenceLabel; @@ -74,12 +74,14 @@ class BaseAddressDetectionWidget : public QWidget QLineEdit* m_reloadBase; QPushButton* m_rebaseButton; QTableWidget* m_resultsTableWidget; + ExpandableGroup* m_advancedSettingsGroup; void DetectBaseAddress(); void RebaseWithFullAnalysis(); void Abort(); void HandleResults(const BaseAddressDetectionQtResults& results); void HideResultsWidgets(bool hide); + void CreateAdvancedSettingsGroup(); public: BaseAddressDetectionWidget(QWidget* parent, BinaryNinja::Ref bv); From 37f394946c75a6e0e4114a27db2f6ea8d03b143c Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Wed, 17 Apr 2024 13:55:59 -0400 Subject: [PATCH 26/50] Moved TryReadPointer to core BinaryReader class --- binaryninjacore.h | 3 ++- binaryreader.cpp | 9 +-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/binaryninjacore.h b/binaryninjacore.h index 083533735..d0f7a5e2c 100644 --- a/binaryninjacore.h +++ b/binaryninjacore.h @@ -44,7 +44,7 @@ // will require rebuilding. The minimum version is increased when there are // incompatible changes that break binary compatibility, such as changes to // existing types or functions. -#define BN_MINIMUM_CORE_ABI_VERSION 59 +#define BN_MINIMUM_CORE_ABI_VERSION 60 #ifdef __GNUC__ #ifdef BINARYNINJACORE_LIBRARY @@ -3918,6 +3918,7 @@ extern "C" BINARYNINJACOREAPI bool BNReadBE16(BNBinaryReader* stream, uint16_t* result); BINARYNINJACOREAPI bool BNReadBE32(BNBinaryReader* stream, uint32_t* result); BINARYNINJACOREAPI bool BNReadBE64(BNBinaryReader* stream, uint64_t* result); + BINARYNINJACOREAPI bool BNReadPointer(BNBinaryView* view, BNBinaryReader* stream, uint64_t* result); BINARYNINJACOREAPI uint64_t BNGetReaderPosition(BNBinaryReader* stream); BINARYNINJACOREAPI void BNSeekBinaryReader(BNBinaryReader* stream, uint64_t offset); diff --git a/binaryreader.cpp b/binaryreader.cpp index c49637f98..4b6983107 100644 --- a/binaryreader.cpp +++ b/binaryreader.cpp @@ -314,14 +314,7 @@ bool BinaryReader::TryRead64(uint64_t& result) bool BinaryReader::TryReadPointer(uint64_t& result) { - size_t addressSize = m_view->GetAddressSize(); - if (addressSize > 8 || addressSize == 0) - return false; - - if (GetEndianness() == BigEndian) - return TryReadBEPointer(result); - - return TryReadLEPointer(result); + return BNReadPointer(m_view->GetObject(), m_stream, &result); } From f3e890aa9333d11c27f235c47490b535e535607b Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Wed, 17 Apr 2024 16:21:09 -0400 Subject: [PATCH 27/50] Using ParseExpression to parse numeric inputs --- examples/triage/baseaddress.cpp | 75 +++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/examples/triage/baseaddress.cpp b/examples/triage/baseaddress.cpp index 663b1ff84..a20a4755b 100644 --- a/examples/triage/baseaddress.cpp +++ b/examples/triage/baseaddress.cpp @@ -49,50 +49,48 @@ string BaseAddressDetectionConfidenceToString(BNBaseAddressDetectionConfidence l } -uint32_t HexOrDecimalQStringToUint32(const QString& str) -{ - if (str.startsWith("0x")) - return str.mid(2).toUInt(nullptr, 16); - return str.toUInt(); -} - - -uint64_t HexOrDecimalQStringToUint64(const QString& str) -{ - if (str.startsWith("0x")) - return str.mid(2).toULongLong(nullptr, 16); - return str.toULongLong(); -} - - void BaseAddressDetectionThread::run() { BaseAddressDetectionQtResults results; - uint32_t alignment = HexOrDecimalQStringToUint32(m_inputs->AlignmentLineEdit->text()); - if (alignment == 0) + uint64_t value; + string errorStr; + + if (!BinaryNinja::BinaryView::ParseExpression( + m_view, m_inputs->AlignmentLineEdit->text().toStdString(), value, 0, errorStr)) { - results.Status = "Invalid alignment value"; + results.Status = "Invalid alignment value (" + errorStr + ")"; emit ResultReady(results); return; } + uint32_t alignment = value; - uint32_t minStrlen = HexOrDecimalQStringToUint32(m_inputs->StrlenLineEdit->text()); - if (minStrlen == 0) + if (!BinaryNinja::BinaryView::ParseExpression( + m_view, m_inputs->StrlenLineEdit->text().toStdString(), value, 0, errorStr)) { - results.Status = "Invalid minimum string length"; + results.Status = "Invalid minimum string length (" + errorStr + ")"; emit ResultReady(results); return; } + uint32_t minStrlen = value; - uint64_t upperBoundary = HexOrDecimalQStringToUint64(m_inputs->UpperBoundary->text()); - if (upperBoundary == 0) + uint64_t upperBoundary; + if (!BinaryNinja::BinaryView::ParseExpression( + m_view, m_inputs->UpperBoundary->text().toStdString(), upperBoundary, 0, errorStr)) { - results.Status = "Invalid upper boundary address"; + results.Status = "Invalid upper boundary address (" + errorStr + ")"; + emit ResultReady(results); + return; + } + + uint64_t lowerBoundary; + if (!BinaryNinja::BinaryView::ParseExpression( + m_view, m_inputs->LowerBoundary->text().toStdString(), lowerBoundary, 0, errorStr)) + { + results.Status = "Invalid lower boundary address (" + errorStr + ")"; emit ResultReady(results); return; } - uint64_t lowerBoundary = HexOrDecimalQStringToUint64(m_inputs->LowerBoundary->text()); if (lowerBoundary >= upperBoundary) { results.Status = "Upper boundary address is less than lower"; @@ -100,7 +98,15 @@ void BaseAddressDetectionThread::run() return; } - uint32_t maxPointersPerCluster = HexOrDecimalQStringToUint32(m_inputs->MaxPointersPerCluster->text()); + if (!BinaryNinja::BinaryView::ParseExpression( + m_view, m_inputs->MaxPointersPerCluster->text().toStdString(), value, 0, errorStr)) + { + results.Status = "Invalid max pointers (" + errorStr + ")"; + emit ResultReady(results); + return; + } + + uint32_t maxPointersPerCluster = value; if (maxPointersPerCluster < 2) { results.Status = "Invalid max pointers (must be >= 2)"; @@ -254,7 +260,14 @@ void BaseAddressDetectionWidget::RebaseWithFullAnalysis() if (!fileMetadata) return; - uint64_t address = HexOrDecimalQStringToUint64(m_reloadBase->text()); + uint64_t address; + string errorStr; + if (!BinaryNinja::BinaryView::ParseExpression(m_view, m_reloadBase->text().toStdString(), address, 0, errorStr)) + { + m_status->setText(QString("Invalid rebase address (%1)").arg(QString::fromStdString(errorStr))); + return; + } + if (!fileMetadata->Rebase(mappedView, address)) return; @@ -291,11 +304,11 @@ void BaseAddressDetectionWidget::CreateAdvancedSettingsGroup() auto grid = new QGridLayout(); grid->addWidget(new QLabel("Min. String Length:"), row, column, Qt::AlignLeft); - m_inputs.StrlenLineEdit = new QLineEdit("10"); + m_inputs.StrlenLineEdit = new QLineEdit("0n10"); grid->addWidget(m_inputs.StrlenLineEdit, row, column + 1, Qt::AlignLeft); grid->addWidget(new QLabel("Alignment:"), row, column + 2, Qt::AlignLeft); - m_inputs.AlignmentLineEdit = new QLineEdit("1024"); + m_inputs.AlignmentLineEdit = new QLineEdit("0n1024"); grid->addWidget(m_inputs.AlignmentLineEdit, row++, column + 3, Qt::AlignLeft); grid->addWidget(new QLabel("Lower Boundary:"), row, column, Qt::AlignLeft); @@ -313,7 +326,7 @@ void BaseAddressDetectionWidget::CreateAdvancedSettingsGroup() grid->addWidget(m_inputs.POIBox, row, column + 1, Qt::AlignLeft); grid->addWidget(new QLabel("Max Pointers:"), row, column + 2, Qt::AlignLeft); - m_inputs.MaxPointersPerCluster = new QLineEdit("128"); + m_inputs.MaxPointersPerCluster = new QLineEdit("0n128"); grid->addWidget(m_inputs.MaxPointersPerCluster, row++, column + 3, Qt::AlignLeft); m_advancedSettingsGroup = new ExpandableGroup(grid); From 6d2f7cd7f26f1d80cd0f591e1901d7cb40e8979b Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Thu, 18 Apr 2024 16:09:30 -0400 Subject: [PATCH 28/50] Base detection rebase navigation improvement Now is able to correctly navigate from raw triage summary to mapped linear view when starting analysis from the identified base address --- examples/triage/baseaddress.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/examples/triage/baseaddress.cpp b/examples/triage/baseaddress.cpp index a20a4755b..e50510a80 100644 --- a/examples/triage/baseaddress.cpp +++ b/examples/triage/baseaddress.cpp @@ -288,12 +288,21 @@ void BaseAddressDetectionWidget::RebaseWithFullAnalysis() uiContext->recreateViewFrames(fileContext); fileContext->refreshDataViewCache(); - auto view = frame->getCurrentViewInterface(); + + auto newFrame = ViewFrame::viewFrameForWidget(this); + if (!newFrame) + return; + + auto view = newFrame->getCurrentViewInterface(); if (!view) return; + auto data = view->getData(); + if (!data) + return; + if (!view->navigate(address)) - m_view->Navigate(string("Linear:" + frame->getCurrentDataType().toStdString()), address); + data->Navigate("Linear:Mapped", address); } From a365f6d72e6ab237bcf736dfcc5fd26e235a5c50 Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Mon, 22 Apr 2024 15:42:38 -0400 Subject: [PATCH 29/50] Changed meaning of base detection results table Now the results table consists of a high level report on number of hits per POI type for each candidate base address. The rebase address LineEdit is auto-populated when the user clicks on a row --- examples/triage/baseaddress.cpp | 68 ++++++++++++++++++++++----------- examples/triage/baseaddress.h | 1 + 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/examples/triage/baseaddress.cpp b/examples/triage/baseaddress.cpp index e50510a80..c4c57b1e9 100644 --- a/examples/triage/baseaddress.cpp +++ b/examples/triage/baseaddress.cpp @@ -167,14 +167,23 @@ void BaseAddressDetectionWidget::HideResultsWidgets(bool hide) } +void BaseAddressDetectionWidget::GetClickedBaseAddress(const QModelIndex& index) +{ + if (index.isValid()) + { + auto baseAddress = m_resultsTableWidget->item(index.row(), 0)->text(); + m_reloadBase->setText(baseAddress); + } +} + + void BaseAddressDetectionWidget::HandleResults(const BaseAddressDetectionQtResults& results) { if (!results.Status.empty()) m_status->setText(QString::fromStdString(results.Status)); if (results.Status.empty() && m_worker->IsAborted()) - m_status->setText(QString("Aborted by user (Last Base: 0x%1)").arg( - QString::number(results.LastTestedBaseAddress, 16))); + m_status->setText(QString("Aborted by user (Last Base: 0x%1)").arg(results.LastTestedBaseAddress, 0, 16)); if (results.Scores.empty()) { @@ -188,35 +197,46 @@ void BaseAddressDetectionWidget::HandleResults(const BaseAddressDetectionQtResul HideResultsWidgets(false); if (results.Status.empty() && !m_worker->IsAborted()) m_status->setText("Completed with results"); - m_preferredBase->setText(QString("0x%1").arg(QString::number(results.Scores.rbegin()->second, 16))); + m_preferredBase->setText(QString("0x%1").arg(results.Scores.rbegin()->second, 0, 16)); m_confidence->setText(QString("%1 (Score: %2)").arg( QString::fromStdString(BaseAddressDetectionConfidenceToString(results.Confidence)), QString::number(results.Scores.rbegin()->first))); - m_reloadBase->setText(QString("0x%1").arg(QString::number(results.Scores.rbegin()->second, 16))); + m_reloadBase->setText(QString("0x%1").arg(results.Scores.rbegin()->second, 0, 16)); } m_resultsTableWidget->clearContents(); - size_t numRows = 0; - for (auto rit = results.Scores.rbegin(); rit != results.Scores.rend(); rit++) - numRows += results.Reasons.at(rit->second).size(); - - m_resultsTableWidget->setRowCount(numRows); + m_resultsTableWidget->setRowCount(results.Scores.size()); size_t row = 0; for (auto rit = results.Scores.rbegin(); rit != results.Scores.rend(); rit++) { auto [score, baseaddr] = *rit; + size_t strHits = 0; + size_t funcHits = 0; + size_t dataHits = 0; for (const auto& reason : results.Reasons.at(baseaddr)) { - m_resultsTableWidget->setItem(row, 0, - new QTableWidgetItem(QString("0x%1").arg(QString::number(baseaddr, 16)))); - m_resultsTableWidget->setItem(row, 1, new QTableWidgetItem( - QString("0x%1").arg(QString::number(reason.Pointer, 16)))); - m_resultsTableWidget->setItem(row, 2, new QTableWidgetItem( - QString("0x%1").arg(QString::number(reason.POIOffset, 16)))); - m_resultsTableWidget->setItem(row, 3, new QTableWidgetItem( - QString::fromStdString(BaseAddressDetectionPOITypeToString(reason.POIType)))); - row++; + switch (reason.POIType) + { + case POIString: + strHits++; + break; + case POIFunction: + funcHits++; + break; + case POIDataVariable: + dataHits++; + break; + default: + break; + } } + + m_resultsTableWidget->setItem(row, 0, new QTableWidgetItem(QString("0x%1").arg(baseaddr, 0, 16))); + m_resultsTableWidget->setItem(row, 1, new QTableWidgetItem(QString::number(score))); + m_resultsTableWidget->setItem(row, 2, new QTableWidgetItem(QString::number(strHits))); + m_resultsTableWidget->setItem(row, 3, new QTableWidgetItem(QString::number(funcHits))); + m_resultsTableWidget->setItem(row, 4, new QTableWidgetItem(QString::number(dataHits))); + row++; } m_abortButton->setHidden(true); @@ -343,7 +363,8 @@ void BaseAddressDetectionWidget::CreateAdvancedSettingsGroup() } -BaseAddressDetectionWidget::BaseAddressDetectionWidget(QWidget* parent, BinaryNinja::Ref bv) +BaseAddressDetectionWidget::BaseAddressDetectionWidget(QWidget* parent, + BinaryNinja::Ref bv) : QWidget(parent) { m_view = bv->GetParentView() ? bv->GetParentView() : bv; m_layout = new QGridLayout(); @@ -401,18 +422,19 @@ BaseAddressDetectionWidget::BaseAddressDetectionWidget(QWidget* parent, BinaryNi m_layout->addWidget(m_confidence, row++, column + 3, Qt::AlignLeft); m_resultsTableWidget = new QTableWidget(this); - m_resultsTableWidget->setColumnCount(4); + m_resultsTableWidget->setColumnCount(5); QStringList header; - header << "Base Address" << "Pointer" << "POI Offset" << "POI Type"; + header << "Base Address" << "Score" << "String Hits" << "Function Hits" << "Data Hits"; m_resultsTableWidget->setHorizontalHeaderLabels(header); m_resultsTableWidget->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); m_resultsTableWidget->horizontalHeader()->setStretchLastSection(true); m_resultsTableWidget->verticalHeader()->setVisible(false); m_resultsTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); - m_resultsTableWidget->setSelectionBehavior(QAbstractItemView::SelectItems); + m_resultsTableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); m_resultsTableWidget->setSelectionMode(QAbstractItemView::SingleSelection); m_resultsTableWidget->setMinimumHeight(150); - m_layout->addWidget(m_resultsTableWidget, row++, column, 1, 4); + m_layout->addWidget(m_resultsTableWidget, row++, column, 1, 5); + connect(m_resultsTableWidget, &QTableWidget::clicked, this, &BaseAddressDetectionWidget::GetClickedBaseAddress); m_reloadBase = new QLineEdit("0x0"); m_layout->addWidget(m_reloadBase, row, column, Qt::AlignLeft); diff --git a/examples/triage/baseaddress.h b/examples/triage/baseaddress.h index 9a282f3e4..5f3dbe982 100644 --- a/examples/triage/baseaddress.h +++ b/examples/triage/baseaddress.h @@ -82,6 +82,7 @@ class BaseAddressDetectionWidget : public QWidget void HandleResults(const BaseAddressDetectionQtResults& results); void HideResultsWidgets(bool hide); void CreateAdvancedSettingsGroup(); + void GetClickedBaseAddress(const QModelIndex& index); public: BaseAddressDetectionWidget(QWidget* parent, BinaryNinja::Ref bv); From 4db391cdfdb53c3ac18fa5f0c15f2976fde12791 Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Thu, 25 Apr 2024 09:43:56 -0400 Subject: [PATCH 30/50] Removed unused function in base detection --- examples/triage/baseaddress.cpp | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/examples/triage/baseaddress.cpp b/examples/triage/baseaddress.cpp index c4c57b1e9..67d8301cc 100644 --- a/examples/triage/baseaddress.cpp +++ b/examples/triage/baseaddress.cpp @@ -13,26 +13,6 @@ BNBaseAddressDetectionPOISetting BaseAddressDetectionPOISettingFromString(const } -string BaseAddressDetectionPOITypeToString(BNBaseAddressDetectionPOIType type) -{ - switch (type) - { - case POIString: - return "String"; - case POIFunction: - return "Function"; - case POIDataVariable: - return "Data variable"; - case POIFileEnd: - return "File end"; - case POIFileStart: - return "File start"; - default: - return "Unknown"; - } -} - - string BaseAddressDetectionConfidenceToString(BNBaseAddressDetectionConfidence level) { switch (level) From 79d8d14eb32a9b1766eb546d3791580dfee7278f Mon Sep 17 00:00:00 2001 From: Rubens Brandao Date: Thu, 25 Apr 2024 12:32:01 -0300 Subject: [PATCH 31/50] Remove From Conf> impl for BNTypeWithConfidence --- rust/src/architecture.rs | 4 ++-- rust/src/binaryview.rs | 4 ++-- rust/src/types.rs | 9 --------- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/rust/src/architecture.rs b/rust/src/architecture.rs index 9a7b3e723..b237ac6a7 100644 --- a/rust/src/architecture.rs +++ b/rust/src/architecture.rs @@ -2474,8 +2474,8 @@ where if let Some(intrinsic) = custom_arch.intrinsic_from_id(intrinsic) { let inputs = intrinsic.outputs(); let mut res = Vec::with_capacity(inputs.len()); - for input in inputs { - res.push(input.into()); + for input in inputs.iter() { + res.push(input.as_ref().into()); } unsafe { diff --git a/rust/src/binaryview.rs b/rust/src/binaryview.rs index 52d688b84..2664d2471 100644 --- a/rust/src/binaryview.rs +++ b/rust/src/binaryview.rs @@ -576,14 +576,14 @@ pub trait BinaryViewExt: BinaryViewBase { fn define_auto_data_var(&self, dv: DataVariable) { unsafe { - BNDefineDataVariable(self.as_ref().handle, dv.address, &mut dv.t.into()); + BNDefineDataVariable(self.as_ref().handle, dv.address, &mut dv.t.as_ref().into()); } } /// You likely would also like to call [`Self::define_user_symbol`] to bind this data variable with a name fn define_user_data_var(&self, dv: DataVariable) { unsafe { - BNDefineUserDataVariable(self.as_ref().handle, dv.address, &mut dv.t.into()); + BNDefineUserDataVariable(self.as_ref().handle, dv.address, &mut dv.t.as_ref().into()); } } diff --git a/rust/src/types.rs b/rust/src/types.rs index 8f14cf00e..df206b34b 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -260,15 +260,6 @@ impl From for Conf { } } -impl From>> for BNTypeWithConfidence { - fn from(conf: Conf>) -> Self { - Self { - type_: conf.contents.handle, - confidence: conf.confidence, - } - } -} - impl From> for BNTypeWithConfidence { fn from(conf: Conf<&Type>) -> Self { Self { From 13c41bc582fa037e03cbc5343a1401ea4c6cf8e7 Mon Sep 17 00:00:00 2001 From: Josh Ferrell Date: Thu, 25 Apr 2024 13:08:34 -0400 Subject: [PATCH 32/50] Recognize uClibc MIPS ELF PLT entries --- arch/mips/arch_mips.cpp | 118 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/arch/mips/arch_mips.cpp b/arch/mips/arch_mips.cpp index 77de378f8..6a3d80e4d 100644 --- a/arch/mips/arch_mips.cpp +++ b/arch/mips/arch_mips.cpp @@ -1829,6 +1829,121 @@ class MipsImportedFunctionRecognizer: public FunctionRecognizer return false; } + + bool RecognizeELFPLTEntries2(BinaryView* data, Function* func, LowLevelILFunction* il) + { + // Look for the following code pattern: + // $t7 = addr_past_got_end + // $t9 = [$t7 - backward_offset_into_got].d + // $t8 = $t7 + (-backward_offset_into_got) + // OPTIONAL: $t7 = addr_past_got_end + // tailcall($t9) + if (il->GetInstructionCount() < 4) + return false; + if (il->GetInstructionCount() > 5) + return false; + + LowLevelILInstruction lui = il->GetInstruction(0); + if (lui.operation != LLIL_SET_REG) + return false; + LowLevelILInstruction luiOperand = lui.GetSourceExpr(); + if (!LowLevelILFunction::IsConstantType(luiOperand.operation)) + return false; + if (luiOperand.size != func->GetArchitecture()->GetAddressSize()) + return false; + uint64_t addrPastGot = luiOperand.GetConstant(); + uint32_t pltReg = lui.GetDestRegister(); + + LowLevelILInstruction ld = il->GetInstruction(1); + if (ld.operation != LLIL_SET_REG) + return false; + uint32_t targetReg = ld.GetDestRegister(); + LowLevelILInstruction ldOperand = ld.GetSourceExpr(); + if (ldOperand.operation != LLIL_LOAD) + return false; + if (ldOperand.size != func->GetArchitecture()->GetAddressSize()) + return false; + LowLevelILInstruction ldAddrOperand = ldOperand.GetSourceExpr(); + uint64_t entry = addrPastGot; + int64_t ldAddrRightOperandValue = 0; + + if ((ldAddrOperand.operation == LLIL_ADD) || (ldAddrOperand.operation == LLIL_SUB)) + { + LowLevelILInstruction ldAddrLeftOperand = ldAddrOperand.GetRawOperandAsExpr(0); + LowLevelILInstruction ldAddrRightOperand = ldAddrOperand.GetRawOperandAsExpr(1); + if (ldAddrLeftOperand.operation != LLIL_REG) + return false; + if (ldAddrLeftOperand.GetSourceRegister() != pltReg) + return false; + if (!LowLevelILFunction::IsConstantType(ldAddrRightOperand.operation)) + return false; + ldAddrRightOperandValue = ldAddrRightOperand.GetConstant(); + if (ldAddrOperand.operation == LLIL_SUB) + ldAddrRightOperandValue = -ldAddrRightOperandValue; + entry = addrPastGot + ldAddrRightOperandValue; + } + else if (ldAddrOperand.operation != LLIL_REG) //If theres no constant + return false; + + Ref sym = data->GetSymbolByAddress(entry); + if (!sym) + return false; + if (sym->GetType() != ImportAddressSymbol) + return false; + + LowLevelILInstruction add = il->GetInstruction(2); + if (add.operation != LLIL_SET_REG) + return false; + LowLevelILInstruction addOperand = add.GetSourceExpr(); + + if (addOperand.operation == LLIL_ADD) + { + LowLevelILInstruction addLeftOperand = addOperand.GetLeftExpr(); + LowLevelILInstruction addRightOperand = addOperand.GetRightExpr(); + if (addLeftOperand.operation != LLIL_REG) + return false; + if (addLeftOperand.GetSourceRegister() != pltReg) + return false; + if (!LowLevelILFunction::IsConstantType(addRightOperand.operation)) + return false; + if (addRightOperand.GetConstant() != ldAddrRightOperandValue) + return false; + } + else if ((addOperand.operation != LLIL_REG) || (addOperand.GetSourceRegister() != pltReg)) //Simple assignment + return false; + + LowLevelILInstruction jump = il->GetInstruction(3); + if (jump.operation == LLIL_SET_REG) + { + if (il->GetInstructionCount() != 5) + return false; + if (jump.GetDestRegister() != pltReg) + return false; + LowLevelILInstruction luiOperand = jump.GetSourceExpr(); + if (!LowLevelILFunction::IsConstantType(luiOperand.operation)) + return false; + if (luiOperand.size != func->GetArchitecture()->GetAddressSize()) + return false; + if (((uint64_t) luiOperand.GetConstant()) != addrPastGot) + return false; + jump = il->GetInstruction(4); + } + + if ((jump.operation != LLIL_JUMP) && (jump.operation != LLIL_TAILCALL)) + return false; + LowLevelILInstruction jumpOperand = (jump.operation == LLIL_JUMP) ? jump.GetDestExpr() : jump.GetDestExpr(); + if (jumpOperand.operation != LLIL_REG) + return false; + if (jumpOperand.GetSourceRegister() != targetReg) + return false; + + Ref funcSym = Symbol::ImportedFunctionFromImportAddressSymbol(sym, func->GetStart()); + data->DefineAutoSymbol(funcSym); + func->ApplyImportedTypes(funcSym); + return true; + } + + public: virtual bool RecognizeLowLevelIL(BinaryView* data, Function* func, LowLevelILFunction* il) override { @@ -1838,6 +1953,9 @@ class MipsImportedFunctionRecognizer: public FunctionRecognizer if (RecognizeELFPLTEntries1(data, func, il)) return true; + if (RecognizeELFPLTEntries2(data, func, il)) + return true; + return false; } }; From 5ebad8c0b30227bf8f367267eaf17d366afd8608 Mon Sep 17 00:00:00 2001 From: Josh Ferrell Date: Thu, 25 Apr 2024 14:00:50 -0400 Subject: [PATCH 33/50] Fix WebsocketClient.connect when `headers` is not passed --- python/websocketprovider.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/websocketprovider.py b/python/websocketprovider.py index 93e88eb86..70239ccfd 100644 --- a/python/websocketprovider.py +++ b/python/websocketprovider.py @@ -163,6 +163,8 @@ def connect(self, url, headers=None, on_connected=nop, on_disconnected=nop, on_e self._connected = True + if headers is None: + headers = {} header_keys = (ctypes.c_char_p * len(headers))() header_values = (ctypes.c_char_p * len(headers))() for (i, item) in enumerate(headers.items()): From 92a87c0a3f981f8e0bd80789b9c19447227445dc Mon Sep 17 00:00:00 2001 From: Jordan Wiens Date: Thu, 25 Apr 2024 19:49:17 -0400 Subject: [PATCH 34/50] example python API documentation on websocket --- python/websocketprovider.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/python/websocketprovider.py b/python/websocketprovider.py index 70239ccfd..420f43c66 100644 --- a/python/websocketprovider.py +++ b/python/websocketprovider.py @@ -46,6 +46,9 @@ def to_bytes(field): class WebsocketClient(object): + """ + This class implements a websocket client. See :py:func:`~WebsocketClient.connect` for more details. + """ _registered_clients = [] def __init__(self, provider, handle=None): @@ -157,6 +160,12 @@ def connect(self, url, headers=None, on_connected=nop, on_disconnected=nop, on_e :param function(bytes) -> bool on_data: function to call when data is read from the websocket :return: if the connection has started, but not necessarily if it succeeded :rtype: bool + + :Example: + >>> provider = list(WebsocketProvider)[0] + >>> client = provider.create_instance() + >>> client.connect("ws://localhost:8080", {}) + True """ if self._connected: raise RuntimeError("Cannot use connect() twice on the same WebsocketClient") From 18af966482b9475fa8fc9ee9baa1ade9f3e6b71f Mon Sep 17 00:00:00 2001 From: Xusheng Date: Fri, 26 Apr 2024 12:59:16 +0800 Subject: [PATCH 35/50] Generate a default name for new enum and union. Fix https://github.com/Vector35/binaryninja-api/issues/5322 --- ui/commands.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/commands.h b/ui/commands.h index 441ca0ede..b0798a9b6 100644 --- a/ui/commands.h +++ b/ui/commands.h @@ -50,8 +50,8 @@ StructureRef BINARYNINJAUIAPI getInnerMostStructureContainingOffset(BinaryViewRe uint64_t BINARYNINJAUIAPI getInnerMostStructureOffset( BinaryViewRef data, StructureRef structure, const std::vector& nameList, size_t nameIndex); -// Auto generate a structure name -std::string BINARYNINJAUIAPI createStructureName(BinaryNinja::TypeContainer types); +// Auto generate a usable type name with the given prefix +std::string BINARYNINJAUIAPI createStructureName(BinaryNinja::TypeContainer types, const std::string& prefix = "struct_"); std::optional BINARYNINJAUIAPI getSplitVariableForAssignment( FunctionRef func, BNFunctionGraphType ilType, uint64_t location, const BinaryNinja::Variable& var); From 53a70331a59b0971761ffa43edd6bf18576cb401 Mon Sep 17 00:00:00 2001 From: Josh Ferrell Date: Fri, 26 Apr 2024 16:00:39 -0400 Subject: [PATCH 36/50] Fix display of possible values in uidf dialog --- ui/possiblevaluesetdialog.h | 4 +--- ui/util.h | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/ui/possiblevaluesetdialog.h b/ui/possiblevaluesetdialog.h index 79d56abdb..8f37f5bb4 100644 --- a/ui/possiblevaluesetdialog.h +++ b/ui/possiblevaluesetdialog.h @@ -23,10 +23,8 @@ class BINARYNINJAUIAPI PossibleValueSetDialog : public QDialog QLineEdit* m_input; QPushButton* m_acceptButton; QLabel* m_formatLabel; + QLabel* m_errorLabel; - std::map m_typeText; - std::map m_formatText; - std::map m_regValueTypes; BNRegisterValueType m_curRegValueType; BinaryNinja::PossibleValueSet m_valueSet; diff --git a/ui/util.h b/ui/util.h index a733acea3..9713713a3 100644 --- a/ui/util.h +++ b/ui/util.h @@ -19,7 +19,7 @@ std::string BINARYNINJAUIAPI getStringForRegisterValue(ArchitectureRef arch, Bin std::string BINARYNINJAUIAPI getPossibleValueSetStateName(BNRegisterValueType state); std::string BINARYNINJAUIAPI getStringForIntegerValue(int64_t value); std::string BINARYNINJAUIAPI getStringForUIntegerValue(uint64_t value); -std::string BINARYNINJAUIAPI getStringForPossibleValueSet(ArchitectureRef arch, const BinaryNinja::PossibleValueSet& values); +std::string BINARYNINJAUIAPI getStringForPossibleValueSet(ArchitectureRef arch, const BinaryNinja::PossibleValueSet& values, bool pretty = true); std::string BINARYNINJAUIAPI getStringForInstructionDataflowDetails(BinaryViewRef data, ArchitectureRef arch, FunctionRef func, uint64_t address); std::optional BINARYNINJAUIAPI getPossibleValueSetForToken(View* view, BinaryViewRef data, ArchitectureRef arch, FunctionRef func, HighlightTokenState token, size_t instrIdx); From ee6abdde45c6e25f03727e1fd5a08c69cc074d5b Mon Sep 17 00:00:00 2001 From: Rubens Brandao Date: Mon, 29 Apr 2024 17:17:24 -0300 Subject: [PATCH 37/50] replace Vec into_raw_parts with Box --- rust/src/architecture.rs | 100 ++++++++++++++++----------------------- rust/src/disassembly.rs | 25 +++++----- 2 files changed, 56 insertions(+), 69 deletions(-) diff --git a/rust/src/architecture.rs b/rust/src/architecture.rs index 9a7b3e723..f54deb23c 100644 --- a/rust/src/architecture.rs +++ b/rust/src/architecture.rs @@ -1162,17 +1162,18 @@ impl Architecture for CoreArchitecture { &mut result as *mut _, &mut count as *mut _, ) { - let vec = Vec::::from_raw_parts(result, count, count) + let vec = slice::from_raw_parts(result, count) .iter() - .map(|x| InstructionTextToken::from_raw(x)) + .map(|x| InstructionTextToken::from_raw(x).to_owned()) .collect(); + BNFreeInstructionText(result, count); Some((consumed, vec)) } else { None } } } - + fn instruction_llil( &self, data: &[u8], @@ -1811,27 +1812,25 @@ where let data = unsafe { slice::from_raw_parts(data, *len) }; let result = unsafe { &mut *result }; - match custom_arch.instruction_text(data, addr) { - Some((res_size, mut res_tokens)) => { - unsafe { - // TODO: Can't use into_raw_parts as it's unstable so we do this instead... - let r_ptr = res_tokens.as_mut_ptr(); - let r_count = res_tokens.len(); - mem::forget(res_tokens); - - *result = &mut (*r_ptr).0; - *count = r_count; - *len = res_size; - } - true - } - None => false, + let Some((res_size, res_tokens)) = custom_arch.instruction_text(data, addr) else { + return false; + }; + + let res_tokens: Box<[_]> = res_tokens.into_boxed_slice(); + unsafe { + let res_tokens = Box::leak(res_tokens); + let r_ptr = res_tokens.as_mut_ptr(); + let r_count = res_tokens.len(); + + *result = &mut (*r_ptr).0; + *count = r_count; + *len = res_size; } + true } extern "C" fn cb_free_instruction_text(tokens: *mut BNInstructionTextToken, count: usize) { - let _tokens = - unsafe { Vec::from_raw_parts(tokens as *mut InstructionTextToken, count, count) }; + let _tokens = unsafe { Box::from_raw(ptr::slice_from_raw_parts_mut(tokens, count)) }; } extern "C" fn cb_instruction_llil( @@ -1931,15 +1930,7 @@ where if len == 0 { ptr::null_mut() } else { - let mut res = Vec::with_capacity(len + 1); - - res.push(len as u32); - - for i in items { - res.push(i); - } - - assert!(res.len() == len + 1); + let mut res: Box<[_]> = [len as u32].into_iter().chain(items).collect(); let raw = res.as_mut_ptr(); mem::forget(res); @@ -2280,7 +2271,8 @@ where unsafe { let actual_start = regs.offset(-1); let len = *actual_start + 1; - let _regs = Vec::from_raw_parts(actual_start, len as usize, len as usize); + let regs_ptr = ptr::slice_from_raw_parts_mut(actual_start, len.try_into().unwrap()); + let _regs = Box::from_raw(regs_ptr); } } @@ -2420,28 +2412,25 @@ where { let custom_arch = unsafe { &*(ctxt as *mut A) }; - if let Some(intrinsic) = custom_arch.intrinsic_from_id(intrinsic) { - let inputs = intrinsic.inputs(); - let mut res = Vec::with_capacity(inputs.len()); - for input in inputs { - res.push(input.into_raw()); - } - - unsafe { - *count = res.len(); - if res.is_empty() { - ptr::null_mut() - } else { - let raw = res.as_mut_ptr(); - mem::forget(res); - raw - } - } - } else { + let Some(intrinsic) = custom_arch.intrinsic_from_id(intrinsic) else { unsafe { *count = 0; } - ptr::null_mut() + return ptr::null_mut(); + }; + + let inputs = intrinsic.inputs(); + let mut res: Box<[_]> = inputs.into_iter().map(|input| input.into_raw()).collect(); + + unsafe { + *count = res.len(); + if res.is_empty() { + ptr::null_mut() + } else { + let raw = res.as_mut_ptr(); + mem::forget(res); + raw + } } } @@ -2453,8 +2442,8 @@ where if !nt.is_null() { unsafe { - let list = Vec::from_raw_parts(nt, count, count); - for nt in list { + let name_and_types = Box::from_raw(ptr::slice_from_raw_parts_mut(nt, count)); + for nt in name_and_types.into_iter() { BnString::from_raw(nt.name); } } @@ -2473,10 +2462,7 @@ where if let Some(intrinsic) = custom_arch.intrinsic_from_id(intrinsic) { let inputs = intrinsic.outputs(); - let mut res = Vec::with_capacity(inputs.len()); - for input in inputs { - res.push(input.into()); - } + let mut res: Box<[_]> = inputs.iter().map(|input| input.as_ref().into()).collect(); unsafe { *count = res.len(); @@ -2505,9 +2491,7 @@ where { let _custom_arch = unsafe { &*(ctxt as *mut A) }; if !tl.is_null() { - unsafe { - let _list = Vec::from_raw_parts(tl, count, count); - } + let _type_list = unsafe { Box::from_raw(ptr::slice_from_raw_parts_mut(tl, count)) }; } } diff --git a/rust/src/disassembly.rs b/rust/src/disassembly.rs index f213fa0eb..7a1bfc757 100644 --- a/rust/src/disassembly.rs +++ b/rust/src/disassembly.rs @@ -73,7 +73,7 @@ pub type InstructionTextTokenContext = BNInstructionTextTokenContext; // IndirectImportToken = 69, // ExternalSymbolToken = 70, -#[repr(C)] +#[repr(transparent)] pub struct InstructionTextToken(pub(crate) BNInstructionTextToken); #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] @@ -99,8 +99,8 @@ pub enum InstructionTextTokenContents { } impl InstructionTextToken { - pub(crate) unsafe fn from_raw(raw: &BNInstructionTextToken) -> Self { - Self(*raw) + pub(crate) unsafe fn from_raw(raw: &BNInstructionTextToken) -> &Self { + mem::transmute(raw) } pub fn new(text: &str, contents: InstructionTextTokenContents) -> Self { @@ -254,13 +254,16 @@ impl Clone for InstructionTextToken { } } -// TODO : There is almost certainly a memory leak here - in the case where -// `impl CoreOwnedArrayProvider for InstructionTextToken` doesn't get triggered -// impl Drop for InstructionTextToken { -// fn drop(&mut self) { -// let _owned = unsafe { BnString::from_raw(self.0.text) }; -// } -// } +impl Drop for InstructionTextToken { + fn drop(&mut self) { + if !self.0.text.is_null() { + let _owned = unsafe { BnString::from_raw(self.0.text) }; + } + if !self.0.typeNames.is_null() && self.0.namesCount != 0 { + unsafe { BNFreeStringList(self.0.typeNames, self.0.namesCount) } + } + } +} pub struct DisassemblyTextLine(pub(crate) BNDisassemblyTextLine); @@ -290,7 +293,7 @@ impl DisassemblyTextLine { unsafe { std::slice::from_raw_parts::(self.0.tokens, self.0.count) .iter() - .map(|&x| InstructionTextToken::from_raw(&x)) + .map(|x| InstructionTextToken::from_raw(x).clone()) .collect() } } From 5fdf81a051ec363c009bc6ae1811e3cba7d56af3 Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Fri, 19 Apr 2024 14:25:03 -0400 Subject: [PATCH 38/50] Python bindings for base address detection API --- python/__init__.py | 1 + python/basedetection.py | 156 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 python/basedetection.py diff --git a/python/__init__.py b/python/__init__.py index 498475f4d..b77025696 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -74,6 +74,7 @@ from .typecontainer import * from .exceptions import * from .project import * +from .basedetection import * # We import each of these by name to prevent conflicts between # log.py and the function 'log' which we don't import below from .log import ( diff --git a/python/basedetection.py b/python/basedetection.py new file mode 100644 index 000000000..ac12c92cb --- /dev/null +++ b/python/basedetection.py @@ -0,0 +1,156 @@ +# coding=utf-8 +# Copyright (c) 2015-2024 Vector 35 Inc +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +import ctypes +from typing import Optional +from . import _binaryninjacore as core + + +class BaseAddressDetection: + """ + ``class BaseAddressDetection`` is a class that is used to detect the base address of position-dependent raw binaries + + >>> from binaryninja import * + >>> bv = load("firmware.bin") + >>> bad = BaseAddressDetection(bv) + >>> bad.detect_base_address() + True + >>> hex(bad.preferred_base_address) + '0x4000000' + """ + + def __init__(self, view: "BinaryView") -> None: + _handle = core.BNCreateBaseAddressDetection(view.handle) + assert _handle is not None, "core.BNCreateBaseAddressDetection returned None" + self._handle = _handle + self._view_arch = view.arch + + self._scores = list() + self._confidence = 0 + self._last_tested_base_address = None + + def __del__(self): + if core is not None: + core.BNFreeBaseAddressDetection(self._handle) + + def detect_base_address( + self, + arch: Optional[str] = "", + analysis: Optional[str] = "basic", + minstrlen: Optional[int] = 10, + alignment: Optional[int] = 1024, + lowerboundary: Optional[int] = 0, + upperboundary: Optional[int] = 0xFFFFFFFFFFFFFFFF, + poi_analysis: Optional[int] = 0, + max_pointers: Optional[int] = 128, + ) -> bool: + """ + ``detect_base_address`` runs analysis and attempts to identify candidate base addresses + + :return: True if initial analysis is valid, False otherwise + :rtype: bool + """ + + if not arch and self._view_arch: + arch = str(self._view_arch) + + if analysis not in ["basic", "controlFlow", "full"]: + raise ValueError("invalid analysis setting") + + if alignment <= 0: + raise ValueError("alignment must be greater than 0") + + if max_pointers < 2: + raise ValueError("max pointers must be at least 2") + + if upperboundary < lowerboundary: + raise ValueError("upper boundary must be greater than lower boundary") + + settings = core.BNBaseAddressDetectionSettings( + arch.encode('utf-8'), analysis.encode('utf-8'), minstrlen, alignment, lowerboundary, upperboundary, + poi_analysis, max_pointers + ) + + if not core.BNDetectBaseAddress(self._handle, settings): + return False + + max_candidates = 10 + scores = (core.BNBaseAddressDetectionScore * max_candidates)() + confidence = core.BaseAddressDetectionConfidenceEnum() + last_base = ctypes.c_ulonglong() + num_candidates = core.BNGetBaseAddressDetectionScores( + self._handle, scores, max_candidates, ctypes.byref(confidence), ctypes.byref(last_base) + ) + + self._scores.clear() + for i in range(num_candidates): + self._scores.append((scores[i].BaseAddress, scores[i].Score)) + + self._confidence = confidence.value + self._last_tested_base_address = last_base.value + return True + + @property + def scores(self) -> list[tuple[int, int]]: + """ + ``get_scores`` returns a list of base addresses and their scores + + :return: list of tuples containing each base address and score + :rtype: OrderedDict + """ + + return self._scores + + @property + def confidence(self) -> "BaseAddressDetectionConfidenceEnum": + """ + ``get_confidence`` returns an enum that indicates confidence that the top base address candidate is correct + + :return: confidence of the base address detection results + :rtype: BaseAddressDetectionConfidenceEnum + """ + + return self._confidence + + @property + def last_tested_base_address(self) -> int: + """ + ``last_tested_base_address`` returns the last base address candidate that was tested + + :return: last base address tested + :rtype: int + """ + + return self._last_tested_base_address + + @property + def preferred_base_address(self) -> int: + """ + ``preferred_base_address`` returns the base address that is preferred by analysis + + :return: preferred base address + :rtype: int + """ + + if not self._scores: + return None + + return self._scores[0][0] From fa38503c09bfbb95404f6fc1993756a855d5c5d1 Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Fri, 19 Apr 2024 16:38:01 -0400 Subject: [PATCH 39/50] Querying base detection reasons from Python API --- python/basedetection.py | 125 ++++++++++++++++++++++++++-------------- 1 file changed, 81 insertions(+), 44 deletions(-) diff --git a/python/basedetection.py b/python/basedetection.py index ac12c92cb..e8a1ef958 100644 --- a/python/basedetection.py +++ b/python/basedetection.py @@ -21,15 +21,26 @@ import ctypes from typing import Optional +from dataclasses import dataclass from . import _binaryninjacore as core +@dataclass +class BaseAddressDetectionReason: + """``class BaseAddressDetectionReason`` is a class that is used to store information about why a base address is a + candidate""" + + pointer: int + offset: int + type: int + + class BaseAddressDetection: """ ``class BaseAddressDetection`` is a class that is used to detect the base address of position-dependent raw binaries >>> from binaryninja import * - >>> bv = load("firmware.bin") + >>> bv = BinaryViewType["Raw"].open("firmware.bin") >>> bad = BaseAddressDetection(bv) >>> bad.detect_base_address() True @@ -51,6 +62,53 @@ def __del__(self): if core is not None: core.BNFreeBaseAddressDetection(self._handle) + @property + def scores(self) -> list[tuple[int, int]]: + """ + ``scores`` returns a list of base addresses and their scores + + :return: list of tuples containing each base address and score + :rtype: OrderedDict + """ + + return self._scores + + @property + def confidence(self) -> "BaseAddressDetectionConfidenceEnum": + """ + ``confidence`` returns an enum that indicates confidence that the top base address candidate is correct + + :return: confidence of the base address detection results + :rtype: BaseAddressDetectionConfidenceEnum + """ + + return self._confidence + + @property + def last_tested_base_address(self) -> int: + """ + ``last_tested_base_address`` returns the last base address candidate that was tested + + :return: last base address tested + :rtype: int + """ + + return self._last_tested_base_address + + @property + def preferred_base_address(self) -> int: + """ + ``preferred_base_address`` returns the base address that is preferred by analysis + + :return: preferred base address + :rtype: int + """ + + if not self._scores: + return None + + return self._scores[0][0] + def detect_base_address( self, arch: Optional[str] = "", @@ -85,8 +143,14 @@ def detect_base_address( raise ValueError("upper boundary must be greater than lower boundary") settings = core.BNBaseAddressDetectionSettings( - arch.encode('utf-8'), analysis.encode('utf-8'), minstrlen, alignment, lowerboundary, upperboundary, - poi_analysis, max_pointers + arch.encode(), + analysis.encode(), + minstrlen, + alignment, + lowerboundary, + upperboundary, + poi_analysis, + max_pointers, ) if not core.BNDetectBaseAddress(self._handle, settings): @@ -108,49 +172,22 @@ def detect_base_address( self._last_tested_base_address = last_base.value return True - @property - def scores(self) -> list[tuple[int, int]]: - """ - ``get_scores`` returns a list of base addresses and their scores - - :return: list of tuples containing each base address and score - :rtype: OrderedDict - """ - - return self._scores - - @property - def confidence(self) -> "BaseAddressDetectionConfidenceEnum": + def get_reasons_for_base_address(self, base_address: int) -> list[BaseAddressDetectionReason]: """ - ``get_confidence`` returns an enum that indicates confidence that the top base address candidate is correct + ``get_reasons_for_base_address`` returns a list of reasons why the specified base address is a candidate - :return: confidence of the base address detection results - :rtype: BaseAddressDetectionConfidenceEnum + :param int base_address: base address to get reasons for + :return: list of reasons for the specified base address + :rtype: list """ - return self._confidence + count = ctypes.c_size_t() + reasons = core.BNGetBaseAddressDetectionReasons(self._handle, base_address, ctypes.byref(count)) + if count.value == 0: + return [] - @property - def last_tested_base_address(self) -> int: - """ - ``last_tested_base_address`` returns the last base address candidate that was tested - - :return: last base address tested - :rtype: int - """ - - return self._last_tested_base_address - - @property - def preferred_base_address(self) -> int: - """ - ``preferred_base_address`` returns the base address that is preferred by analysis - - :return: preferred base address - :rtype: int - """ - - if not self._scores: - return None - - return self._scores[0][0] + result = list() + for i in range(count.value): + result.append(BaseAddressDetectionReason(reasons[i].Pointer, reasons[i].POIOffset, reasons[i].POIType)) + core.BNFreeBaseAddressDetectionReasons(reasons) + return result From f484e285a779b85ec7aeedb515dc4314904cbc25 Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Thu, 25 Apr 2024 16:19:31 -0400 Subject: [PATCH 40/50] Helpers for querying base detection hits --- python/basedetection.py | 70 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/python/basedetection.py b/python/basedetection.py index e8a1ef958..88a691bdc 100644 --- a/python/basedetection.py +++ b/python/basedetection.py @@ -19,9 +19,12 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. +import os import ctypes -from typing import Optional +from typing import Optional, Union from dataclasses import dataclass +from .enums import BaseAddressDetectionPOIType, BaseAddressDetectionConfidence, BaseAddressDetectionPOISetting +from .binaryview import BinaryView from . import _binaryninjacore as core @@ -32,7 +35,7 @@ class BaseAddressDetectionReason: pointer: int offset: int - type: int + type: BaseAddressDetectionPOIType class BaseAddressDetection: @@ -40,15 +43,17 @@ class BaseAddressDetection: ``class BaseAddressDetection`` is a class that is used to detect the base address of position-dependent raw binaries >>> from binaryninja import * - >>> bv = BinaryViewType["Raw"].open("firmware.bin") - >>> bad = BaseAddressDetection(bv) + >>> bad = BaseAddressDetection("firmware.bin") >>> bad.detect_base_address() True >>> hex(bad.preferred_base_address) '0x4000000' """ - def __init__(self, view: "BinaryView") -> None: + def __init__(self, view: Union[str, os.PathLike, BinaryView]) -> None: + if isinstance(view, str) or isinstance(view, os.PathLike): + view = BinaryView.load(view, update_analysis=False) + _handle = core.BNCreateBaseAddressDetection(view.handle) assert _handle is not None, "core.BNCreateBaseAddressDetection returned None" self._handle = _handle @@ -74,7 +79,7 @@ def scores(self) -> list[tuple[int, int]]: return self._scores @property - def confidence(self) -> "BaseAddressDetectionConfidenceEnum": + def confidence(self) -> BaseAddressDetectionConfidence: """ ``confidence`` returns an enum that indicates confidence that the top base address candidate is correct @@ -117,7 +122,7 @@ def detect_base_address( alignment: Optional[int] = 1024, lowerboundary: Optional[int] = 0, upperboundary: Optional[int] = 0xFFFFFFFFFFFFFFFF, - poi_analysis: Optional[int] = 0, + poi_analysis: Optional[BaseAddressDetectionPOISetting] = BaseAddressDetectionPOISetting.POIAnalysisAll, max_pointers: Optional[int] = 128, ) -> bool: """ @@ -164,6 +169,9 @@ def detect_base_address( self._handle, scores, max_candidates, ctypes.byref(confidence), ctypes.byref(last_base) ) + if num_candidates == 0: + return False + self._scores.clear() for i in range(num_candidates): self._scores.append((scores[i].BaseAddress, scores[i].Score)) @@ -191,3 +199,51 @@ def get_reasons_for_base_address(self, base_address: int) -> list[BaseAddressDet result.append(BaseAddressDetectionReason(reasons[i].Pointer, reasons[i].POIOffset, reasons[i].POIType)) core.BNFreeBaseAddressDetectionReasons(reasons) return result + + def _get_data_hits_by_type(self, base_address: int, poi_type: int) -> int: + reasons = self.get_reasons_for_base_address(base_address) + if not reasons: + return 0 + + hits = 0 + for reason in reasons: + if reason.type == poi_type: + hits += 1 + + return hits + + def get_string_hits_for_base_address(self, base_address: int) -> int: + """ + ``get_string_hits_for_base_address`` returns the number of times a pointer pointed to a string at the specified + base address + + :param int base_address: base address to get data hits for + :return: number of string hits for the specified base address + :rtype: int + """ + + return self._get_data_hits_by_type(base_address, BaseAddressDetectionPOIType.POIString) + + def get_function_hits_for_base_address(self, base_address: int) -> int: + """ + ``get_function_hits_for_base_address`` returns the number of times a pointer pointed to a function at the + specified base address + + :param int base_address: base address to get function hits for + :return: number of function hits for the specified base address + :rtype: int + """ + + return self._get_data_hits_by_type(base_address, BaseAddressDetectionPOIType.POIFunction) + + def get_data_hits_for_base_address(self, base_address: int) -> int: + """ + ``get_data_hits_for_base_address`` returns the number of times a pointer pointed to a data variable at the + specified base address + + :param int base_address: base address to get data hits for + :return: number of data hits for the specified base address + :rtype: int + """ + + return self._get_data_hits_by_type(base_address, BaseAddressDetectionPOIType.POIDataVariable) From 81f6484d2a9f4214685516073fbd1beeb553d255 Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Fri, 26 Apr 2024 11:08:44 -0400 Subject: [PATCH 41/50] Python helpers for aborting base detection Also updated and cleaned up documentation --- python/basedetection.py | 150 +++++++++++++++++++++++++++++----------- 1 file changed, 110 insertions(+), 40 deletions(-) diff --git a/python/basedetection.py b/python/basedetection.py index 88a691bdc..724063468 100644 --- a/python/basedetection.py +++ b/python/basedetection.py @@ -30,8 +30,9 @@ @dataclass class BaseAddressDetectionReason: - """``class BaseAddressDetectionReason`` is a class that is used to store information about why a base address is a - candidate""" + """``class BaseAddressDetectionReason`` is a class that stores information used to understand why a base address + is a candidate. It consists of a pointer, the offset of the point-of-interest that the pointer aligns with, and the + type of point-of-interest (string, function, or data variable)""" pointer: int offset: int @@ -40,19 +41,22 @@ class BaseAddressDetectionReason: class BaseAddressDetection: """ - ``class BaseAddressDetection`` is a class that is used to detect the base address of position-dependent raw binaries - - >>> from binaryninja import * - >>> bad = BaseAddressDetection("firmware.bin") - >>> bad.detect_base_address() - True - >>> hex(bad.preferred_base_address) - '0x4000000' + ``class BaseAddressDetection`` is a class that is used to detect candidate base addresses for position-dependent + raw binaries + + :Example: + + >>> from binaryninja import * + >>> bad = BaseAddressDetection("firmware.bin") + >>> bad.detect_base_address() + True + >>> hex(bad.preferred_base_address) + '0x4000000' """ def __init__(self, view: Union[str, os.PathLike, BinaryView]) -> None: if isinstance(view, str) or isinstance(view, os.PathLike): - view = BinaryView.load(view, update_analysis=False) + view = BinaryView.load(str(view), update_analysis=False) _handle = core.BNCreateBaseAddressDetection(view.handle) assert _handle is not None, "core.BNCreateBaseAddressDetection returned None" @@ -70,10 +74,30 @@ def __del__(self): @property def scores(self) -> list[tuple[int, int]]: """ - ``scores`` returns a list of base addresses and their scores + ``scores`` returns a list of candidate base addresses and their scores + + .. note:: The score is set to the number of times a pointer pointed to a point-of-interest at that base address + + :Example: + + >>> from binaryninja import * + >>> bad = BaseAddressDetection("firmware.bin") + >>> bad.detect_base_address() + True + >>> for addr, score in bad.scores: + ... print(f"0x{addr:x}: {score}") + ... + 0x4000000: 7 + 0x400dc00: 1 + 0x400d800: 1 + 0x400cc00: 1 + 0x400c400: 1 + 0x400bc00: 1 + 0x400b800: 1 + 0x3fffc00: 1 :return: list of tuples containing each base address and score - :rtype: OrderedDict + :rtype: list[tuple[int, int]] """ return self._scores @@ -81,10 +105,10 @@ def scores(self) -> list[tuple[int, int]]: @property def confidence(self) -> BaseAddressDetectionConfidence: """ - ``confidence`` returns an enum that indicates confidence that the top base address candidate is correct + ``confidence`` returns an enum that indicates confidence the preferred candidate base address is correct :return: confidence of the base address detection results - :rtype: BaseAddressDetectionConfidenceEnum + :rtype: BaseAddressDetectionConfidence """ return self._confidence @@ -92,9 +116,12 @@ def confidence(self) -> BaseAddressDetectionConfidence: @property def last_tested_base_address(self) -> int: """ - ``last_tested_base_address`` returns the last base address candidate that was tested + ``last_tested_base_address`` returns the last candidate base address that was tested + + .. note:: This is useful for situations where the user aborts the analysis and wants to restart from the last \ + tested base address by setting the ``low_boundary`` parameter in ``BaseAddressDetection.detect_base_address`` - :return: last base address tested + :return: last candidate base address tested :rtype: int """ @@ -103,9 +130,15 @@ def last_tested_base_address(self) -> int: @property def preferred_base_address(self) -> int: """ - ``preferred_base_address`` returns the base address that is preferred by analysis + ``preferred_base_address`` returns the candidate base address which contains the most amount of pointers that + align with discovered points-of-interest in the binary - :return: preferred base address + .. note:: ``BaseAddressDetection.confidence`` reports a confidence level that the preferred base is correct + + .. note:: ``BaseAddressDetection.scores`` returns a list of the top 10 candidate base addresses and their \ + scores and can be used to discover other potential candidates + + :return: preferred candidate base address :rtype: int """ @@ -114,21 +147,43 @@ def preferred_base_address(self) -> int: return self._scores[0][0] + @property + def aborted(self) -> bool: + """ + ``aborted`` indicates whether or not base address detection analysis was aborted early + + :return: True if the analysis was aborted, False otherwise + :rtype: bool + """ + + return core.BNIsBaseAddressDetectionAborted(self._handle) + def detect_base_address( self, arch: Optional[str] = "", analysis: Optional[str] = "basic", - minstrlen: Optional[int] = 10, + min_strlen: Optional[int] = 10, alignment: Optional[int] = 1024, - lowerboundary: Optional[int] = 0, - upperboundary: Optional[int] = 0xFFFFFFFFFFFFFFFF, + low_boundary: Optional[int] = 0, + high_boundary: Optional[int] = 0xFFFFFFFFFFFFFFFF, poi_analysis: Optional[BaseAddressDetectionPOISetting] = BaseAddressDetectionPOISetting.POIAnalysisAll, max_pointers: Optional[int] = 128, ) -> bool: """ - ``detect_base_address`` runs analysis and attempts to identify candidate base addresses - - :return: True if initial analysis is valid, False otherwise + ``detect_base_address`` runs initial analysis and attempts to identify candidate base addresses + + .. note:: This operation can take a long time to complete depending on the size and complexity of the binary \ + and the settings used + + :param str arch: CPU architecture of the binary (defaults to using auto-detection) + :param str analysis: analysis mode (``basic``, ``controlFlow``, or ``full``) + :param int min_strlen: minimum length of a string to be considered a point-of-interest + :param int alignment: byte boundary to align the base address to while brute-forcing + :param int low_boundary: lower boundary of the base address range to test + :param int high_boundary: upper boundary of the base address range to test + :param BaseAddressDetectionPOISetting poi_analysis: specifies types of points-of-interest to use for analysis + :param int max_pointers: maximum number of candidate pointers to collect per pointer cluster + :return: True if initial analysis completed with results, False otherwise :rtype: bool """ @@ -144,16 +199,16 @@ def detect_base_address( if max_pointers < 2: raise ValueError("max pointers must be at least 2") - if upperboundary < lowerboundary: + if high_boundary < low_boundary: raise ValueError("upper boundary must be greater than lower boundary") settings = core.BNBaseAddressDetectionSettings( arch.encode(), analysis.encode(), - minstrlen, + min_strlen, alignment, - lowerboundary, - upperboundary, + low_boundary, + high_boundary, poi_analysis, max_pointers, ) @@ -180,13 +235,25 @@ def detect_base_address( self._last_tested_base_address = last_base.value return True - def get_reasons_for_base_address(self, base_address: int) -> list[BaseAddressDetectionReason]: + def abort(self) -> None: + """ + ``abort`` aborts base address detection analysis + + .. note:: ``abort`` does not stop base address detection until after initial analysis has completed and it is \ + in the base address enumeration phase + + :rtype: None """ - ``get_reasons_for_base_address`` returns a list of reasons why the specified base address is a candidate + + core.BNAbortBaseAddressDetection(self._handle) + + def get_reasons(self, base_address: int) -> list[BaseAddressDetectionReason]: + """ + ``get_reasons`` returns a list of reasons that can be used to determine why a base address is a candidate :param int base_address: base address to get reasons for :return: list of reasons for the specified base address - :rtype: list + :rtype: list[BaseAddressDetectionReason] """ count = ctypes.c_size_t() @@ -201,7 +268,7 @@ def get_reasons_for_base_address(self, base_address: int) -> list[BaseAddressDet return result def _get_data_hits_by_type(self, base_address: int, poi_type: int) -> int: - reasons = self.get_reasons_for_base_address(base_address) + reasons = self.get_reasons(base_address) if not reasons: return 0 @@ -212,21 +279,24 @@ def _get_data_hits_by_type(self, base_address: int, poi_type: int) -> int: return hits - def get_string_hits_for_base_address(self, base_address: int) -> int: + def get_string_hits(self, base_address: int) -> int: """ - ``get_string_hits_for_base_address`` returns the number of times a pointer pointed to a string at the specified + ``get_string_hits`` returns the number of times a pointer pointed to a string at the specified base address - :param int base_address: base address to get data hits for + .. note:: Data variables are only used as points-of-interest if analysis doesn't discover enough strings and \ + functions + + :param int base_address: base address to get string hits for :return: number of string hits for the specified base address :rtype: int """ return self._get_data_hits_by_type(base_address, BaseAddressDetectionPOIType.POIString) - def get_function_hits_for_base_address(self, base_address: int) -> int: + def get_function_hits(self, base_address: int) -> int: """ - ``get_function_hits_for_base_address`` returns the number of times a pointer pointed to a function at the + ``get_function_hits`` returns the number of times a pointer pointed to a function at the specified base address :param int base_address: base address to get function hits for @@ -236,9 +306,9 @@ def get_function_hits_for_base_address(self, base_address: int) -> int: return self._get_data_hits_by_type(base_address, BaseAddressDetectionPOIType.POIFunction) - def get_data_hits_for_base_address(self, base_address: int) -> int: + def get_data_hits(self, base_address: int) -> int: """ - ``get_data_hits_for_base_address`` returns the number of times a pointer pointed to a data variable at the + ``get_data_hits`` returns the number of times a pointer pointed to a data variable at the specified base address :param int base_address: base address to get data hits for From 6b3165fba7c05a8b200215385ec4478609eb38d3 Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Mon, 29 Apr 2024 07:49:33 -0400 Subject: [PATCH 42/50] Added Python example script for base detection --- python/basedetection.py | 18 ++-- python/examples/raw_binary_base_detection.py | 101 +++++++++++++++++++ 2 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 python/examples/raw_binary_base_detection.py diff --git a/python/basedetection.py b/python/basedetection.py index 724063468..b682705b4 100644 --- a/python/basedetection.py +++ b/python/basedetection.py @@ -21,7 +21,7 @@ import os import ctypes -from typing import Optional, Union +from typing import Optional, Union, Literal from dataclasses import dataclass from .enums import BaseAddressDetectionPOIType, BaseAddressDetectionConfidence, BaseAddressDetectionPOISetting from .binaryview import BinaryView @@ -128,7 +128,7 @@ def last_tested_base_address(self) -> int: return self._last_tested_base_address @property - def preferred_base_address(self) -> int: + def preferred_base_address(self) -> Optional[int]: """ ``preferred_base_address`` returns the candidate base address which contains the most amount of pointers that align with discovered points-of-interest in the binary @@ -161,7 +161,7 @@ def aborted(self) -> bool: def detect_base_address( self, arch: Optional[str] = "", - analysis: Optional[str] = "basic", + analysis: Optional[str] = Literal["basic", "controlFlow", "full"], min_strlen: Optional[int] = 10, alignment: Optional[int] = 1024, low_boundary: Optional[int] = 0, @@ -261,11 +261,13 @@ def get_reasons(self, base_address: int) -> list[BaseAddressDetectionReason]: if count.value == 0: return [] - result = list() - for i in range(count.value): - result.append(BaseAddressDetectionReason(reasons[i].Pointer, reasons[i].POIOffset, reasons[i].POIType)) - core.BNFreeBaseAddressDetectionReasons(reasons) - return result + try: + result = list() + for i in range(count.value): + result.append(BaseAddressDetectionReason(reasons[i].Pointer, reasons[i].POIOffset, reasons[i].POIType)) + return result + finally: + core.BNFreeBaseAddressDetectionReasons(reasons) def _get_data_hits_by_type(self, base_address: int, poi_type: int) -> int: reasons = self.get_reasons(base_address) diff --git a/python/examples/raw_binary_base_detection.py b/python/examples/raw_binary_base_detection.py new file mode 100644 index 000000000..76268fbec --- /dev/null +++ b/python/examples/raw_binary_base_detection.py @@ -0,0 +1,101 @@ +# Copyright (c) 2015-2024 Vector 35 Inc +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +"""Headless script for demonstrating Binary Ninja automated base address detection for +raw position-dependent firmware binaries +""" + +import argparse +import json +from os import walk, path +from binaryninja import BaseAddressDetection, log_to_stderr, LogLevel, log_info, log_error + + +def _get_directory_listing(_path: str) -> list[str]: + if path.isfile(_path): + return [_path] + + if not path.isdir(_path): + raise FileNotFoundError(f"Path '{_path}' is not a file or directory") + + files = [] + for dirpath, _, filenames in walk(_path): + for filename in filenames: + files.append(path.join(dirpath, filename)) + return files + + +def _parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="detect base address of position-dependent raw firmware binaries") + parser.add_argument("path", help="path to the position-dependent raw firmware binary or directory") + parser.add_argument("--debug", action="store_true", help="enable debug logging") + parser.add_argument("--reasons", action="store_true", help="show reasons for base address selection") + parser.add_argument("--analysis", type=str, help="analysis level", default="basic") + parser.add_argument("--arch", type=str, default="", help="architecture of the binary") + return parser.parse_args() + + +def _setup_logger(debug: bool) -> None: + if debug: + log_to_stderr(LogLevel.DebugLog) + else: + log_to_stderr(LogLevel.InfoLog) + + +def main() -> None: + """Run the program""" + args = _parse_args() + _setup_logger(args.debug) + + files = _get_directory_listing(args.path) + for _file in files: + log_info(f"Running base address detection analysis on '{_file}'...") + bad = BaseAddressDetection(_file) + if not bad.detect_base_address(analysis=args.analysis, arch=args.arch): + log_error("Base address detection analysis failed") + continue + + json_dict = dict() + json_dict["filename"] = path.basename(_file) + json_dict["preferred_candidate"] = dict() + json_dict["preferred_candidate"]["address"] = f"0x{bad.preferred_base_address:x}" + json_dict["preferred_candidate"]["confidence"] = bad.confidence + json_dict["aborted"] = bad.aborted + json_dict["last_tested"] = f"0x{bad.last_tested_base_address:x}" + json_dict["candidates"] = dict() + for baseaddr, score in bad.scores: + json_dict["candidates"][f"0x{baseaddr:x}"] = dict() + json_dict["candidates"][f"0x{baseaddr:x}"]["score"] = score + json_dict["candidates"][f"0x{baseaddr:x}"]["function hits"] = bad.get_function_hits(baseaddr) + json_dict["candidates"][f"0x{baseaddr:x}"]["string hits"] = bad.get_string_hits(baseaddr) + json_dict["candidates"][f"0x{baseaddr:x}"]["data hits"] = bad.get_data_hits(baseaddr) + if args.reasons: + json_dict["candidates"][f"0x{baseaddr:x}"]["reasons"] = dict() + for reason in bad.get_reasons(baseaddr): + json_dict["candidates"][f"0x{baseaddr:x}"]["reasons"][f"0x{reason.pointer:x}"] = { + "poi_offset": f"0x{reason.offset:x}", + "poi_type": reason.type, + } + + print(json.dumps(json_dict, indent=4)) + + +if __name__ == "__main__": + main() From 62ce7de2bc288bb2716177b3fa86470f1e19d383 Mon Sep 17 00:00:00 2001 From: Jordan Wiens Date: Tue, 30 Apr 2024 12:39:31 -0400 Subject: [PATCH 43/50] link up methods and properties in base address API docs --- python/basedetection.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/basedetection.py b/python/basedetection.py index b682705b4..655f017b1 100644 --- a/python/basedetection.py +++ b/python/basedetection.py @@ -119,7 +119,7 @@ def last_tested_base_address(self) -> int: ``last_tested_base_address`` returns the last candidate base address that was tested .. note:: This is useful for situations where the user aborts the analysis and wants to restart from the last \ - tested base address by setting the ``low_boundary`` parameter in ``BaseAddressDetection.detect_base_address`` + tested base address by setting the ``low_boundary`` parameter in :py:func:`BaseAddressDetection.detect_base_address` :return: last candidate base address tested :rtype: int @@ -133,9 +133,9 @@ def preferred_base_address(self) -> Optional[int]: ``preferred_base_address`` returns the candidate base address which contains the most amount of pointers that align with discovered points-of-interest in the binary - .. note:: ``BaseAddressDetection.confidence`` reports a confidence level that the preferred base is correct + .. note:: :py:attr:`BaseAddressDetection.confidence` reports a confidence level that the preferred base is correct - .. note:: ``BaseAddressDetection.scores`` returns a list of the top 10 candidate base addresses and their \ + .. note:: :py:attr:`BaseAddressDetection.scores` returns a list of the top 10 candidate base addresses and their \ scores and can be used to discover other potential candidates :return: preferred candidate base address From e88d386127b751d17b9005b2e374150473f42729 Mon Sep 17 00:00:00 2001 From: Rusty Wagner Date: Fri, 26 Apr 2024 18:22:11 -0400 Subject: [PATCH 44/50] Fill out int_arg_registers and float_arg_registers in the Rust API --- rust/src/callingconvention.rs | 36 +++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/rust/src/callingconvention.rs b/rust/src/callingconvention.rs index 815f4d42a..57f76a11a 100644 --- a/rust/src/callingconvention.rs +++ b/rust/src/callingconvention.rs @@ -569,11 +569,43 @@ impl CallingConventionBase for CallingConvention { } fn int_arg_registers(&self) -> Vec { - Vec::new() + unsafe { + let mut count = 0; + let regs = BNGetIntegerArgumentRegisters(self.handle, &mut count); + let arch = self.arch_handle.borrow(); + + let res = slice::from_raw_parts(regs, count) + .iter() + .map(|&r| { + arch.register_from_id(r) + .expect("bad reg id from CallingConvention") + }) + .collect(); + + BNFreeRegisterList(regs); + + res + } } fn float_arg_registers(&self) -> Vec { - Vec::new() + unsafe { + let mut count = 0; + let regs = BNGetFloatArgumentRegisters(self.handle, &mut count); + let arch = self.arch_handle.borrow(); + + let res = slice::from_raw_parts(regs, count) + .iter() + .map(|&r| { + arch.register_from_id(r) + .expect("bad reg id from CallingConvention") + }) + .collect(); + + BNFreeRegisterList(regs); + + res + } } fn arg_registers_shared_index(&self) -> bool { From 7c04b4b1f27d1d388bfc6bb793f076d38366b849 Mon Sep 17 00:00:00 2001 From: Rusty Wagner Date: Fri, 26 Apr 2024 18:22:50 -0400 Subject: [PATCH 45/50] Add parameter_variables API to Function in Rust --- rust/src/function.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/rust/src/function.rs b/rust/src/function.rs index 273a08612..7f900a255 100644 --- a/rust/src/function.rs +++ b/rust/src/function.rs @@ -30,7 +30,6 @@ pub use binaryninjacore_sys::BNAnalysisSkipReason as AnalysisSkipReason; pub use binaryninjacore_sys::BNFunctionAnalysisSkipOverride as FunctionAnalysisSkipOverride; pub use binaryninjacore_sys::BNFunctionUpdateType as FunctionUpdateType; - use std::hash::Hash; use std::{fmt, mem}; @@ -315,6 +314,22 @@ impl Function { } } + pub fn parameter_variables(&self) -> Conf> { + unsafe { + let mut variables = BNGetFunctionParameterVariables(self.handle); + let mut result = Vec::with_capacity(variables.count); + let confidence = variables.confidence; + let vars = std::slice::from_raw_parts(variables.vars, variables.count); + + for i in 0..variables.count { + result.push(Variable::from_raw(vars[i])); + } + + BNFreeParameterVariables(&mut variables); + Conf::new(result, confidence) + } + } + pub fn apply_imported_types(&self, sym: &Symbol, t: Option<&Type>) { unsafe { BNApplyImportedTypes( From 2cd83c7221f8587975bcfc7582eb5b8c73f1653e Mon Sep 17 00:00:00 2001 From: Rusty Wagner Date: Fri, 26 Apr 2024 18:24:46 -0400 Subject: [PATCH 46/50] Allow fetching target instruction index in LLIL branches from the Rust API --- rust/src/llil/operation.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/rust/src/llil/operation.rs b/rust/src/llil/operation.rs index 3c40f2077..3ba4fa785 100644 --- a/rust/src/llil/operation.rs +++ b/rust/src/llil/operation.rs @@ -89,10 +89,10 @@ pub struct Syscall; pub struct Intrinsic; impl<'func, A, M, V> Operation<'func, A, M, NonSSA, Intrinsic> - where - A: 'func + Architecture, - M: FunctionMutability, - V: NonSSAVariant, +where + A: 'func + Architecture, + M: FunctionMutability, + V: NonSSAVariant, { // TODO: Support register and expression lists pub fn intrinsic(&self) -> Option { @@ -382,12 +382,20 @@ where } } + pub fn true_target_idx(&self) -> usize { + self.op.operands[1] as usize + } + pub fn false_target(&self) -> Instruction<'func, A, M, F> { Instruction { function: self.function, instr_idx: self.op.operands[2] as usize, } } + + pub fn false_target_idx(&self) -> usize { + self.op.operands[2] as usize + } } // LLIL_GOTO @@ -405,6 +413,10 @@ where instr_idx: self.op.operands[0] as usize, } } + + pub fn target_idx(&self) -> usize { + self.op.operands[0] as usize + } } // LLIL_FLAG_COND From f193132145f62cac0ffeebc3ff918ebf9dd37103 Mon Sep 17 00:00:00 2001 From: Rusty Wagner Date: Fri, 26 Apr 2024 18:25:36 -0400 Subject: [PATCH 47/50] Add MLIL APIs for getting by instruction index, and expose the operation size in the Rust API --- rust/src/mlil/function.rs | 14 ++++++++++++++ rust/src/mlil/instruction.rs | 10 +++++++++- rust/src/mlil/lift.rs | 1 + 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/rust/src/mlil/function.rs b/rust/src/mlil/function.rs index 16cc51020..0b662578c 100644 --- a/rust/src/mlil/function.rs +++ b/rust/src/mlil/function.rs @@ -2,6 +2,7 @@ use core::hash::{Hash, Hasher}; use binaryninjacore_sys::BNFreeMediumLevelILFunction; use binaryninjacore_sys::BNGetMediumLevelILBasicBlockList; +use binaryninjacore_sys::BNGetMediumLevelILIndexForInstruction; use binaryninjacore_sys::BNGetMediumLevelILInstructionCount; use binaryninjacore_sys::BNGetMediumLevelILOwnerFunction; use binaryninjacore_sys::BNGetMediumLevelILSSAForm; @@ -65,6 +66,19 @@ impl MediumLevelILFunction { self.instruction_from_idx(expr_idx).lift() } + pub fn instruction_from_instruction_idx(&self, instr_idx: usize) -> MediumLevelILInstruction { + MediumLevelILInstruction::new(self.to_owned(), unsafe { + BNGetMediumLevelILIndexForInstruction(self.handle, instr_idx) + }) + } + + pub fn lifted_instruction_from_instruction_idx( + &self, + instr_idx: usize, + ) -> MediumLevelILLiftedInstruction { + self.instruction_from_instruction_idx(instr_idx).lift() + } + pub fn instruction_count(&self) -> usize { unsafe { BNGetMediumLevelILInstructionCount(self.handle) } } diff --git a/rust/src/mlil/instruction.rs b/rust/src/mlil/instruction.rs index 3b5dcfb42..bd2cc7171 100644 --- a/rust/src/mlil/instruction.rs +++ b/rust/src/mlil/instruction.rs @@ -18,6 +18,7 @@ pub struct MediumLevelILInstruction { pub function: Ref, pub address: u64, pub index: usize, + pub size: usize, pub kind: MediumLevelILInstructionKind, } @@ -704,7 +705,12 @@ impl MediumLevelILInstruction { }), // translated directly into a list for Expression or Variables // TODO MLIL_MEMORY_INTRINSIC_SSA needs to be handled properly - MLIL_CALL_OUTPUT | MLIL_CALL_PARAM | MLIL_CALL_PARAM_SSA | MLIL_CALL_OUTPUT_SSA | MLIL_MEMORY_INTRINSIC_OUTPUT_SSA | MLIL_MEMORY_INTRINSIC_SSA => { + MLIL_CALL_OUTPUT + | MLIL_CALL_PARAM + | MLIL_CALL_PARAM_SSA + | MLIL_CALL_OUTPUT_SSA + | MLIL_MEMORY_INTRINSIC_OUTPUT_SSA + | MLIL_MEMORY_INTRINSIC_SSA => { unreachable!() } }; @@ -713,6 +719,7 @@ impl MediumLevelILInstruction { function, address: op.address, index, + size: op.size, kind, } } @@ -1022,6 +1029,7 @@ impl MediumLevelILInstruction { function: self.function.clone(), address: self.address, index: self.index, + size: self.size, kind, } } diff --git a/rust/src/mlil/lift.rs b/rust/src/mlil/lift.rs index 39e8e9830..e8548b064 100644 --- a/rust/src/mlil/lift.rs +++ b/rust/src/mlil/lift.rs @@ -27,6 +27,7 @@ pub struct MediumLevelILLiftedInstruction { pub function: Ref, pub address: u64, pub index: usize, + pub size: usize, pub kind: MediumLevelILLiftedInstructionKind, } From bec653143bd78f1662ed1a556c4b00471f395fcc Mon Sep 17 00:00:00 2001 From: Rusty Wagner Date: Fri, 26 Apr 2024 18:26:12 -0400 Subject: [PATCH 48/50] Add HLIL APIs to fetch the root or by instruction index, and expose the operation size in the Rust API --- rust/src/hlil/function.rs | 41 ++++++++++++++++++++++++++++++++++++ rust/src/hlil/instruction.rs | 3 +++ rust/src/hlil/lift.rs | 1 + rust/src/hlil/operation.rs | 2 +- 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/rust/src/hlil/function.rs b/rust/src/hlil/function.rs index 4bad7f0f0..25608d714 100644 --- a/rust/src/hlil/function.rs +++ b/rust/src/hlil/function.rs @@ -2,8 +2,10 @@ use std::hash::{Hash, Hasher}; use binaryninjacore_sys::BNFreeHighLevelILFunction; use binaryninjacore_sys::BNGetHighLevelILBasicBlockList; +use binaryninjacore_sys::BNGetHighLevelILIndexForInstruction; use binaryninjacore_sys::BNGetHighLevelILInstructionCount; use binaryninjacore_sys::BNGetHighLevelILOwnerFunction; +use binaryninjacore_sys::BNGetHighLevelILRootExpr; use binaryninjacore_sys::BNGetHighLevelILSSAForm; use binaryninjacore_sys::BNHighLevelILFunction; use binaryninjacore_sys::BNNewHighLevelILFunctionReference; @@ -52,6 +54,29 @@ impl HighLevelILFunction { self.instruction_from_idx(expr_idx).lift() } + pub fn instruction_from_instruction_idx(&self, instr_idx: usize) -> HighLevelILInstruction { + HighLevelILInstruction::new(self.as_non_ast(), unsafe { + BNGetHighLevelILIndexForInstruction(self.handle, instr_idx) + }) + } + + pub fn lifted_instruction_from_instruction_idx( + &self, + instr_idx: usize, + ) -> HighLevelILLiftedInstruction { + self.instruction_from_instruction_idx(instr_idx).lift() + } + + pub fn root(&self) -> HighLevelILInstruction { + HighLevelILInstruction::new(self.as_ast(), unsafe { + BNGetHighLevelILRootExpr(self.handle) + }) + } + + pub fn lifted_root(&self) -> HighLevelILLiftedInstruction { + self.root().lift() + } + pub fn instruction_count(&self) -> usize { unsafe { BNGetHighLevelILInstructionCount(self.handle) } } @@ -81,6 +106,22 @@ impl HighLevelILFunction { unsafe { Array::new(blocks, count, context) } } + + pub fn as_ast(&self) -> Ref { + Self { + handle: self.handle, + full_ast: true, + } + .to_owned() + } + + pub fn as_non_ast(&self) -> Ref { + Self { + handle: self.handle, + full_ast: false, + } + .to_owned() + } } impl ToOwned for HighLevelILFunction { diff --git a/rust/src/hlil/instruction.rs b/rust/src/hlil/instruction.rs index 9bffadaf4..7e77e379e 100644 --- a/rust/src/hlil/instruction.rs +++ b/rust/src/hlil/instruction.rs @@ -16,6 +16,7 @@ pub struct HighLevelILInstruction { pub function: Ref, pub address: u64, pub index: usize, + pub size: usize, pub kind: HighLevelILInstructionKind, } @@ -629,6 +630,7 @@ impl HighLevelILInstruction { function, address: op.address, index, + size: op.size, kind, } } @@ -878,6 +880,7 @@ impl HighLevelILInstruction { function: self.function.clone(), address: self.address, index: self.index, + size: self.size, kind, } } diff --git a/rust/src/hlil/lift.rs b/rust/src/hlil/lift.rs index 74ae0c64e..731a785c1 100644 --- a/rust/src/hlil/lift.rs +++ b/rust/src/hlil/lift.rs @@ -24,6 +24,7 @@ pub struct HighLevelILLiftedInstruction { pub function: Ref, pub address: u64, pub index: usize, + pub size: usize, pub kind: HighLevelILLiftedInstructionKind, } diff --git a/rust/src/hlil/operation.rs b/rust/src/hlil/operation.rs index 965d95173..ee0d437b5 100644 --- a/rust/src/hlil/operation.rs +++ b/rust/src/hlil/operation.rs @@ -9,7 +9,7 @@ use super::HighLevelILLiftedInstruction; #[derive(Clone, Debug, PartialEq, Eq)] pub struct GotoLabel { pub(crate) function: Ref, - pub(crate) target: u64, + pub target: u64, } impl GotoLabel { From c20b29fd64c16720d4c14586a14bbb9e97d43ad4 Mon Sep 17 00:00:00 2001 From: Ryan Snyder Date: Wed, 1 May 2024 08:59:02 -0400 Subject: [PATCH 49/50] riscv: account for minor rust API updates --- arch/riscv/src/lib.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/arch/riscv/src/lib.rs b/arch/riscv/src/lib.rs index 645bb45a2..91a418f7e 100644 --- a/arch/riscv/src/lib.rs +++ b/arch/riscv/src/lib.rs @@ -508,23 +508,23 @@ impl architecture::Intrinsic for RiscVIntrinsic { } } - fn inputs(&self) -> Vec> { + fn inputs(&self) -> Vec> { match self.id { Intrinsic::Uret | Intrinsic::Sret | Intrinsic::Mret | Intrinsic::Wfi => { vec![] } Intrinsic::Csrrd => { vec![NameAndType::new( - "csr".into(), + "csr", &Type::int(4, false), max_confidence(), )] } Intrinsic::Csrrw | Intrinsic::Csrwr | Intrinsic::Csrrs | Intrinsic::Csrrc => { vec![ - NameAndType::new("csr".into(), &Type::int(4, false), max_confidence()), + NameAndType::new("csr", &Type::int(4, false), max_confidence()), NameAndType::new( - "value".into(), + "value", &Type::int(::Int::width(), false), min_confidence(), ), @@ -540,8 +540,8 @@ impl architecture::Intrinsic for RiscVIntrinsic { | Intrinsic::Fmin(size) | Intrinsic::Fmax(size) => { vec![ - NameAndType::new("".into(), &Type::float(size as usize), max_confidence()), - NameAndType::new("".into(), &Type::float(size as usize), max_confidence()), + NameAndType::new("", &Type::float(size as usize), max_confidence()), + NameAndType::new("", &Type::float(size as usize), max_confidence()), ] } Intrinsic::Fsqrt(size, _) @@ -550,28 +550,28 @@ impl architecture::Intrinsic for RiscVIntrinsic { | Intrinsic::FcvtFToI(size, _, _) | Intrinsic::FcvtFToU(size, _, _) => { vec![NameAndType::new( - "".into(), + "", &Type::float(size as usize), max_confidence(), )] } Intrinsic::FcvtIToF(size, _, _) => { vec![NameAndType::new( - "".into(), + "", &Type::int(size as usize, true), max_confidence(), )] } Intrinsic::FcvtUToF(size, _, _) => { vec![NameAndType::new( - "".into(), + "", &Type::int(size as usize, false), max_confidence(), )] } Intrinsic::Fence => { vec![NameAndType::new( - "".into(), + "", &Type::int(4, false), min_confidence(), )] @@ -2431,10 +2431,9 @@ impl RelocationHandler .iter() .find(|r| r.info().native_type == Self::R_RISCV_PCREL_HI20) { - Some(target) => target, + Some(target) => target.target().wrapping_add(target.info().addend as u64), None => return false, }; - let target = target.target().wrapping_add(target.info().addend as u64); let offset = target.wrapping_sub(reloc.target()) as u32; let low_offset = offset & 0xfff; From 87b1470b2de74640c856d8a90ba88ec672713d71 Mon Sep 17 00:00:00 2001 From: Ryan Snyder Date: Wed, 1 May 2024 10:43:58 -0400 Subject: [PATCH 50/50] rust: account for changes to NameAndType bindings --- rust/src/architecture.rs | 6 +++--- rust/src/debuginfo.rs | 4 ++-- rust/src/types.rs | 19 +++++-------------- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/rust/src/architecture.rs b/rust/src/architecture.rs index db887e4f0..0e1349e67 100644 --- a/rust/src/architecture.rs +++ b/rust/src/architecture.rs @@ -1000,7 +1000,7 @@ impl Intrinsic for crate::architecture::CoreIntrinsic { let ret = slice::from_raw_parts_mut(inputs, count) .iter() - .map(NameAndType::from_raw) + .map(|x| NameAndType::from_raw(x).to_owned()) .collect(); BNFreeNameAndTypeList(inputs, count); @@ -2419,7 +2419,7 @@ where }; let inputs = intrinsic.inputs(); - let mut res: Box<[_]> = inputs.into_iter().map(|input| input.into_raw()).collect(); + let mut res: Box<[_]> = inputs.into_iter().map(|input| unsafe { Ref::into_raw(input) }.0).collect(); unsafe { *count = res.len(); @@ -2443,7 +2443,7 @@ where unsafe { let name_and_types = Box::from_raw(ptr::slice_from_raw_parts_mut(nt, count)); for nt in name_and_types.into_iter() { - BnString::from_raw(nt.name); + Ref::new(NameAndType::from_raw(nt)); } } } diff --git a/rust/src/debuginfo.rs b/rust/src/debuginfo.rs index ac3983341..d53faef3a 100644 --- a/rust/src/debuginfo.rs +++ b/rust/src/debuginfo.rs @@ -390,7 +390,7 @@ impl DebugInfo { let result: Vec> = unsafe { slice::from_raw_parts_mut(debug_types_ptr, count) .iter() - .map(NameAndType::from_raw) + .map(|x| NameAndType::from_raw(x).to_owned()) .collect() }; @@ -405,7 +405,7 @@ impl DebugInfo { let result: Vec> = unsafe { slice::from_raw_parts_mut(debug_types_ptr, count) .iter() - .map(NameAndType::from_raw) + .map(|x| NameAndType::from_raw(x).to_owned()) .collect() }; diff --git a/rust/src/types.rs b/rust/src/types.rs index dae8a6cc1..75c8fb363 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -2440,16 +2440,11 @@ unsafe impl CoreArrayWrapper for QualifiedNameTypeAndId { ////////////////////////// // NameAndType -#[repr(transparent)] pub struct NameAndType(pub(crate) BNNameAndType); impl NameAndType { - pub(crate) fn from_raw(raw: &BNNameAndType) -> Ref { - Self::new( - raw_to_string(raw.name).unwrap(), - unsafe { &Type::ref_from_raw(raw.type_) }, - raw.typeConfidence, - ) + pub(crate) unsafe fn from_raw(raw: &BNNameAndType) -> Self { + Self ( *raw ) } } @@ -2464,10 +2459,6 @@ impl NameAndType { } } - pub(crate) fn into_raw(self) -> BNNameAndType { - self.0 - } - pub fn name(&self) -> &str { let c_str = unsafe { CStr::from_ptr(self.0.name) }; c_str.to_str().unwrap() @@ -2495,7 +2486,7 @@ unsafe impl RefCountable for NameAndType { Self::new( CStr::from_ptr(handle.0.name), handle.t(), - handle.type_with_confidence().confidence, + handle.0.typeConfidence, ) } @@ -2519,10 +2510,10 @@ unsafe impl CoreOwnedArrayProvider for NameAndType { } unsafe impl CoreArrayWrapper for NameAndType { - type Wrapped<'a> = &'a NameAndType; + type Wrapped<'a> = Guard<'a, NameAndType>; unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { - mem::transmute(raw) + unsafe { Guard::new(NameAndType::from_raw(raw), raw) } } }