diff --git a/libafl_frida/Cargo.toml b/libafl_frida/Cargo.toml index 71cf32c4f3..582f77829a 100644 --- a/libafl_frida/Cargo.toml +++ b/libafl_frida/Cargo.toml @@ -104,6 +104,8 @@ document-features = { version = "0.2", optional = true } # Document all features [target.'cfg(windows)'.dependencies] winsafe = { version = "0.0.21", features = ["kernel"] } +[target.'cfg(target_vendor="apple")'.dependencies] +mach-sys = { version = "0.5.4" } [dev-dependencies] serial_test = { version = "3", default-features = false, features = [ diff --git a/libafl_frida/src/alloc.rs b/libafl_frida/src/alloc.rs index e12bd4013a..b57f20a4e8 100644 --- a/libafl_frida/src/alloc.rs +++ b/libafl_frida/src/alloc.rs @@ -1,3 +1,5 @@ +#[cfg(target_vendor = "apple")] +use std::ptr::addr_of_mut; #[cfg(any( windows, target_os = "linux", @@ -13,6 +15,16 @@ use backtrace::Backtrace; use frida_gum::{PageProtection, RangeDetails}; use hashbrown::HashMap; use libafl_bolts::cli::FuzzerOptions; +#[cfg(target_vendor = "apple")] +use mach_sys::{ + kern_return::KERN_SUCCESS, + message::mach_msg_type_number_t, + traps::mach_task_self, + vm::mach_vm_region_recurse, + vm_prot::VM_PROT_READ, + vm_region::{vm_region_recurse_info_t, vm_region_submap_info_64}, + vm_types::{mach_vm_address_t, mach_vm_size_t, natural_t}, +}; #[cfg(any( windows, target_os = "linux", @@ -28,6 +40,9 @@ use serde::{Deserialize, Serialize}; use crate::asan::errors::{AsanError, AsanErrors}; +#[cfg(target_vendor = "apple")] +const VM_REGION_SUBMAP_INFO_COUNT_64: mach_msg_type_number_t = 19; + /// An allocator wrapper with binary-only address sanitization #[derive(Debug)] pub struct Allocator { @@ -236,7 +251,11 @@ impl Allocator { let address = (metadata.address + self.page_size) as *mut c_void; self.allocations.insert(address as usize, metadata); - log::trace!("serving address: {:?}, size: {:x}", address, size); + log::trace!( + "serving address: {:#x}, size: {:#x}", + address as usize, + size + ); address } @@ -478,14 +497,11 @@ impl Allocator { //4. The aligned check is where the address and the size is 8 byte aligned. Use check_shadow_aligned to check it //5. The post-alignment is the same as pre-alignment except it is the qword following the aligned portion. Use a specialized check to ensure that [end & ~7, end) is valid. - if size == 0 - /*|| !self.is_managed(address as *mut c_void)*/ - { + if size == 0 { return true; } if !self.is_managed(address as *mut c_void) { - log::trace!("unmanaged address to check_shadow: {:?}, {size:x}", address); return true; } @@ -544,11 +560,11 @@ impl Allocator { map_to_shadow!(self, start) } - /// Checks if the currennt address is one of ours + /// Checks if the current address is one of ours - is this address in the allocator region #[inline] pub fn is_managed(&self, ptr: *mut c_void) -> bool { //self.allocations.contains_key(&(ptr as usize)) - self.shadow_offset <= ptr as usize && (ptr as usize) < self.current_mapping_addr + self.base_mapping_addr <= ptr as usize && (ptr as usize) < self.current_mapping_addr } /// Checks if any of the allocations has not been freed @@ -562,14 +578,70 @@ impl Allocator { } /// Unpoison all the memory that is currently mapped with read permissions. + #[cfg(target_vendor = "apple")] + pub fn unpoison_all_existing_memory(&mut self) { + let task = unsafe { mach_task_self() }; + let mut address: mach_vm_address_t = 0; + let mut size: mach_vm_size_t = 0; + let mut depth: natural_t = 0; + + loop { + let mut kr; + let mut info_count: mach_msg_type_number_t = VM_REGION_SUBMAP_INFO_COUNT_64; + let mut info = vm_region_submap_info_64::default(); + loop { + kr = unsafe { + mach_vm_region_recurse( + task, + addr_of_mut!(address), + addr_of_mut!(size), + addr_of_mut!(depth), + addr_of_mut!(info) as vm_region_recurse_info_t, + addr_of_mut!(info_count), + ) + }; + + if kr != KERN_SUCCESS { + break; + } + + if info.is_submap != 0 { + depth += 1; + continue; + } else { + break; + } + } + + if kr != KERN_SUCCESS { + break; + } + + let start = address as usize; + let end = (address + size) as usize; + + if info.protection & VM_PROT_READ == VM_PROT_READ { + //if its at least readable + if self.shadow_offset <= start && end <= self.current_mapping_addr { + log::trace!("Reached the shadow/allocator region - skipping"); + } else { + log::trace!("Unpoisoning: {:#x}:{:#x}", address, address + size); + self.map_shadow_for_region(start, end, true); + } + } + address += size; + size = 0; + } + } + #[cfg(not(target_vendor = "apple"))] pub fn unpoison_all_existing_memory(&mut self) { RangeDetails::enumerate_with_prot( PageProtection::Read, &mut |range: &RangeDetails| -> bool { let start = range.memory_range().base_address().0 as usize; let end = start + range.memory_range().size(); - if self.is_managed(start as *mut c_void) { - log::trace!("Not unpoisoning: {:#x}-{:#x}, is_managed", start, end); + if self.shadow_offset <= start && end <= self.current_mapping_addr { + log::trace!("Reached the shadow/allocator region - skipping"); } else { log::trace!("Unpoisoning: {:#x}-{:#x}", start, end); self.map_shadow_for_region(start, end, true); diff --git a/libafl_frida/src/asan/asan_rt.rs b/libafl_frida/src/asan/asan_rt.rs index 3ef4bc8cb4..aff797862f 100644 --- a/libafl_frida/src/asan/asan_rt.rs +++ b/libafl_frida/src/asan/asan_rt.rs @@ -60,7 +60,7 @@ extern "C" { fn __register_frame(begin: *mut c_void); } -#[cfg(not(target_os = "ios"))] +#[cfg(not(target_vendor = "apple"))] extern "C" { fn tls_ptr() -> *const c_void; } @@ -186,7 +186,6 @@ impl FridaRuntime for AsanRuntime { self.register_hooks(gum); self.generate_instrumentation_blobs(); self.unpoison_all_existing_memory(); - self.register_thread(); } @@ -320,7 +319,7 @@ impl AsanRuntime { /// Register the current thread with the runtime, implementing shadow memory for its stack and /// tls mappings. #[allow(clippy::unused_self)] - #[cfg(not(target_os = "ios"))] + #[cfg(not(target_vendor = "apple"))] pub fn register_thread(&mut self) { let (stack_start, stack_end) = Self::current_stack(); let (tls_start, tls_end) = Self::current_tls(); @@ -337,7 +336,7 @@ impl AsanRuntime { /// Register the current thread with the runtime, implementing shadow memory for its stack mapping. #[allow(clippy::unused_self)] - #[cfg(target_os = "ios")] + #[cfg(target_vendor = "apple")] pub fn register_thread(&mut self) { let (stack_start, stack_end) = Self::current_stack(); self.allocator @@ -379,13 +378,17 @@ impl AsanRuntime { fn range_for_address(address: usize) -> (usize, usize) { let mut start = 0; let mut end = 0; - RangeDetails::enumerate_with_prot(PageProtection::NoAccess, &mut |range: &RangeDetails| { + + RangeDetails::enumerate_with_prot(PageProtection::Read, &mut |range: &RangeDetails| { let range_start = range.memory_range().base_address().0 as usize; let range_end = range_start + range.memory_range().size(); if range_start <= address && range_end >= address { start = range_start; end = range_end; - // I want to stop iteration here + return false; + } + if address < start { + //if the address is less than the start then we cannot find it return false; } true @@ -410,51 +413,21 @@ impl AsanRuntime { let stack_address = addr_of_mut!(stack_var) as usize; // let range_details = RangeDetails::with_address(stack_address as u64).unwrap(); // Write something to (hopefully) make sure the val isn't optimized out + unsafe { write_volatile(&mut stack_var, 0xfadbeef); } - let mut range = None; - for area in mmap_rs::MemoryAreas::open(None).unwrap() { - let area_ref = area.as_ref().unwrap(); - if area_ref.start() <= stack_address && stack_address <= area_ref.end() { - range = Some((area_ref.end() - 1024 * 1024, area_ref.end())); - break; - } - } - if let Some((start, end)) = range { - // #[cfg(unix)] - // { - // let max_start = end - Self::max_stack_size(); - // - // let flags = ANONYMOUS_FLAG | MapFlags::MAP_FIXED | MapFlags::MAP_PRIVATE; - // #[cfg(not(target_vendor = "apple"))] - // let flags = flags | MapFlags::MAP_STACK; - // - // if start != max_start { - // let mapping = unsafe { - // mmap( - // NonZeroUsize::new(max_start), - // NonZeroUsize::new(start - max_start).unwrap(), - // ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, - // flags, - // -1, - // 0, - // ) - // }; - // assert!(mapping.unwrap() as usize == max_start); - // } - // (max_start, end) - // } - // #[cfg(windows)] - (start, end) - } else { - panic!("Couldn't find stack mapping!"); - } + + let range = Self::range_for_address(stack_address); + + assert_ne!(range.0, 0, "Couldn't find stack mapping!"); + + (range.1 - 1024 * 1024, range.1) } /// Determine the tls start, end for the currently running thread #[must_use] - #[cfg(not(target_os = "ios"))] + #[cfg(not(target_vendor = "apple"))] fn current_tls() -> (usize, usize) { let tls_address = unsafe { tls_ptr() } as usize; @@ -513,22 +486,18 @@ impl AsanRuntime { let this = &mut *(invocation.replacement_data().unwrap().0 as *mut AsanRuntime); //is this necessary? The stalked return address will always be the real return address // let real_address = this.real_address_for_stalked(invocation.return_addr()); - let original = [<$name:snake:upper _PTR>].get().unwrap(); - if this.hooks_enabled { - let previous_hook_state = this.hooks_enabled; - this.hooks_enabled = false; - let ret = this.[](*original, $($param),*); - this.hooks_enabled = previous_hook_state; - ret - } else { + let original = [<$name:snake:upper _PTR>].get().unwrap(); - let previous_hook_state = this.hooks_enabled; - this.hooks_enabled = false; - let ret = (original)($($param),*); - this.hooks_enabled = previous_hook_state; - ret - } + if !ASAN_IN_HOOK.get() && this.hooks_enabled { + ASAN_IN_HOOK.set(true); + let ret = this.[](*original, $($param),*); + ASAN_IN_HOOK.set(false); + ret + } else { + let ret = (original)($($param),*); + ret } + } let self_ptr = core::ptr::from_ref(self) as usize; let _ = interceptor.replace( @@ -600,8 +569,9 @@ impl AsanRuntime { let mut invocation = Interceptor::current_invocation(); let this = &mut *(invocation.replacement_data().unwrap().0 as *mut AsanRuntime); let original = [<$name:snake:upper _PTR>].get().unwrap(); - - if !ASAN_IN_HOOK.get() && this.hooks_enabled && this.[]($($param),*){ + //don't check if hooks are enabled as there are certain cases where we want to run the hook even if we are out of the program + //For example, sometimes libafl will allocate certain things during the run and free them after the run. This results in a bug where a buffer will come from libafl-frida alloc and be freed in the normal allocator. + if !ASAN_IN_HOOK.get() && this.[]($($param),*){ ASAN_IN_HOOK.set(true); let ret = this.[](*original, $($param),*); ASAN_IN_HOOK.set(false); @@ -639,8 +609,9 @@ impl AsanRuntime { let mut invocation = Interceptor::current_invocation(); let this = &mut *(invocation.replacement_data().unwrap().0 as *mut AsanRuntime); let original = [<$lib_ident:snake:upper _ $name:snake:upper _PTR>].get().unwrap(); - - if !ASAN_IN_HOOK.get() && this.hooks_enabled && this.[]($($param),*){ + //don't check if hooks are enabled as there are certain cases where we want to run the hook even if we are out of the program + //For example, sometimes libafl will allocate certain things during the run and free them after the run. This results in a bug where a buffer will come from libafl-frida alloc and be freed in the normal allocator. + if !ASAN_IN_HOOK.get() && this.[]($($param),*){ ASAN_IN_HOOK.set(true); let ret = this.[](*original, $($param),*); ASAN_IN_HOOK.set(false); @@ -1760,14 +1731,14 @@ impl AsanRuntime { macro_rules! shadow_check { ($ops:ident, $width:expr) => {dynasm!($ops ; .arch aarch64 -// ; brk #0xe + //; brk #0xe ; stp x2, x3, [sp, #-0x10]! ; mov x1, xzr // ; add x1, xzr, x1, lsl #shadow_bit ; add x1, x1, x0, lsr #3 ; ubfx x1, x1, #0, #(shadow_bit + 1) ; mov x2, #1 - ; add x1, x1, x2, lsl #shadow_bit + ; add x1, x1, x2, lsl #shadow_bit //x1 contains the offset of the shadow byte ; ldr w1, [x1, #0] //w1 contains our shadow check ; and x0, x0, #7 //x0 is the offset for unaligned accesses ; rev32 x1, x1 diff --git a/libafl_frida/test_harness.cpp b/libafl_frida/test_harness.cpp index a861cd479f..0344727942 100644 --- a/libafl_frida/test_harness.cpp +++ b/libafl_frida/test_harness.cpp @@ -4,7 +4,8 @@ #ifdef _MSC_VER #include - + #include + #include BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { (void)hModule; @@ -37,9 +38,6 @@ EXTERN int heap_uaf_write(const uint8_t *_data, size_t _size) { return 0; } -#include -#include - static volatile bool stop = false; EXTERN int heap_oob_read(const uint8_t *_data, size_t _size) {