Skip to content

Commit

Permalink
Add bindings for memory access monitor
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
XiangruLiu0 authored Oct 14, 2023
1 parent aadfdeb commit c2935c0
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 2 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]
Expand All @@ -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",
]
2 changes: 1 addition & 1 deletion examples/gum/fast_interceptor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
12 changes: 12 additions & 0 deletions examples/gum/memory_access_monitor/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "monitor-memory-access"
version = "0.1.0"
authors = ["Liu Xiangru <[email protected]>"]
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",
] }
37 changes: 37 additions & 0 deletions examples/gum/memory_access_monitor/src/main.rs
Original file line number Diff line number Diff line change
@@ -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");
}
}
1 change: 1 addition & 0 deletions frida-gum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
3 changes: 3 additions & 0 deletions frida-gum/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ impl fmt::Debug for Error {
write!(fmt, "{self:}")
}
}

#[allow(unused)]
pub type GumResult<T> = Result<T, Error>;
14 changes: 13 additions & 1 deletion frida-gum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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::*;

Expand Down
169 changes: 169 additions & 0 deletions frida-gum/src/memory_access_monitor.rs
Original file line number Diff line number Diff line change
@@ -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<F> CallbackFn for F where F: Fn(&mut MemoryAccessMonitor, &MemoryAccessDetails) {}

pub struct CallbackWrapper<F>
where
F: CallbackFn,
{
callback: F,
}

extern "C" fn c_callback<F>(
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<F> = 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<F>(
_gum: &crate::Gum,
ranges: Vec<MemoryRange>,
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::<F>),
&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) };
}
}

0 comments on commit c2935c0

Please sign in to comment.