diff --git a/fuzzers/binary_only/frida_executable_libpng/Cargo.toml b/fuzzers/binary_only/frida_executable_libpng/Cargo.toml index 56d438116f..e9f3ccee2e 100644 --- a/fuzzers/binary_only/frida_executable_libpng/Cargo.toml +++ b/fuzzers/binary_only/frida_executable_libpng/Cargo.toml @@ -25,10 +25,11 @@ libafl = { path = "../../../libafl", features = [ "frida_cli", ] } #, "llmp_small_maps", "llmp_debug"]} libafl_bolts = { path = "../../../libafl_bolts" } -frida-gum = { version = "0.14.0", features = [ +frida-gum = { version = "0.14.2", features = [ "auto-download", "event-sink", "invocation-listener", + "script", ] } libafl_frida = { path = "../../../libafl_frida", features = ["cmplog"] } libafl_targets = { path = "../../../libafl_targets", features = [ diff --git a/fuzzers/binary_only/frida_libpng/Cargo.toml b/fuzzers/binary_only/frida_libpng/Cargo.toml index dd1ce4b950..fefbf6a459 100644 --- a/fuzzers/binary_only/frida_libpng/Cargo.toml +++ b/fuzzers/binary_only/frida_libpng/Cargo.toml @@ -26,10 +26,11 @@ libafl = { path = "../../../libafl", features = [ "errors_backtrace", ] } #, "llmp_small_maps", "llmp_debug"]} libafl_bolts = { path = "../../../libafl_bolts" } -frida-gum = { version = "0.14.0", features = [ +frida-gum = { version = "0.14.2", features = [ "auto-download", "event-sink", "invocation-listener", + "script", ] } libafl_frida = { path = "../../../libafl_frida", features = ["cmplog"] } libafl_targets = { path = "../../../libafl_targets", features = [ diff --git a/fuzzers/binary_only/frida_libpng/build.rs b/fuzzers/binary_only/frida_libpng/build.rs new file mode 100644 index 0000000000..538285b196 --- /dev/null +++ b/fuzzers/binary_only/frida_libpng/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rustc-link-arg=-rdynamic"); +} diff --git a/fuzzers/binary_only/frida_windows_gdiplus/Cargo.toml b/fuzzers/binary_only/frida_windows_gdiplus/Cargo.toml index cde9880a7b..f5d0ad47a2 100644 --- a/fuzzers/binary_only/frida_windows_gdiplus/Cargo.toml +++ b/fuzzers/binary_only/frida_windows_gdiplus/Cargo.toml @@ -23,10 +23,11 @@ libafl = { path = "../../../libafl", features = [ "errors_backtrace", ] } #, "llmp_small_maps", "llmp_debug"]} libafl_bolts = { path = "../../../libafl_bolts" } -frida-gum = { version = "0.14.0", features = [ +frida-gum = { version = "0.14.2", features = [ "auto-download", "event-sink", "invocation-listener", + "script", ] } libafl_frida = { path = "../../../libafl_frida", features = ["cmplog"] } libafl_targets = { path = "../../../libafl_targets", features = [ diff --git a/libafl_bolts/src/cli.rs b/libafl_bolts/src/cli.rs index f97437b6ef..51cc199b76 100644 --- a/libafl_bolts/src/cli.rs +++ b/libafl_bolts/src/cli.rs @@ -68,6 +68,8 @@ use alloc::{string::String, vec::Vec}; use std::error; use std::{net::SocketAddr, path::PathBuf, time::Duration}; +#[cfg(feature = "frida_cli")] +use clap::ValueEnum; use clap::{Command, CommandFactory, Parser}; 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 #[derive(Parser, Clone, Debug, Serialize, Deserialize)] #[command( @@ -300,6 +313,16 @@ pub struct FuzzerOptions { requires = "replay" )] pub repeat: Option, + + /// The backend scripting engine to use for JavaScript scripting support + #[cfg(feature = "frida_cli")] + #[arg(long, help_heading = "Frida Options")] + pub backend: Option, + + /// The path to the Frida script to load into the target + #[cfg(feature = "frida_cli")] + #[arg(long, help_heading = "Frida Options")] + pub script: Option, } impl FuzzerOptions { diff --git a/libafl_frida/Cargo.toml b/libafl_frida/Cargo.toml index eab21ba289..0346982f50 100644 --- a/libafl_frida/Cargo.toml +++ b/libafl_frida/Cargo.toml @@ -66,14 +66,15 @@ nix = { workspace = true, default-features = true, features = ["mman"] } libc = { workspace = true } hashbrown = { workspace = true, default-features = true } rangemap = { workspace = true } -frida-gum-sys = { version = "0.14.0", features = [ +frida-gum-sys = { version = "0.14.2", features = [ "event-sink", "invocation-listener", ] } -frida-gum = { version = "0.14.0", features = [ +frida-gum = { version = "0.14.2", features = [ "event-sink", "invocation-listener", "module-names", + "script", ] } dynasmrt = "2.0.0" diff --git a/libafl_frida/src/helper.rs b/libafl_frida/src/helper.rs index b0e1ea1f7f..d9718987f5 100644 --- a/libafl_frida/src/helper.rs +++ b/libafl_frida/src/helper.rs @@ -1,7 +1,8 @@ use core::fmt::{self, Debug, Formatter}; use std::{ cell::{Ref, RefCell, RefMut}, - fs, + ffi::CStr, + fs::{self, read_to_string}, path::{Path, PathBuf}, rc::Rc, }; @@ -9,13 +10,17 @@ use std::{ use frida_gum::{ instruction_writer::InstructionWriter, stalker::{StalkerIterator, StalkerOutput, Transformer}, - Gum, Module, ModuleDetails, ModuleMap, PageProtection, + Backend, Gum, Module, ModuleDetails, ModuleMap, PageProtection, Script, }; +use frida_gum_sys::gchar; use libafl::{ inputs::{HasTargetBytes, Input}, Error, }; -use libafl_bolts::{cli::FuzzerOptions, tuples::MatchFirstType}; +use libafl_bolts::{ + cli::{FridaScriptBackend, FuzzerOptions}, + tuples::MatchFirstType, +}; use libafl_targets::drcov::DrCovBasicBlock; #[cfg(unix)] use nix::sys::mman::{mmap_anonymous, MapFlags, ProtFlags}; @@ -152,6 +157,32 @@ impl FridaInstrumentationHelperBuilder { Self::default() } + /// Load a script + /// + /// See [`Script::new`] for details + #[must_use] + pub fn load_script( + self, + backend: FridaScriptBackend, + path: &Path, + callback: Option, + ) -> 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/) /// /// Required for all instrumentation, such as coverage collection, `ASan`, and `CmpLog`. @@ -374,6 +405,16 @@ impl 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 #[must_use] pub fn get_module_size(module_name: &str) -> usize { @@ -430,7 +471,7 @@ where .iter() .map(PathBuf::from) .collect::>(); - FridaInstrumentationHelper::builder() + let builder = FridaInstrumentationHelper::builder() .enable_stalker(options.cmplog || options.asan || !options.disable_coverage) .disable_excludes(options.disable_excludes) .instrument_module_if(move |module| pathlist_contains_module(&harness, module)) @@ -442,8 +483,22 @@ where name: name.clone(), 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::::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)] diff --git a/libafl_frida/src/script.js b/libafl_frida/src/script.js new file mode 100644 index 0000000000..6b4be28a3e --- /dev/null +++ b/libafl_frida/src/script.js @@ -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"]);