diff --git a/Cargo.toml b/Cargo.toml index 58cc75c..d2a3a9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ members = [ "examples/core/usb_device", "examples/core/console_log", "examples/core/list_exports", - "examples/core/get_processes", + "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..e95f284 --- /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 +Error: 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..2f2f45e --- /dev/null +++ b/examples/core/rpc_execute_function/src/main.rs @@ -0,0 +1,115 @@ +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(&js_functions[0], None); // Increment + + // Example calling a JS function, giving the function name as &str, and a "Number" parameter. + let _ = script.exports("nIncrement", Some(json!([2]))); + + // Example showing how to get the returned value. + let js_global_var = script.exports("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("bye", Some(json!(["Potato"]))); + + // Example showing sending multiple arguments. + let total = script + .exports("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("NonExistentFunc", Some(json!([[1, 2, 3, 4], true]))) { + println!("Error: {}", 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..b58c5f8 100644 --- a/frida/src/script.rs +++ b/frida/src/script.rs @@ -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 @@ -238,6 +237,46 @@ impl<'a> Script<'a> { self.rpc_id_counter } + /// Access exported functions from a Frida script. + pub fn exports(&mut self, function_name: &str, args: Option) -> Result> { + let json_req = { + 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 + ) + }; + + self.post(&json_req, None).unwrap(); + let (_, rx) = &self.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), + } + } + /// List all the exported attributes from the script's rpc pub fn list_exports(&mut self) -> Result> { let json_req = {