Skip to content

Commit 03af6aa

Browse files
WorksButNotTestedYour Name
andauthored
Frida scripting support (#2506)
Co-authored-by: Your Name <you@example.com>
1 parent 053d125 commit 03af6aa

File tree

8 files changed

+109
-11
lines changed

8 files changed

+109
-11
lines changed

fuzzers/binary_only/frida_executable_libpng/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ libafl = { path = "../../../libafl", features = [
2525
"frida_cli",
2626
] } #, "llmp_small_maps", "llmp_debug"]}
2727
libafl_bolts = { path = "../../../libafl_bolts" }
28-
frida-gum = { version = "0.14.0", features = [
28+
frida-gum = { version = "0.14.2", features = [
2929
"auto-download",
3030
"event-sink",
3131
"invocation-listener",
32+
"script",
3233
] }
3334
libafl_frida = { path = "../../../libafl_frida", features = ["cmplog"] }
3435
libafl_targets = { path = "../../../libafl_targets", features = [

fuzzers/binary_only/frida_libpng/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ libafl = { path = "../../../libafl", features = [
2626
"errors_backtrace",
2727
] } #, "llmp_small_maps", "llmp_debug"]}
2828
libafl_bolts = { path = "../../../libafl_bolts" }
29-
frida-gum = { version = "0.14.0", features = [
29+
frida-gum = { version = "0.14.2", features = [
3030
"auto-download",
3131
"event-sink",
3232
"invocation-listener",
33+
"script",
3334
] }
3435
libafl_frida = { path = "../../../libafl_frida", features = ["cmplog"] }
3536
libafl_targets = { path = "../../../libafl_targets", features = [
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fn main() {
2+
println!("cargo:rustc-link-arg=-rdynamic");
3+
}

fuzzers/binary_only/frida_windows_gdiplus/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ libafl = { path = "../../../libafl", features = [
2323
"errors_backtrace",
2424
] } #, "llmp_small_maps", "llmp_debug"]}
2525
libafl_bolts = { path = "../../../libafl_bolts" }
26-
frida-gum = { version = "0.14.0", features = [
26+
frida-gum = { version = "0.14.2", features = [
2727
"auto-download",
2828
"event-sink",
2929
"invocation-listener",
30+
"script",
3031
] }
3132
libafl_frida = { path = "../../../libafl_frida", features = ["cmplog"] }
3233
libafl_targets = { path = "../../../libafl_targets", features = [

libafl_bolts/src/cli.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ use alloc::{string::String, vec::Vec};
6868
use std::error;
6969
use std::{net::SocketAddr, path::PathBuf, time::Duration};
7070

71+
#[cfg(feature = "frida_cli")]
72+
use clap::ValueEnum;
7173
use clap::{Command, CommandFactory, Parser};
7274
use serde::{Deserialize, Serialize};
7375

@@ -102,6 +104,17 @@ fn parse_instrumentation_location(
102104
))
103105
}
104106

107+
/// The scripting engine to use for JavaScript scripting support
108+
#[cfg(feature = "frida_cli")]
109+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, ValueEnum, Default)]
110+
pub enum FridaScriptBackend {
111+
/// The Google V8 engine
112+
V8,
113+
/// `QuickJS` by Fabrice Bellard
114+
#[default]
115+
QuickJS,
116+
}
117+
105118
/// Top-level container for cli options/arguments/subcommands
106119
#[derive(Parser, Clone, Debug, Serialize, Deserialize)]
107120
#[command(
@@ -300,6 +313,16 @@ pub struct FuzzerOptions {
300313
requires = "replay"
301314
)]
302315
pub repeat: Option<usize>,
316+
317+
/// The backend scripting engine to use for JavaScript scripting support
318+
#[cfg(feature = "frida_cli")]
319+
#[arg(long, help_heading = "Frida Options")]
320+
pub backend: Option<FridaScriptBackend>,
321+
322+
/// The path to the Frida script to load into the target
323+
#[cfg(feature = "frida_cli")]
324+
#[arg(long, help_heading = "Frida Options")]
325+
pub script: Option<PathBuf>,
303326
}
304327

305328
impl FuzzerOptions {

libafl_frida/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,15 @@ nix = { workspace = true, default-features = true, features = ["mman"] }
6666
libc = { workspace = true }
6767
hashbrown = { workspace = true, default-features = true }
6868
rangemap = { workspace = true }
69-
frida-gum-sys = { version = "0.14.0", features = [
69+
frida-gum-sys = { version = "0.14.2", features = [
7070
"event-sink",
7171
"invocation-listener",
7272
] }
73-
frida-gum = { version = "0.14.0", features = [
73+
frida-gum = { version = "0.14.2", features = [
7474
"event-sink",
7575
"invocation-listener",
7676
"module-names",
77+
"script",
7778
] }
7879
dynasmrt = "2.0.0"
7980

libafl_frida/src/helper.rs

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
use core::fmt::{self, Debug, Formatter};
22
use std::{
33
cell::{Ref, RefCell, RefMut},
4-
fs,
4+
ffi::CStr,
5+
fs::{self, read_to_string},
56
path::{Path, PathBuf},
67
rc::Rc,
78
};
89

910
use frida_gum::{
1011
instruction_writer::InstructionWriter,
1112
stalker::{StalkerIterator, StalkerOutput, Transformer},
12-
Gum, Module, ModuleDetails, ModuleMap, PageProtection,
13+
Backend, Gum, Module, ModuleDetails, ModuleMap, PageProtection, Script,
1314
};
15+
use frida_gum_sys::gchar;
1416
use libafl::{
1517
inputs::{HasTargetBytes, Input},
1618
Error,
1719
};
18-
use libafl_bolts::{cli::FuzzerOptions, tuples::MatchFirstType};
20+
use libafl_bolts::{
21+
cli::{FridaScriptBackend, FuzzerOptions},
22+
tuples::MatchFirstType,
23+
};
1924
use libafl_targets::drcov::DrCovBasicBlock;
2025
#[cfg(unix)]
2126
use nix::sys::mman::{mmap_anonymous, MapFlags, ProtFlags};
@@ -152,6 +157,32 @@ impl FridaInstrumentationHelperBuilder {
152157
Self::default()
153158
}
154159

160+
/// Load a script
161+
///
162+
/// See [`Script::new`] for details
163+
#[must_use]
164+
pub fn load_script<F: Fn(&str, &[u8])>(
165+
self,
166+
backend: FridaScriptBackend,
167+
path: &Path,
168+
callback: Option<F>,
169+
) -> Self {
170+
let name = path
171+
.file_name()
172+
.and_then(|name| name.to_str())
173+
.expect("Failed to get script file name from path: {path:}");
174+
let script_prefix = include_str!("script.js");
175+
let file_contents = read_to_string(path).expect("Failed to read script: {path:}");
176+
let payload = script_prefix.to_string() + &file_contents;
177+
let gum = Gum::obtain();
178+
let backend = match backend {
179+
FridaScriptBackend::V8 => Backend::obtain_v8(&gum),
180+
FridaScriptBackend::QuickJS => Backend::obtain_qjs(&gum),
181+
};
182+
Script::load(&backend, name, payload, callback).unwrap();
183+
self
184+
}
185+
155186
/// Enable or disable the [`Stalker`](https://frida.re/docs/stalker/)
156187
///
157188
/// Required for all instrumentation, such as coverage collection, `ASan`, and `CmpLog`.
@@ -374,6 +405,16 @@ impl<RT> Debug for FridaInstrumentationHelper<'_, RT> {
374405
}
375406
}
376407

408+
/// A callback function to test calling back from FRIDA's JavaScript scripting support
409+
/// # Safety
410+
/// This function receives a raw pointer to a C string
411+
#[no_mangle]
412+
pub unsafe extern "C" fn test_function(message: *const gchar) {
413+
if let Ok(msg) = CStr::from_ptr(message).to_str() {
414+
println!("{msg}");
415+
}
416+
}
417+
377418
/// Helper function to get the size of a module's CODE section from frida
378419
#[must_use]
379420
pub fn get_module_size(module_name: &str) -> usize {
@@ -430,7 +471,7 @@ where
430471
.iter()
431472
.map(PathBuf::from)
432473
.collect::<Vec<_>>();
433-
FridaInstrumentationHelper::builder()
474+
let builder = FridaInstrumentationHelper::builder()
434475
.enable_stalker(options.cmplog || options.asan || !options.disable_coverage)
435476
.disable_excludes(options.disable_excludes)
436477
.instrument_module_if(move |module| pathlist_contains_module(&harness, module))
@@ -442,8 +483,22 @@ where
442483
name: name.clone(),
443484
range: *offset..*offset + 4,
444485
}
445-
}))
446-
.build(gum, runtimes)
486+
}));
487+
488+
let builder = if let Some(script) = &options.script {
489+
builder.load_script(
490+
options.backend.unwrap_or_default(),
491+
script,
492+
Some(FridaInstrumentationHelper::<RT>::script_callback),
493+
)
494+
} else {
495+
builder
496+
};
497+
builder.build(gum, runtimes)
498+
}
499+
500+
fn script_callback(msg: &str, bytes: &[u8]) {
501+
println!("msg: {msg:}, bytes: {bytes:x?}");
447502
}
448503

449504
#[allow(clippy::too_many_lines)]

libafl_frida/src/script.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"use strict";
2+
class LibAfl {
3+
static testFunction(message) {
4+
const buf = Memory.allocUtf8String(message);
5+
LibAfl.jsApiTestFunction(buf);
6+
}
7+
8+
static jsApiGetFunction(name, retType, argTypes) {
9+
const addr = Module.getExportByName(null, name);
10+
return new NativeFunction(addr, retType, argTypes);
11+
}
12+
};
13+
LibAfl.jsApiTestFunction = LibAfl.jsApiGetFunction("test_function", "void", ["pointer"]);

0 commit comments

Comments
 (0)