From 5edc0e2189cdf7490699446c546dc41c2b428a56 Mon Sep 17 00:00:00 2001
From: Fabian Freyer <fabian.freyer@physik.tu-berlin.de>
Date: Mon, 18 Sep 2023 16:28:33 +0200
Subject: [PATCH] feat(device): Add spawn/resume methods

---
 frida/src/device.rs  |  81 +++++++++++++++++++++++++-
 frida/src/error.rs   |  27 +++++++++
 frida/src/process.rs | 134 ++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 239 insertions(+), 3 deletions(-)

diff --git a/frida/src/device.rs b/frida/src/device.rs
index e138991..a6385e3 100644
--- a/frida/src/device.rs
+++ b/frida/src/device.rs
@@ -12,7 +12,7 @@ use std::marker::PhantomData;
 use crate::process::Process;
 use crate::session::Session;
 use crate::variant::Variant;
-use crate::{Error, Result};
+use crate::{Error, Result, SpawnOptions};
 
 /// Access to a Frida device.
 pub struct Device<'a> {
@@ -172,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<S: AsRef<str>>(&mut self, program: S, options: &SpawnOptions) -> Result<u32> {
+        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> {
diff --git a/frida/src/error.rs b/frida/src/error.rs
index 419b255..471adec 100644
--- a/frida/src/error.rs
+++ b/frida/src/error.rs
@@ -54,4 +54,31 @@ pub enum Error {
         /// 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/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<S, L>(self, args: L) -> Self
+    where
+        S: AsRef<str>,
+        L: IntoIterator<Item = S>,
+    {
+        let args: Vec<CString> = 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<S: AsRef<CStr>>(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<K, V, M>(self, env: M) -> Self
+    where
+        K: AsRef<str>,
+        V: AsRef<str>,
+        M: IntoIterator<Item = (K, V)>,
+    {
+        let env: Vec<CString> = 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<K, V, M>(self, envp: M) -> Self
+    where
+        K: AsRef<str>,
+        V: AsRef<str>,
+        M: IntoIterator<Item = (K, V)>,
+    {
+        let envp: Vec<CString> = 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 _) }
+    }
+}