Skip to content

Commit

Permalink
Added script.exports. Fixes #105 (#159)
Browse files Browse the repository at this point in the history
* script.exports added

* new line added

* exports is now exports.call

* Fix CI issue

* s1341's notes addressed
  • Loading branch information
Xoffio authored Sep 29, 2024
1 parent 456e68e commit 38ef99d
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 13 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
12 changes: 12 additions & 0 deletions examples/core/rpc_execute_function/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
18 changes: 18 additions & 0 deletions examples/core/rpc_execute_function/README.md
Original file line number Diff line number Diff line change
@@ -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...
```
119 changes: 119 additions & 0 deletions examples/core/rpc_execute_function/src/main.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}
12 changes: 12 additions & 0 deletions frida/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
}
106 changes: 93 additions & 13 deletions frida/src/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<RefCell<usize>>,
callback_handler: Rc<RefCell<CallbackHandler>>,
///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<RefCell<usize>>,
callback_handler: Rc<RefCell<CallbackHandler>>,
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,
},
}
}

Expand Down Expand Up @@ -194,7 +219,10 @@ impl<'a> Script<'a> {
/// ```
pub fn handle_message<I: ScriptHandler + 'static>(&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,
Expand All @@ -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,
)
Expand Down Expand Up @@ -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
Expand All @@ -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<String> = match rpc_result {
Expand All @@ -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<Value>) -> Result<Option<Value>> {
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 _) }
Expand Down

0 comments on commit 38ef99d

Please sign in to comment.