Frida scripting support (#2506)

Co-authored-by: Your Name <you@example.com>
This commit is contained in:
WorksButNotTested 2024-10-25 16:18:25 +01:00 committed by GitHub
parent 053d125254
commit 03af6aaf0c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 109 additions and 11 deletions

View File

@ -25,10 +25,11 @@ libafl = { path = "../../../libafl", features = [
"frida_cli", "frida_cli",
] } #, "llmp_small_maps", "llmp_debug"]} ] } #, "llmp_small_maps", "llmp_debug"]}
libafl_bolts = { path = "../../../libafl_bolts" } libafl_bolts = { path = "../../../libafl_bolts" }
frida-gum = { version = "0.14.0", features = [ frida-gum = { version = "0.14.2", features = [
"auto-download", "auto-download",
"event-sink", "event-sink",
"invocation-listener", "invocation-listener",
"script",
] } ] }
libafl_frida = { path = "../../../libafl_frida", features = ["cmplog"] } libafl_frida = { path = "../../../libafl_frida", features = ["cmplog"] }
libafl_targets = { path = "../../../libafl_targets", features = [ libafl_targets = { path = "../../../libafl_targets", features = [

View File

@ -26,10 +26,11 @@ libafl = { path = "../../../libafl", features = [
"errors_backtrace", "errors_backtrace",
] } #, "llmp_small_maps", "llmp_debug"]} ] } #, "llmp_small_maps", "llmp_debug"]}
libafl_bolts = { path = "../../../libafl_bolts" } libafl_bolts = { path = "../../../libafl_bolts" }
frida-gum = { version = "0.14.0", features = [ frida-gum = { version = "0.14.2", features = [
"auto-download", "auto-download",
"event-sink", "event-sink",
"invocation-listener", "invocation-listener",
"script",
] } ] }
libafl_frida = { path = "../../../libafl_frida", features = ["cmplog"] } libafl_frida = { path = "../../../libafl_frida", features = ["cmplog"] }
libafl_targets = { path = "../../../libafl_targets", features = [ libafl_targets = { path = "../../../libafl_targets", features = [

View File

@ -0,0 +1,3 @@
fn main() {
println!("cargo:rustc-link-arg=-rdynamic");
}

View File

@ -23,10 +23,11 @@ libafl = { path = "../../../libafl", features = [
"errors_backtrace", "errors_backtrace",
] } #, "llmp_small_maps", "llmp_debug"]} ] } #, "llmp_small_maps", "llmp_debug"]}
libafl_bolts = { path = "../../../libafl_bolts" } libafl_bolts = { path = "../../../libafl_bolts" }
frida-gum = { version = "0.14.0", features = [ frida-gum = { version = "0.14.2", features = [
"auto-download", "auto-download",
"event-sink", "event-sink",
"invocation-listener", "invocation-listener",
"script",
] } ] }
libafl_frida = { path = "../../../libafl_frida", features = ["cmplog"] } libafl_frida = { path = "../../../libafl_frida", features = ["cmplog"] }
libafl_targets = { path = "../../../libafl_targets", features = [ libafl_targets = { path = "../../../libafl_targets", features = [

View File

@ -68,6 +68,8 @@ use alloc::{string::String, vec::Vec};
use std::error; use std::error;
use std::{net::SocketAddr, path::PathBuf, time::Duration}; use std::{net::SocketAddr, path::PathBuf, time::Duration};
#[cfg(feature = "frida_cli")]
use clap::ValueEnum;
use clap::{Command, CommandFactory, Parser}; use clap::{Command, CommandFactory, Parser};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -102,6 +104,17 @@ fn parse_instrumentation_location(
)) ))
} }
/// The scripting engine to use for JavaScript scripting support
#[cfg(feature = "frida_cli")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, ValueEnum, Default)]
pub enum FridaScriptBackend {
/// The Google V8 engine
V8,
/// `QuickJS` by Fabrice Bellard
#[default]
QuickJS,
}
/// Top-level container for cli options/arguments/subcommands /// Top-level container for cli options/arguments/subcommands
#[derive(Parser, Clone, Debug, Serialize, Deserialize)] #[derive(Parser, Clone, Debug, Serialize, Deserialize)]
#[command( #[command(
@ -300,6 +313,16 @@ pub struct FuzzerOptions {
requires = "replay" requires = "replay"
)] )]
pub repeat: Option<usize>, pub repeat: Option<usize>,
/// The backend scripting engine to use for JavaScript scripting support
#[cfg(feature = "frida_cli")]
#[arg(long, help_heading = "Frida Options")]
pub backend: Option<FridaScriptBackend>,
/// The path to the Frida script to load into the target
#[cfg(feature = "frida_cli")]
#[arg(long, help_heading = "Frida Options")]
pub script: Option<PathBuf>,
} }
impl FuzzerOptions { impl FuzzerOptions {

View File

@ -66,14 +66,15 @@ nix = { workspace = true, default-features = true, features = ["mman"] }
libc = { workspace = true } libc = { workspace = true }
hashbrown = { workspace = true, default-features = true } hashbrown = { workspace = true, default-features = true }
rangemap = { workspace = true } rangemap = { workspace = true }
frida-gum-sys = { version = "0.14.0", features = [ frida-gum-sys = { version = "0.14.2", features = [
"event-sink", "event-sink",
"invocation-listener", "invocation-listener",
] } ] }
frida-gum = { version = "0.14.0", features = [ frida-gum = { version = "0.14.2", features = [
"event-sink", "event-sink",
"invocation-listener", "invocation-listener",
"module-names", "module-names",
"script",
] } ] }
dynasmrt = "2.0.0" dynasmrt = "2.0.0"

View File

@ -1,7 +1,8 @@
use core::fmt::{self, Debug, Formatter}; use core::fmt::{self, Debug, Formatter};
use std::{ use std::{
cell::{Ref, RefCell, RefMut}, cell::{Ref, RefCell, RefMut},
fs, ffi::CStr,
fs::{self, read_to_string},
path::{Path, PathBuf}, path::{Path, PathBuf},
rc::Rc, rc::Rc,
}; };
@ -9,13 +10,17 @@ use std::{
use frida_gum::{ use frida_gum::{
instruction_writer::InstructionWriter, instruction_writer::InstructionWriter,
stalker::{StalkerIterator, StalkerOutput, Transformer}, stalker::{StalkerIterator, StalkerOutput, Transformer},
Gum, Module, ModuleDetails, ModuleMap, PageProtection, Backend, Gum, Module, ModuleDetails, ModuleMap, PageProtection, Script,
}; };
use frida_gum_sys::gchar;
use libafl::{ use libafl::{
inputs::{HasTargetBytes, Input}, inputs::{HasTargetBytes, Input},
Error, Error,
}; };
use libafl_bolts::{cli::FuzzerOptions, tuples::MatchFirstType}; use libafl_bolts::{
cli::{FridaScriptBackend, FuzzerOptions},
tuples::MatchFirstType,
};
use libafl_targets::drcov::DrCovBasicBlock; use libafl_targets::drcov::DrCovBasicBlock;
#[cfg(unix)] #[cfg(unix)]
use nix::sys::mman::{mmap_anonymous, MapFlags, ProtFlags}; use nix::sys::mman::{mmap_anonymous, MapFlags, ProtFlags};
@ -152,6 +157,32 @@ impl FridaInstrumentationHelperBuilder {
Self::default() Self::default()
} }
/// Load a script
///
/// See [`Script::new`] for details
#[must_use]
pub fn load_script<F: Fn(&str, &[u8])>(
self,
backend: FridaScriptBackend,
path: &Path,
callback: Option<F>,
) -> Self {
let name = path
.file_name()
.and_then(|name| name.to_str())
.expect("Failed to get script file name from path: {path:}");
let script_prefix = include_str!("script.js");
let file_contents = read_to_string(path).expect("Failed to read script: {path:}");
let payload = script_prefix.to_string() + &file_contents;
let gum = Gum::obtain();
let backend = match backend {
FridaScriptBackend::V8 => Backend::obtain_v8(&gum),
FridaScriptBackend::QuickJS => Backend::obtain_qjs(&gum),
};
Script::load(&backend, name, payload, callback).unwrap();
self
}
/// Enable or disable the [`Stalker`](https://frida.re/docs/stalker/) /// Enable or disable the [`Stalker`](https://frida.re/docs/stalker/)
/// ///
/// Required for all instrumentation, such as coverage collection, `ASan`, and `CmpLog`. /// Required for all instrumentation, such as coverage collection, `ASan`, and `CmpLog`.
@ -374,6 +405,16 @@ impl<RT> Debug for FridaInstrumentationHelper<'_, RT> {
} }
} }
/// A callback function to test calling back from FRIDA's JavaScript scripting support
/// # Safety
/// This function receives a raw pointer to a C string
#[no_mangle]
pub unsafe extern "C" fn test_function(message: *const gchar) {
if let Ok(msg) = CStr::from_ptr(message).to_str() {
println!("{msg}");
}
}
/// Helper function to get the size of a module's CODE section from frida /// Helper function to get the size of a module's CODE section from frida
#[must_use] #[must_use]
pub fn get_module_size(module_name: &str) -> usize { pub fn get_module_size(module_name: &str) -> usize {
@ -430,7 +471,7 @@ where
.iter() .iter()
.map(PathBuf::from) .map(PathBuf::from)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
FridaInstrumentationHelper::builder() let builder = FridaInstrumentationHelper::builder()
.enable_stalker(options.cmplog || options.asan || !options.disable_coverage) .enable_stalker(options.cmplog || options.asan || !options.disable_coverage)
.disable_excludes(options.disable_excludes) .disable_excludes(options.disable_excludes)
.instrument_module_if(move |module| pathlist_contains_module(&harness, module)) .instrument_module_if(move |module| pathlist_contains_module(&harness, module))
@ -442,8 +483,22 @@ where
name: name.clone(), name: name.clone(),
range: *offset..*offset + 4, range: *offset..*offset + 4,
} }
})) }));
.build(gum, runtimes)
let builder = if let Some(script) = &options.script {
builder.load_script(
options.backend.unwrap_or_default(),
script,
Some(FridaInstrumentationHelper::<RT>::script_callback),
)
} else {
builder
};
builder.build(gum, runtimes)
}
fn script_callback(msg: &str, bytes: &[u8]) {
println!("msg: {msg:}, bytes: {bytes:x?}");
} }
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]

View File

@ -0,0 +1,13 @@
"use strict";
class LibAfl {
static testFunction(message) {
const buf = Memory.allocUtf8String(message);
LibAfl.jsApiTestFunction(buf);
}
static jsApiGetFunction(name, retType, argTypes) {
const addr = Module.getExportByName(null, name);
return new NativeFunction(addr, retType, argTypes);
}
};
LibAfl.jsApiTestFunction = LibAfl.jsApiGetFunction("test_function", "void", ["pointer"]);