Frida updates and FASAN fixes (#2838)

* Frida updates and FASAN fixes

* fmt

* Fixes

* clippy

* fmt

* Clippy

* Update to frida 0.16.2

* fix windows hooks

* Fix

* Fmt

* windows fix

* Bump frida version to 0.16.3

* Get rid of call to LLVMFuzzerInitialize

* bump version 0.16.5; use find_global_export_by_name

* allow unused_macro_rules

* Don't do stdout_file on windows

* fmt

* Add tmate to debug

* fix windows frida_libpng

---------

Co-authored-by: Dongjia "toka" Zhang <tokazerkje@outlook.com>
This commit is contained in:
s1341 2025-01-28 11:48:52 +02:00 committed by GitHub
parent ba0da5121b
commit 7c84a7903a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 924 additions and 499 deletions

View File

@ -25,7 +25,7 @@ libafl = { path = "../../../libafl", features = [
"frida_cli",
] } #, "llmp_small_maps", "llmp_debug"]}
libafl_bolts = { path = "../../../libafl_bolts" }
frida-gum = { version = "0.15.1", features = [
frida-gum = { version = "0.16.5", features = [
"auto-download",
"event-sink",
"invocation-listener",

View File

@ -16,6 +16,7 @@ lto = true
codegen-units = 1
opt-level = 3
debug = true
panic = 'abort'
[dependencies]
libafl = { path = "../../../libafl", features = [
@ -26,7 +27,7 @@ libafl = { path = "../../../libafl", features = [
"errors_backtrace",
] } #, "llmp_small_maps", "llmp_debug"]}
libafl_bolts = { path = "../../../libafl_bolts" }
frida-gum = { version = "0.15.1", features = [
frida-gum = { version = "0.16.5", features = [
"auto-download",
"event-sink",
"invocation-listener",
@ -37,7 +38,9 @@ libafl_targets = { path = "../../../libafl_targets", features = [
"sancov_cmplog",
] }
libloading = "0.8.5"
log = { version = "0.4.22", features = ["release_max_level_info"] }
mimalloc = { version = "0.1.43", default-features = false }
color-backtrace = "0.6.1"
log = { version = "0.4.22", features = ["release_max_level_trace"] }
mimalloc = { version = "0.1.43", default-features = true, features = [
"local_dynamic_tls",
] }
color-backtrace = { version = "0.6.1", features = ["resolve-modules"] }
env_logger = "0.11.5"

View File

@ -25,8 +25,6 @@ use libafl::{
state::{HasCorpus, StdState},
Error, HasMetadata,
};
#[cfg(unix)]
use libafl::{feedback_and_fast, feedbacks::ConstFeedback};
use libafl_bolts::{
cli::{parse_args, FuzzerOptions},
rands::StdRand,
@ -56,19 +54,16 @@ pub fn main() {
color_backtrace::install();
let options = parse_args();
unsafe {
match fuzz(&options) {
Ok(()) | Err(Error::ShuttingDown) => println!("\nFinished fuzzing. Good bye."),
Err(e) => panic!("Error during fuzzing: {e:?}"),
}
log::info!("Frida fuzzer starting up.");
match fuzz(&options) {
Ok(()) | Err(Error::ShuttingDown) => println!("\nFinished fuzzing. Good bye."),
Err(e) => panic!("Error during fuzzing: {e:?}"),
}
}
/// The actual fuzzer
#[expect(clippy::too_many_lines)]
unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
log::info!("Frida fuzzer starting up.");
fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
// 'While the stats are state, they are usually used in the broker - which is likely never restarted
let monitor = MultiMonitor::new(|s| println!("{s}"));
@ -81,193 +76,185 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
};
let mut run_client = |state: Option<_>,
mgr: LlmpRestartingEventManager<_, _, _, _, _>,
mut mgr: LlmpRestartingEventManager<_, _, _, _, _>,
client_description: ClientDescription| {
// The restarting state will spawn the same process again as child, then restarted it each time it crashes.
// println!("{:?}", mgr.mgr_id());
let lib = libloading::Library::new(options.clone().harness.unwrap()).unwrap();
let lib = unsafe { libloading::Library::new(options.clone().harness.unwrap()).unwrap() };
let target_func: libloading::Symbol<
unsafe extern "C" fn(data: *const u8, size: usize) -> i32,
> = lib.get(options.harness_function.as_bytes()).unwrap();
> = unsafe { lib.get(options.harness_function.as_bytes()).unwrap() };
let mut frida_harness = |input: &BytesInput| {
let target = input.target_bytes();
let buf = target.as_slice();
(target_func)(buf.as_ptr(), buf.len());
unsafe { (target_func)(buf.as_ptr(), buf.len()) };
ExitKind::Ok
};
// if options.asan && options.asan_cores.contains(client_description.core_id()) {
(|state: Option<_>,
mut mgr: LlmpRestartingEventManager<_, _, _, _, _>,
_client_description| {
let gum = Gum::obtain();
let gum = Gum::obtain();
let coverage = CoverageRuntime::new();
let asan = AsanRuntime::new(options);
let cmplog = CmpLogRuntime::new();
let coverage = CoverageRuntime::new();
let asan = AsanRuntime::new(options);
let cmplog = CmpLogRuntime::new();
let client_description_clone = client_description.clone();
let options_clone = options.clone();
let client_description_clone2 = client_description.clone();
let options_clone2 = options.clone();
let mut frida_helper = FridaInstrumentationHelper::new(
&gum,
options,
tuple_list!(
IfElseRuntime::new(
move || Ok(is_asan(&options_clone, &client_description_clone)),
tuple_list!(asan),
tuple_list!()
),
IfElseRuntime::new(
move || Ok(is_cmplog(&options_clone2, &client_description_clone2)),
tuple_list!(cmplog),
tuple_list!()
),
coverage
),
);
// Create an observation channel using the coverage map
let edges_observer = HitcountsMapObserver::new(StdMapObserver::from_mut_ptr(
"edges",
frida_helper.map_mut_ptr().unwrap(),
MAP_SIZE,
))
.track_indices();
// Create an observation channel to keep track of the execution time
let time_observer = TimeObserver::new("time");
let asan_observer = AsanErrorsObserver::from_static_asan_errors();
// Feedback to rate the interestingness of an input
// This one is composed by two Feedbacks in OR
let mut feedback = feedback_or!(
// New maximization map feedback linked to the edges observer and the feedback state
MaxMapFeedback::new(&edges_observer),
// Time feedback, this one does not need a feedback state
TimeFeedback::new(&time_observer)
);
// Feedbacks to recognize an input as solution
#[cfg(unix)]
let mut objective = feedback_or_fast!(
CrashFeedback::new(),
TimeoutFeedback::new(),
// true enables the AsanErrorFeedback
feedback_and_fast!(
ConstFeedback::from(true),
AsanErrorsFeedback::new(&asan_observer)
)
);
#[cfg(windows)]
let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new());
// If not restarting, create a State from scratch
let mut state = state.unwrap_or_else(|| {
StdState::new(
// RNG
StdRand::new(),
// Corpus that will be evolved, we keep it in memory for performance
CachedOnDiskCorpus::no_meta(PathBuf::from("./corpus_discovered"), 64).unwrap(),
// Corpus in which we store solutions (crashes in this example),
// on disk so the user can get them after stopping the fuzzer
OnDiskCorpus::new(options.output.clone()).unwrap(),
&mut feedback,
&mut objective,
)
.unwrap()
});
println!("We're a client, let's fuzz :)");
// Create a PNG dictionary if not existing
if state.metadata_map().get::<Tokens>().is_none() {
state.add_metadata(Tokens::from([
vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header
b"IHDR".to_vec(),
b"IDAT".to_vec(),
b"PLTE".to_vec(),
b"IEND".to_vec(),
]));
}
// Setup a basic mutator with a mutational stage
let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations()));
// A minimization+queue policy to get testcasess from the corpus
let scheduler =
IndexesLenTimeMinimizerScheduler::new(&edges_observer, QueueScheduler::new());
// A fuzzer with feedbacks and a corpus scheduler
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
#[cfg(unix)]
let observers = tuple_list!(edges_observer, time_observer, asan_observer);
#[cfg(windows)]
let observers = tuple_list!(edges_observer, time_observer);
// Create the executor for an in-process function with just one observer for edge coverage
let mut executor = FridaInProcessExecutor::new(
&gum,
InProcessExecutor::new(
&mut frida_harness,
observers,
&mut fuzzer,
&mut state,
&mut mgr,
)?,
&mut frida_helper,
);
// Create an observation channel using cmplog map
let cmplog_observer = CmpLogObserver::new("cmplog", true);
let mut executor = ShadowExecutor::new(executor, tuple_list!(cmplog_observer));
let tracing = ShadowTracingStage::new(&mut executor);
// Setup a randomic Input2State stage
let i2s = StdMutationalStage::new(StdScheduledMutator::new(tuple_list!(
I2SRandReplace::new()
)));
// In case the corpus is empty (on first run), reset
if state.must_load_initial_inputs() {
state
.load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &options.input)
.unwrap_or_else(|_| {
panic!("Failed to load initial corpus at {:?}", &options.input)
});
println!("We imported {} inputs from disk.", state.corpus().count());
}
let mut stages = tuple_list!(
IfElseStage::new(
|_, _, _, _| Ok(is_cmplog(&options, &client_description)),
tuple_list!(tracing, i2s),
let client_description_clone = client_description.clone();
let options_clone = options.clone();
let client_description_clone2 = client_description.clone();
let options_clone2 = options.clone();
let mut frida_helper = FridaInstrumentationHelper::new(
&gum,
options,
tuple_list!(
IfElseRuntime::new(
move || Ok(is_asan(&options_clone, &client_description_clone)),
tuple_list!(asan),
tuple_list!()
),
StdMutationalStage::new(mutator)
);
IfElseRuntime::new(
move || Ok(is_cmplog(&options_clone2, &client_description_clone2)),
tuple_list!(cmplog),
tuple_list!()
),
coverage
),
);
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?;
// Create an observation channel using the coverage map
let edges_observer = HitcountsMapObserver::new(unsafe {
StdMapObserver::from_mut_ptr("edges", frida_helper.map_mut_ptr().unwrap(), MAP_SIZE)
})
.track_indices();
Ok(())
})(state, mgr, client_description.clone())
// Create an observation channel to keep track of the execution time
let time_observer = TimeObserver::new("time");
let asan_observer = AsanErrorsObserver::from_static_asan_errors();
// Feedback to rate the interestingness of an input
// This one is composed by two Feedbacks in OR
let mut feedback = feedback_or!(
// New maximization map feedback linked to the edges observer and the feedback state
MaxMapFeedback::new(&edges_observer),
// Time feedback, this one does not need a feedback state
TimeFeedback::new(&time_observer)
);
// Feedbacks to recognize an input as solution
let mut objective = feedback_or_fast!(
CrashFeedback::new(),
AsanErrorsFeedback::new(&asan_observer),
TimeoutFeedback::new(),
);
// If not restarting, create a State from scratch
let mut state = state.unwrap_or_else(|| {
StdState::new(
// RNG
StdRand::new(),
// Corpus that will be evolved, we keep it in memory for performance
CachedOnDiskCorpus::no_meta(PathBuf::from("./corpus_discovered"), 64).unwrap(),
// Corpus in which we store solutions (crashes in this example),
// on disk so the user can get them after stopping the fuzzer
OnDiskCorpus::new(options.output.clone()).unwrap(),
&mut feedback,
&mut objective,
)
.unwrap()
});
println!("We're a client, let's fuzz :)");
// Create a PNG dictionary if not existing
if state.metadata_map().get::<Tokens>().is_none() {
state.add_metadata(Tokens::from([
vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header
b"IHDR".to_vec(),
b"IDAT".to_vec(),
b"PLTE".to_vec(),
b"IEND".to_vec(),
]));
}
// Setup a basic mutator with a mutational stage
let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations()));
// A minimization+queue policy to get testcasess from the corpus
let scheduler =
IndexesLenTimeMinimizerScheduler::new(&edges_observer, QueueScheduler::new());
// A fuzzer with feedbacks and a corpus scheduler
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
let observers = tuple_list!(edges_observer, time_observer, asan_observer);
// Create the executor for an in-process function with just one observer for edge coverage
let executor = FridaInProcessExecutor::new(
&gum,
InProcessExecutor::with_timeout(
&mut frida_harness,
observers,
&mut fuzzer,
&mut state,
&mut mgr,
options.timeout,
)?,
&mut frida_helper,
);
// Create an observation channel using cmplog map
let cmplog_observer = CmpLogObserver::new("cmplog", true);
let mut executor = ShadowExecutor::new(executor, tuple_list!(cmplog_observer));
let tracing = ShadowTracingStage::new(&mut executor);
// Setup a randomic Input2State stage
let i2s =
StdMutationalStage::new(StdScheduledMutator::new(tuple_list!(I2SRandReplace::new())));
// In case the corpus is empty (on first run), reset
if state.must_load_initial_inputs() {
state
.load_initial_inputs_multicore(
&mut fuzzer,
&mut executor,
&mut mgr,
&options.input,
&client_description.core_id(),
&options.cores,
)
.unwrap_or_else(|_| {
panic!("Failed to load initial corpus at {:?}", &options.input)
});
println!("We imported {} inputs from disk.", state.corpus().count());
}
let mut stages = tuple_list!(
IfElseStage::new(
|_, _, _, _| Ok(is_cmplog(&options, &client_description)),
tuple_list!(tracing, i2s),
tuple_list!()
),
StdMutationalStage::new(mutator)
);
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?;
Ok(())
};
Launcher::builder()
let builder = Launcher::builder()
.configuration(EventConfig::AlwaysUnique)
.shmem_provider(shmem_provider)
.monitor(monitor)
.run_client(&mut run_client)
.cores(&options.cores)
.broker_port(options.broker_port)
// .stdout_file(Some(&options.stdout))
.remote_broker_addr(options.remote_broker_addr)
.build()
.launch()
.remote_broker_addr(options.remote_broker_addr);
#[cfg(not(windows))]
let builder = builder.stdout_file(Some(&options.stdout));
builder.build().launch()
}

View File

@ -23,7 +23,7 @@ libafl = { path = "../../../libafl", features = [
"errors_backtrace",
] } #, "llmp_small_maps", "llmp_debug"]}
libafl_bolts = { path = "../../../libafl_bolts" }
frida-gum = { version = "0.15.1", features = [
frida-gum = { version = "0.16.5", features = [
"auto-download",
"event-sink",
"invocation-listener",

View File

@ -62,15 +62,16 @@ 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.15.1", features = [
frida-gum-sys = { version = "0.16.5", features = [
"event-sink",
"invocation-listener",
] }
frida-gum = { version = "0.15.1", features = [
frida-gum = { version = "0.16.5", features = [
"event-sink",
"invocation-listener",
"module-names",
"script",
"backtrace",
] }
dynasmrt = "3.0.1"
@ -98,7 +99,6 @@ serial_test = { workspace = true, default-features = false, features = [
clap = { workspace = true, features = ["derive"] }
libloading = "0.8.5"
mimalloc = { workspace = true, default-features = false }
dlmalloc = { version = "0.2.6", features = ["global"] }
[lints]
workspace = true

View File

@ -14,6 +14,7 @@ fn main() {
let target_family = std::env::var("CARGO_CFG_TARGET_FAMILY").unwrap();
// Force linking against libc++
#[cfg(not(target_vendor = "apple"))]
if target_family == "unix" {
println!("cargo:rustc-link-lib=dylib=c++");
}

View File

@ -11,7 +11,6 @@ use std::{collections::BTreeMap, ffi::c_void};
use backtrace::Backtrace;
use frida_gum::{PageProtection, RangeDetails};
use hashbrown::HashMap;
use libafl_bolts::cli::FuzzerOptions;
#[cfg(target_vendor = "apple")]
use mach_sys::{
@ -59,9 +58,9 @@ pub struct Allocator {
/// Whether we've pre allocated a shadow mapping:
using_pre_allocated_shadow_mapping: bool,
/// All tracked allocations
allocations: HashMap<usize, AllocationMetadata>,
allocations: BTreeMap<usize, AllocationMetadata>,
/// All mappings
mappings: HashMap<usize, MmapMut>,
mappings: BTreeMap<usize, MmapMut>,
/// The shadow memory pages
shadow_pages: RangeSet<usize>,
/// A list of allocations
@ -171,7 +170,6 @@ impl Allocator {
#[must_use]
#[expect(clippy::missing_safety_doc)]
pub unsafe fn alloc(&mut self, size: usize, _alignment: usize) -> *mut c_void {
log::trace!("alloc");
let mut is_malloc_zero = false;
let size = if size == 0 {
is_malloc_zero = true;
@ -249,7 +247,7 @@ impl Allocator {
let address = (metadata.address + self.page_size) as *mut c_void;
self.allocations.insert(address as usize, metadata);
log::trace!(
log::info!(
"serving address: {:#x}, size: {:#x}",
address as usize,
size
@ -260,21 +258,25 @@ impl Allocator {
/// Releases the allocation at the given address.
#[expect(clippy::missing_safety_doc)]
pub unsafe fn release(&mut self, ptr: *mut c_void) {
log::trace!("release {:?}", ptr);
log::info!("release {:?}", ptr);
let Some(metadata) = self.allocations.get_mut(&(ptr as usize)) else {
if !ptr.is_null() {
AsanErrors::get_mut_blocking()
.report_error(AsanError::UnallocatedFree((ptr as usize, Backtrace::new())));
if !ptr.is_null()
&& AsanErrors::get_mut_blocking()
.report_error(AsanError::UnallocatedFree((ptr as usize, Backtrace::new())))
{
panic!("ASAN: Crashing target!");
}
return;
};
if metadata.freed {
AsanErrors::get_mut_blocking().report_error(AsanError::DoubleFree((
if metadata.freed
&& AsanErrors::get_mut_blocking().report_error(AsanError::DoubleFree((
ptr as usize,
metadata.clone(),
Backtrace::new(),
)));
)))
{
panic!("ASAN: Crashing target!");
}
let shadow_mapping_start = map_to_shadow!(self, ptr as usize);
@ -316,7 +318,7 @@ impl Allocator {
/// Resets the allocator contents
pub fn reset(&mut self) {
let mut tmp_allocations = Vec::new();
for (address, mut allocation) in self.allocations.drain() {
while let Some((address, mut allocation)) = self.allocations.pop_first() {
if !allocation.freed {
tmp_allocations.push(allocation);
continue;
@ -579,9 +581,17 @@ impl Allocator {
/// Checks if any of the allocations has not been freed
pub fn check_for_leaks(&self) {
for metadata in self.allocations.values() {
if !metadata.freed {
AsanErrors::get_mut_blocking()
.report_error(AsanError::Leak((metadata.address, metadata.clone())));
if !metadata.freed
&& AsanErrors::get_mut_blocking()
.report_error(AsanError::Leak((metadata.address, metadata.clone())))
{
unsafe {
println!(
"{:x?}",
std::slice::from_raw_parts(metadata.address as *const u8, metadata.size)
);
};
panic!("ASAN: Crashing target!");
}
}
}
@ -818,10 +828,10 @@ impl Default for Allocator {
page_size,
pre_allocated_shadow_mappings: Vec::new(),
using_pre_allocated_shadow_mapping: false,
mappings: HashMap::new(),
mappings: BTreeMap::new(),
shadow_offset: 0,
shadow_bit: 0,
allocations: HashMap::new(),
allocations: BTreeMap::new(),
shadow_pages: RangeSet::new(),
allocation_queue: BTreeMap::new(),
largest_allocation: 0,

View File

@ -23,7 +23,7 @@ use frida_gum::instruction_writer::X86Register;
use frida_gum::instruction_writer::{Aarch64Register, IndexMode};
use frida_gum::{
instruction_writer::InstructionWriter, interceptor::Interceptor, stalker::StalkerOutput, Gum,
Module, ModuleDetails, ModuleMap, NativePointer, PageProtection, RangeDetails,
Module, ModuleMap, NativePointer, PageProtection, Process, RangeDetails,
};
use frida_gum_sys::Insn;
use hashbrown::HashMap;
@ -96,6 +96,25 @@ thread_local! {
static ASAN_IN_HOOK: Cell<bool> = const { Cell::new(false) };
}
#[inline]
#[cfg(target_arch = "aarch64")]
unsafe fn thread_local_initted() -> bool {
let mut tid: u64;
std::arch::asm!(
"mrs {tid}, TPIDRRO_EL0",
tid = out(reg) tid,
);
tid &= 0xffff_ffff_ffff_fff8;
let tlsptr = tid as *const u64;
tlsptr.add(0x102).read() != 0u64
}
#[inline]
#[cfg(not(target_arch = "aarch64"))]
unsafe fn thread_local_initted() -> bool {
true
}
/// The count of registers that need to be saved by the asan runtime
#[cfg(target_arch = "aarch64")]
pub const ASAN_SAVE_REGISTER_COUNT: usize = 32;
@ -174,8 +193,8 @@ impl FridaRuntime for AsanRuntime {
.extend(self.skip_ranges.iter().map(|skip| match skip {
SkipRange::Absolute(range) => range.start,
SkipRange::ModuleRelative { name, range } => {
let module_details = ModuleDetails::with_name(name.clone()).unwrap();
let lib_start = module_details.range().base_address().0 as usize;
let module = Module::load(gum, name);
let lib_start = module.range().base_address().0 as usize;
lib_start + range.start
}
}));
@ -456,37 +475,42 @@ impl AsanRuntime {
#[expect(clippy::too_many_lines)]
pub fn register_hooks(&mut self, gum: &Gum) {
let mut interceptor = Interceptor::obtain(gum);
let module = Module::obtain(gum);
let process = Process::obtain(gum);
macro_rules! hook_func {
//No library case
($name:ident, ($($param:ident : $param_type:ty),*), $return_type:ty) => {
paste::paste! {
log::trace!("Hooking {}", stringify!($name));
let target_function = module.find_export_by_name(None, stringify!($name)).expect("Failed to find function");
let target_function = Module::find_global_export_by_name(stringify!($name)).expect("Failed to find function");
log::warn!("Hooking {} = {:?}", stringify!($name), target_function.0);
static [<$name:snake:upper _PTR>]: std::sync::OnceLock<extern "C" fn($($param: $param_type),*) -> $return_type> = std::sync::OnceLock::new();
let _ = [<$name:snake:upper _PTR>].set(unsafe {std::mem::transmute::<*const c_void, extern "C" fn($($param: $param_type),*) -> $return_type>(target_function.0)}).unwrap();
#[allow(non_snake_case)] // depends on the values the macro is invoked with
#[allow(non_snake_case)]
unsafe extern "C" fn [<replacement_ $name>]($($param: $param_type),*) -> $return_type {
let mut invocation = Interceptor::current_invocation();
let this = &mut *(invocation.replacement_data().unwrap().0 as *mut AsanRuntime);
//is this necessary? The stalked return address will always be the real return address
// let real_address = this.real_address_for_stalked(invocation.return_addr());
let original = [<$name:snake:upper _PTR>].get().unwrap();
if !ASAN_IN_HOOK.get() && this.hooks_enabled {
ASAN_IN_HOOK.set(true);
let ret = this.[<hook_ $name>](*original, $($param),*);
ASAN_IN_HOOK.set(false);
ret
} else {
let ret = (original)($($param),*);
ret
let original = [<$name:snake:upper _PTR>].get().unwrap();
if this.hooks_enabled {
if thread_local_initted() {
if !ASAN_IN_HOOK.get() {
ASAN_IN_HOOK.set(true);
let ret = this.[<hook_ $name>](*original, $($param),*);
ASAN_IN_HOOK.set(false);
ret
} else {
(original)($($param),*)
}
} else {
(original)($($param),*)
}
} else {
(original)($($param),*)
}
}
}
let self_ptr = core::ptr::from_ref(self) as usize;
let _ = interceptor.replace(
@ -501,9 +525,10 @@ impl AsanRuntime {
//Library specific macro rule. lib and lib_ident are both needed because we need to generate a unique static variable and only name is insufficient. In addition, the lib name could contain invalid characters (i.e., lib.so is an invalid name)
($lib:literal, $lib_ident:ident, $name:ident, ($($param:ident : $param_type:ty),*), $return_type:ty) => {
paste::paste! {
log::trace!("Hooking {}:{}", $lib, stringify!($name));
let target_function = module.find_export_by_name(Some($lib), stringify!($name)).expect("Failed to find function");
log::warn!("Hooking {}:{}", $lib, stringify!($name));
let target_function = process.find_module_by_name($lib).expect("Failed to find module").find_export_by_name(stringify!($name)).expect("Failed to find function");
log::warn!("Hooking {}:{} = {:?}", $lib, stringify!($name), target_function.0);
static [<$lib_ident:snake:upper _ $name:snake:upper _PTR>]: std::sync::OnceLock<extern "C" fn($($param: $param_type),*) -> $return_type> = std::sync::OnceLock::new();
@ -516,14 +541,21 @@ impl AsanRuntime {
//is this necessary? The stalked return address will always be the real return address
// let real_address = this.real_address_for_stalked(invocation.return_addr());
let original = [<$lib_ident:snake:upper _ $name:snake:upper _PTR>].get().unwrap();
if !ASAN_IN_HOOK.get() && this.hooks_enabled {
ASAN_IN_HOOK.set(true);
let ret = this.[<hook_ $name>](*original, $($param),*);
ASAN_IN_HOOK.set(false);
ret
if this.hooks_enabled {
if thread_local_initted() {
if !ASAN_IN_HOOK.get() {
ASAN_IN_HOOK.set(true);
let ret = this.[<hook_ $name>](*original, $($param),*);
ASAN_IN_HOOK.set(false);
ret
} else {
(original)($($param),*)
}
} else {
(original)($($param),*)
}
} else {
let ret = (original)($($param),*);
ret
(original)($($param),*)
}
}
@ -539,35 +571,48 @@ impl AsanRuntime {
};
}
#[expect(unused_macro_rules)]
#[allow(unused_macro_rules)]
macro_rules! hook_func_with_check {
//No library case
($name:ident, ($($param:ident : $param_type:ty),*), $return_type:ty) => {
($name:ident, ($($param:ident : $param_type:ty),*), $return_type:ty, $always_enabled:expr) => {
paste::paste! {
log::trace!("Hooking {}", stringify!($name));
let target_function = module.find_export_by_name(None, stringify!($name)).expect("Failed to find function");
let target_function = Module::find_global_export_by_name(stringify!($name)).expect("Failed to find function");
log::warn!("Hooking {} = {:?}", stringify!($name), target_function.0);
static [<$name:snake:upper _PTR>]: std::sync::OnceLock<extern "C" fn($($param: $param_type),*) -> $return_type> = std::sync::OnceLock::new();
let _ = [<$name:snake:upper _PTR>].set(unsafe {std::mem::transmute::<*const c_void, extern "C" fn($($param: $param_type),*) -> $return_type>(target_function.0)}).unwrap_or_else(|e| println!("{:?}", e));
#[allow(non_snake_case)] // depends on the values the macro is invoked with
#[allow(non_snake_case)]
unsafe extern "C" fn [<replacement_ $name>]($($param: $param_type),*) -> $return_type {
let mut invocation = Interceptor::current_invocation();
let this = &mut *(invocation.replacement_data().unwrap().0 as *mut AsanRuntime);
let original = [<$name:snake:upper _PTR>].get().unwrap();
//don't check if hooks are enabled as there are certain cases where we want to run the hook even if we are out of the program
//For example, sometimes libafl will allocate certain things during the run and free them after the run. This results in a bug where a buffer will come from libafl-frida alloc and be freed in the normal allocator.
if !ASAN_IN_HOOK.get() && this.[<hook_check_ $name>]($($param),*){
ASAN_IN_HOOK.set(true);
let ret = this.[<hook_ $name>](*original, $($param),*);
ASAN_IN_HOOK.set(false);
ret
if $always_enabled || this.hooks_enabled {
if thread_local_initted() {
if !ASAN_IN_HOOK.get() {
ASAN_IN_HOOK.set(true);
let ret = if this.[<hook_check_ $name>]($($param),*) {
this.[<hook_ $name>](*original, $($param),*)
} else {
(original)($($param),*)
};
ASAN_IN_HOOK.set(false);
ret
} else {
(original)($($param),*)
}
} else {
let ret = if $always_enabled && this.[<hook_check_ $name>]($($param),*) {
this.[<hook_ $name>](*original, $($param),*)
} else {
(original)($($param),*)
};
ret
}
} else {
let ret = (original)($($param),*);
ret
(original)($($param),*)
}
}
@ -582,11 +627,11 @@ impl AsanRuntime {
}
};
//Library specific macro rule. lib and lib_ident are both needed because we need to generate a unique static variable and only name is insufficient. In addition, the lib name could contain invalid characters (i.e., lib.so is an invalid name)
($lib:literal, $lib_ident:ident, $name:ident, ($($param:ident : $param_type:ty),*), $return_type:ty) => {
($lib:literal, $lib_ident:ident, $name:ident, ($($param:ident : $param_type:ty),*), $return_type:ty, $always_enabled:expr) => {
paste::paste! {
log::trace!("Hooking {}:{}", $lib, stringify!($name));
let target_function = module.find_export_by_name(Some($lib), stringify!($name)).expect("Failed to find function");
let target_function = process.find_module_by_name($lib).expect("Failed to find module").find_export_by_name(stringify!($name)).expect("Failed to find function");
log::warn!("Hooking {}:{} = {:?}", $lib, stringify!($name), target_function.0);
static [<$lib_ident:snake:upper _ $name:snake:upper _PTR>]: std::sync::OnceLock<extern "C" fn($($param: $param_type),*) -> $return_type> = std::sync::OnceLock::new();
@ -600,14 +645,30 @@ impl AsanRuntime {
let original = [<$lib_ident:snake:upper _ $name:snake:upper _PTR>].get().unwrap();
//don't check if hooks are enabled as there are certain cases where we want to run the hook even if we are out of the program
//For example, sometimes libafl will allocate certain things during the run and free them after the run. This results in a bug where a buffer will come from libafl-frida alloc and be freed in the normal allocator.
if !ASAN_IN_HOOK.get() && this.[<hook_check_ $name>]($($param),*){
ASAN_IN_HOOK.set(true);
let ret = this.[<hook_ $name>](*original, $($param),*);
ASAN_IN_HOOK.set(false);
ret
if $always_enabled || this.hooks_enabled {
if thread_local_initted() {
if !ASAN_IN_HOOK.get() {
ASAN_IN_HOOK.set(true);
let ret = if this.[<hook_check_ $name>]($($param),*) {
this.[<hook_ $name>](*original, $($param),*)
} else {
(original)($($param),*)
};
ASAN_IN_HOOK.set(false);
ret
} else {
(original)($($param),*)
}
} else {
let ret = if $always_enabled && this.[<hook_check_ $name>]($($param),*) {
this.[<hook_ $name>](*original, $($param),*)
} else {
(original)($($param),*)
};
ret
}
} else {
let ret = (original)($($param),*);
ret
(original)($($param),*)
}
}
@ -629,9 +690,9 @@ impl AsanRuntime {
#[cfg(not(windows))]
hook_func!(calloc, (nmemb: usize, size: usize), *mut c_void);
#[cfg(not(windows))]
hook_func!(realloc, (ptr: *mut c_void, size: usize), *mut c_void);
hook_func_with_check!(realloc, (ptr: *mut c_void, size: usize), *mut c_void, false);
#[cfg(not(windows))]
hook_func_with_check!(free, (ptr: *mut c_void), usize);
hook_func_with_check!(free, (ptr: *mut c_void), usize, true);
#[cfg(not(any(target_vendor = "apple", windows)))]
hook_func!(memalign, (size: usize, alignment: usize), *mut c_void);
#[cfg(not(windows))]
@ -642,6 +703,28 @@ impl AsanRuntime {
);
#[cfg(not(any(target_vendor = "apple", windows)))]
hook_func!(malloc_usable_size, (ptr: *mut c_void), usize);
#[cfg(target_vendor = "apple")]
hook_func!(valloc, (size: usize), *mut c_void);
#[cfg(target_vendor = "apple")]
hook_func_with_check!(reallocf, (ptr: *mut c_void, size: usize), *mut c_void, false);
#[cfg(target_vendor = "apple")]
hook_func_with_check!(malloc_size, (ptr: *mut c_void), usize, false);
#[cfg(target_vendor = "apple")]
hook_func_with_check!(malloc_good_size, (ptr: *mut c_void), usize, false);
#[cfg(target_vendor = "apple")]
hook_func!("libSystem.B.dylib", libSystemB, os_log_type_enabled, (oslog: *mut c_void, r#type: u8), bool);
#[cfg(target_vendor = "apple")]
hook_func!("libSystem.B.dylib", libSystemB, _os_log_impl, (dso: *const c_void, log: *mut c_void, r#type: u8, format: *const c_char, buf: *const u8, size: u32), ());
#[cfg(target_vendor = "apple")]
hook_func!("libSystem.B.dylib", libSystemB, _os_log_fault_impl, (dso: *const c_void, log: *mut c_void, r#type: u8, format: *const c_char, buf: *const u8, size: u32), ());
#[cfg(target_vendor = "apple")]
hook_func!("libSystem.B.dylib", libSystemB, _os_log_error_impl, (dso: *const c_void, log: *mut c_void, r#type: u8, format: *const c_char, buf: *const u8, size: u32), ());
#[cfg(target_vendor = "apple")]
hook_func!("libSystem.B.dylib", libSystemB, _os_log_debug_impl, (dso: *const c_void, log: *mut c_void, r#type: u8, format: *const c_char, buf: *const u8, size: u32), ());
#[cfg(target_vendor = "apple")]
hook_func!("libc++.1.dylib", libcpp, __cxa_allocate_exception, (size: usize), *const c_void);
#[cfg(target_vendor = "apple")]
hook_func!("libc++.1.dylib", libcpp, __cxa_free_exception, (ptr: *mut c_void), usize);
// // #[cfg(windows)]
// hook_priv_func!(
// "c:\\windows\\system32\\ntdll.dll",
@ -674,7 +757,8 @@ impl AsanRuntime {
macro_rules! hook_heap_windows {
($libname:literal, $lib_ident:ident) => {
log::info!("Hooking allocator functions in {}", $libname);
for export in module.enumerate_exports($libname) {
if let Some(module) = process.find_module_by_name($libname) {
for export in module.enumerate_exports() {
// log::trace!("- {}", export.name);
match &export.name[..] {
"NtGdiCreateCompatibleDC" => {
@ -693,16 +777,16 @@ impl AsanRuntime {
hook_func!($libname, $lib_ident, RtlAllocateHeap, (handle: *mut c_void, flags: u32, bytes: usize), *mut c_void);
}
"HeapFree" => {
hook_func_with_check!($libname, $lib_ident, HeapFree, (handle: *mut c_void, flags: u32, mem: *mut c_void), bool);
hook_func_with_check!($libname, $lib_ident, HeapFree, (handle: *mut c_void, flags: u32, mem: *mut c_void), bool, true);
}
"RtlFreeHeap" => {
hook_func_with_check!($libname, $lib_ident, RtlFreeHeap, (handle: *mut c_void, flags: u32, mem: *mut c_void), usize);
hook_func_with_check!($libname, $lib_ident, RtlFreeHeap, (handle: *mut c_void, flags: u32, mem: *mut c_void), usize, true);
}
"HeapSize" => {
hook_func_with_check!($libname, $lib_ident, HeapSize, (handle: *mut c_void, flags: u32, mem: *mut c_void), usize);
hook_func_with_check!($libname, $lib_ident, HeapSize, (handle: *mut c_void, flags: u32, mem: *mut c_void), usize, false);
}
"RtlSizeHeap" => {
hook_func_with_check!($libname, $lib_ident, RtlSizeHeap , (handle: *mut c_void, flags: u32, mem: *mut c_void), usize);
hook_func_with_check!($libname, $lib_ident, RtlSizeHeap , (handle: *mut c_void, flags: u32, mem: *mut c_void), usize, false);
}
"RtlReAllocateHeap" => {
hook_func!(
@ -737,22 +821,22 @@ impl AsanRuntime {
hook_func!($libname, $lib_ident, LocalReAlloc, (mem: *mut c_void, size: usize, flags: u32), *mut c_void);
}
"LocalHandle" => {
hook_func_with_check!($libname, $lib_ident, LocalHandle, (mem: *mut c_void), *mut c_void);
hook_func_with_check!($libname, $lib_ident, LocalHandle, (mem: *mut c_void), *mut c_void, false);
}
"LocalLock" => {
hook_func_with_check!($libname, $lib_ident, LocalLock, (mem: *mut c_void), *mut c_void);
hook_func_with_check!($libname, $lib_ident, LocalLock, (mem: *mut c_void), *mut c_void, false);
}
"LocalUnlock" => {
hook_func_with_check!($libname, $lib_ident, LocalUnlock, (mem: *mut c_void), bool);
hook_func_with_check!($libname, $lib_ident, LocalUnlock, (mem: *mut c_void), bool, false);
}
"LocalSize" => {
hook_func_with_check!($libname, $lib_ident, LocalSize, (mem: *mut c_void),usize);
hook_func_with_check!($libname, $lib_ident, LocalSize, (mem: *mut c_void),usize, false);
}
"LocalFree" => {
hook_func_with_check!($libname, $lib_ident, LocalFree, (mem: *mut c_void), *mut c_void);
hook_func_with_check!($libname, $lib_ident, LocalFree, (mem: *mut c_void), *mut c_void, true);
}
"LocalFlags" => {
hook_func_with_check!($libname, $lib_ident, LocalFlags, (mem: *mut c_void),u32);
hook_func_with_check!($libname, $lib_ident, LocalFlags, (mem: *mut c_void),u32, false);
}
"GlobalAlloc" => {
hook_func!($libname, $lib_ident, GlobalAlloc, (flags: u32, size: usize), *mut c_void);
@ -761,22 +845,22 @@ impl AsanRuntime {
hook_func!($libname, $lib_ident, GlobalReAlloc, (mem: *mut c_void, flags: u32, size: usize), *mut c_void);
}
"GlobalHandle" => {
hook_func_with_check!($libname, $lib_ident, GlobalHandle, (mem: *mut c_void), *mut c_void);
hook_func_with_check!($libname, $lib_ident, GlobalHandle, (mem: *mut c_void), *mut c_void, false);
}
"GlobalLock" => {
hook_func_with_check!($libname, $lib_ident, GlobalLock, (mem: *mut c_void), *mut c_void);
hook_func_with_check!($libname, $lib_ident, GlobalLock, (mem: *mut c_void), *mut c_void, false);
}
"GlobalUnlock" => {
hook_func_with_check!($libname, $lib_ident, GlobalUnlock, (mem: *mut c_void), bool);
hook_func_with_check!($libname, $lib_ident, GlobalUnlock, (mem: *mut c_void), bool, false);
}
"GlobalSize" => {
hook_func_with_check!($libname, $lib_ident, GlobalSize, (mem: *mut c_void),usize);
hook_func_with_check!($libname, $lib_ident, GlobalSize, (mem: *mut c_void),usize, false);
}
"GlobalFree" => {
hook_func_with_check!($libname, $lib_ident, GlobalFree, (mem: *mut c_void), *mut c_void);
hook_func_with_check!($libname, $lib_ident, GlobalFree, (mem: *mut c_void), *mut c_void, true);
}
"GlobalFlags" => {
hook_func_with_check!($libname, $lib_ident, GlobalFlags, (mem: *mut c_void),u32);
hook_func_with_check!($libname, $lib_ident, GlobalFlags, (mem: *mut c_void),u32, false);
}
"memmove" => {
hook_func!(
@ -813,10 +897,10 @@ impl AsanRuntime {
hook_func!($libname, $lib_ident, _o_realloc, (ptr: *mut c_void, size: usize), *mut c_void);
}
"free" => {
hook_func_with_check!($libname, $lib_ident, free, (ptr: *mut c_void), usize);
hook_func_with_check!($libname, $lib_ident, free, (ptr: *mut c_void), usize, true);
}
"_o_free" | "o_free" => {
hook_func_with_check!($libname, $lib_ident, _o_free, (ptr: *mut c_void), usize);
hook_func_with_check!($libname, $lib_ident, _o_free, (ptr: *mut c_void), usize, true);
}
"_write" => {
hook_func!(
@ -860,7 +944,7 @@ impl AsanRuntime {
}
_ => (),
}
}
}}
}
}
#[cfg(windows)]
@ -900,7 +984,8 @@ impl AsanRuntime {
macro_rules! hook_cpp {
($libname:literal, $lib_ident:ident) => {
log::info!("Hooking c++ functions in {}", $libname);
for export in module.enumerate_exports($libname) {
if let Some(module) = process.find_module_by_name($libname) {
for export in module.enumerate_exports() {
match &export.name[..] {
"_Znam" => {
hook_func!($libname, $lib_ident, _Znam, (size: usize), *mut c_void);
@ -1034,7 +1119,7 @@ impl AsanRuntime {
}
_ => {}
}
}
}}
}
}
#[cfg(target_os = "linux")]
@ -1057,6 +1142,7 @@ impl AsanRuntime {
#[cfg(not(windows))]
hook_func!(
mmap,
(
addr: *const c_void,
@ -1074,6 +1160,7 @@ impl AsanRuntime {
// Hook libc functions which may access allocated memory
#[cfg(not(windows))]
hook_func!(
write,
(fd: i32, buf: *const c_void, count: usize),
usize
@ -1081,22 +1168,26 @@ impl AsanRuntime {
#[cfg(not(windows))]
hook_func!(read, (fd: i32, buf: *mut c_void, count: usize), usize);
hook_func!(
fgets,
(s: *mut c_void, size: u32, stream: *mut c_void),
*mut c_void
);
hook_func!(
memcmp,
(s1: *const c_void, s2: *const c_void, n: usize),
i32
);
hook_func!(
memcpy,
(dest: *mut c_void, src: *const c_void, n: usize),
*mut c_void
);
#[cfg(not(any(target_vendor = "apple", windows)))]
hook_func!(
mempcpy,
(dest: *mut c_void, src: *const c_void, n: usize),
*mut c_void
@ -1109,23 +1200,27 @@ impl AsanRuntime {
// *mut c_void
// );
hook_func!(
memset,
(s: *mut c_void, c: i32, n: usize),
*mut c_void
);
hook_func!(
memchr,
(s: *mut c_void, c: i32, n: usize),
*mut c_void
);
#[cfg(not(any(target_vendor = "apple", windows)))]
hook_func!(
memrchr,
(s: *mut c_void, c: i32, n: usize),
*mut c_void
);
#[cfg(not(windows))]
hook_func!(
memmem,
(
haystack: *const c_void,
@ -1150,39 +1245,46 @@ impl AsanRuntime {
hook_func!(strrchr, (s: *mut c_char, c: i32), *mut c_char);
#[cfg(not(windows))]
hook_func!(
strcasecmp,
(s1: *const c_char, s2: *const c_char),
i32
);
#[cfg(not(windows))]
hook_func!(
strncasecmp,
(s1: *const c_char, s2: *const c_char, n: usize),
i32
);
hook_func!(
strcat,
(dest: *mut c_char, src: *const c_char),
*mut c_char
);
hook_func!(strcmp, (s1: *const c_char, s2: *const c_char), i32);
hook_func!(
strncmp,
(s1: *const c_char, s2: *const c_char, n: usize),
i32
);
hook_func!(
strcpy,
(dest: *mut c_char, src: *const c_char),
*mut c_char
);
hook_func!(
strncpy,
(dest: *mut c_char, src: *const c_char, n: usize),
*mut c_char
);
#[cfg(not(windows))]
hook_func!(
stpcpy,
(dest: *mut c_char, src: *const c_char),
*mut c_char
@ -1194,12 +1296,14 @@ impl AsanRuntime {
hook_func!(strlen, (s: *const c_char), usize);
hook_func!(strnlen, (s: *const c_char, n: usize), usize);
hook_func!(
strstr,
(haystack: *const c_char, needle: *const c_char),
*mut c_char
);
#[cfg(not(windows))]
hook_func!(
strcasestr,
(haystack: *const c_char, needle: *const c_char),
*mut c_char
@ -1209,6 +1313,7 @@ impl AsanRuntime {
hook_func!(atoll, (nptr: *const c_char), i64);
hook_func!(wcslen, (s: *const wchar_t), usize);
hook_func!(
wcscpy,
(dest: *mut wchar_t, src: *const wchar_t),
*mut wchar_t
@ -1216,18 +1321,21 @@ impl AsanRuntime {
hook_func!(wcscmp, (s1: *const wchar_t, s2: *const wchar_t), i32);
#[cfg(target_vendor = "apple")]
hook_func!(
memset_pattern4,
(s: *mut c_void, c: *const c_void, n: usize),
()
);
#[cfg(target_vendor = "apple")]
hook_func!(
memset_pattern8,
(s: *mut c_void, c: *const c_void, n: usize),
()
);
#[cfg(target_vendor = "apple")]
hook_func!(
memset_pattern16,
(s: *mut c_void, c: *const c_void, n: usize),
()
@ -1397,16 +1505,19 @@ impl AsanRuntime {
backtrace,
))
};
AsanErrors::get_mut_blocking().report_error(error);
#[allow(clippy::manual_assert)]
if AsanErrors::get_mut_blocking().report_error(error) {
panic!("ASAN: Crashing target!");
}
// This is not even a mem instruction??
} else {
AsanErrors::get_mut_blocking().report_error(AsanError::Unknown((
self.regs,
actual_pc,
(None, None, 0, fault_address),
backtrace,
)));
} else if AsanErrors::get_mut_blocking().report_error(AsanError::Unknown((
self.regs,
actual_pc,
(None, None, 0, fault_address),
backtrace,
))) {
panic!("ASAN: Crashing target!");
}
// log::info!("ASAN Error, attach the debugger!");
@ -1536,7 +1647,10 @@ impl AsanRuntime {
backtrace,
))
};
AsanErrors::get_mut_blocking().report_error(error);
#[allow(clippy::manual_assert)]
if AsanErrors::get_mut_blocking().report_error(error) {
panic!("ASAN: Crashing target!");
}
self.enable_hooks();
}

View File

@ -11,7 +11,7 @@ use backtrace::Backtrace;
use color_backtrace::{default_output_stream, BacktracePrinter, Verbosity};
#[cfg(target_arch = "aarch64")]
use frida_gum::interceptor::Interceptor;
use frida_gum::ModuleDetails;
use frida_gum::{Gum, Process};
use libafl::{
corpus::Testcase,
executors::ExitKind,
@ -24,6 +24,7 @@ use libafl_bolts::{
tuples::{Handle, Handled, MatchNameRef},
Named, SerdeAny,
};
use mmap_rs::MmapOptions;
use serde::{Deserialize, Serialize};
use termcolor::{Color, ColorSpec, WriteColor};
#[cfg(target_arch = "aarch64")]
@ -151,9 +152,9 @@ impl AsanErrors {
self.continue_on_error = continue_on_error;
}
/// Report an error
/// Report an error, returns true if the caller should panic
#[expect(clippy::too_many_lines)]
pub(crate) fn report_error(&mut self, error: AsanError) {
pub(crate) fn report_error(&mut self, error: AsanError) -> bool {
let mut out_stream = default_output_stream();
let output = out_stream.as_mut();
@ -180,7 +181,9 @@ impl AsanErrors {
| AsanError::WriteAfterFree(error) => {
let (basereg, indexreg, _displacement, fault_address) = error.fault;
if let Some(module_details) = ModuleDetails::with_address(error.pc as u64) {
if let Some(module_details) =
Process::obtain(&Gum::obtain()).find_module_by_address(error.pc)
{
writeln!(
output,
" at 0x{:x} ({}@0x{:04x}), faulting address 0x{:x}",
@ -294,7 +297,9 @@ impl AsanErrors {
writeln!(output, "{:━^100}", " ALLOCATION INFO ").unwrap();
let fault_address: i64 = fault_address.try_into().unwrap();
let metadata_address: i64 = error.metadata.address.try_into().unwrap();
let offset: i64 = fault_address - (metadata_address + 0x1000);
#[allow(clippy::cast_possible_wrap)]
let offset: i64 =
fault_address - (metadata_address + MmapOptions::page_size() as i64);
let direction = if offset > 0 { "right" } else { "left" };
writeln!(
output,
@ -302,7 +307,7 @@ impl AsanErrors {
offset,
direction,
error.metadata.size,
error.metadata.address + 0x1000
error.metadata.address + MmapOptions::page_size()
)
.unwrap();
@ -343,7 +348,9 @@ impl AsanErrors {
{
let invocation = Interceptor::current_invocation();
let cpu_context = invocation.cpu_context();
if let Some(module_details) = ModuleDetails::with_address(*_pc as u64) {
if let Some(module_details) =
Process::obtain(&Gum::obtain()).find_module_by_address(*_pc)
{
writeln!(
output,
" at 0x{:x} ({}@0x{:04x})",
@ -388,7 +395,7 @@ impl AsanErrors {
writeln!(
output,
"allocation at 0x{:x}, with size 0x{:x}",
metadata.address + 0x1000,
metadata.address + MmapOptions::page_size(),
metadata.size
)
.unwrap();
@ -426,7 +433,7 @@ impl AsanErrors {
writeln!(
output,
"allocation at 0x{:x}, with size 0x{:x}",
metadata.address + 0x1000,
metadata.address + MmapOptions::page_size(),
metadata.size
)
.unwrap();
@ -447,7 +454,9 @@ impl AsanErrors {
| AsanError::StackOobWrite((registers, pc, fault, backtrace)) => {
let (basereg, indexreg, _displacement, fault_address) = fault;
if let Some(module_details) = ModuleDetails::with_address(*pc as u64) {
if let Some(module_details) =
Process::obtain(&Gum::obtain()).find_module_by_address(*pc)
{
writeln!(
output,
" at 0x{:x} ({}:0x{:04x}), faulting address 0x{:x}",
@ -556,10 +565,7 @@ impl AsanErrors {
self.errors.push(error);
#[expect(clippy::manual_assert)]
if !self.continue_on_error {
panic!("ASAN: Crashing target!");
}
!self.continue_on_error
}
}

File diff suppressed because it is too large Load Diff

View File

@ -177,13 +177,15 @@ where
// Include the current module (the fuzzer) in stalked ranges. We clone the ranges so that
// we don't add it to the INSTRUMENTED ranges.
let mut ranges = helper.ranges().clone();
for module in frida_gum::Module::obtain(gum).enumerate_modules() {
if module.base_address < Self::with_target_bytes_converter as usize
for module in frida_gum::Process::obtain(gum).enumerate_modules() {
let range = module.range();
if (range.base_address().0 as usize) < Self::with_target_bytes_converter as usize
&& (Self::with_target_bytes_converter as usize as u64)
< module.base_address as u64 + module.size as u64
< range.base_address().0 as u64 + range.size() as u64
{
ranges.insert(
module.base_address as u64..(module.base_address as u64 + module.size as u64),
range.base_address().0 as u64
..(range.base_address().0 as u64 + range.size() as u64),
(0xffff, "fuzzer".to_string()),
);
break;

View File

@ -11,7 +11,7 @@ use std::{
use frida_gum::{
instruction_writer::InstructionWriter,
stalker::{StalkerIterator, StalkerOutput, Transformer},
Backend, Gum, ModuleDetails, ModuleMap, Script,
Backend, Gum, Module, ModuleMap, Script,
};
use frida_gum_sys::gchar;
use libafl::Error;
@ -276,8 +276,8 @@ pub struct FridaInstrumentationHelperBuilder {
stalker_enabled: bool,
disable_excludes: bool,
#[expect(clippy::type_complexity)]
instrument_module_predicate: Option<Box<dyn FnMut(&ModuleDetails) -> bool>>,
skip_module_predicate: Box<dyn FnMut(&ModuleDetails) -> bool>,
instrument_module_predicate: Option<Box<dyn FnMut(&Module) -> bool>>,
skip_module_predicate: Box<dyn FnMut(&Module) -> bool>,
skip_ranges: Vec<SkipRange>,
}
@ -353,7 +353,7 @@ impl FridaInstrumentationHelperBuilder {
/// .instrument_module_if(|module| module.path().starts_with("/usr/lib"));
/// ```
#[must_use]
pub fn instrument_module_if<F: FnMut(&ModuleDetails) -> bool + 'static>(
pub fn instrument_module_if<F: FnMut(&Module) -> bool + 'static>(
mut self,
mut predicate: F,
) -> Self {
@ -382,10 +382,7 @@ impl FridaInstrumentationHelperBuilder {
/// .skip_module_if(|module| module.name() == "libfoo.so");
/// ```
#[must_use]
pub fn skip_module_if<F: FnMut(&ModuleDetails) -> bool + 'static>(
mut self,
mut predicate: F,
) -> Self {
pub fn skip_module_if<F: FnMut(&Module) -> bool + 'static>(mut self, mut predicate: F) -> Self {
let new = move |module: &_| (self.skip_module_predicate)(module) || predicate(module);
Self {
skip_module_predicate: Box::new(new),
@ -430,7 +427,7 @@ impl FridaInstrumentationHelperBuilder {
!skip_module_predicate(&module)
}
});
let module_map = Rc::new(ModuleMap::new_with_filter(gum, &mut module_filter));
let module_map = Rc::new(ModuleMap::new_with_filter(&mut module_filter));
let ranges = RangeMap::new();
// Wrap ranges and runtimes in reference-counted refcells in order to move
@ -461,7 +458,7 @@ impl FridaInstrumentationHelperBuilder {
.borrow_mut()
.remove(range.start as u64..range.end as u64),
SkipRange::ModuleRelative { name, range } => {
let module_details = ModuleDetails::with_name(name).unwrap();
let module_details = Module::load(gum, &name);
let lib_start = module_details.range().base_address().0 as u64;
ranges.borrow_mut().remove(
(lib_start + range.start as u64)..(lib_start + range.end as u64),
@ -550,7 +547,7 @@ pub unsafe extern "C" fn test_function(message: *const gchar) {
}
}
fn pathlist_contains_module<I, P>(list: I, module: &ModuleDetails) -> bool
fn pathlist_contains_module<I, P>(list: I, module: &Module) -> bool
where
I: IntoIterator<Item = P>,
P: AsRef<Path>,

View File

@ -1,7 +1,7 @@
// Based on the example of setting hooks: Https://github.com/frida/frida-rust/blob/main/examples/gum/hook_open/src/lib.rs
use std::ffi::c_void;
use frida_gum::{interceptor::Interceptor, Gum, Module, NativePointer};
use frida_gum::{interceptor::Interceptor, Gum, NativePointer, Process};
use libafl_bolts::os::windows_exceptions::{
handle_exception, IsProcessorFeaturePresent, UnhandledExceptionFilter, EXCEPTION_POINTERS,
PROCESSOR_FEATURE_ID,
@ -21,16 +21,16 @@ unsafe extern "C" fn unhandled_exception_filter_detour(
}
/// Initialize the hooks
pub fn initialize(gum: &Gum) {
let module = Module::obtain(gum);
let is_processor_feature_present =
module.find_export_by_name(Some("kernel32.dll"), "IsProcessorFeaturePresent");
let module = Process::obtain(gum)
.find_module_by_name("kernel32.dll")
.unwrap();
let is_processor_feature_present = module.find_export_by_name("IsProcessorFeaturePresent");
let is_processor_feature_present = is_processor_feature_present.unwrap();
assert!(
!is_processor_feature_present.is_null(),
"IsProcessorFeaturePresent not found"
);
let unhandled_exception_filter =
module.find_export_by_name(Some("kernel32.dll"), "UnhandledExceptionFilter");
let unhandled_exception_filter = module.find_export_by_name("UnhandledExceptionFilter");
let unhandled_exception_filter = unhandled_exception_filter.unwrap();
assert!(
!unhandled_exception_filter.is_null(),