From c2935c03c673a532a7df7cc0035498452d65b15b Mon Sep 17 00:00:00 2001 From: Liu Xiangru Date: Sun, 15 Oct 2023 04:39:37 +0800 Subject: [PATCH] Add bindings for memory access monitor * fix: fix compilation error caused by thread_id * fix: only disable those uncompatible for backtrace * feat: std when backtrace enabled * fix: CpuContext and backtrace * fix: clippy warnings * WIP: bindings for memory access monitor * feat: bindings for memory access monitor * refactor: fix clippy errors * refactor: fix fmt error * fix: bug when filling ranges * fix: delete frida-gum-sys/.vscode/settings.json * fix: undo changes on event_sink.h * docs: memory_access_monitor.rs * fix: fix cargo fmt error --- Cargo.toml | 2 + examples/gum/fast_interceptor/Cargo.toml | 2 +- examples/gum/memory_access_monitor/Cargo.toml | 12 ++ .../gum/memory_access_monitor/src/main.rs | 37 ++++ frida-gum/Cargo.toml | 1 + frida-gum/src/error.rs | 3 + frida-gum/src/lib.rs | 14 +- frida-gum/src/memory_access_monitor.rs | 169 ++++++++++++++++++ 8 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 examples/gum/memory_access_monitor/Cargo.toml create mode 100644 examples/gum/memory_access_monitor/src/main.rs create mode 100644 frida-gum/src/memory_access_monitor.rs diff --git a/Cargo.toml b/Cargo.toml index 49e0fd3..151fd0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "examples/gum/debug_symbol", "examples/gum/fast_interceptor", "examples/gum/linux_no_std", + "examples/gum/memory_access_monitor", "examples/core/hello", "examples/core/console_log", ] @@ -30,6 +31,7 @@ default-members = [ "examples/gum/hook_instruction", "examples/gum/debug_symbol", "examples/gum/fast_interceptor", + "examples/gum/memory_access_monitor", "examples/core/hello", "examples/core/console_log", ] diff --git a/examples/gum/fast_interceptor/Cargo.toml b/examples/gum/fast_interceptor/Cargo.toml index 567d320..4ad6107 100644 --- a/examples/gum/fast_interceptor/Cargo.toml +++ b/examples/gum/fast_interceptor/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" license = "MIT" [dependencies] -frida-gum = { path = "../../../frida-gum"} +frida-gum = { path = "../../../frida-gum" } lazy_static = "1.4" ctor = "0.1" libc = "0.2.126" diff --git a/examples/gum/memory_access_monitor/Cargo.toml b/examples/gum/memory_access_monitor/Cargo.toml new file mode 100644 index 0000000..38a66e1 --- /dev/null +++ b/examples/gum/memory_access_monitor/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "monitor-memory-access" +version = "0.1.0" +authors = ["Liu Xiangru "] +edition = "2021" +license = "MIT" +description = "Example of monitoring memory access using Frida's MemoryAccessMonitor API" + +[dependencies] +frida-gum = { path = "../../../frida-gum", features = [ + "memory-access-monitor", +] } diff --git a/examples/gum/memory_access_monitor/src/main.rs b/examples/gum/memory_access_monitor/src/main.rs new file mode 100644 index 0000000..4f43746 --- /dev/null +++ b/examples/gum/memory_access_monitor/src/main.rs @@ -0,0 +1,37 @@ +use frida_gum::{MemoryAccessMonitor, MemoryRange, NativePointer}; +use std::sync::atomic::AtomicUsize; + +static HIT: AtomicUsize = AtomicUsize::new(0); +const BLK_SIZE: usize = 0x3; + +fn main() { + let block = + unsafe { std::alloc::alloc(std::alloc::Layout::from_size_align_unchecked(BLK_SIZE, 1)) }; + let range = MemoryRange::new(NativePointer(block as *mut _), BLK_SIZE); + let gum = unsafe { frida_gum::Gum::obtain() }; + let mam = MemoryAccessMonitor::new( + &gum, + vec![range], + frida_gum::PageProtection::Write, + true, + |_, details| { + println!( + "[monitor callback] hit: {}, details: {}", + HIT.fetch_add(1, std::sync::atomic::Ordering::SeqCst), + details + ); + }, + ); + if let Ok(()) = mam.enable() { + unsafe { + for i in 0..BLK_SIZE { + println!("writing at block + {:#x}", i); + let ptr = block.add(i); + std::ptr::write(ptr, 0); + } + } + mam.disable(); + } else { + println!("failed to enable memory access monitor"); + } +} diff --git a/frida-gum/Cargo.toml b/frida-gum/Cargo.toml index 28d347b..2704680 100644 --- a/frida-gum/Cargo.toml +++ b/frida-gum/Cargo.toml @@ -12,6 +12,7 @@ auto-download = ["frida-gum-sys/auto-download"] backtrace = ["libc"] event-sink = ["frida-gum-sys/event-sink"] invocation-listener = ["frida-gum-sys/invocation-listener"] +memory-access-monitor = [] module-names = [] stalker-observer = ["frida-gum-sys/stalker-observer"] stalker-params = ["frida-gum-sys/stalker-params"] diff --git a/frida-gum/src/error.rs b/frida-gum/src/error.rs index 566b1c7..e331289 100644 --- a/frida-gum/src/error.rs +++ b/frida-gum/src/error.rs @@ -43,3 +43,6 @@ impl fmt::Debug for Error { write!(fmt, "{self:}") } } + +#[allow(unused)] +pub type GumResult = Result; diff --git a/frida-gum/src/lib.rs b/frida-gum/src/lib.rs index b77dec5..b5aa11b 100644 --- a/frida-gum/src/lib.rs +++ b/frida-gum/src/lib.rs @@ -45,7 +45,14 @@ //! } //! ``` -#![cfg_attr(not(any(feature = "module-names", feature = "backtrace")), no_std)] +#![cfg_attr( + not(any( + feature = "module-names", + feature = "backtrace", + feature = "memory-access-monitor" + )), + no_std +)] #![cfg_attr(doc_cfg, feature(doc_cfg))] #![deny(warnings)] #![allow(clippy::needless_doctest_main)] @@ -86,6 +93,11 @@ pub use error::Error; mod cpu_context; pub use cpu_context::*; +#[cfg(feature = "memory-access-monitor")] +mod memory_access_monitor; +#[cfg(feature = "memory-access-monitor")] +pub use memory_access_monitor::*; + mod memory_range; pub use memory_range::*; diff --git a/frida-gum/src/memory_access_monitor.rs b/frida-gum/src/memory_access_monitor.rs new file mode 100644 index 0000000..43561c8 --- /dev/null +++ b/frida-gum/src/memory_access_monitor.rs @@ -0,0 +1,169 @@ +use super::{memory_range::MemoryRange, range_details::PageProtection}; +use crate::{error::GumResult, NativePointer}; +use core::{ffi::c_void, ptr::null_mut}; +use frida_gum_sys::{ + _GumMemoryRange, false_, gum_memory_access_monitor_disable, gum_memory_access_monitor_enable, + gum_memory_access_monitor_new, GError, GumMemoryAccessDetails, GumMemoryAccessMonitor, + GumPageProtection, _GumMemoryOperation_GUM_MEMOP_EXECUTE, + _GumMemoryOperation_GUM_MEMOP_INVALID, _GumMemoryOperation_GUM_MEMOP_READ, + _GumMemoryOperation_GUM_MEMOP_WRITE, +}; + +pub trait CallbackFn: Fn(&mut MemoryAccessMonitor, &MemoryAccessDetails) {} + +impl CallbackFn for F where F: Fn(&mut MemoryAccessMonitor, &MemoryAccessDetails) {} + +pub struct CallbackWrapper +where + F: CallbackFn, +{ + callback: F, +} + +extern "C" fn c_callback( + monitor: *mut GumMemoryAccessMonitor, + details: *const GumMemoryAccessDetails, + user_data: *mut c_void, +) where + F: CallbackFn, +{ + let details = unsafe { &*(details as *const GumMemoryAccessDetails) }; + let details = MemoryAccessDetails::from(details); + let mut monitor = MemoryAccessMonitor { monitor }; + let cw: &mut CallbackWrapper = unsafe { &mut *(user_data as *mut _) }; + (cw.callback)(&mut monitor, &details); +} + +#[derive(FromPrimitive)] +#[repr(u32)] +pub enum MemoryOperation { + Invalid = _GumMemoryOperation_GUM_MEMOP_INVALID as _, + Read = _GumMemoryOperation_GUM_MEMOP_READ as _, + Write = _GumMemoryOperation_GUM_MEMOP_WRITE as _, + Execute = _GumMemoryOperation_GUM_MEMOP_EXECUTE as _, +} + +/// Details about a memory access +/// +/// # Fields +/// +/// * `operation` - The kind of operation that triggered the access +/// * `from` - Address of instruction performing the access as a [`NativePointer`] +/// * `address` - Address being accessed as a [`NativePointer`] +/// * `range_index` - Index of the accessed range in the ranges provided to +/// * `page_index` - Index of the accessed memory page inside the specified range +/// * `pages_completed` - Overall number of pages which have been accessed so far (and are no longer being monitored) +/// * `pages_total` - Overall number of pages that were initially monitored +pub struct MemoryAccessDetails { + pub operation: MemoryOperation, + pub from: NativePointer, + pub address: NativePointer, + pub range_index: usize, + pub page_index: usize, + pub pages_completed: usize, + pub pages_total: usize, +} + +impl std::fmt::Display for MemoryAccessDetails { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let operation = match self.operation { + MemoryOperation::Invalid => "invalid", + MemoryOperation::Read => "read", + MemoryOperation::Write => "write", + MemoryOperation::Execute => "execute", + }; + write!( + f, + "MemoryAccessDetails {{ operation: {}, from: {:#x}, address: {:#x}, range_index: {}, page_index: {}, pages_completed: {}, pages_total: {} }}", + operation, + self.from.0 as usize, + self.address.0 as usize, + self.range_index, + self.page_index, + self.pages_completed, + self.pages_total, + ) + } +} + +impl From<&GumMemoryAccessDetails> for MemoryAccessDetails { + fn from(details: &GumMemoryAccessDetails) -> Self { + Self { + operation: num::FromPrimitive::from_u32(details.operation).unwrap(), + from: NativePointer(details.from), + address: NativePointer(details.address), + range_index: details.range_index as _, + page_index: details.page_index as _, + pages_completed: details.pages_completed as _, + pages_total: details.pages_total as _, + } + } +} + +pub struct MemoryAccessMonitor { + monitor: *mut GumMemoryAccessMonitor, +} + +impl MemoryAccessMonitor { + /// Create a new [`MemoryAccessMonitor`] + /// + /// # Arguments + /// + /// * `ranges` - The memory ranges to monitor + /// * `mask` - The page protection mask to monitor + /// * `auto_reset` - Whether to automatically reset the monitor after each access + /// * `callback` - The callback to call when an access occurs + pub fn new( + _gum: &crate::Gum, + ranges: Vec, + mask: PageProtection, + auto_reset: bool, + callback: F, + ) -> Self + where + F: CallbackFn, + { + let mut cw = CallbackWrapper { callback }; + let monitor = unsafe { + let size = std::mem::size_of::<_GumMemoryRange>() * ranges.len(); + let block = std::alloc::alloc(std::alloc::Layout::from_size_align_unchecked( + size, + std::mem::align_of::<_GumMemoryRange>(), + )) as *mut _GumMemoryRange; + // copy ranges into the buffer + for (i, range) in ranges.iter().enumerate() { + std::ptr::write(block.add(i), range.memory_range); + } + let num_ranges = ranges.len() as u32; + + gum_memory_access_monitor_new( + block, + num_ranges, + mask as GumPageProtection, + auto_reset as _, + Some(c_callback::), + &mut cw as *mut _ as *mut c_void, + None, + ) + }; + Self { monitor } + } + + /// Enable the monitor + pub fn enable(&self) -> GumResult<()> { + let mut error: *mut GError = null_mut(); + if unsafe { gum_memory_access_monitor_enable(self.monitor, &mut error) } == false_ as _ { + Err(crate::error::Error::MemoryAccessError) + } else { + Ok(()) + } + } + + /// Disable the monitor + pub fn disable(&self) { + if self.monitor.is_null() { + return; + } + unsafe { gum_memory_access_monitor_disable(self.monitor) }; + } +}