diff --git a/Cargo.toml b/Cargo.toml index 49e0fd3..24faf57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,9 @@ members = [ "examples/gum/debug_symbol", "examples/gum/fast_interceptor", "examples/gum/linux_no_std", + "examples/gum/memory_access_monitor", "examples/core/hello", + "examples/core/usb_device", "examples/core/console_log", ] # We miss our linux_no_std example from the default members since `cargo check` @@ -30,6 +32,8 @@ default-members = [ "examples/gum/hook_instruction", "examples/gum/debug_symbol", "examples/gum/fast_interceptor", + "examples/gum/memory_access_monitor", "examples/core/hello", + "examples/core/usb_device", "examples/core/console_log", ] diff --git a/FRIDA_VERSION b/FRIDA_VERSION index 4ecf255..d74be34 100644 --- a/FRIDA_VERSION +++ b/FRIDA_VERSION @@ -1 +1 @@ -16.0.19 +16.1.10 diff --git a/examples/core/usb_device/Cargo.toml b/examples/core/usb_device/Cargo.toml new file mode 100644 index 0000000..a74b7de --- /dev/null +++ b/examples/core/usb_device/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "usb_device" +version = "0.1.0" +authors = ["Andras Marczell "] +edition = "2018" +license = "wxWindows" +publish = false + +[dependencies] +frida = { path = "../../../frida" } +frida-sys = { path = "../../../frida-sys" } +lazy_static = "1.4" diff --git a/examples/core/usb_device/src/main.rs b/examples/core/usb_device/src/main.rs new file mode 100644 index 0000000..c64d532 --- /dev/null +++ b/examples/core/usb_device/src/main.rs @@ -0,0 +1,21 @@ +use frida::DeviceType; + +fn main() { + let frida = unsafe { frida::Frida::obtain() }; + let device_manager = frida::DeviceManager::obtain(&frida); + + // get the first usb device (assuming there is one attached) + let device = device_manager.get_device_by_type(DeviceType::USB).unwrap(); + assert_eq!(device.get_type(), DeviceType::USB); + println!( + "found {} with type: {}", + device.get_name(), + device.get_type() + ); + + // get the device id and use it to obtain a the device by the id + let device_id = device.get_id(); + let device = device_manager.get_device_by_id(device_id).unwrap(); + assert_eq!(device.get_id(), device_id); + println!("found {} with id: {}", device.get_name(), device.get_id()); +} 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-sys/Cargo.toml b/frida-gum-sys/Cargo.toml index 8fb8f46..868407f 100644 --- a/frida-gum-sys/Cargo.toml +++ b/frida-gum-sys/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "frida-gum-sys" -version = "0.8.1" -authors = ["Keegan Saunders "] +version = "0.8.3" +authors = ["Keegan Saunders ", "Shmarya Rubenstein "] edition = "2018" license = "wxWindows" repository = "https://github.com/frida/frida-rust" @@ -15,7 +15,7 @@ stalker-observer = ["cc"] stalker-params = ["cc"] [build-dependencies] -bindgen = "0.63" +bindgen = "0.69.1" cc = { version = "1.0", optional = true } frida-build = { path = "../frida-build", version = "0.2.1", optional = true } diff --git a/frida-gum-sys/FRIDA_VERSION b/frida-gum-sys/FRIDA_VERSION index 4ecf255..d74be34 100644 --- a/frida-gum-sys/FRIDA_VERSION +++ b/frida-gum-sys/FRIDA_VERSION @@ -1 +1 @@ -16.0.19 +16.1.10 diff --git a/frida-gum-sys/build.rs b/frida-gum-sys/build.rs index eb3fedd..b544310 100644 --- a/frida-gum-sys/build.rs +++ b/frida-gum-sys/build.rs @@ -77,7 +77,11 @@ fn main() { .header("probe_listener.h") .header("stalker_observer.h") .header("stalker_params.h") - .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + .blocklist_type("GumChainedPtr64Rebase") + .blocklist_type("GumChainedPtrArm64eRebase") + .blocklist_type("_GumChainedPtr64Rebase") + .blocklist_type("_GumChainedPtrArm64eRebase") .generate_comments(false) .layout_tests(false) .generate() diff --git a/frida-gum/Cargo.toml b/frida-gum/Cargo.toml index 28d347b..d4f7772 100644 --- a/frida-gum/Cargo.toml +++ b/frida-gum/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "frida-gum" -version = "0.13.2" -authors = ["Keegan Saunders "] +version = "0.13.4" +authors = ["Keegan Saunders ", "Shmarya Rubenstein "] edition = "2018" license = "wxWindows" repository = "https://github.com/frida/frida-rust" @@ -12,13 +12,14 @@ 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"] [dependencies] cstr_core = { version = "0.2.6", default-features = false, features = ["alloc"] } -frida-gum-sys = { path = "../frida-gum-sys", version = "0.8.1" } +frida-gum-sys = { path = "../frida-gum-sys", version = "0.8.3" } libc = { version = "0.2.93", default-features = false, optional = true } num = { version = "0.3.1", default-features = false } num-derive = { version = "0.3.3", default-features = false } diff --git a/frida-gum/src/backtracer.rs b/frida-gum/src/backtracer.rs index acda2f6..6a3f0ff 100644 --- a/frida-gum/src/backtracer.rs +++ b/frida-gum/src/backtracer.rs @@ -12,6 +12,7 @@ use {core::mem::MaybeUninit, frida_gum_sys as gum_sys}; // The following function is not exposed through the `frida-gum.h` header, so we don't have an // auto-generated binding for it. This may change in a future version. +#[cfg(not(target_os = "windows"))] extern "C" { // On some platforms `ucontext` contains a u128 which does not have a defined ABI. In this case, // we disable the error as we assume the behaviour is correct (all other platforms are unaffected). @@ -83,6 +84,7 @@ impl Backtracer { /// Generate an accurate backtrace as a list of return addresses for the supplied signal /// context. + #[cfg(not(target_os = "windows"))] pub fn accurate_with_signal_context(context: &libc::ucontext_t) -> Vec { let mut cpu_context = MaybeUninit::::uninit(); @@ -94,6 +96,7 @@ impl Backtracer { /// Generate a fuzzy backtrace as a list of return addresses for the supplied signal /// context. + #[cfg(not(target_os = "windows"))] pub fn fuzzy_with_signal_context(context: &libc::ucontext_t) -> Vec { let mut cpu_context = MaybeUninit::::uninit(); diff --git a/frida-gum/src/cpu_context.rs b/frida-gum/src/cpu_context.rs index 08d1b60..7db1073 100644 --- a/frida-gum/src/cpu_context.rs +++ b/frida-gum/src/cpu_context.rs @@ -94,14 +94,14 @@ impl<'a> CpuContext<'a> { unsafe { (*self.cpu_context).x[index] = value }; } - #[cfg(all(feature = "backtrace", not(target_os = "windows")))] + #[cfg(feature = "backtrace")] #[cfg_attr(doc_cfg, doc(cfg(feature = "backtrace")))] /// Get an accurate backtrace from this CPU context. pub fn backtrace_accurate(&self) -> Vec { crate::Backtracer::accurate_with_context(unsafe { &*self.cpu_context }) } - #[cfg(all(feature = "backtrace", not(target_os = "windows")))] + #[cfg(feature = "backtrace")] #[cfg_attr(doc_cfg, doc(cfg(feature = "backtrace")))] /// Get a fuzzy backtrace from this CPU context. pub fn backtrace_fuzzy(&self) -> Vec { 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 b780560..b5aa11b 100644 --- a/frida-gum/src/lib.rs +++ b/frida-gum/src/lib.rs @@ -45,7 +45,14 @@ //! } //! ``` -#![cfg_attr(not(feature = "module-names"), 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)] @@ -62,6 +69,7 @@ extern crate num_derive; use core::{ convert::TryFrom, ffi::{c_char, c_void, CStr}, + fmt::{Debug, Display, Formatter, LowerHex, UpperHex}, }; #[cfg(not(feature = "module-names"))] @@ -85,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::*; @@ -94,10 +107,10 @@ pub use range_details::*; mod debug_symbol; pub use debug_symbol::*; -#[cfg(all(feature = "backtrace", not(target_os = "windows")))] +#[cfg(feature = "backtrace")] #[cfg_attr(doc_cfg, doc(cfg(feature = "backtrace")))] mod backtracer; -#[cfg(all(feature = "backtrace", not(target_os = "windows")))] +#[cfg(feature = "backtrace")] #[cfg_attr(doc_cfg, doc(cfg(feature = "backtrace")))] pub use backtracer::*; @@ -123,7 +136,7 @@ impl Drop for Gum { } } -#[derive(Copy, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] #[repr(transparent)] pub struct NativePointer(pub *mut c_void); @@ -168,3 +181,21 @@ impl AsRef for NativePointer { self } } + +impl LowerHex for NativePointer { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + LowerHex::fmt(&(self.0 as usize), f) + } +} + +impl UpperHex for NativePointer { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + UpperHex::fmt(&(self.0 as usize), f) + } +} + +impl Display for NativePointer { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + Display::fmt(&(self.0 as usize), f) + } +} 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) }; + } +} diff --git a/frida-gum/src/memory_range.rs b/frida-gum/src/memory_range.rs index 69b7f19..4408613 100644 --- a/frida-gum/src/memory_range.rs +++ b/frida-gum/src/memory_range.rs @@ -6,6 +6,11 @@ use {crate::NativePointer, core::ffi::c_void, cstr_core::CString, frida_gum_sys as gum_sys}; +use core::{ + fmt::{Debug, Display, LowerHex, UpperHex}, + ops::Range, +}; + #[cfg(not(feature = "module-names"))] use alloc::vec::Vec; @@ -105,3 +110,49 @@ impl MemoryRange { results } } + +impl LowerHex for MemoryRange { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + LowerHex::fmt(&self.base_address(), f)?; + write!(f, "..")?; + LowerHex::fmt(&(self.base_address().0 as usize + self.size()), f) + } +} + +impl UpperHex for MemoryRange { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + UpperHex::fmt(&self.base_address(), f)?; + write!(f, "..")?; + UpperHex::fmt(&(self.base_address().0 as usize + self.size()), f) + } +} + +impl Display for MemoryRange { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "{}..{}", + self.base_address(), + self.base_address().0 as usize + self.size() + ) + } +} + +impl Debug for MemoryRange { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let range: Range = self.into(); + f.debug_tuple("MemoryRange").field(&range).finish() + } +} + +impl From<&MemoryRange> for Range { + fn from(value: &MemoryRange) -> Self { + value.base_address().0 as usize..(value.base_address().0 as usize + value.size()) + } +} + +impl From for Range { + fn from(value: MemoryRange) -> Self { + (&value).into() + } +} diff --git a/frida-gum/src/stalker/event_sink.rs b/frida-gum/src/stalker/event_sink.rs index 0c790f0..49326e3 100644 --- a/frida-gum/src/stalker/event_sink.rs +++ b/frida-gum/src/stalker/event_sink.rs @@ -27,6 +27,7 @@ pub enum EventMask { } #[cfg_attr(doc_cfg, doc(cfg(feature = "event-sink")))] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum Event { Call { location: NativePointer, @@ -136,10 +137,10 @@ unsafe extern "C" fn call_query_mask( } pub(crate) fn event_sink_transform( - mut event_sink: &S, + event_sink: &mut S, ) -> *mut frida_gum_sys::GumEventSink { let rust = frida_gum_sys::RustEventSinkVTable { - user_data: &mut event_sink as *mut _ as *mut c_void, + user_data: event_sink as *mut _ as *mut c_void, query_mask: Some(call_query_mask::), start: Some(call_start::), process: Some(call_process::), diff --git a/frida-sys/Cargo.toml b/frida-sys/Cargo.toml index 59cc970..1e8a677 100644 --- a/frida-sys/Cargo.toml +++ b/frida-sys/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "frida-sys" -version = "0.4.2" -authors = ["Keegan Saunders "] +version = "0.4.4" +authors = ["Keegan Saunders ", "Shmarya Rubenstein "] edition = "2018" license = "wxWindows" repository = "https://github.com/frida/frida-rust" @@ -11,7 +11,7 @@ description = "Rust generated bindings for Frida" auto-download = ["frida-build"] [build-dependencies] -bindgen = "0.63" +bindgen = "0.69.1" frida-build = { path = "../frida-build", version = "0.2.1", optional = true } [badges] diff --git a/frida-sys/FRIDA_VERSION b/frida-sys/FRIDA_VERSION index 4ecf255..d74be34 100644 --- a/frida-sys/FRIDA_VERSION +++ b/frida-sys/FRIDA_VERSION @@ -1 +1 @@ -16.0.19 +16.1.10 diff --git a/frida-sys/build.rs b/frida-sys/build.rs index bfd6bed..2a835ca 100644 --- a/frida-sys/build.rs +++ b/frida-sys/build.rs @@ -54,7 +54,7 @@ fn main() { let bindings = bindings .header_contents("core.h", "#include \"frida-core.h\"") - .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .generate_comments(false) .layout_tests(false) .generate() diff --git a/frida-sys/src/lib.rs b/frida-sys/src/lib.rs index cd096de..5473465 100644 --- a/frida-sys/src/lib.rs +++ b/frida-sys/src/lib.rs @@ -19,5 +19,17 @@ pub use bindings::*; #[cfg(not(any(target_vendor = "apple", target_os = "windows")))] pub use crate::{ _frida_g_bytes_new as g_bytes_new, _frida_g_bytes_unref as g_bytes_unref, - _frida_g_clear_object as g_clear_object, _frida_g_signal_connect_data as g_signal_connect_data, + _frida_g_clear_object as g_clear_object, + _frida_g_hash_table_iter_init as g_hash_table_iter_init, + _frida_g_hash_table_iter_next as g_hash_table_iter_next, + _frida_g_hash_table_size as g_hash_table_size, _frida_g_idle_source_new as g_idle_source_new, + _frida_g_signal_connect_data as g_signal_connect_data, + _frida_g_source_attach as g_source_attach, + _frida_g_source_set_callback as g_source_set_callback, _frida_g_source_unref as g_source_unref, + _frida_g_variant_get_boolean as g_variant_get_boolean, + _frida_g_variant_get_int64 as g_variant_get_int64, + _frida_g_variant_get_string as g_variant_get_string, + _frida_g_variant_get_type_string as g_variant_get_type_string, + _frida_g_variant_iter_init as g_variant_iter_init, + _frida_g_variant_iter_loop as g_variant_iter_loop, }; diff --git a/frida/Cargo.toml b/frida/Cargo.toml index 3637158..c0d780c 100644 --- a/frida/Cargo.toml +++ b/frida/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "frida" -version = "0.4.1" -authors = ["Keegan Saunders "] +version = "0.4.4" +authors = ["Keegan Saunders ", "Shmarya Rubenstein "] edition = "2018" license = "wxWindows" repository = "https://github.com/frida/frida-rust" @@ -11,7 +11,7 @@ description = "Rust bindings for Frida" auto-download = ["frida-sys/auto-download"] [dependencies] -frida-sys = { path = "../frida-sys", version = "0.4.1" } +frida-sys = { path = "../frida-sys", version = "0.4.4" } thiserror = "1" [dev-dependencies] diff --git a/frida/src/device.rs b/frida/src/device.rs index 1533b85..a6385e3 100644 --- a/frida/src/device.rs +++ b/frida/src/device.rs @@ -5,12 +5,14 @@ */ use frida_sys::_FridaDevice; -use std::ffi::CStr; +use std::collections::HashMap; +use std::ffi::{CStr, CString}; use std::marker::PhantomData; use crate::process::Process; use crate::session::Session; -use crate::{Error, Result}; +use crate::variant::Variant; +use crate::{Error, Result, SpawnOptions}; /// Access to a Frida device. pub struct Device<'a> { @@ -39,6 +41,78 @@ impl<'a> Device<'a> { id.to_str().unwrap_or_default() } + /// Returns the device's type + /// + /// # Example + /// ``` + ///# use frida::DeviceType; + ///# let frida = unsafe { frida::Frida::obtain() }; + ///# let device_manager = frida::DeviceManager::obtain(&frida); + ///# let device = device_manager.enumerate_all_devices().into_iter().find(|device| device.get_id() == "local").unwrap(); + /// assert_eq!(device.get_type(), DeviceType::Local); + /// ``` + pub fn get_type(&self) -> DeviceType { + unsafe { frida_sys::frida_device_get_dtype(self.device_ptr).into() } + } + + /// Returns the device's system parameters + /// + /// # Example + /// ``` + ///# use std::collections::HashMap; + ///# let frida = unsafe { frida::Frida::obtain() }; + ///# let device_manager = frida::DeviceManager::obtain(&frida); + ///# let device = device_manager.enumerate_all_devices().into_iter().find(|device| device.get_id() == "local").unwrap(); + /// let params = device.query_system_parameters().unwrap(); + /// let os_version = params + /// .get("os") + /// .expect("No parameter \"os\" present") + /// .get_map() + /// .expect("Parameter \"os\" was not a mapping") + /// .get("version") + /// .expect("Parameter \"os\" did not contain a version field") + /// .get_string() + /// .expect("Version is not a string"); + /// ``` + pub fn query_system_parameters(&self) -> Result> { + let mut error: *mut frida_sys::GError = std::ptr::null_mut(); + + let ht = unsafe { + frida_sys::frida_device_query_system_parameters_sync( + self.device_ptr, + std::ptr::null_mut(), + &mut error, + ) + }; + + if !error.is_null() { + let message = unsafe { CString::from_raw((*error).message) } + .into_string() + .map_err(|_| Error::CStringFailed)?; + let code = unsafe { (*error).code }; + + return Err(Error::DeviceQuerySystemParametersFailed { code, message }); + } + + let mut iter: frida_sys::GHashTableIter = + unsafe { std::mem::MaybeUninit::zeroed().assume_init() }; + unsafe { frida_sys::g_hash_table_iter_init(&mut iter, ht) }; + let size = unsafe { frida_sys::g_hash_table_size(ht) }; + let mut map = HashMap::with_capacity(size as usize); + + let mut key = std::ptr::null_mut(); + let mut val = std::ptr::null_mut(); + while (unsafe { frida_sys::g_hash_table_iter_next(&mut iter, &mut key, &mut val) } + != frida_sys::FALSE as _) + { + let key = unsafe { CStr::from_ptr(key as _) }; + let val = unsafe { Variant::from_ptr(val as _) }; + map.insert(key.to_string_lossy().to_string(), val); + } + + Ok(map) + } + /// Returns if the device is lost or not. pub fn is_lost(&self) -> bool { unsafe { frida_sys::frida_device_is_lost(self.device_ptr) == 1 } @@ -98,6 +172,85 @@ impl<'a> Device<'a> { Err(Error::DeviceAttachError) } } + + /// Spawn a process on the device + /// + /// Returns the PID of the newly spawned process. + /// On spawn, the process will be halted, and [`resume`](Device::resume) will need to be + /// called to continue execution. + pub fn spawn>(&mut self, program: S, options: &SpawnOptions) -> Result { + let mut error: *mut frida_sys::GError = std::ptr::null_mut(); + let program = CString::new(program.as_ref()).unwrap(); + + let pid = unsafe { + frida_sys::frida_device_spawn_sync( + self.device_ptr, + program.as_ptr(), + options.options_ptr, + std::ptr::null_mut(), + &mut error, + ) + }; + + if !error.is_null() { + let message = unsafe { CString::from_raw((*error).message) } + .into_string() + .map_err(|_| Error::CStringFailed)?; + let code = unsafe { (*error).code }; + + return Err(Error::SpawnFailed { code, message }); + } + + Ok(pid) + } + + /// Resumes the process with given pid. + pub fn resume(&self, pid: u32) -> Result<()> { + let mut error: *mut frida_sys::GError = std::ptr::null_mut(); + unsafe { + frida_sys::frida_device_resume_sync( + self.device_ptr, + pid, + std::ptr::null_mut(), + &mut error, + ) + }; + + if !error.is_null() { + let message = unsafe { CString::from_raw((*error).message) } + .into_string() + .map_err(|_| Error::CStringFailed)?; + let code = unsafe { (*error).code }; + + return Err(Error::ResumeFailed { code, message }); + } + + Ok(()) + } + + /// Kill a process on the device + pub fn kill(&mut self, pid: u32) -> Result<()> { + let mut error: *mut frida_sys::GError = std::ptr::null_mut(); + unsafe { + frida_sys::frida_device_kill_sync( + self.device_ptr, + pid, + std::ptr::null_mut(), + &mut error, + ) + }; + + if !error.is_null() { + let message = unsafe { CString::from_raw((*error).message) } + .into_string() + .map_err(|_| Error::CStringFailed)?; + let code = unsafe { (*error).code }; + + return Err(Error::KillFailed { code, message }); + } + + Ok(()) + } } impl<'a> Drop for Device<'a> { @@ -105,3 +258,61 @@ impl<'a> Drop for Device<'a> { unsafe { frida_sys::frida_unref(self.device_ptr as _) } } } + +#[repr(u32)] +#[non_exhaustive] +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] +/// Frida device type. +/// +/// Represents different connection types +// On Windows, the constants are i32 instead of u32, so we need to cast accordingly. +pub enum DeviceType { + /// Local Frida device. + Local = frida_sys::FridaDeviceType_FRIDA_DEVICE_TYPE_LOCAL as _, + + /// Remote Frida device, connected via network + Remote = frida_sys::FridaDeviceType_FRIDA_DEVICE_TYPE_REMOTE as _, + + /// Device connected via USB + USB = frida_sys::FridaDeviceType_FRIDA_DEVICE_TYPE_USB as _, +} + +#[cfg(not(target_family = "windows"))] +impl From for DeviceType { + fn from(value: u32) -> Self { + match value { + frida_sys::FridaDeviceType_FRIDA_DEVICE_TYPE_LOCAL => Self::Local, + frida_sys::FridaDeviceType_FRIDA_DEVICE_TYPE_REMOTE => Self::Remote, + frida_sys::FridaDeviceType_FRIDA_DEVICE_TYPE_USB => Self::USB, + value => unreachable!("Invalid Device type {}", value), + } + } +} + +#[cfg(target_family = "windows")] +impl From for DeviceType { + fn from(value: i32) -> Self { + match value { + frida_sys::FridaDeviceType_FRIDA_DEVICE_TYPE_LOCAL => Self::Local, + frida_sys::FridaDeviceType_FRIDA_DEVICE_TYPE_REMOTE => Self::Remote, + frida_sys::FridaDeviceType_FRIDA_DEVICE_TYPE_USB => Self::USB, + value => unreachable!("Invalid Device type {}", value), + } + } +} + +impl From for frida_sys::FridaDeviceType { + fn from(value: DeviceType) -> Self { + match value { + DeviceType::Local => frida_sys::FridaDeviceType_FRIDA_DEVICE_TYPE_LOCAL, + DeviceType::Remote => frida_sys::FridaDeviceType_FRIDA_DEVICE_TYPE_REMOTE, + DeviceType::USB => frida_sys::FridaDeviceType_FRIDA_DEVICE_TYPE_USB, + } + } +} + +impl std::fmt::Display for DeviceType { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} diff --git a/frida/src/device_manager.rs b/frida/src/device_manager.rs index 426e4f2..75e6dab 100644 --- a/frida/src/device_manager.rs +++ b/frida/src/device_manager.rs @@ -5,10 +5,14 @@ */ use frida_sys::_FridaDeviceManager; +use std::ffi::CString; use std::marker::PhantomData; use crate::device::Device; +use crate::DeviceType; +use crate::Error; use crate::Frida; +use crate::Result; /// Platform-independent device manager abstraction access. pub struct DeviceManager<'a> { @@ -59,6 +63,59 @@ impl<'a> DeviceManager<'a> { unsafe { frida_sys::frida_unref(devices_ptr as _) } devices } + + /// Returns the device of the specified type. + pub fn get_device_by_type(&'a self, r#type: DeviceType) -> Result> { + let mut error: *mut frida_sys::GError = std::ptr::null_mut(); + + let device_ptr = unsafe { + frida_sys::frida_device_manager_get_device_by_type_sync( + self.manager_ptr, + r#type.into(), + 0, + std::ptr::null_mut(), + &mut error, + ) + }; + + if !error.is_null() { + return Err(Error::DeviceLookupFailed); + } + + return Ok(Device::from_raw(device_ptr)); + } + + /// Returns the device with the specified id. + /// + /// # Example + /// + /// let frida = unsafe { frida::Frida::obtain() }; + /// let device_manager = frida::DeviceManager::obtain(&frida); + /// + /// let id = ""; + /// let device = device_manager.get_device_by_id(id).unwrap(); + /// assert_eq!(device.get_id(), id); + /// + pub fn get_device_by_id(&'a self, device_id: &str) -> Result> { + let mut error: *mut frida_sys::GError = std::ptr::null_mut(); + let cstring = CString::new(device_id).unwrap(); + + let device_ptr = unsafe { + frida_sys::frida_device_manager_get_device_by_id_sync( + self.manager_ptr, + cstring.as_ptr(), + 0, + std::ptr::null_mut(), + &mut error, + ) + }; + + if !error.is_null() { + return Err(Error::DeviceLookupFailed); + } + + return Ok(Device::from_raw(device_ptr)); + } } impl<'a> Drop for DeviceManager<'a> { diff --git a/frida/src/error.rs b/frida/src/error.rs index f6870ec..471adec 100644 --- a/frida/src/error.rs +++ b/frida/src/error.rs @@ -13,6 +13,10 @@ pub enum Error { #[error("Failed to attach")] DeviceAttachError, + /// Failled to lookup a device. + #[error("Failed to lookup device")] + DeviceLookupFailed, + /// Failed to detach a session. #[error("Failed to detach the current session")] SessionDetachError, @@ -41,4 +45,40 @@ pub enum Error { /// Error message message: String, }, + + /// Failed to query device parameters + #[error("Failed to query device system parameters ({code}) {message}")] + DeviceQuerySystemParametersFailed { + /// Error code + code: i32, + /// Error message + message: String, + }, + + /// Failed to spawn program + #[error("Failed to spawn program ({code}) {message}")] + SpawnFailed { + /// Error code + code: i32, + /// Error message + message: String, + }, + + /// Failed to resume + #[error("Failed to resume ({code}) {message}")] + ResumeFailed { + /// Error code + code: i32, + /// Error message + message: String, + }, + + /// Failed to kill + #[error("Failed to kill PID ({code}) {message}")] + KillFailed { + /// Error code + code: i32, + /// Error message + message: String, + }, } diff --git a/frida/src/lib.rs b/frida/src/lib.rs index b131443..0dde5b2 100644 --- a/frida/src/lib.rs +++ b/frida/src/lib.rs @@ -35,6 +35,9 @@ pub use script::*; mod session; pub use session::*; +mod variant; +pub use variant::*; + #[doc(hidden)] pub type Result = std::result::Result; @@ -55,6 +58,43 @@ impl Frida { let version = unsafe { CStr::from_ptr(frida_sys::frida_version_string() as _) }; version.to_str().unwrap_or_default() } + + /// Schedules the closure to be executed on the main frida context. + pub fn schedule_on_main(&self, func: F) + where + F: FnOnce() + Send + 'static, + { + unsafe { + unsafe extern "C" fn trampoline( + func: frida_sys::gpointer, + ) -> frida_sys::gboolean { + let func: &mut Option = &mut *(func as *mut Option); + let func = func + .take() + .expect("schedule_on_main closure called multiple times"); + func(); + frida_sys::G_SOURCE_REMOVE as frida_sys::gboolean + } + unsafe extern "C" fn destroy_closure( + ptr: frida_sys::gpointer, + ) { + let _ = Box::>::from_raw(ptr as *mut _); + } + + let func = Box::into_raw(Box::new(Some(func))); + let source = frida_sys::g_idle_source_new(); + let ctx = frida_sys::frida_get_main_context(); + + frida_sys::g_source_set_callback( + source, + Some(trampoline::), + func as frida_sys::gpointer, + Some(destroy_closure::), + ); + frida_sys::g_source_attach(source, ctx); + frida_sys::g_source_unref(source); + } + } } impl Drop for Frida { diff --git a/frida/src/process.rs b/frida/src/process.rs index 17ac493..4741c64 100644 --- a/frida/src/process.rs +++ b/frida/src/process.rs @@ -4,8 +4,9 @@ * Licence: wxWindows Library Licence, Version 3.1 */ -use frida_sys::_FridaProcess; -use std::ffi::CStr; +use frida_sys::{FridaSpawnOptions, _FridaProcess}; +use std::convert::TryInto; +use std::ffi::{CStr, CString}; use std::marker::PhantomData; /// Process management in Frida. @@ -41,3 +42,132 @@ impl<'a> Drop for Process<'a> { unsafe { frida_sys::frida_unref(self.process_ptr as _) } } } + +#[repr(u32)] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +/// Standard I/O routing for a spawn +pub enum SpawnStdio { + /// Inherit parent's Standard I/O + Inherit = 0, + + /// Use pipes for Standard I/O + Pipe = 1, +} + +/// Process Spawn Options +pub struct SpawnOptions<'a> { + pub(crate) options_ptr: *mut FridaSpawnOptions, + phantom: PhantomData<&'a FridaSpawnOptions>, +} + +impl<'a> SpawnOptions<'a> { + pub(crate) fn from_raw(options_ptr: *mut FridaSpawnOptions) -> Self { + Self { + options_ptr, + phantom: PhantomData, + } + } + + /// Create an empty SpawnOptions instance + pub fn new() -> Self { + Self::from_raw(unsafe { frida_sys::frida_spawn_options_new() }) + } + + /// Set the argv vector + pub fn argv(self, args: L) -> Self + where + S: AsRef, + L: IntoIterator, + { + let args: Vec = args + .into_iter() + .map(|s| CString::new(s.as_ref()).unwrap()) + .collect(); + let mut arg_ptrs: Vec<*mut _> = args.iter().map(|s| s.as_ptr() as *mut _).collect(); + unsafe { + frida_sys::frida_spawn_options_set_argv( + self.options_ptr, + arg_ptrs.as_mut_ptr(), + arg_ptrs.len().try_into().unwrap(), + ); + } + self + } + + /// Set the working directory + pub fn cwd>(self, cwd: S) -> Self { + unsafe { + frida_sys::frida_spawn_options_set_cwd( + self.options_ptr, + cwd.as_ref().as_ptr() as *mut _, + ); + } + self + } + + /// Set the env vector + pub fn env(self, env: M) -> Self + where + K: AsRef, + V: AsRef, + M: IntoIterator, + { + let env: Vec = env + .into_iter() + .map(|(key, value)| { + CString::new(format!("{}={}", key.as_ref(), value.as_ref())).unwrap() + }) + .collect(); + let mut env_ptrs: Vec<*mut _> = env.iter().map(|s| s.as_ptr() as *mut _).collect(); + unsafe { + frida_sys::frida_spawn_options_set_env( + self.options_ptr, + env_ptrs.as_mut_ptr(), + env_ptrs.len().try_into().unwrap(), + ); + } + self + } + + /// Set the envp vector + pub fn envp(self, envp: M) -> Self + where + K: AsRef, + V: AsRef, + M: IntoIterator, + { + let envp: Vec = envp + .into_iter() + .map(|(key, value)| { + CString::new(format!("{}={}", key.as_ref(), value.as_ref())).unwrap() + }) + .collect(); + let mut envp_ptrs: Vec<*mut _> = envp.iter().map(|s| s.as_ptr() as *mut _).collect(); + unsafe { + frida_sys::frida_spawn_options_set_envp( + self.options_ptr, + envp_ptrs.as_mut_ptr(), + envp_ptrs.len().try_into().unwrap(), + ); + } + self + } + + /// Set the Standard I/O handling + pub fn stdio(self, stdio: SpawnStdio) -> Self { + unsafe { frida_sys::frida_spawn_options_set_stdio(self.options_ptr, stdio as _) } + self + } +} + +impl<'a> Default for SpawnOptions<'a> { + fn default() -> Self { + Self::new() + } +} + +impl<'a> Drop for SpawnOptions<'a> { + fn drop(&mut self) { + unsafe { frida_sys::frida_unref(self.options_ptr as _) } + } +} diff --git a/frida/src/variant.rs b/frida/src/variant.rs new file mode 100644 index 0000000..823089b --- /dev/null +++ b/frida/src/variant.rs @@ -0,0 +1,135 @@ +use std::collections::HashMap; +use std::ffi::{CStr, CString}; + +#[derive(Clone, PartialEq, Eq)] +/// GVariant types used by Frida +pub enum Variant { + /// String + String(String), + + /// Boolean value + Boolean(bool), + + /// Integer value + Int64(i64), + + /// Map + Map(HashMap), + + /// Array of Maps + MapList(Vec>), +} + +impl Variant { + /// Construct a GVariant from a raw pointer + pub(crate) unsafe fn from_ptr(variant: *mut frida_sys::GVariant) -> Self { + match variant_string(variant).as_str() { + "s" => { + let mut sz = 0; + let value = CStr::from_ptr(frida_sys::g_variant_get_string(variant, &mut sz)) + .to_string_lossy() + .to_string(); + Self::String(value) + } + "b" => { + Self::Boolean(frida_sys::g_variant_get_boolean(variant) != frida_sys::FALSE as _) + } + "x" => Self::Int64(frida_sys::g_variant_get_int64(variant)), + "a{sv}" => Self::Map(sv_array_to_map(variant)), + "aa{sv}" => Self::MapList(asv_array_to_maplist(variant)), + other => todo!("Unimplemented variant: {other}"), + } + } + + /// Get the string value of a variant, if any + pub fn get_string(&self) -> Option<&str> { + let Self::String(ref s) = self else { + return None; + }; + Some(s) + } + + /// Get the integer value of a variant, if any + pub fn get_int(&self) -> Option { + let Self::Int64(i) = self else { return None }; + Some(*i) + } + + /// Get the boolean value of a variant, if any + pub fn get_bool(&self) -> Option { + let Self::Boolean(b) = self else { return None }; + Some(*b) + } + + /// Get the mapping value of a variant, if any + pub fn get_map(&self) -> Option<&HashMap> { + let Self::Map(ref m) = self else { return None }; + Some(m) + } + + /// Get the mapping list value of a variant, if any + pub fn get_maplist(&self) -> Option<&[HashMap]> { + let Self::MapList(ref l) = self else { + return None; + }; + Some(l) + } +} + +impl std::fmt::Debug for Variant { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::String(s) => s.fmt(f), + Self::Int64(num) => num.fmt(f), + Self::Boolean(b) => b.fmt(f), + Self::Map(m) => m.fmt(f), + Self::MapList(l) => l.fmt(f), + } + } +} + +unsafe fn variant_string(variant: *mut frida_sys::GVariant) -> String { + CStr::from_ptr(frida_sys::g_variant_get_type_string(variant)) + .to_string_lossy() + .to_string() +} + +unsafe fn sv_array_to_map(variant: *mut frida_sys::GVariant) -> HashMap { + let mut ret = HashMap::new(); + + let mut iter: frida_sys::GVariantIter = std::mem::MaybeUninit::zeroed().assume_init(); + let mut value: *mut frida_sys::GVariant = std::ptr::null_mut(); + let mut key: *const i8 = std::ptr::null_mut(); + + frida_sys::g_variant_iter_init(&mut iter, variant); + let sv = CString::new("{sv}").unwrap(); + while frida_sys::g_variant_iter_loop(&mut iter, sv.as_ptr(), &mut key, &mut value) != 0 { + let key = CStr::from_ptr(key).to_string_lossy().to_string(); + let value = Variant::from_ptr(value); + ret.insert(key, value); + } + ret +} + +unsafe fn asv_array_to_maplist(variant: *mut frida_sys::GVariant) -> Vec> { + let mut ret = Vec::new(); + let mut outer: frida_sys::GVariantIter = std::mem::MaybeUninit::zeroed().assume_init(); + let mut inner = std::ptr::null_mut(); + let mut key: *const i8 = std::ptr::null_mut(); + let mut value: *mut frida_sys::GVariant = std::ptr::null_mut(); + + frida_sys::g_variant_iter_init(&mut outer, variant); + let asv = CString::new("a{sv}").unwrap(); + let sv = CString::new("{sv}").unwrap(); + while frida_sys::g_variant_iter_loop(&mut outer, asv.as_ptr(), &mut inner) != 0 { + let mut map = HashMap::new(); + while frida_sys::g_variant_iter_loop(inner, sv.as_ptr(), &mut key, &mut value) != 0 { + let key = CStr::from_ptr(key).to_string_lossy().to_string(); + let value = Variant::from_ptr(value); + map.insert(key, value); + } + ret.push(map) + } + + ret +}