From a31c6eb14ad7a4845387a35bd8b16dabff9fd606 Mon Sep 17 00:00:00 2001 From: Fabian Freyer Date: Tue, 14 Nov 2023 04:08:58 +0100 Subject: [PATCH] Add a couple of useful features to Device * feat(device): Add method to query system params This requires some GVariant handling which was mostly inspired by the implementation in frida-go's types_converter.go. * feat(device): Add a getter for the device type * feat(device): Add spawn/resume methods --- frida-sys/src/lib.rs | 11 ++- frida/src/device.rs | 205 +++++++++++++++++++++++++++++++++++++++++-- frida/src/error.rs | 36 ++++++++ frida/src/lib.rs | 3 + frida/src/process.rs | 134 +++++++++++++++++++++++++++- frida/src/variant.rs | 135 ++++++++++++++++++++++++++++ 6 files changed, 514 insertions(+), 10 deletions(-) create mode 100644 frida/src/variant.rs diff --git a/frida-sys/src/lib.rs b/frida-sys/src/lib.rs index 8abb8f0..5473465 100644 --- a/frida-sys/src/lib.rs +++ b/frida-sys/src/lib.rs @@ -19,8 +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_idle_source_new as g_idle_source_new, + _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/src/device.rs b/frida/src/device.rs index b89e591..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> { @@ -106,14 +259,46 @@ impl<'a> Drop for Device<'a> { } } +#[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, - /// Remote Frida device. - Remote, - /// USB Frida device. - USB, + 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 { @@ -125,3 +310,9 @@ impl From for frida_sys::FridaDeviceType { } } } + +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/error.rs b/frida/src/error.rs index 9b1ebd9..471adec 100644 --- a/frida/src/error.rs +++ b/frida/src/error.rs @@ -45,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 30992c6..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; 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 +}