diff --git a/examples/gum/process_check/Cargo.toml b/examples/gum/process_check/Cargo.toml index 55e0f23a..4d303dd4 100644 --- a/examples/gum/process_check/Cargo.toml +++ b/examples/gum/process_check/Cargo.toml @@ -10,8 +10,5 @@ publish = false crate-type = ["cdylib"] [dependencies] -frida-gum = { path = "../../../frida-gum", features = [ - "invocation-listener", - # "std", -] } +frida-gum = { path = "../../../frida-gum", features = ["invocation-listener"] } ctor = "0.6.1" diff --git a/examples/gum/process_check/src/lib.rs b/examples/gum/process_check/src/lib.rs index 49e73465..99f8380c 100644 --- a/examples/gum/process_check/src/lib.rs +++ b/examples/gum/process_check/src/lib.rs @@ -66,7 +66,7 @@ fn init() { println!(" - Enumerate threads:"); let threads = process.enumerate_threads(); for thread in threads { - println!(" - {:?}", thread); + println!(" - {:#?}", thread); } println!(" - Enumerate modules:"); diff --git a/frida-gum/src/cpu_context.rs b/frida-gum/src/cpu_context.rs index 303c3b41..2ac80636 100644 --- a/frida-gum/src/cpu_context.rs +++ b/frida-gum/src/cpu_context.rs @@ -4,121 +4,225 @@ * Licence: wxWindows Library Licence, Version 3.1 */ use { - core::{ffi::c_void, marker::PhantomData}, + core::{ffi::c_void, fmt}, frida_gum_sys as gum_sys, gum_sys::GumCpuContext, paste::paste, }; +#[cfg(target_arch = "x86_64")] +macro_rules! REG_LIST { + ($mac:ident) => { + $mac!( + u64, rip, r15, r14, r13, r12, r11, r10, r9, r8, rdi, rsi, rbp, rsp, rbx, rdx, rcx, rax + ); + }; +} + +#[cfg(target_arch = "x86")] +macro_rules! REG_LIST { + ($mac:ident) => { + $mac!(u32, eip, edi, esi, ebp, esp, ebx, edx, ecx, eax); + }; +} + +#[cfg(target_arch = "arm")] +// TODO(meme) uint32_t r[8]; +macro_rules! REG_LIST { + ($mac:ident) => { + $mac!(u32, cpsr, pc, sp, r8, r9, r10, r11, r12, lr); + }; +} + +#[cfg(target_arch = "aarch64")] +// TODO(meme) uint8_t q[128]; uint64_t x[29]; +macro_rules! REG_LIST { + ($mac:ident) => { + $mac!(u64, pc, sp, fp, lr); + }; +} + macro_rules! cpu_accesors { ($reg:ty,$($name:ident),*) => { $( pub fn $name(&self) -> $reg { - unsafe { (*self.cpu_context).$name } + unsafe {(*self.as_ptr()).$name} } paste! { pub fn [](&mut self, $name: $reg) { - unsafe { (*self.cpu_context).$name = $name } + let ctx = self.as_mut_ptr(); + unsafe {(*ctx).$name = $name} } } )* } } +macro_rules! gen_debug { + ($reg:ty,$($name:ident),*) => { + impl fmt::Debug for CpuContext { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut ds = f.debug_struct("CpuContext"); + $( + ds.field(stringify!($name), &format_args!("0x{:x}", &self.$name())); + )* + ds.finish() + } + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CpuContextError { + ReadOnly, +} + +pub enum CpuContextAccess { + CpuContextReadOnly = 1, + CpuCcontextReadWrite, +} + +pub enum CpuContextType { + ReadOnly(GumCpuContext), + ReadWrite(*mut GumCpuContext), +} + /// Platform-dependent access to processor state. -pub struct CpuContext<'a> { - cpu_context: *mut GumCpuContext, - phantom: PhantomData<&'a GumCpuContext>, +pub struct CpuContext { + cpu_context: CpuContextType, } -impl<'a> CpuContext<'a> { - pub(crate) fn from_raw(cpu_context: *mut GumCpuContext) -> CpuContext<'a> { - CpuContext { - cpu_context, - phantom: PhantomData, +impl CpuContext { + pub(crate) fn from_raw( + cpu_context: *mut GumCpuContext, + access: CpuContextAccess, + ) -> CpuContext { + unsafe { + match access { + CpuContextAccess::CpuContextReadOnly => { + let snapshot = *cpu_context; + + CpuContext { + cpu_context: CpuContextType::ReadOnly(snapshot), + } + } + CpuContextAccess::CpuCcontextReadWrite => CpuContext { + cpu_context: CpuContextType::ReadWrite(cpu_context), + }, + } + } + } + + fn as_ptr(&self) -> *const GumCpuContext { + match self.cpu_context { + CpuContextType::ReadOnly(ctx) => &ctx as *const GumCpuContext, + CpuContextType::ReadWrite(ctx) => ctx as *const GumCpuContext, + } + } + + fn as_mut_ptr(&mut self) -> *mut GumCpuContext { + match self.cpu_context { + CpuContextType::ReadOnly(_) => { + panic!("Error: trying to write a read only CPU context.") + } + CpuContextType::ReadWrite(ctx) => ctx as *mut GumCpuContext, } } /// Get a numbered argument from the processor context, determined by the platform calling convention. pub fn arg(&self, n: u32) -> usize { - unsafe { gum_sys::gum_cpu_context_get_nth_argument(self.cpu_context, n) as usize } + unsafe { gum_sys::gum_cpu_context_get_nth_argument(self.as_ptr() as *mut _, n) as usize } } /// Set a numbered argument in the processor context, determined by the platform calling convention. - pub fn set_arg(&mut self, n: u32, value: usize) { - unsafe { - gum_sys::gum_cpu_context_replace_nth_argument(self.cpu_context, n, value as *mut c_void) + pub fn set_arg(&mut self, n: u32, value: usize) -> Result<(), CpuContextError> { + match self.cpu_context { + CpuContextType::ReadOnly(_) => Err(CpuContextError::ReadOnly), + CpuContextType::ReadWrite(_) => { + unsafe { + gum_sys::gum_cpu_context_replace_nth_argument( + self.as_mut_ptr(), + n, + value as *mut c_void, + ) + }; + + Ok(()) + } } } /// Get the value of the register used for the platform calling convention's return value. pub fn return_value(&self) -> usize { - unsafe { gum_sys::gum_cpu_context_get_return_value(self.cpu_context) as usize } + unsafe { gum_sys::gum_cpu_context_get_return_value(self.as_ptr() as *mut _) as usize } } /// Set the value of the register used for the platform calling convention's return value. - pub fn set_return_value(&mut self, value: usize) { - unsafe { - gum_sys::gum_cpu_context_replace_return_value(self.cpu_context, value as *mut c_void) + pub fn set_return_value(&mut self, value: usize) -> Result<(), CpuContextError> { + match self.cpu_context { + CpuContextType::ReadOnly(_) => Err(CpuContextError::ReadOnly), + CpuContextType::ReadWrite(_) => { + unsafe { + gum_sys::gum_cpu_context_replace_return_value( + self.as_mut_ptr(), + value as *mut c_void, + ) + }; + Ok(()) + } } } - #[cfg(target_arch = "x86_64")] - cpu_accesors!( - u64, rip, r15, r14, r13, r12, r11, r10, r9, r8, rdi, rsi, rbp, rsp, rbx, rdx, rcx, rax - ); - - #[cfg(target_arch = "x86")] - cpu_accesors!(u32, eip, edi, esi, ebp, esp, ebx, edx, ecx, eax); - - #[cfg(target_arch = "arm")] - cpu_accesors!(u32, cpsr, pc, sp, r8, r9, r10, r11, r12, lr); - // TODO(meme) uint32_t r[8]; - - #[cfg(target_arch = "aarch64")] - cpu_accesors!(u64, pc, sp, fp, lr); - // TODO(meme) uint8_t q[128]; uint64_t x[29]; + REG_LIST!(cpu_accesors); #[cfg(target_arch = "aarch64")] /// Get the value of the specified general purpose register. pub fn reg(&self, index: usize) -> u64 { assert!(index < 29); - unsafe { (*self.cpu_context).x[index] } + unsafe { (*self.as_ptr()).x[index] } } #[cfg(target_arch = "aarch64")] /// Set the value of the specified general purpose register. - pub fn set_reg(&mut self, index: usize, value: u64) { - assert!(index < 29); - unsafe { (*self.cpu_context).x[index] = value }; + pub fn set_reg(&mut self, index: usize, value: u64) -> Result<(), CpuContextError> { + match self.cpu_context { + CpuContextType::ReadOnly(_) => Err(CpuContextError::ReadOnly), + CpuContextType::ReadWrite(_) => { + assert!(index < 29); + unsafe { (*self.as_mut_ptr()).x[index] = value }; + Ok(()) + } + } } #[cfg(feature = "backtrace")] #[cfg_attr(docsrs, 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 }) + crate::Backtracer::accurate_with_context(unsafe { &*self.as_ptr() }) } #[cfg(feature = "backtrace")] #[cfg_attr(docsrs, doc(cfg(feature = "backtrace")))] /// Get a fuzzy backtrace from this CPU context. pub fn backtrace_fuzzy(&self) -> Vec { - crate::Backtracer::fuzzy_with_context(unsafe { &*self.cpu_context }) + crate::Backtracer::fuzzy_with_context(unsafe { &*self.as_ptr() }) } #[cfg(feature = "backtrace")] #[cfg_attr(docsrs, doc(cfg(feature = "backtrace")))] /// Get an accurate backtrace from this CPU context. pub fn backtrace_accurate_with_limit(&self, limit: u32) -> Vec { - crate::Backtracer::accurate_with_context_and_limit(unsafe { &*self.cpu_context }, limit) + crate::Backtracer::accurate_with_context_and_limit(unsafe { &*self.as_ptr() }, limit) } #[cfg(feature = "backtrace")] #[cfg_attr(docsrs, doc(cfg(feature = "backtrace")))] /// Get a fuzzy backtrace from this CPU context. pub fn backtrace_fuzzy_with_limit(&self, limit: u32) -> Vec { - crate::Backtracer::fuzzy_with_context_and_limit(unsafe { &*self.cpu_context }, limit) + crate::Backtracer::fuzzy_with_context_and_limit(unsafe { &*self.as_ptr() }, limit) } } + +REG_LIST!(gen_debug); diff --git a/frida-gum/src/interceptor/invocation_listener.rs b/frida-gum/src/interceptor/invocation_listener.rs index fd125404..f29497b5 100644 --- a/frida-gum/src/interceptor/invocation_listener.rs +++ b/frida-gum/src/interceptor/invocation_listener.rs @@ -10,7 +10,7 @@ )] use { - crate::{CpuContext, NativePointer}, + crate::{CpuContext, CpuContextAccess, NativePointer}, core::{ffi::c_void, marker::PhantomData}, frida_gum_sys as gum_sys, }; @@ -167,7 +167,10 @@ impl<'a> InvocationContext<'a> { } /// Get the [`CpuContext`] at the time of invocation. - pub fn cpu_context(&self) -> CpuContext<'_> { - CpuContext::from_raw(unsafe { (*self.context).cpu_context }) + pub fn cpu_context(&self) -> CpuContext { + CpuContext::from_raw( + unsafe { (*self.context).cpu_context }, + CpuContextAccess::CpuCcontextReadWrite, + ) } } diff --git a/frida-gum/src/stalker/transformer.rs b/frida-gum/src/stalker/transformer.rs index ed8675fd..cc6f3e9b 100644 --- a/frida-gum/src/stalker/transformer.rs +++ b/frida-gum/src/stalker/transformer.rs @@ -4,7 +4,9 @@ * Licence: wxWindows Library Licence, Version 3.1 */ +use crate::CpuContextAccess; use frida_gum_sys::Insn; + use { crate::{instruction_writer::TargetInstructionWriter, CpuContext, Gum}, core::{ffi::c_void, marker::PhantomData}, @@ -23,7 +25,10 @@ extern "C" fn put_callout_callback( user_data: *mut c_void, ) { let mut f = unsafe { Box::from_raw(user_data as *mut Box) }; - f(CpuContext::from_raw(cpu_context)); + f(CpuContext::from_raw( + cpu_context, + CpuContextAccess::CpuCcontextReadWrite, + )); // Leak the box again, we want to destruct it in the data_destroy callback. // Box::leak(f); diff --git a/frida-gum/src/thread.rs b/frida-gum/src/thread.rs index 7134e997..14e43956 100644 --- a/frida-gum/src/thread.rs +++ b/frida-gum/src/thread.rs @@ -16,7 +16,7 @@ use num::FromPrimitive; #[cfg(feature = "backtrace")] use crate::Backtracer; -use crate::{CpuContext, NativePointer}; +use crate::{CpuContext, CpuContextAccess, NativePointer}; bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -81,11 +81,11 @@ impl Thread { unsafe { &(*self.thread).cpu_context } } - // todo: return an immutable reference instead of an owned object; without this multiple threads can obtain a Process instance, - // get the same CpuContexts and cause a data race - /// Modifying CpuContexts from multiple threads is unsound, as they may share the same pointer - pub fn cpu_context(&self) -> CpuContext<'_> { - CpuContext::from_raw(self.gum_cpu_context() as *const _ as *mut GumCpuContext) + pub fn cpu_context(&self) -> CpuContext { + CpuContext::from_raw( + self.gum_cpu_context() as *const _ as *mut GumCpuContext, + CpuContextAccess::CpuContextReadOnly, + ) } pub fn entrypoint(&self) -> Entrypoint { @@ -119,6 +119,7 @@ impl Debug for Thread { .field("id", &self.id()) .field("name", &self.name()) .field("state", &self.state()) - .finish_non_exhaustive() + .field("context", &self.cpu_context()) + .finish() } }