Frida scripting support (#2506)
Co-authored-by: Your Name <you@example.com>
This commit is contained in:
parent
053d125254
commit
03af6aaf0c
@ -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 = [
|
||||||
|
@ -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 = [
|
||||||
|
3
fuzzers/binary_only/frida_libpng/build.rs
Normal file
3
fuzzers/binary_only/frida_libpng/build.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
println!("cargo:rustc-link-arg=-rdynamic");
|
||||||
|
}
|
@ -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 = [
|
||||||
|
@ -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 {
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
@ -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)]
|
||||||
|
13
libafl_frida/src/script.js
Normal file
13
libafl_frida/src/script.js
Normal 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"]);
|
Loading…
x
Reference in New Issue
Block a user