diff --git a/Cargo.toml b/Cargo.toml index 224bc35..8f70af2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ members = [ "examples/core/console_log", "examples/core/list_exports", "examples/core/get_processes", + "examples/core/rpc_execute_function", ] # We miss our linux_no_std example from the default members since `cargo check` # and `cargo test` both attempt to link the `std` library into it in error. diff --git a/examples/core/rpc_execute_function/Cargo.toml b/examples/core/rpc_execute_function/Cargo.toml new file mode 100644 index 0000000..81e21d1 --- /dev/null +++ b/examples/core/rpc_execute_function/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rpc_execute_function" +version = "0.1.0" +edition = "2021" +authors = ["Ricardo J Marques Montilla / Xoffio"] + +[dependencies] +frida = { path = "../../../frida" } +frida-sys = { path = "../../../frida-sys" } +lazy_static = "1.5.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.127" diff --git a/examples/core/rpc_execute_function/README.md b/examples/core/rpc_execute_function/README.md new file mode 100644 index 0000000..f6aecfd --- /dev/null +++ b/examples/core/rpc_execute_function/README.md @@ -0,0 +1,18 @@ +Example to showing how to execute a JavaScript Frida function from Rust using `script.exports`. +Once ran you should expect an output similar to the next one: + +``` +[*] Frida version: 16.4.8 +[*] Device name: Local System +[*] Script loaded. +["increment", "nIncrement", "getValue", "sumVals", "bye"] +- Log(MessageLog { level: Info, payload: "globalVar incremented by 1" }) +- Log(MessageLog { level: Info, payload: "globalVar incremented by 2" }) +js_global_var: 3 +- Log(MessageLog { level: Info, payload: "Bye Potato" }) +total: 10 +This is an error from JS: Error on the JavaScript side: "unable to find method 'NonExistentFunc'" +[*] Script unloaded +[*] Session detached +Exiting... +``` diff --git a/examples/core/rpc_execute_function/src/main.rs b/examples/core/rpc_execute_function/src/main.rs new file mode 100644 index 0000000..615a57d --- /dev/null +++ b/examples/core/rpc_execute_function/src/main.rs @@ -0,0 +1,119 @@ +use frida::{Frida, Message}; +use lazy_static::lazy_static; +use serde_json::json; + +lazy_static! { + static ref FRIDA: Frida = unsafe { Frida::obtain() }; +} + +fn main() { + let device_manager = frida::DeviceManager::obtain(&FRIDA); + let local_device = device_manager.get_local_device(); + + if let Ok(device) = local_device { + println!("[*] Frida version: {}", frida::Frida::version()); + println!("[*] Device name: {}", device.get_name()); + + // Attach to the program + let session = device.attach(0).unwrap(); + + let script_source = r#" + var globalVar = 0; + + rpc.exports = { + increment: function() { + globalVar += 1; + console.log("globalVar incremented by 1"); + }, + + nIncrement: function(n) { + globalVar += n; + console.log("globalVar incremented by " + n); + }, + + getValue: function() { + return globalVar; + }, + + sumVals: function(vals, b) { + let sum = 0 + for (let i=0; i < vals.length; i++) { + sum += vals[i] + } + return sum; + }, + + bye: function(name) { + console.log("Bye " + name); + } + }; + "#; + + let mut script_option = frida::ScriptOption::default(); + let mut script = match session.create_script(script_source, &mut script_option) { + Ok(s) => s, + Err(err) => { + println!("{}", err); + return; + } + }; + + let msg_handler = script.handle_message(Handler); + if let Err(err) = msg_handler { + panic!("{:?}", err); + } + + script.load().unwrap(); + println!("[*] Script loaded."); + + let js_functions = script.list_exports().unwrap(); + println!("{:?}", &js_functions); + + // Example calling a function in JS and giving the function name from `list_exports` + // Expect a log message to be printed. + let _ = script.exports.call(&js_functions[0], None); // Increment + + // Example calling a JS function, giving the function name as &str, and a "Number" parameter. + let _ = script.exports.call("nIncrement", Some(json!([2]))); + + // // Example showing how to get the returned value. + let js_global_var = script.exports.call("getValue", None).unwrap().unwrap(); + println!("js_global_var: {}", js_global_var); + + // Example sending a String as parameter. + // Expect a log message to be printed. + let _ = script.exports.call("bye", Some(json!(["Potato"]))); + + // Example showing sending multiple arguments. + let total = script + .exports + .call("sumVals", Some(json!([[1, 2, 3, 4], true]))) + .unwrap() + .unwrap(); + println!("total: {}", total); + + // Here I show how errors look like + if let Err(err_msg) = script + .exports + .call("NonExistentFunc", Some(json!([[1, 2, 3, 4], true]))) + { + println!("This is an error from JS: {}", err_msg); + } + + script.unload().unwrap(); + println!("[*] Script unloaded"); + + session.detach().unwrap(); + println!("[*] Session detached"); + } + + println!("Exiting..."); +} + +struct Handler; + +impl frida::ScriptHandler for Handler { + fn on_message(&mut self, message: &Message) { + println!("- {:?}", message); + } +} diff --git a/frida/src/error.rs b/frida/src/error.rs index 471adec..fb85772 100644 --- a/frida/src/error.rs +++ b/frida/src/error.rs @@ -81,4 +81,16 @@ pub enum Error { /// Error message message: String, }, + + /// Received unexpected RPC message. + #[error("Unexpected RPC message received.")] + RpcUnexpectedMessage, + + /// RPC JavaScript Error. The RPC communication was + /// successful but there was an error in the JavaScript side + #[error("Error on the JavaScript side: {message}")] + RpcJsError { + /// Error message from JavaScript. + message: String, + }, } diff --git a/frida/src/script.rs b/frida/src/script.rs index 866e2e6..412a9f0 100644 --- a/frida/src/script.rs +++ b/frida/src/script.rs @@ -7,8 +7,8 @@ use frida_sys::{FridaScriptOptions, _FridaScript, g_bytes_new, g_bytes_unref}; use serde::Deserialize; use serde_json::Value; -use std::marker::PhantomData; use std::sync::mpsc::{channel, Receiver, Sender}; +use std::{cell::RefCell, marker::PhantomData, rc::Rc}; use std::{ ffi::{c_char, c_void, CStr, CString}, ptr::null_mut, @@ -79,8 +79,7 @@ pub enum MessageLogLevel { Error, } -/// Represents a Message Log Level Types. -/// Used by `MessageLog._level` +/// Represents a MessageSend's payload. #[derive(Deserialize, Debug)] pub struct SendPayload { /// Send message type @@ -135,21 +134,47 @@ pub trait ScriptHandler { fn on_message(&mut self, message: &Message); } -/// Reprents a Frida script. +/// Represents a Frida script. pub struct Script<'a> { script_ptr: *mut _FridaScript, + rpc_id_counter: Rc>, + callback_handler: Rc>, + ///Exports of the script. + pub exports: Exports<'a>, + phantom: PhantomData<&'a _FridaScript>, +} + +/// This represents the exports of the script. +pub struct Exports<'a> { + script_ptr: *mut _FridaScript, + rpc_id_counter: Rc>, + callback_handler: Rc>, phantom: PhantomData<&'a _FridaScript>, - rpc_id_counter: usize, - callback_handler: CallbackHandler, +} + +impl<'a> Exports<'a> { + fn inc_id(&mut self) -> usize { + let mut counter_borrow = self.rpc_id_counter.borrow_mut(); + *counter_borrow += 1; + *counter_borrow + } } impl<'a> Script<'a> { pub(crate) fn from_raw(script_ptr: *mut _FridaScript) -> Script<'a> { + let rpc_counter = Rc::new(RefCell::new(0)); + let handler = Rc::new(RefCell::new(CallbackHandler::new())); Script { script_ptr, phantom: PhantomData, - rpc_id_counter: 0, - callback_handler: CallbackHandler::new(), + rpc_id_counter: rpc_counter.clone(), + callback_handler: handler.clone(), + exports: Exports { + script_ptr, + phantom: PhantomData, + rpc_id_counter: rpc_counter, + callback_handler: handler, + }, } } @@ -194,7 +219,10 @@ impl<'a> Script<'a> { /// ``` pub fn handle_message(&mut self, handler: I) -> Result<()> { let message = CString::new("message").map_err(|_| Error::CStringFailed)?; - self.callback_handler.add_handler(handler); + let mut borrowed_callback_handler = self.callback_handler.borrow_mut(); + (*borrowed_callback_handler).add_handler(handler); + let user_data = + (&(*borrowed_callback_handler) as *const _ as *mut CallbackHandler) as *mut c_void; unsafe { let callback = Some(std::mem::transmute::< *mut std::ffi::c_void, @@ -205,7 +233,7 @@ impl<'a> Script<'a> { self.script_ptr as _, message.as_ptr(), callback, - (&self.callback_handler as *const _ as *mut CallbackHandler) as *mut c_void, + user_data, None, 0, ) @@ -234,8 +262,9 @@ impl<'a> Script<'a> { } fn inc_id(&mut self) -> usize { - self.rpc_id_counter += 1; - self.rpc_id_counter + let mut counter_borrow = self.rpc_id_counter.borrow_mut(); + *counter_borrow += 1; + *counter_borrow } /// List all the exported attributes from the script's rpc @@ -253,7 +282,8 @@ impl<'a> Script<'a> { }; self.post(&json_req, None).unwrap(); - let (_, rx) = &self.callback_handler.channel; + let borrowed_callback_handler = self.callback_handler.borrow(); + let (_, rx) = &borrowed_callback_handler.channel; let rpc_result = rx.recv().unwrap(); let func_list: Vec = match rpc_result { @@ -276,6 +306,56 @@ impl<'a> Script<'a> { } } +impl<'a> Exports<'a> { + /// Run exported functions from a Frida script. + pub fn call(&mut self, function_name: &str, args: Option) -> Result> { + let json_req: String = { + let name = "frida:rpc"; + let id = self.inc_id(); + let rpc_type = "call"; + + let args: String = match args { + Some(a) => serde_json::to_string(&a).unwrap(), + None => "[]".into(), + }; + + format!( + "[\"{}\", {}, \"{}\", \"{}\", {}]", + name, id, rpc_type, function_name, args + ) + }; + + let message = CString::new(json_req.as_str()).map_err(|_| Error::CStringFailed)?; + + unsafe { + let g_data = std::ptr::null_mut(); + frida_sys::frida_script_post(self.script_ptr as _, message.as_ptr() as _, g_data); + g_bytes_unref(g_data); + } + + let borrowed_callback_handler = self.callback_handler.borrow(); + let (_, rx) = &borrowed_callback_handler.channel; + let rpc_result = rx.recv().unwrap(); + + match rpc_result { + Message::Send(r) => { + if r.payload.result == "ok" { + let returns = r.payload.returns; + + match returns { + Value::Null => Ok(None), + _ => Ok(Some(returns)), + } + } else { + let err_msg = r.payload.returns.to_string(); + Err(Error::RpcJsError { message: err_msg }) + } + } + _ => Err(Error::RpcUnexpectedMessage), + } + } +} + impl<'a> Drop for Script<'a> { fn drop(&mut self) { unsafe { frida_sys::frida_unref(self.script_ptr as _) }