diff --git a/Cargo.toml b/Cargo.toml index 4b3b5234..58cc75ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ members = [ "examples/core/hello", "examples/core/usb_device", "examples/core/console_log", + "examples/core/list_exports", "examples/core/get_processes", ] # We miss our linux_no_std example from the default members since `cargo check` diff --git a/examples/core/console_log/src/main.rs b/examples/core/console_log/src/main.rs index af14cd20..cbc1b9a1 100644 --- a/examples/core/console_log/src/main.rs +++ b/examples/core/console_log/src/main.rs @@ -27,11 +27,11 @@ fn main() { let mut script_option = ScriptOption::new() .set_name("example") .set_runtime(ScriptRuntime::QJS); - let script = session + let mut script = session .create_script("console.log('Log test');", &mut script_option) .unwrap(); - script.handle_message(&mut Handler).unwrap(); + script.handle_message(Handler).unwrap(); script.load().unwrap(); println!("[*] Script loaded"); @@ -48,7 +48,7 @@ fn main() { struct Handler; impl ScriptHandler for Handler { - fn on_message(&mut self, message: &str) { - println!("{message}"); + fn on_message(&mut self, message: &frida::Message) { + println!("{:?}", message); } } diff --git a/examples/core/list_exports/Cargo.toml b/examples/core/list_exports/Cargo.toml new file mode 100644 index 00000000..9c0dc94c --- /dev/null +++ b/examples/core/list_exports/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "list_exports" +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" diff --git a/examples/core/list_exports/README.md b/examples/core/list_exports/README.md new file mode 100644 index 00000000..342f5ca7 --- /dev/null +++ b/examples/core/list_exports/README.md @@ -0,0 +1,18 @@ +Example to showing how to use `script.list_exports()`. +Once ran you should expect an output similar to the next one: + +``` +[*] Frida version: 16.4.8 +[*] Device name: Local System +- Log(MessageLog { level: Info, payload: "Logging message from JS" }) +- Log(MessageLog { level: Warning, payload: "Warning message from JS" }) +- Log(MessageLog { level: Debug, payload: "Debug message from JS" }) +- Log(MessageLog { level: Error, payload: "Error message from JS" }) +[*] Script loaded. +["increment", "getvalue"] +["increment", "getvalue"] +["increment", "getvalue"] +[*] Script unloaded +[*] Session detached +Exiting... +``` diff --git a/examples/core/list_exports/src/main.rs b/examples/core/list_exports/src/main.rs new file mode 100644 index 00000000..68d300fe --- /dev/null +++ b/examples/core/list_exports/src/main.rs @@ -0,0 +1,82 @@ +use frida::{Frida, Message}; +use lazy_static::lazy_static; +use std::{thread, time::Duration}; + +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(); + + if session.is_detached() { + println!("Session is detached"); + return; + } + + let script_source = r#" + var globalVar = 0; + console.log("Logging message from JS"); + console.warn("Warning message from JS"); + console.debug("Debug message from JS"); + console.error("Error message from JS"); + + rpc.exports = { + increment: function() { + globalVar += 1; + return globalVar; + }, + getvalue: function() { + return globalVar; + } + }; + "#; + 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."); + + println!("{:?}", script.list_exports().unwrap()); + + for _ in 0..2 { + thread::sleep(Duration::from_secs(1)); + println!("{:?}", script.list_exports().unwrap()); + } + + 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-gum/src/interceptor.rs b/frida-gum/src/interceptor.rs index d5693d05..391864f8 100644 --- a/frida-gum/src/interceptor.rs +++ b/frida-gum/src/interceptor.rs @@ -30,10 +30,7 @@ impl<'a> Interceptor<'a> { /// Obtain an Interceptor handle, ensuring that the runtime is properly initialized. This may /// be called as many times as needed, and results in a no-op if the Interceptor is /// already initialized. - pub fn obtain<'b>(_gum: &'b Gum) -> Interceptor - where - 'b: 'a, - { + pub fn obtain<'b: 'a>(_gum: &'b Gum) -> Interceptor<'b> { Interceptor { interceptor: unsafe { gum_sys::gum_interceptor_obtain() }, phantom: PhantomData, diff --git a/frida-gum/src/stalker.rs b/frida-gum/src/stalker.rs index 1c8388b8..a78e8b73 100644 --- a/frida-gum/src/stalker.rs +++ b/frida-gum/src/stalker.rs @@ -104,10 +104,7 @@ impl<'a> Stalker<'a> { /// This call has the overhead of checking if the Stalker is /// available on the current platform, as creating a Stalker on an /// unsupported platform results in unwanted behaviour. - pub fn new<'b>(gum: &'b Gum) -> Stalker - where - 'b: 'a, - { + pub fn new<'b: 'a>(gum: &'b Gum) -> Stalker<'b> { assert!(Self::is_supported(gum)); Stalker { diff --git a/frida/Cargo.toml b/frida/Cargo.toml index 328a4875..dcfcbf29 100644 --- a/frida/Cargo.toml +++ b/frida/Cargo.toml @@ -11,8 +11,10 @@ description.workspace = true auto-download = ["frida-sys/auto-download"] [dependencies] -frida-sys = { path = "../frida-sys" , version = "0.13.7"} +frida-sys = { path = "../frida-sys", version = "0.13.7" } thiserror = "1" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.127" [dev-dependencies] lazy_static = "1" diff --git a/frida/src/device.rs b/frida/src/device.rs index a6385e30..886a4efd 100644 --- a/frida/src/device.rs +++ b/frida/src/device.rs @@ -103,7 +103,7 @@ impl<'a> Device<'a> { 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 _) + != frida_sys::FALSE as i32) { let key = unsafe { CStr::from_ptr(key as _) }; let val = unsafe { Variant::from_ptr(val as _) }; diff --git a/frida/src/script.rs b/frida/src/script.rs index 33d2affe..866e2e66 100644 --- a/frida/src/script.rs +++ b/frida/src/script.rs @@ -5,7 +5,10 @@ */ 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::{ ffi::{c_char, c_void, CStr, CString}, ptr::null_mut, @@ -13,31 +16,131 @@ use std::{ use crate::{Error, Result}; +/// Represents a Frida message +#[derive(Deserialize, Debug)] +#[serde(tag = "type")] +#[serde(rename_all = "lowercase")] +pub enum Message { + /// Message of type "send" + Send(MessageSend), + /// Message of type "log" + Log(MessageLog), + /// Message of type "error" + Error(MessageError), + /// Any other type of message. + Other(Value), +} + +/// Send Message. +#[derive(Deserialize, Debug)] +pub struct MessageSend { + /// Payload of a Send Message. + pub payload: SendPayload, +} + +/// Log Message. +#[derive(Deserialize, Debug)] +pub struct MessageLog { + /// Log Level. + pub level: MessageLogLevel, + /// Payload of a Message Log. + pub payload: String, +} + +/// Error message. +/// This message is sent when a JavaScript runtime error occurs, such as a misspelled word. +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct MessageError { + /// Error description. + pub description: String, + /// Stack trace string. + pub stack: String, + /// Script file name that failed. + pub file_name: String, + /// Line number with the error. + pub line_number: usize, + /// Column number with the error. + pub column_number: usize, +} + +/// Represents a Message Log Level Types. +/// Used by `MessageLog._level` +#[derive(Deserialize, Debug)] +#[serde(rename_all = "lowercase")] +pub enum MessageLogLevel { + /// Indicates an informal message. + Info, + /// Represents a debugging message. + Debug, + /// Signifies a warning message. + Warning, + /// Represents an error message. + Error, +} + +/// Represents a Message Log Level Types. +/// Used by `MessageLog._level` +#[derive(Deserialize, Debug)] +pub struct SendPayload { + /// Send message type + pub r#type: String, + /// Send message ID + pub id: usize, + /// Send message result. + pub result: String, + /// Send message returns. + pub returns: Value, +} + unsafe extern "C" fn call_on_message( _script_ptr: *mut _FridaScript, message: *const i8, _data: &frida_sys::_GBytes, user_data: *mut c_void, ) { - let handler: &mut I = &mut *(user_data as *mut I); + let c_msg = CStr::from_ptr(message as *const c_char) + .to_str() + .unwrap_or_default(); + + let formatted_msg: Message = serde_json::from_str(c_msg).unwrap_or_else(|err| { + Message::Other(serde_json::json!({ + "error": err.to_string(), + "data": c_msg + })) + }); - handler.on_message( - CStr::from_ptr(message as *const c_char) - .to_str() - .unwrap_or_default(), - ); + match formatted_msg { + Message::Send(msg) => { + if msg.payload.r#type == "frida:rpc" { + let callback_handler: *mut CallbackHandler = user_data as _; + on_message(callback_handler.as_mut().unwrap(), Message::Send(msg)); + } + } + _ => { + let handler: &mut I = &mut *(user_data as *mut I); + handler.on_message(&formatted_msg); + } + } +} + +fn on_message(cb_handler: &mut CallbackHandler, message: Message) { + let (tx, _) = &cb_handler.channel; + let _ = tx.send(message); } /// Represents a script signal handler. pub trait ScriptHandler { /// Handler called when a message is shared from JavaScript to Rust. - fn on_message(&mut self, message: &str); + fn on_message(&mut self, message: &Message); } /// Reprents a Frida script. pub struct Script<'a> { script_ptr: *mut _FridaScript, phantom: PhantomData<&'a _FridaScript>, + rpc_id_counter: usize, + callback_handler: CallbackHandler, } impl<'a> Script<'a> { @@ -45,6 +148,8 @@ impl<'a> Script<'a> { Script { script_ptr, phantom: PhantomData, + rpc_id_counter: 0, + callback_handler: CallbackHandler::new(), } } @@ -82,13 +187,14 @@ impl<'a> Script<'a> { /// struct Handler; /// /// impl ScriptHandler for Handler { - /// fn on_message(&mut self, message: &str) { - /// println!("{message}"); + /// fn on_message(&mut self, message: &frida::Message) { + /// println!("{:?}", message); /// } /// } /// ``` - pub fn handle_message(&self, handler: &mut I) -> Result<()> { + pub fn handle_message(&mut self, handler: I) -> Result<()> { let message = CString::new("message").map_err(|_| Error::CStringFailed)?; + self.callback_handler.add_handler(handler); unsafe { let callback = Some(std::mem::transmute::< *mut std::ffi::c_void, @@ -99,7 +205,7 @@ impl<'a> Script<'a> { self.script_ptr as _, message.as_ptr(), callback, - handler as *mut _ as *mut c_void, + (&self.callback_handler as *const _ as *mut CallbackHandler) as *mut c_void, None, 0, ) @@ -126,6 +232,48 @@ impl<'a> Script<'a> { Ok(()) } + + fn inc_id(&mut self) -> usize { + self.rpc_id_counter += 1; + self.rpc_id_counter + } + + /// List all the exported attributes from the script's rpc + pub fn list_exports(&mut self) -> Result> { + let json_req = { + let name = "frida:rpc".into(); + let id = self.inc_id().into(); + let rpc_type = "list".into(); + let rpc_function = Value::Null; + let args = Value::Null; + + let rpc_query: [Value; 5] = [name, id, rpc_type, rpc_function, args]; + + serde_json::to_string(&rpc_query).unwrap() + }; + + self.post(&json_req, None).unwrap(); + let (_, rx) = &self.callback_handler.channel; + let rpc_result = rx.recv().unwrap(); + + let func_list: Vec = match rpc_result { + Message::Send(r) => { + let tmp_list: Vec = r + .payload + .returns + .as_array() + .unwrap_or(&Vec::new()) + .iter() + .map(|i| i.as_str().unwrap_or("").to_string()) + .collect(); + + tmp_list + } + _ => Vec::new(), + }; + + Ok(func_list) + } } impl<'a> Drop for Script<'a> { @@ -204,3 +352,21 @@ impl Drop for ScriptOption { } } } + +struct CallbackHandler { + channel: (Sender, Receiver), + script_handler: Option>, +} + +impl CallbackHandler { + fn new() -> Self { + Self { + channel: channel(), + script_handler: None, + } + } + + fn add_handler(&mut self, handler: I) { + self.script_handler = Some(Box::from(handler)); + } +} diff --git a/frida/src/variant.rs b/frida/src/variant.rs index 823089b7..d9a89f2d 100644 --- a/frida/src/variant.rs +++ b/frida/src/variant.rs @@ -32,7 +32,7 @@ impl Variant { Self::String(value) } "b" => { - Self::Boolean(frida_sys::g_variant_get_boolean(variant) != frida_sys::FALSE as _) + Self::Boolean(frida_sys::g_variant_get_boolean(variant) != frida_sys::FALSE as i32) } "x" => Self::Int64(frida_sys::g_variant_get_int64(variant)), "a{sv}" => Self::Map(sv_array_to_map(variant)),