Skip to content

Commit

Permalink
script.exports added
Browse files Browse the repository at this point in the history
  • Loading branch information
Xoffio committed Sep 11, 2024
1 parent fea112a commit 9504342
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 3 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
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
Error: Error on the JavaScript side: "unable to find method 'NonExistentFunc'"
[*] Script unloaded
[*] Session detached
Exiting...
```
115 changes: 115 additions & 0 deletions examples/core/rpc_execute_function/src/main.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}
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,
},
}
43 changes: 41 additions & 2 deletions frida/src/script.rs
Original file line number Diff line number Diff line change
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 @@ -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<Value>) -> Result<Option<Value>> {
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<Vec<String>> {
let json_req = {
Expand Down

0 comments on commit 9504342

Please sign in to comment.