Pr/fasan multithreading fixes upstream (#2955)

* Fixing the test_harness library name

* Fasan works, but testing of all features is pending

* Tests pass, before fixing clippy and fmt

* CLippy+fmt

* CLippy+fmt+tests running on linux

* Clippy

* Not stalkering the fuzzer. In the correct way

* Removing the instrumentation upon crash. Proper hooking of UnmapViewOfFile

* Fixes after the merge from the upstream (before 0.15.0). Still need to add the observer, clippy, fmt, and at least linux compilation

* Adding the helper observer and using it in the test

* Removing the observer from the wrong location

* Adapting to the new helper ownership model

* Adding an observer to shut down instrumentation upon crash

* Clippy + fmt

* Using mimalloc everywhere

* Deactivating before activating with the harness. Otherwise, gets stuck on Linux.

* Fixing imports for windows

* Using the new way of passing the handler

* Using frida_helper_shutdown_observer

* Clippy+fmt

* no-std, clippy

* Fmt

* Stable thread_id

* Clippy 18

* More clippy

* Formatting toml

* Fixing apples

* Fixing apples 2

* Fixing apples 3

* Upping to 0.16.7 (necessary for Windows)

* Clippy+fmt

* Enabling the allocator test after the fix and clarifying the importantce of the static runtime linking.

* Moving has_tls to bolts

* Proper handling of no-std, hopefully

* Another attempt to fix win no-std

* Not mine clippy complaint...

* Not mine clippy complaint #2...

* Dlmalloc not used, removing from dependencies

* Restoring target in config.toml (otherwise fails CI on Linux)

* lots of digging around, pray for us

* fixup?

* Revert "lots of digging around, pray for us"

This reverts commit 706c27201918e906e3401cd0d9e76546f889d1f5.

* Revert "fixup?"

This reverts commit 1d7c5d4fb5b1bd31f5e0c07492aa8ed64c6822f3.

* Revert artifact

* Revert fixups

* Removing unused

* Reverting to upstream/main

---------

Co-authored-by: Addison Crump <addison.crump@cispa.de>
Co-authored-by: Dongjia "toka" Zhang <tokazerkje@outlook.com>
This commit is contained in:
mkravchik 2025-02-14 14:45:38 +02:00 committed by GitHub
parent f9715392af
commit b3fe744e57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 1162 additions and 312 deletions

2
.gitignore vendored
View File

@ -75,3 +75,5 @@ harness
program
fuzzer_libpng*
forkserver_simple
*.patch

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.16.5", features = [
frida-gum = { version = "0.16.7", features = [
"auto-download",
"event-sink",
"invocation-listener",

View File

@ -1,6 +1,6 @@
//! A libfuzzer-like fuzzer with llmp-multithreading support and restarts
//! The example harness is built for libpng.
use std::{path::PathBuf, ptr::null};
use std::{cell::RefCell, path::PathBuf, ptr::null, rc::Rc};
use frida_gum::Gum;
use libafl::{
@ -43,6 +43,7 @@ use libafl_frida::{
cmplog_rt::CmpLogRuntime,
coverage_rt::{CoverageRuntime, MAP_SIZE},
executor::FridaInProcessExecutor,
frida_helper_shutdown_observer::FridaHelperObserver,
helper::FridaInstrumentationHelper,
};
use libafl_targets::cmplog::CmpLogObserver;
@ -113,16 +114,22 @@ unsafe fn fuzz(
let asan = AsanRuntime::new(options);
#[cfg(unix)]
let mut frida_helper =
FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage, asan));
let frida_helper = Rc::new(RefCell::new(FridaInstrumentationHelper::new(
&gum,
options,
tuple_list!(coverage, asan),
)));
#[cfg(windows)]
let mut frida_helper =
FridaInstrumentationHelper::new(&gum, &options, tuple_list!(coverage));
let frida_helper = Rc::new(RefCell::new(FridaInstrumentationHelper::new(
&gum,
&options,
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(),
frida_helper.borrow_mut().map_mut_ptr().unwrap(),
MAP_SIZE,
))
.track_indices();
@ -131,6 +138,7 @@ unsafe fn fuzz(
let time_observer = TimeObserver::new("time");
#[cfg(unix)]
let asan_observer = AsanErrorsObserver::from_static_asan_errors();
let frida_helper_observer = FridaHelperObserver::new(Rc::clone(&frida_helper));
// Feedback to rate the interestingness of an input
// This one is composed by two Feedbacks in OR
@ -196,9 +204,14 @@ unsafe fn fuzz(
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
#[cfg(unix)]
let observers = tuple_list!(edges_observer, time_observer, asan_observer);
let observers = tuple_list!(
frida_helper_observer,
edges_observer,
time_observer,
asan_observer
);
#[cfg(windows)]
let observers = tuple_list!(edges_observer, time_observer);
let observers = tuple_list!(frida_helper_observer, edges_observer, time_observer);
// Create the executor for an in-process function with just one observer for edge coverage
let mut executor = FridaInProcessExecutor::new(
@ -210,7 +223,7 @@ unsafe fn fuzz(
&mut state,
&mut mgr,
)?,
&mut frida_helper,
Rc::clone(&frida_helper),
);
// In case the corpus is empty (on first run), reset
@ -238,13 +251,16 @@ unsafe fn fuzz(
let coverage = CoverageRuntime::new();
let cmplog = CmpLogRuntime::new();
let mut frida_helper =
FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage, cmplog));
let mut frida_helper = Rc::new(RefCell::new(FridaInstrumentationHelper::new(
&gum,
options,
tuple_list!(coverage, cmplog),
)));
// Create an observation channel using the coverage map
let edges_observer = HitcountsMapObserver::new(StdMapObserver::from_mut_ptr(
"edges",
frida_helper.map_mut_ptr().unwrap(),
frida_helper.borrow_mut().map_mut_ptr().unwrap(),
MAP_SIZE,
))
.track_indices();
@ -253,6 +269,7 @@ unsafe fn fuzz(
let time_observer = TimeObserver::new("time");
#[cfg(unix)]
let asan_observer = AsanErrorsObserver::from_static_asan_errors();
let frida_helper_observer = FridaHelperObserver::new(Rc::clone(&frida_helper));
// Feedback to rate the interestingness of an input
// This one is composed by two Feedbacks in OR
@ -316,9 +333,14 @@ unsafe fn fuzz(
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
#[cfg(unix)]
let observers = tuple_list!(edges_observer, time_observer, asan_observer);
let observers = tuple_list!(
frida_helper_observer,
edges_observer,
time_observer,
asan_observer
);
#[cfg(windows)]
let observers = tuple_list!(edges_observer, time_observer);
let observers = tuple_list!(frida_helper_observer, edges_observer, time_observer);
// Create the executor for an in-process function with just one observer for edge coverage
let mut executor = FridaInProcessExecutor::new(
@ -330,7 +352,7 @@ unsafe fn fuzz(
&mut state,
&mut mgr,
)?,
&mut frida_helper,
Rc::clone(&frida_helper),
);
// In case the corpus is empty (on first run), reset
@ -373,13 +395,16 @@ unsafe fn fuzz(
let coverage = CoverageRuntime::new();
let mut frida_helper =
FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage));
let mut frida_helper = Rc::new(RefCell::new(FridaInstrumentationHelper::new(
&gum,
options,
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(),
frida_helper.borrow_mut().map_mut_ptr().unwrap(),
MAP_SIZE,
))
.track_indices();
@ -388,6 +413,7 @@ unsafe fn fuzz(
let time_observer = TimeObserver::new("time");
#[cfg(unix)]
let asan_observer = AsanErrorsObserver::from_static_asan_errors();
let frida_helper_observer = FridaHelperObserver::new(Rc::clone(&frida_helper));
// Feedback to rate the interestingness of an input
// This one is composed by two Feedbacks in OR
@ -451,9 +477,14 @@ unsafe fn fuzz(
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
#[cfg(unix)]
let observers = tuple_list!(edges_observer, time_observer, asan_observer);
let observers = tuple_list!(
frida_helper_observer,
edges_observer,
time_observer,
asan_observer
);
#[cfg(windows)]
let observers = tuple_list!(edges_observer, time_observer);
let observers = tuple_list!(frida_helper_observer, edges_observer, time_observer);
// Create the executor for an in-process function with just one observer for edge coverage
let mut executor = FridaInProcessExecutor::new(
@ -465,7 +496,7 @@ unsafe fn fuzz(
&mut state,
&mut mgr,
)?,
&mut frida_helper,
Rc::clone(&frida_helper),
);
// In case the corpus is empty (on first run), reset

View File

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

View File

@ -1,6 +1,6 @@
//! A libfuzzer-like fuzzer with llmp-multithreading support and restarts
//! The example harness is built for libpng.
use std::path::PathBuf;
use std::{cell::RefCell, path::PathBuf, rc::Rc};
use frida_gum::Gum;
use libafl::{
@ -40,6 +40,7 @@ use libafl_frida::{
cmplog_rt::CmpLogRuntime,
coverage_rt::{CoverageRuntime, MAP_SIZE},
executor::FridaInProcessExecutor,
frida_helper_shutdown_observer::FridaHelperObserver,
helper::{FridaInstrumentationHelper, IfElseRuntime},
};
use libafl_targets::cmplog::CmpLogObserver;
@ -104,7 +105,7 @@ fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
let options_clone = options.clone();
let client_description_clone2 = client_description.clone();
let options_clone2 = options.clone();
let mut frida_helper = FridaInstrumentationHelper::new(
let frida_helper = Rc::new(RefCell::new(FridaInstrumentationHelper::new(
&gum,
options,
tuple_list!(
@ -120,17 +121,22 @@ fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
),
coverage
),
);
)));
// 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)
StdMapObserver::from_mut_ptr(
"edges",
frida_helper.borrow_mut().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();
let frida_helper_observer = FridaHelperObserver::new(Rc::clone(&frida_helper));
// Feedback to rate the interestingness of an input
// This one is composed by two Feedbacks in OR
@ -187,7 +193,12 @@ fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
// 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);
let observers = tuple_list!(
frida_helper_observer,
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(
@ -200,7 +211,7 @@ fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
&mut mgr,
options.timeout,
)?,
&mut frida_helper,
Rc::clone(&frida_helper),
);
// Create an observation channel using cmplog map
let cmplog_observer = CmpLogObserver::new("cmplog", true);

View File

@ -0,0 +1,2 @@
[build]
rustflags = ["-C", "target-feature=+crt-static"]

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.16.5", features = [
frida-gum = { version = "0.16.7", features = [
"auto-download",
"event-sink",
"invocation-listener",

View File

@ -9,6 +9,8 @@ To build this example, run `cargo build --release` in this folder.
Then compile the harness `cl.exe /LD harness.cc /link /dll gdiplus.lib ole32.lib`
Note: this fuzzer is **statically linked** with C runtime. This is achieved by specifying `rustflags = ["-C", "target-feature=+crt-static"]` in `.cargo/config.toml`. The static linking is necessary to avoid Asan function hooks to hook the calls from the fuzzer itself, as such self-hooking can eventually lead to deadlocks in internal Frida mechanisms.
## Run
To run the example `target\release\frida_windows_gdiplus.exe -H harness.dll -i corpus -o output --libs-to-instrument gdi32.dll --libs-to-instrument gdi32full.dll --libs-to-instrument gdiplus.dll --libs-to-instrument WindowsCodecs.dll --disable-excludes`

View File

@ -1,2 +0,0 @@
[build]
target = "x86_64-pc-windows-msvc"

View File

@ -10,7 +10,7 @@ use mimalloc::MiMalloc;
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;
use std::path::PathBuf;
use std::{cell::RefCell, path::PathBuf, rc::Rc};
use frida_gum::Gum;
use libafl::{
@ -50,6 +50,7 @@ use libafl_frida::{
cmplog_rt::CmpLogRuntime,
coverage_rt::{CoverageRuntime, MAP_SIZE},
executor::FridaInProcessExecutor,
frida_helper_shutdown_observer::FridaHelperObserver,
helper::FridaInstrumentationHelper,
};
use libafl_targets::cmplog::CmpLogObserver;
@ -105,13 +106,16 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
let coverage = CoverageRuntime::new();
let asan = AsanRuntime::new(options);
let mut frida_helper =
FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage, asan));
let frida_helper = Rc::new(RefCell::new(FridaInstrumentationHelper::new(
&gum,
options,
tuple_list!(coverage, asan),
)));
//
// Create an observation channel using the coverage map
let edges_observer = HitcountsMapObserver::new(StdMapObserver::from_mut_ptr(
"edges",
frida_helper.map_mut_ptr().unwrap(),
frida_helper.borrow_mut().map_mut_ptr().unwrap(),
MAP_SIZE,
))
.track_indices();
@ -121,6 +125,8 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
let asan_observer = AsanErrorsObserver::from_static_asan_errors();
let frida_helper_observer = FridaHelperObserver::new(Rc::clone(&frida_helper));
// Feedback to rate the interestingness of an input
// This one is composed by two Feedbacks in OR
let mut feedback = feedback_or!(
@ -180,7 +186,12 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
// 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,);
let observers = tuple_list!(
frida_helper_observer,
edges_observer,
time_observer,
asan_observer,
);
// Create the executor for an in-process function with just one observer for edge coverage
let mut executor = FridaInProcessExecutor::new(
@ -193,7 +204,7 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
&mut mgr,
options.timeout,
)?,
&mut frida_helper,
Rc::clone(&frida_helper),
);
// In case the corpus is empty (on first run), reset
@ -220,14 +231,16 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
let coverage = CoverageRuntime::new();
let cmplog = CmpLogRuntime::new();
let mut frida_helper =
FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage, cmplog));
let frida_helper = Rc::new(RefCell::new(FridaInstrumentationHelper::new(
&gum,
options,
tuple_list!(coverage, cmplog),
)));
// Create an observation channel using the coverage map
let edges_observer = HitcountsMapObserver::new(StdMapObserver::from_mut_ptr(
"edges",
frida_helper.map_mut_ptr().unwrap(),
frida_helper.borrow_mut().map_mut_ptr().unwrap(),
MAP_SIZE,
))
.track_indices();
@ -235,6 +248,7 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
// 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();
let frida_helper_observer = FridaHelperObserver::new(Rc::clone(&frida_helper));
// Feedback to rate the interestingness of an input
// This one is composed by two Feedbacks in OR
@ -294,7 +308,12 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
// 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);
let observers = tuple_list!(
frida_helper_observer,
edges_observer,
time_observer,
asan_observer
);
// Create the executor for an in-process function with just one observer for edge coverage
let mut executor = FridaInProcessExecutor::new(
@ -306,7 +325,7 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
&mut state,
&mut mgr,
)?,
&mut frida_helper,
Rc::clone(&frida_helper),
);
// In case the corpus is empty (on first run), reset
@ -350,13 +369,16 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
let coverage = CoverageRuntime::new();
let mut frida_helper =
FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage));
let frida_helper = Rc::new(RefCell::new(FridaInstrumentationHelper::new(
&gum,
options,
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(),
frida_helper.borrow_mut().map_mut_ptr().unwrap(),
MAP_SIZE,
))
.track_indices();
@ -366,6 +388,8 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
let asan_observer = AsanErrorsObserver::from_static_asan_errors();
let frida_helper_observer = FridaHelperObserver::new(Rc::clone(&frida_helper));
// Feedback to rate the interestingness of an input
// This one is composed by two Feedbacks in OR
let mut feedback = feedback_or!(
@ -424,7 +448,12 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
// 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);
let observers = tuple_list!(
frida_helper_observer,
edges_observer,
time_observer,
asan_observer
);
// Create the executor for an in-process function with just one observer for edge coverage
let mut executor = FridaInProcessExecutor::new(
@ -437,7 +466,7 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
&mut mgr,
options.timeout,
)?,
&mut frida_helper,
Rc::clone(&frida_helper),
);
// In case the corpus is empty (on first run), reset

View File

@ -18,7 +18,8 @@ pub fn main() {
let mut cc = ClangWrapper::new();
if let Some(code) = cc
.cpp(is_cpp)
.cpp(true) // Link with C++ standard library (Frida links to it in order to hook C++ functions)
// .cpp(is_cpp)
// silence the compiler wrapper output, needed for some configure scripts.
.silence(true)
.parse_args(&args)

View File

@ -327,6 +327,12 @@ windows = { workspace = true, features = [
"Win32_Security",
"Win32_System_SystemInformation",
] }
winapi = { version = "0.3", features = [
"dbghelp",
"minwindef",
"winnt",
"errhandlingapi",
"processthreadsapi",
] }
[target.'cfg(windows)'.build-dependencies]
windows = { workspace = true }

View File

@ -369,7 +369,15 @@ pub mod windows_exception_handler {
let exception_list = data.exceptions();
if exception_list.contains(&code) {
log::error!("Crashed with {code}");
log::error!(
"Crashed with {code} at {:?} in thread {:?}",
exception_pointers
.ExceptionRecord
.as_mut()
.unwrap()
.ExceptionAddress,
winapi::um::processthreadsapi::GetCurrentThreadId()
);
} else {
// log::trace!("Exception code received, but {code} is not in CRASH_EXCEPTIONS");
is_crash = false;
@ -422,7 +430,17 @@ pub mod windows_exception_handler {
// Make sure we don't crash in the crash handler forever.
if is_crash {
let input = data.take_current_input::<I>();
log::warn!("Running observers and exiting!");
// // I want to disable the hooks before doing anything, especially before taking a stack dump
let input = data.take_current_input::<I>(); // log::set_max_level(log::LevelFilter::Trace);
run_observers_and_save_state::<E, EM, I, OF, S, Z>(
executor,
state,
input,
fuzzer,
event_mgr,
ExitKind::Crash,
);
{
let mut bsod = Vec::new();
{
@ -434,14 +452,6 @@ pub mod windows_exception_handler {
}
log::error!("{}", std::str::from_utf8(&bsod).unwrap());
}
run_observers_and_save_state::<E, EM, I, OF, S, Z>(
executor,
state,
input,
fuzzer,
event_mgr,
ExitKind::Crash,
);
} else {
// This is not worth saving
}

View File

@ -185,6 +185,15 @@ windows = { workspace = true, features = [
"Win32_System_SystemInformation",
"Win32_System_Console",
] }
once_cell = "1.10.0"
winapi = { version = "0.3", features = [
"fileapi",
"handleapi",
"processenv",
"processthreadsapi",
"winbase",
"winnt",
] }
windows-result = "0.3.0"
[target.'cfg(windows)'.build-dependencies]

View File

@ -970,6 +970,109 @@ impl SimpleStdoutLogger {
}
}
#[cfg(feature = "std")]
#[cfg(target_os = "windows")]
#[allow(clippy::cast_ptr_alignment)]
#[must_use]
/// Return thread ID without using TLS
pub fn get_thread_id() -> u64 {
use std::arch::asm;
#[cfg(target_arch = "x86_64")]
unsafe {
let teb: *const u8;
asm!("mov {}, gs:[0x30]", out(reg) teb);
let thread_id_ptr = teb.add(0x48) as *const u32;
u64::from(*thread_id_ptr)
}
#[cfg(target_arch = "x86")]
unsafe {
let teb: *const u8;
asm!("mov {}, fs:[0x18]", out(reg) teb);
let thread_id_ptr = teb.add(0x24) as *const u32;
*thread_id_ptr as u64
}
}
#[cfg(target_os = "linux")]
#[must_use]
#[allow(clippy::cast_sign_loss)]
/// Return thread ID without using TLS
pub fn get_thread_id() -> u64 {
use libc::{syscall, SYS_gettid};
unsafe { syscall(SYS_gettid) as u64 }
}
#[cfg(feature = "std")]
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
#[must_use]
/// Return thread ID using Rust's `std::thread`
pub fn get_thread_id() -> u64 {
// Fallback for other platforms
let thread_id = std::thread::current().id();
unsafe { mem::transmute::<_, u64>(thread_id) }
}
#[cfg(feature = "std")]
#[cfg(target_os = "windows")]
mod windows_logging {
use std::ptr;
use once_cell::sync::OnceCell;
use winapi::um::{
fileapi::WriteFile, handleapi::INVALID_HANDLE_VALUE, processenv::GetStdHandle,
winbase::STD_OUTPUT_HANDLE, winnt::HANDLE,
};
// Safe wrapper around HANDLE
struct StdOutHandle(HANDLE);
// Implement Send and Sync for StdOutHandle, assuming it's safe to share
unsafe impl Send for StdOutHandle {}
unsafe impl Sync for StdOutHandle {}
static H_STDOUT: OnceCell<StdOutHandle> = OnceCell::new();
fn get_stdout_handle() -> HANDLE {
H_STDOUT
.get_or_init(|| {
let handle = unsafe { GetStdHandle(STD_OUTPUT_HANDLE) };
StdOutHandle(handle)
})
.0
}
/// A function that writes directly to stdout using `WinAPI`.
/// Works much faster than println and does not need TLS
pub fn direct_log(message: &str) {
// Get the handle to standard output
let h_stdout: HANDLE = get_stdout_handle();
if h_stdout == INVALID_HANDLE_VALUE {
eprintln!("Failed to get standard output handle");
return;
}
let bytes = message.as_bytes();
let mut bytes_written = 0;
// Write the message to standard output
let result = unsafe {
WriteFile(
h_stdout,
bytes.as_ptr() as *const _,
bytes.len() as u32,
&mut bytes_written,
ptr::null_mut(),
)
};
if result == 0 {
eprintln!("Failed to write to standard output");
}
}
}
#[cfg(feature = "std")]
impl log::Log for SimpleStdoutLogger {
#[inline]
@ -977,16 +1080,32 @@ impl log::Log for SimpleStdoutLogger {
true
}
#[cfg(not(target_os = "windows"))]
fn log(&self, record: &Record) {
println!(
"[{:?}, {:?}] {}: {}",
"[{:?}, {:?}:{:?}] {}: {}",
current_time(),
std::process::id(),
get_thread_id(),
record.level(),
record.args()
);
}
#[cfg(target_os = "windows")]
fn log(&self, record: &Record) {
// println is not safe in TLS-less environment
let msg = format!(
"[{:?}, {:?}:{:?}] {}: {}\n",
current_time(),
std::process::id(),
get_thread_id(),
record.level(),
record.args()
);
windows_logging::direct_log(msg.as_str());
}
fn flush(&self) {}
}
@ -1143,6 +1262,65 @@ pub unsafe fn set_error_print_panic_hook(new_stderr: RawFd) {
}));
}
#[cfg(feature = "std")]
#[cfg(target_os = "windows")]
#[repr(C)]
#[allow(clippy::upper_case_acronyms)]
struct TEB {
reserved1: [u8; 0x58],
tls_pointer: *mut *mut u8,
reserved2: [u8; 0xC0],
}
#[cfg(feature = "std")]
#[cfg(target_arch = "x86_64")]
#[inline(always)]
#[cfg(target_os = "windows")]
fn nt_current_teb() -> *mut TEB {
use std::arch::asm;
let teb: *mut TEB;
unsafe {
asm!("mov {}, gs:0x30", out(reg) teb);
}
teb
}
/// Some of our hooks can be invoked from threads that do not have TLS yet.
/// Many Rust and Frida functions require TLS to be set up, so we need to check if we have TLS.
/// This was observed on Windows, so for now for other platforms we assume that we have TLS.
#[cfg(feature = "std")]
#[inline]
#[allow(unreachable_code)]
#[must_use]
pub fn has_tls() -> bool {
#[cfg(target_os = "windows")]
unsafe {
let teb = nt_current_teb();
if teb.is_null() {
return false;
}
let tls_array = (*teb).tls_pointer;
if tls_array.is_null() {
return false;
}
return true;
}
#[cfg(target_arch = "aarch64")]
unsafe {
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;
return tlsptr.add(0x102).read() != 0u64;
}
// Default
true
}
/// Zero-cost way to construct [`core::num::NonZeroUsize`] at compile-time.
#[macro_export]
macro_rules! nonzero {

View File

@ -62,17 +62,18 @@ 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.16.5", features = [
frida-gum-sys = { version = "0.16.7", features = [
"event-sink",
"invocation-listener",
] }
frida-gum = { version = "0.16.5", features = [
frida-gum = { version = "0.16.7", features = [
"event-sink",
"invocation-listener",
"module-names",
"script",
"backtrace",
] }
os-thread-local = "0.1.3"
dynasmrt = "3.0.1"
color-backtrace = { version = "0.7.0", features = ["resolve-modules"] }
@ -112,6 +113,15 @@ iced-x86 = { version = "1.21.0", features = ["code_asm"], optional = true }
[target.'cfg(windows)'.dependencies]
winsafe = { version = "0.0.22", features = ["kernel"] }
winapi = { version = "0.3", features = [
"processthreadsapi",
"winnt",
"memoryapi",
"errhandlingapi",
"debugapi",
] }
[target.'cfg(unix)'.dependencies]
errno = "0.2"
[target.'cfg(target_vendor="apple")'.dependencies]
mach-sys = { version = "0.5.4" }

View File

@ -57,7 +57,7 @@ fn main() {
cmd.arg("/dll").arg(format!(
"/OUT:{}",
Path::new(&out_dir)
.join("test_harness.so")
.join("test_harness.dll")
.to_str()
.unwrap()
));

View File

@ -200,7 +200,10 @@ impl Allocator {
}
metadata
} else {
// log::trace!("{:x}, {:x}", self.current_mapping_addr, rounded_up_size);
// log::info!(
// "Mapping {:x}, size {rounded_up_size:x}",
// self.current_mapping_addr
// );
let mapping = match MmapOptions::new(rounded_up_size)
.unwrap()
.with_address(self.current_mapping_addr)
@ -247,18 +250,14 @@ impl Allocator {
let address = (metadata.address + self.page_size) as *mut c_void;
self.allocations.insert(address as usize, metadata);
log::info!(
"serving address: {:#x}, size: {:#x}",
address as usize,
size
);
// log::info!("serving address: {address:?}, size: {size:x}");
address
}
/// Releases the allocation at the given address.
#[expect(clippy::missing_safety_doc)]
pub unsafe fn release(&mut self, ptr: *mut c_void) {
log::info!("release {:?}", ptr);
// log::info!("releasing {:?}", ptr);
let Some(metadata) = self.allocations.get_mut(&(ptr as usize)) else {
if !ptr.is_null()
&& AsanErrors::get_mut_blocking()
@ -405,14 +404,14 @@ impl Allocator {
unpoison: bool,
) -> (usize, usize) {
let shadow_mapping_start = map_to_shadow!(self, start);
log::trace!("map_shadow_for_region: {:x}, {:x}", start, end);
// log::trace!("map_shadow_for_region: {:x}, {:x}", start, end);
let shadow_start = self.round_down_to_page(shadow_mapping_start);
let shadow_end = self.round_up_to_page((end - start) / 8 + self.page_size + shadow_start);
log::trace!(
"map_shadow_for_region: shadow_start {:x}, shadow_end {:x}",
shadow_start,
shadow_end
);
// log::trace!(
// "map_shadow_for_region: shadow_start {:x}, shadow_end {:x}",
// shadow_start,
// shadow_end
// );
if self.using_pre_allocated_shadow_mapping {
let mut newly_committed_regions = Vec::new();
for gap in self.shadow_pages.gaps(&(shadow_start..shadow_end)) {
@ -441,11 +440,11 @@ impl Allocator {
}
}
for newly_committed_region in newly_committed_regions {
log::trace!(
"committed shadow pages: start {:x}, end {:x}",
newly_committed_region.start(),
newly_committed_region.end()
);
// log::trace!(
// "committed shadow pages: start {:x}, end {:x}",
// newly_committed_region.start(),
// newly_committed_region.end()
// );
self.shadow_pages
.insert(newly_committed_region.start()..newly_committed_region.end());
self.mappings
@ -571,7 +570,21 @@ impl Allocator {
map_to_shadow!(self, start)
}
/// Checks if the current address is one of ours - is this address in the allocator region
/// Is this a valid and mapped shadow address?
#[must_use]
pub fn valid_shadow(&self, start: usize, size: usize) -> bool {
let range_to_check = start..(start + size);
let valid = self
.shadow_pages
.overlapping(&range_to_check)
.any(|r| r.start <= start && r.end >= start + size);
if !valid {
log::error!("Not a valid shadow: {:#x}!", start);
}
valid
}
/// Checks if the currennt address is one of ours
#[inline]
pub fn is_managed(&self, ptr: *mut c_void) -> bool {
//self.allocations.contains_key(&(ptr as usize))
@ -664,7 +677,7 @@ impl Allocator {
if self.shadow_offset <= start && end <= self.current_mapping_addr {
log::trace!("Reached the shadow/allocator region - skipping");
} else {
log::trace!("Unpoisoning: {:#x}-{:#x}", start, end);
// log::trace!("Unpoisoning: {:#x}-{:#x}", start, end);
self.map_shadow_for_region(start, end, true);
}
true
@ -843,8 +856,9 @@ impl Default for Allocator {
}
#[test]
#[cfg(not(windows))] // not working yet
fn check_shadow() {
use frida_gum::Gum;
let _gum = Gum::obtain();
let mut allocator = Allocator::default();
allocator.init();

View File

@ -12,7 +12,7 @@ use std::{
ffi::{c_char, c_void},
ptr::write_volatile,
rc::Rc,
sync::MutexGuard,
sync::{Mutex, MutexGuard},
};
use backtrace::Backtrace;
@ -27,7 +27,7 @@ use frida_gum::{
};
use frida_gum_sys::Insn;
use hashbrown::HashMap;
use libafl_bolts::cli::FuzzerOptions;
use libafl_bolts::{cli::FuzzerOptions, get_thread_id, has_tls};
use libc::wchar_t;
use rangemap::RangeMap;
#[cfg(target_arch = "aarch64")]
@ -62,6 +62,144 @@ extern "C" {
fn tls_ptr() -> *const c_void;
}
// Reentrancy guard for the hooks
// We don't want to hook any operation initiated by the code of our hook
// Otherwise, we get into infinite recursion or deadlock
thread_local! {
static ASAN_IN_HOOK: Cell<bool> = const { Cell::new(false) };
}
/// RAII guard to set and reset the `ASAN_IN_HOOK` properly
#[derive(Debug)]
pub struct AsanInHookGuard;
impl AsanInHookGuard {
/// Constructor to save the current last error
#[must_use]
pub fn new() -> Self {
ASAN_IN_HOOK.set(true);
AsanInHookGuard
}
}
impl Drop for AsanInHookGuard {
fn drop(&mut self) {
ASAN_IN_HOOK.set(false);
}
}
impl Default for AsanInHookGuard {
fn default() -> Self {
Self::new()
}
}
/// The Lock below is a simple spinlock that uses the thread id as the lock value.
/// This is a simple way to prevent reentrancy in the hooks when we don't have TLS.
/// This is not a perfect solution, as it is global so it orders all threads without TLS.
/// However, this is a rare situation and should not affect performance significantly.
use std::sync::atomic::{AtomicU64, Ordering};
use std::thread;
#[derive(Debug)]
struct Lock {
state: AtomicU64,
}
impl Lock {
const fn new() -> Self {
Lock {
state: AtomicU64::new(u64::MAX),
}
}
fn lock(&self) -> LockResult {
let current_thread_id = get_thread_id();
loop {
let current_lock = self.state.load(Ordering::Relaxed);
if current_lock == u64::MAX {
if self
.state
.compare_exchange(
u64::MAX,
current_thread_id,
Ordering::Acquire,
Ordering::Relaxed,
)
.is_ok()
{
return LockResult::Acquired; // Lock acquired
}
} else if current_lock == current_thread_id {
return LockResult::AlreadyLocked; // Already locked by the same thread
}
thread::yield_now(); // Busy wait
}
}
fn unlock(&self) -> UnlockResult {
let current_thread_id = get_thread_id();
let current_lock = self.state.load(Ordering::Relaxed);
if current_lock == current_thread_id {
self.state.store(u64::MAX, Ordering::Release);
return UnlockResult::Success; // Lock released
}
UnlockResult::NotOwner // Lock not owned by the current thread
}
}
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
use errno::{errno, set_errno, Errno};
#[cfg(target_os = "windows")]
use winapi::shared::minwindef::DWORD;
/// We need to save and restore the last error in the hooks
#[cfg(target_os = "windows")]
use winapi::um::errhandlingapi::{GetLastError, SetLastError};
struct LastErrorGuard {
#[cfg(target_os = "windows")]
last_error: DWORD,
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
last_error: Errno,
}
impl LastErrorGuard {
// Constructor to save the current last error
fn new() -> Self {
#[cfg(target_os = "windows")]
let last_error = unsafe { GetLastError() };
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
let last_error = errno();
LastErrorGuard { last_error }
}
}
// Implement the Drop trait to restore the last error
impl Drop for LastErrorGuard {
fn drop(&mut self) {
#[cfg(target_os = "windows")]
unsafe {
SetLastError(self.last_error);
}
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
set_errno(self.last_error);
}
}
#[derive(Debug, PartialEq)]
enum LockResult {
Acquired,
AlreadyLocked,
}
#[derive(Debug, PartialEq)]
enum UnlockResult {
Success,
NotOwner,
}
// For threads without TLS, we use a static lock to prevent hook reentrancy
// This is not as efficient as using TLS, because it prevent TLS-free threads
// from running in parallel, but such situations are very rare (Windows loaded thread pool)
// and should not affect performance significantly
static TLS_LESS_LOCK: Lock = Lock::new();
/// The count of registers that need to be saved by the `ASan` runtime.
///
/// Sixteen general purpose registers are put in this order, `rax`, `rbx`, `rcx`, `rdx`, `rbp`, `rsp`, `rsi`, `rdi`, `r8-r15`, plus instrumented `rip`, accessed memory addr and true `rip`
@ -92,29 +230,6 @@ pub const ASAN_SAVE_REGISTER_NAMES: [&str; ASAN_SAVE_REGISTER_COUNT] = [
"actual rip",
];
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;
@ -135,7 +250,7 @@ const ASAN_EH_FRAME_FDE_ADDRESS_OFFSET: u32 = 28;
pub struct AsanRuntime {
check_for_leaks_enabled: bool,
current_report_impl: u64,
allocator: Allocator,
allocator: Mutex<Allocator>,
regs: [usize; ASAN_SAVE_REGISTER_COUNT],
blob_report: Option<Box<[u8]>>,
blob_check_mem_byte: Option<Box<[u8]>>,
@ -158,6 +273,7 @@ pub struct AsanRuntime {
pc: Option<usize>,
hooks: Vec<NativePointer>,
pub(crate) hooks_enabled: bool,
// thread_in_hook: ThreadLocal<Cell<bool>>,
#[cfg(target_arch = "aarch64")]
eh_frame: [u32; ASAN_EH_FRAME_DWORD_COUNT],
}
@ -184,7 +300,7 @@ impl FridaRuntime for AsanRuntime {
_ranges: &RangeMap<u64, (u16, String)>,
module_map: &Rc<ModuleMap>,
) {
self.allocator.init();
self.allocator_mut().init();
AsanErrors::get_mut_blocking().set_continue_on_error(self.continue_on_error);
@ -247,7 +363,7 @@ impl AsanRuntime {
let continue_on_error = options.continue_on_error;
Self {
check_for_leaks_enabled: options.detect_leaks,
allocator: Allocator::new(options),
allocator: Mutex::new(Allocator::new(options)),
skip_ranges,
continue_on_error,
..Self::default()
@ -256,23 +372,22 @@ impl AsanRuntime {
/// Reset all allocations so that they can be reused for new allocation requests.
pub fn reset_allocations(&mut self) {
self.allocator.reset();
self.allocator_mut().reset();
}
/// Gets the allocator
#[must_use]
pub fn allocator(&self) -> &Allocator {
&self.allocator
pub fn allocator(&self) -> MutexGuard<Allocator> {
self.allocator.lock().unwrap()
}
/// Gets the allocator (mutable)
pub fn allocator_mut(&mut self) -> &mut Allocator {
&mut self.allocator
pub fn allocator_mut(&mut self) -> MutexGuard<Allocator> {
self.allocator.lock().unwrap()
}
/// Check if the test leaked any memory and report it if so.
pub fn check_for_leaks(&mut self) {
self.allocator.check_for_leaks();
self.allocator_mut().check_for_leaks();
}
/// Returns the `AsanErrors` from the recent run.
@ -283,7 +398,7 @@ impl AsanRuntime {
/// Make sure the specified memory is unpoisoned
pub fn unpoison(&mut self, address: usize, size: usize) {
self.allocator
self.allocator_mut()
.map_shadow_for_region(address, address + size, true);
}
@ -293,7 +408,10 @@ impl AsanRuntime {
/// The address needs to be a valid address, the size needs to be correct.
/// This will dereference at the address.
pub unsafe fn poison(&mut self, address: usize, size: usize) {
Allocator::poison(self.allocator.map_to_shadow(address), size);
let start = self.allocator_mut().map_to_shadow(address);
if self.allocator_mut().valid_shadow(start, size) {
Allocator::poison(start, size);
}
}
/// Add a stalked address to real address mapping.
@ -313,16 +431,18 @@ impl AsanRuntime {
/// Unpoison all the memory that is currently mapped with read/write permissions.
pub fn unpoison_all_existing_memory(&mut self) {
self.allocator.unpoison_all_existing_memory();
self.allocator_mut().unpoison_all_existing_memory();
}
/// Enable all function hooks
pub fn enable_hooks(&mut self) {
log::info!("Enabling hooks");
self.hooks_enabled = true;
}
/// Disable all function hooks
pub fn disable_hooks(&mut self) {
self.hooks_enabled = false;
log::info!("Disabling hooks");
}
/// Register the current thread with the runtime, implementing shadow memory for its stack and
@ -331,14 +451,15 @@ impl AsanRuntime {
pub fn register_thread(&mut self) {
let (stack_start, stack_end) = Self::current_stack();
let (tls_start, tls_end) = Self::current_tls();
log::info!(
"registering thread with stack {stack_start:x}:{stack_end:x} and tls {tls_start:x}:{tls_end:x}"
println!(
"registering thread {:?} with stack {stack_start:x}:{stack_end:x} and tls {tls_start:x}:{tls_end:x}",
get_thread_id()
);
self.allocator
self.allocator_mut()
.map_shadow_for_region(stack_start, stack_end, true);
#[cfg(unix)]
self.allocator
self.allocator_mut()
.map_shadow_for_region(tls_start, tls_end, true);
}
@ -346,7 +467,7 @@ impl AsanRuntime {
#[cfg(target_vendor = "apple")]
pub fn register_thread(&mut self) {
let (stack_start, stack_end) = Self::current_stack();
self.allocator
self.allocator_mut()
.map_shadow_for_region(stack_start, stack_end, true);
log::info!("registering thread with stack {stack_start:x}:{stack_end:x}");
@ -489,27 +610,28 @@ impl AsanRuntime {
#[allow(non_snake_case)]
unsafe extern "C" fn [<replacement_ $name>]($($param: $param_type),*) -> $return_type {
let _last_error_guard = LastErrorGuard::new();
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 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),*)
if has_tls() {
if !ASAN_IN_HOOK.get(){
let _guard = AsanInHookGuard::new(); // Ensure ASAN_IN_HOOK is set and reset
return this.[<hook_ $name>](*original, $($param),*);
}
}
} else {
(original)($($param),*)
// else{
// log::warn!("{} called without TLS", stringify!($name));
// $(
// log::warn!("{}: {:?}", stringify!($param), $param);
// )*
// }
}
(original)($($param),*)
}
let self_ptr = core::ptr::from_ref(self) as usize;
@ -536,27 +658,21 @@ impl AsanRuntime {
#[allow(non_snake_case)]
unsafe extern "C" fn [<replacement_ $name>]($($param: $param_type),*) -> $return_type {
let _last_error_guard = LastErrorGuard::new();
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 = [<$lib_ident:snake:upper _ $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),*)
if has_tls() {
if !ASAN_IN_HOOK.get(){
let _guard = AsanInHookGuard::new(); // Ensure ASAN_IN_HOOK is set and reset
return this.[<hook_ $name>](*original, $($param),*);
}
}
} else {
(original)($($param),*)
}
(original)($($param),*)
}
let self_ptr = core::ptr::from_ref(self) as usize;
@ -573,7 +689,8 @@ impl AsanRuntime {
#[allow(unused_macro_rules)]
macro_rules! hook_func_with_check {
($name:ident, ($($param:ident : $param_type:ty),*), $return_type:ty, $always_enabled:expr) => {
//No library case
($name:ident, ($($param:ident : $param_type:ty),*), $return_type:ty, $always_enabled:expr ) => {
paste::paste! {
let target_function = Module::find_global_export_by_name(stringify!($name)).expect("Failed to find function");
@ -582,39 +699,43 @@ impl AsanRuntime {
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)]
#[allow(non_snake_case)] // depends on the values the macro is invoked with
#[allow(clippy::redundant_else)]
unsafe extern "C" fn [<replacement_ $name>]($($param: $param_type),*) -> $return_type {
let _last_error_guard = LastErrorGuard::new();
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 $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
if has_tls() {
if !ASAN_IN_HOOK.get(){
let _guard = AsanInHookGuard::new(); // Ensure ASAN_IN_HOOK is set and reset
if this.[<hook_check_ $name>]($($param),*){
return this.[<hook_ $name>](*original, $($param),*);
}
}
}
} else {
(original)($($param),*)
}
else{
// log::warn!("{} called without TLS", stringify!($name));
// $(
// log::warn!("Params: {}: {:?}", stringify!($param), $param);
// )*
if $always_enabled {
if TLS_LESS_LOCK.lock() == LockResult::Acquired && this.[<hook_check_ $name>]($($param),*){
// There is no TLS and we have grabbed the lock - call the hook
let ret = this.[<hook_ $name>](*original, $($param),*);
TLS_LESS_LOCK.unlock();
return ret;
}
else {
TLS_LESS_LOCK.unlock(); // Return the original function
}
}
}
}
(original)($($param),*)
}
let self_ptr = core::ptr::from_ref(self) as usize;
@ -627,50 +748,47 @@ 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, $always_enabled:expr) => {
($lib:literal, $lib_ident:ident, $name:ident, ($($param:ident : $param_type:ty),*), $return_type:ty, $always_enabled:expr ) => {
paste::paste! {
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();
let _ = [<$lib_ident:snake:upper _ $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)]
#[allow(clippy::redundant_else)]
unsafe extern "C" fn [<replacement_ $name>]($($param: $param_type),*) -> $return_type {
let _last_error_guard = LastErrorGuard::new();
let mut invocation = Interceptor::current_invocation();
let this = &mut *(invocation.replacement_data().unwrap().0 as *mut 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 $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
if has_tls() {
if !ASAN_IN_HOOK.get(){
let _guard = AsanInHookGuard::new(); // Ensure ASAN_IN_HOOK is set and reset
if this.[<hook_check_ $name>]($($param),*){
return this.[<hook_ $name>](*original, $($param),*);
}
}
}
} else {
(original)($($param),*)
}
else{
if $always_enabled {
if TLS_LESS_LOCK.lock() == LockResult::Acquired && this.[<hook_check_ $name>]($($param),*){
// There is no TLS and we have grabbed the lock - call the hook
let ret = this.[<hook_ $name>](*original, $($param),*);
TLS_LESS_LOCK.unlock();
return ret;
}
else {
TLS_LESS_LOCK.unlock(); // Return the original function
}
}
}
}
(original)($($param),*)
}
let self_ptr = core::ptr::from_ref(self) as usize;
@ -681,7 +799,14 @@ impl AsanRuntime {
);
self.hooks.push(target_function);
}
}
};
// Default case without check_enabled parameter
($name:ident, ($($param:ident : $param_type:ty),*), $return_type:ty) => {
hook_func_with_check!($name, ($($param: $param_type),*), $return_type, false);
};
($lib:literal, $lib_ident:ident, $name:ident, ($($param:ident : $param_type:ty),*), $return_type:ty) => {
hook_func_with_check!($lib, $lib_ident, $name, ($($param: $param_type),*), $return_type, false);
};
}
// Hook the memory allocator functions
@ -779,6 +904,8 @@ impl AsanRuntime {
"HeapFree" => {
hook_func_with_check!($libname, $lib_ident, HeapFree, (handle: *mut c_void, flags: u32, mem: *mut c_void), bool, true);
}
// NOTE: we call it with always_enabled, because on Windows, some COM memory deallocation occurs later in the process
// after we have completed the run
"RtlFreeHeap" => {
hook_func_with_check!($libname, $lib_ident, RtlFreeHeap, (handle: *mut c_void, flags: u32, mem: *mut c_void), usize, true);
}
@ -926,6 +1053,14 @@ impl AsanRuntime {
*const c_void
);
}
"UnmapViewOfFile" => {
hook_func!(
$libname, $lib_ident,
UnmapViewOfFile,
(ptr: *const c_void),
bool
);
}
"LoadLibraryExW" => {
hook_func!(
$libname, $lib_ident,
@ -963,6 +1098,8 @@ impl AsanRuntime {
"api-ms-win-core-heap-obsolete-l1-1-0",
api_ms_heap2_obsolete
);
hook_heap_windows!("api-ms-win-core-memory-l1-1-0", api_ms_memory1);
hook_heap_windows!("VCRUNTIME140", VCRUNTIME140);
}
/*
@ -1360,6 +1497,8 @@ impl AsanRuntime {
#[expect(clippy::cast_sign_loss)]
#[expect(clippy::too_many_lines)]
extern "system" fn handle_trap(&mut self) {
// log::error!("Attach the debugger to process {:#?}", std::process::id());
// std::thread::sleep(std::time::Duration::from_secs(30));
self.disable_hooks();
self.dump_registers();
@ -1454,6 +1593,8 @@ impl AsanRuntime {
} else if base_value.is_some() {
if let Some(metadata) = self
.allocator
.lock()
.unwrap()
.find_metadata(fault_address, base_value.unwrap())
{
match access_type {
@ -1609,6 +1750,8 @@ impl AsanRuntime {
}
} else if let Some(metadata) = self
.allocator
.lock()
.unwrap()
.find_metadata(fault_address, self.regs[base_reg as usize])
{
let asan_readwrite_error = AsanReadWriteError {
@ -1778,7 +1921,7 @@ impl AsanRuntime {
*/
#[cfg(target_arch = "x86_64")]
fn generate_shadow_check_blob(&mut self, size: u32) -> Box<[u8]> {
let shadow_bit = self.allocator.shadow_bit();
let shadow_bit = self.allocator_mut().shadow_bit();
// Rcx, Rax, Rdi, Rdx, Rsi, R8 are used, so we save them in emit_shadow_check
//at this point RDI contains the
let mask_shift = 32 - size;
@ -1826,7 +1969,7 @@ impl AsanRuntime {
x0 and x1 are saved by the asan_check
The maximum size this supports is up to 25 bytes. This is because we load 4 bytes of the shadow value. And, in the case that we have a misaligned address with an offset of 7 into the word. For example, if we load 25 bytes from 0x1007 - [0x1007,0x101f], then we require the shadow values from 0x1000, 0x1008, 0x1010, and 0x1018 */
let shadow_bit = self.allocator.shadow_bit();
let shadow_bit = self.allocator_mut().shadow_bit();
macro_rules! shadow_check {
($ops:ident, $width:expr) => {dynasm!($ops
; .arch aarch64
@ -1872,7 +2015,7 @@ impl AsanRuntime {
assert!(width <= 64, "width must be <= 64");
let shift = 64 - width;
let shadow_bit = self.allocator.shadow_bit();
let shadow_bit = self.allocator_mut().shadow_bit();
macro_rules! shadow_check_exact {
($ops:ident, $shift:expr) => {dynasm!($ops
; .arch aarch64
@ -2543,6 +2686,8 @@ impl AsanRuntime {
// on amd64 jump can takes 10 bytes at most, so that's why I put 10 bytes.
writer.put_nop();
}
} else {
log::trace!("Cannot check instructions for {:?} bytes.", width);
}
writer.put_pop_reg(X86Register::Rdi);
@ -2788,7 +2933,7 @@ impl Default for AsanRuntime {
Self {
check_for_leaks_enabled: false,
current_report_impl: 0,
allocator: Allocator::default(),
allocator: Mutex::new(Allocator::default()),
regs: [0; ASAN_SAVE_REGISTER_COUNT],
blob_report: None,
blob_check_mem_byte: None,
@ -2813,6 +2958,7 @@ impl Default for AsanRuntime {
pc: None,
hooks: Vec::new(),
hooks_enabled: false,
// thread_in_hook: ThreadLocal::new(|| Cell::new(false)),
}
}
}

View File

@ -20,6 +20,14 @@ extern "system" {
extern "system" {
fn memset(s: *mut c_void, c: i32, n: usize) -> *mut c_void;
}
use std::ptr;
#[cfg(windows)]
use winapi::um::memoryapi::VirtualQuery;
#[cfg(windows)]
use winapi::um::winnt::MEMORY_BASIC_INFORMATION;
#[expect(clippy::not_unsafe_ptr_arg_deref)]
impl AsanRuntime {
#[inline]
@ -131,6 +139,7 @@ impl AsanRuntime {
_context: usize,
_entry_point: usize,
) -> usize {
log::trace!("LdrpCallInitRoutine");
// winsafe::OutputDebugString("LdrpCallInitRoutine");
// let result = unsafe { LdrLoadDll(path, file, flags,x )};
// self.allocator_mut().unpoison_all_existing_memory();
@ -174,6 +183,7 @@ impl AsanRuntime {
_lock: *const c_void,
_parameters: *const c_void,
) -> *mut c_void {
log::trace!("RtlCreateHeap");
0xc0debeef as *mut c_void
}
#[inline]
@ -184,7 +194,8 @@ impl AsanRuntime {
_original: extern "C" fn(_handle: *const c_void) -> *mut c_void,
_handle: *const c_void,
) -> *mut c_void {
std::ptr::null_mut()
log::trace!("RtlDestroyHeap");
ptr::null_mut()
}
#[inline]
@ -197,7 +208,8 @@ impl AsanRuntime {
flags: u32,
size: usize,
) -> *mut c_void {
let allocator = self.allocator_mut();
log::trace!("HeapAlloc");
let mut allocator = self.allocator_mut();
let ret = unsafe { allocator.alloc(size, 8) };
if flags & 8 == 8 {
@ -210,6 +222,7 @@ impl AsanRuntime {
}
ret
}
#[inline]
#[expect(non_snake_case)]
#[cfg(windows)]
@ -220,7 +233,9 @@ impl AsanRuntime {
flags: u32,
size: usize,
) -> *mut c_void {
let allocator = self.allocator_mut();
log::trace!("hook_RtlAllocateHeap handle {_handle:#?} flags {flags:x} size {size}");
let mut allocator = self.allocator_mut();
let ret = unsafe { allocator.alloc(size, 8) };
if flags & 8 == 8 {
@ -249,7 +264,8 @@ impl AsanRuntime {
ptr: *mut c_void,
size: usize,
) -> *mut c_void {
let allocator = self.allocator_mut();
log::trace!("hook_HeapReAlloc handle {handle:#?} flags {flags:x} ptr {ptr:#?} size {size}");
let mut allocator = self.allocator_mut();
if !allocator.is_managed(ptr) {
return original(handle, flags, ptr, size);
}
@ -293,7 +309,7 @@ impl AsanRuntime {
ptr: *mut c_void,
size: usize,
) -> *mut c_void {
let allocator = self.allocator_mut();
let mut allocator = self.allocator_mut();
log::trace!("RtlReAllocateHeap({ptr:?}, {size:x})");
if !allocator.is_managed(ptr) {
return original(handle, flags, ptr, size);
@ -331,6 +347,7 @@ impl AsanRuntime {
_flags: u32,
ptr: *mut c_void,
) -> bool {
log::trace!("hook_check_RtlFreeHeap ptr {ptr:#?}");
self.allocator_mut().is_managed(ptr)
}
#[inline]
@ -343,6 +360,7 @@ impl AsanRuntime {
_flags: u32,
ptr: *mut c_void,
) -> usize {
log::trace!("hook_RtlFreeHeap address handle {_handle:#?} flags 0x{_flags:x} ptr {ptr:#?}");
unsafe { self.allocator_mut().release(ptr) };
0
}
@ -355,6 +373,7 @@ impl AsanRuntime {
_flags: u32,
ptr: *mut c_void,
) -> bool {
log::trace!("hook_check_HeapFree");
self.allocator_mut().is_managed(ptr)
}
#[inline]
@ -367,6 +386,7 @@ impl AsanRuntime {
_flags: u32,
ptr: *mut c_void,
) -> bool {
log::trace!("hook_HeapFree");
unsafe { self.allocator_mut().release(ptr) };
true
}
@ -379,6 +399,7 @@ impl AsanRuntime {
_flags: u32,
ptr: *mut c_void,
) -> bool {
log::trace!("hook_check_HeapSize");
self.allocator_mut().is_managed(ptr)
}
@ -391,6 +412,7 @@ impl AsanRuntime {
_flags: u32,
ptr: *mut c_void,
) -> usize {
log::trace!("hook_HeapSize");
self.allocator().get_usable_size(ptr)
}
#[inline]
@ -402,6 +424,7 @@ impl AsanRuntime {
_flags: u32,
ptr: *mut c_void,
) -> bool {
log::trace!("hook_check_RtlSizeHeap");
self.allocator_mut().is_managed(ptr)
}
@ -414,6 +437,7 @@ impl AsanRuntime {
_flags: u32,
ptr: *mut c_void,
) -> usize {
log::trace!("hook_RtlSizeHeap");
self.allocator().get_usable_size(ptr)
}
#[inline]
@ -425,6 +449,7 @@ impl AsanRuntime {
_flags: u32,
ptr: *mut c_void,
) -> bool {
log::trace!("hook_check_RtlValidateHeap");
self.allocator_mut().is_managed(ptr)
}
@ -437,6 +462,7 @@ impl AsanRuntime {
_flags: u32,
_ptr: *mut c_void,
) -> bool {
log::trace!("hook_RtlValidateHeap");
true
}
@ -448,6 +474,7 @@ impl AsanRuntime {
flags: u32,
size: usize,
) -> *mut c_void {
log::trace!("hook_LocalAlloc");
let ret = unsafe { self.allocator_mut().alloc(size, 8) };
if flags & 0x40 == 0x40 {
@ -466,6 +493,7 @@ impl AsanRuntime {
size: usize,
_flags: u32,
) -> *mut c_void {
log::trace!("hook_LocalReAlloc");
unsafe {
let ret = self.allocator_mut().alloc(size, 0x8);
if !mem.is_null() && !ret.is_null() {
@ -480,6 +508,7 @@ impl AsanRuntime {
#[expect(non_snake_case)]
#[cfg(windows)]
pub fn hook_check_LocalFree(&mut self, mem: *mut c_void) -> bool {
log::trace!("hook_check_LocalFree");
let res = self.allocator_mut().is_managed(mem);
res
}
@ -491,6 +520,7 @@ impl AsanRuntime {
_original: extern "C" fn(mem: *mut c_void) -> *mut c_void,
mem: *mut c_void,
) -> *mut c_void {
log::trace!("hook_LocalFree");
unsafe { self.allocator_mut().release(mem) };
mem
}
@ -498,6 +528,7 @@ impl AsanRuntime {
#[expect(non_snake_case)]
#[cfg(windows)]
pub fn hook_check_LocalHandle(&mut self, mem: *mut c_void) -> bool {
log::trace!("hook_check_LocalHandle");
self.allocator_mut().is_managed(mem)
}
#[expect(non_snake_case)]
@ -507,11 +538,13 @@ impl AsanRuntime {
_soriginal: extern "C" fn(mem: *mut c_void) -> *mut c_void,
mem: *mut c_void,
) -> *mut c_void {
log::trace!("hook_LocalHandle");
mem
}
#[expect(non_snake_case)]
#[cfg(windows)]
pub fn hook_check_LocalLock(&mut self, mem: *mut c_void) -> bool {
log::trace!("hook_check_LocalLock");
self.allocator_mut().is_managed(mem)
}
@ -522,11 +555,13 @@ impl AsanRuntime {
_original: extern "C" fn(mem: *mut c_void) -> *mut c_void,
mem: *mut c_void,
) -> *mut c_void {
log::trace!("hook_LocalLock");
mem
}
#[expect(non_snake_case)]
#[cfg(windows)]
pub fn hook_check_LocalUnlock(&mut self, mem: *mut c_void) -> bool {
log::trace!("hook_check_LocalUnlock");
self.allocator_mut().is_managed(mem)
}
#[expect(non_snake_case)]
@ -536,11 +571,13 @@ impl AsanRuntime {
_original: extern "C" fn(_mem: *mut c_void) -> bool,
_mem: *mut c_void,
) -> bool {
log::trace!("hook_LocalUnlock");
false
}
#[expect(non_snake_case)]
#[cfg(windows)]
pub fn hook_check_LocalSize(&mut self, mem: *mut c_void) -> bool {
log::trace!("hook_check_LocalSize");
self.allocator_mut().is_managed(mem)
}
#[expect(non_snake_case)]
@ -550,11 +587,13 @@ impl AsanRuntime {
_original: extern "C" fn(mem: *mut c_void) -> usize,
mem: *mut c_void,
) -> usize {
log::trace!("hook_LocalSize");
self.allocator_mut().get_usable_size(mem)
}
#[expect(non_snake_case)]
#[cfg(windows)]
pub fn hook_check_LocalFlags(&mut self, mem: *mut c_void) -> bool {
log::trace!("hook_check_LocalFlags");
self.allocator_mut().is_managed(mem)
}
#[expect(non_snake_case)]
@ -564,6 +603,7 @@ impl AsanRuntime {
_original: extern "C" fn(_mem: *mut c_void) -> u32,
_mem: *mut c_void,
) -> u32 {
log::trace!("hook_LocalFlags");
0
}
@ -575,6 +615,7 @@ impl AsanRuntime {
flags: u32,
size: usize,
) -> *mut c_void {
log::trace!("hook_GlobalAlloc");
let ret = unsafe { self.allocator_mut().alloc(size, 8) };
if flags & 0x40 == 0x40 {
@ -596,6 +637,7 @@ impl AsanRuntime {
_flags: u32,
size: usize,
) -> *mut c_void {
log::trace!("hook_GlobalReAlloc");
unsafe {
let ret = self.allocator_mut().alloc(size, 0x8);
if !mem.is_null() && !ret.is_null() {
@ -610,6 +652,7 @@ impl AsanRuntime {
#[expect(non_snake_case)]
#[cfg(windows)]
pub fn hook_check_GlobalFree(&mut self, mem: *mut c_void) -> bool {
log::trace!("hook_check_GlobalFree");
self.allocator_mut().is_managed(mem)
}
#[expect(non_snake_case)]
@ -619,6 +662,7 @@ impl AsanRuntime {
_original: extern "C" fn(mem: *mut c_void) -> *mut c_void,
mem: *mut c_void,
) -> *mut c_void {
log::trace!("hook_GlobalFree");
unsafe { self.allocator_mut().release(mem) };
mem
}
@ -635,6 +679,7 @@ impl AsanRuntime {
_original: extern "C" fn(mem: *mut c_void) -> *mut c_void,
mem: *mut c_void,
) -> *mut c_void {
log::trace!("hook_GlobalHandle");
mem
}
#[expect(non_snake_case)]
@ -650,6 +695,7 @@ impl AsanRuntime {
_original: extern "C" fn(mem: *mut c_void) -> *mut c_void,
mem: *mut c_void,
) -> *mut c_void {
log::trace!("hook_GlobalLock");
mem
}
#[expect(non_snake_case)]
@ -664,6 +710,7 @@ impl AsanRuntime {
_original: extern "C" fn(_mem: *mut c_void) -> bool,
_mem: *mut c_void,
) -> bool {
log::trace!("hook_GlobalUnlock");
false
}
#[expect(non_snake_case)]
@ -678,6 +725,7 @@ impl AsanRuntime {
_original: extern "C" fn(mem: *mut c_void) -> usize,
mem: *mut c_void,
) -> usize {
log::trace!("hook_GlobalSize");
self.allocator_mut().get_usable_size(mem)
}
#[expect(non_snake_case)]
@ -692,6 +740,7 @@ impl AsanRuntime {
_original: extern "C" fn(mem: *mut c_void) -> u32,
_mem: *mut c_void,
) -> u32 {
log::trace!("hook_GlobalFlags");
0
}
@ -702,7 +751,10 @@ impl AsanRuntime {
_original: extern "C" fn(size: usize) -> *mut c_void,
size: usize,
) -> *mut c_void {
unsafe { self.allocator_mut().alloc(size, 8) }
unsafe {
log::trace!("hook_malloc");
self.allocator_mut().alloc(size, 8)
}
}
#[inline]
@ -711,7 +763,10 @@ impl AsanRuntime {
_original: extern "C" fn(size: usize) -> *mut c_void,
size: usize,
) -> *mut c_void {
unsafe { self.allocator_mut().alloc(size, 8) }
unsafe {
log::trace!("hook_malloc");
self.allocator_mut().alloc(size, 8)
}
}
#[inline]
@ -720,7 +775,10 @@ impl AsanRuntime {
_original: extern "C" fn(size: usize) -> *mut c_void,
size: usize,
) -> *mut c_void {
unsafe { self.allocator_mut().alloc(size, 8) }
unsafe {
log::trace!("hook_o_malloc");
self.allocator_mut().alloc(size, 8)
}
}
#[expect(non_snake_case)]
@ -730,7 +788,10 @@ impl AsanRuntime {
_original: extern "C" fn(size: usize) -> *mut c_void,
size: usize,
) -> *mut c_void {
unsafe { self.allocator_mut().alloc(size, 8) }
unsafe {
log::trace!("hook__Znam");
self.allocator_mut().alloc(size, 8)
}
}
#[expect(non_snake_case)]
@ -741,6 +802,7 @@ impl AsanRuntime {
size: usize,
_nothrow: *const c_void,
) -> *mut c_void {
log::trace!("hook__ZnamRKSt9nothrow_t");
unsafe { self.allocator_mut().alloc(size, 8) }
}
@ -752,6 +814,7 @@ impl AsanRuntime {
size: usize,
alignment: usize,
) -> *mut c_void {
log::trace!("hook__ZnamSt11align_val_t");
unsafe { self.allocator_mut().alloc(size, alignment) }
}
@ -768,6 +831,7 @@ impl AsanRuntime {
alignment: usize,
_nothrow: *const c_void,
) -> *mut c_void {
log::trace!("hook__ZnamSt11align_val_tRKSt9nothrow_t");
unsafe { self.allocator_mut().alloc(size, alignment) }
}
@ -780,6 +844,7 @@ impl AsanRuntime {
_original: extern "C" fn(size: usize) -> *mut c_void,
size: usize,
) -> *mut c_void {
log::trace!("hook__Znwm");
let result = unsafe { self.allocator_mut().alloc(size, 8) };
if result.is_null() {
extern "system" {
@ -803,6 +868,7 @@ impl AsanRuntime {
size: usize,
_nothrow: *const c_void,
) -> *mut c_void {
log::trace!("hook__ZnwmRKSt9nothrow_t");
unsafe { self.allocator_mut().alloc(size, 8) }
}
@ -816,6 +882,7 @@ impl AsanRuntime {
size: usize,
alignment: usize,
) -> *mut c_void {
log::trace!("hook__ZnwmSt11align_val_t");
let result = unsafe { self.allocator_mut().alloc(size, alignment) };
if result.is_null() {
extern "system" {
@ -842,6 +909,7 @@ impl AsanRuntime {
alignment: usize,
_nothrow: *const c_void,
) -> *mut c_void {
log::trace!("hook__ZnwmSt11align_val_tRKSt9nothrow_t");
unsafe { self.allocator_mut().alloc(size, alignment) }
}
@ -852,6 +920,7 @@ impl AsanRuntime {
_original: extern "C" fn(size: usize) -> *mut c_void,
size: usize,
) -> *mut c_void {
log::trace!("hook__o_malloc");
unsafe { self.allocator_mut().alloc(size, 8) }
}
#[inline]
@ -864,6 +933,7 @@ impl AsanRuntime {
extern "system" {
fn memset(s: *mut c_void, c: i32, n: usize) -> *mut c_void;
}
log::trace!("hook_calloc");
let ret = unsafe { self.allocator_mut().alloc(size * nmemb, 8) };
// if size * nmemb == 0x10 {
// log::error!("backtrace: {:0x?}", frida_gum::Backtracer::accurate());
@ -887,6 +957,7 @@ impl AsanRuntime {
extern "system" {
fn memset(s: *mut c_void, c: i32, n: usize) -> *mut c_void;
}
log::trace!("hook__o_calloc");
let ret = unsafe { self.allocator_mut().alloc(size * nmemb, 8) };
unsafe {
memset(ret, 0, size * nmemb);
@ -907,16 +978,17 @@ impl AsanRuntime {
ptr: *mut c_void,
size: usize,
) -> *mut c_void {
log::trace!("hook_realloc");
unsafe {
if size == 0 {
self.allocator_mut().release(ptr);
#[cfg(not(target_vendor = "apple"))]
return std::ptr::null_mut();
return ptr::null_mut();
#[cfg(target_vendor = "apple")]
return self.allocator_mut().alloc(0, 0x8);
}
let ret = self.allocator_mut().alloc(size, 0x8);
if ptr != std::ptr::null_mut() && ret != std::ptr::null_mut() {
if ptr != ptr::null_mut() && ret != ptr::null_mut() {
let old_size = self.allocator_mut().get_usable_size(ptr);
let copy_size = if size < old_size { size } else { old_size };
(ptr as *mut u8).copy_to(ret as *mut u8, copy_size);
@ -947,7 +1019,7 @@ impl AsanRuntime {
return self.allocator_mut().alloc(0, 0x8);
}
let ret = self.allocator_mut().alloc(size, 0x8);
if ptr != std::ptr::null_mut() && ret != std::ptr::null_mut() {
if ptr != ptr::null_mut() && ret != ptr::null_mut() {
let old_size = self.allocator_mut().get_usable_size(ptr);
let copy_size = if size < old_size { size } else { old_size };
(ptr as *mut u8).copy_to(ret as *mut u8, copy_size);
@ -966,9 +1038,10 @@ impl AsanRuntime {
ptr: *mut c_void,
size: usize,
) -> *mut c_void {
log::trace!("hook__o_realloc");
unsafe {
let ret = self.allocator_mut().alloc(size, 0x8);
if ptr != std::ptr::null_mut() && ret != std::ptr::null_mut() {
if ptr != ptr::null_mut() && ret != ptr::null_mut() {
let old_size = self.allocator_mut().get_usable_size(ptr);
let copy_size = if size < old_size { size } else { old_size };
(ptr as *mut u8).copy_to(ret as *mut u8, copy_size);
@ -981,6 +1054,7 @@ impl AsanRuntime {
#[expect(non_snake_case)]
#[inline]
pub fn hook_check__o_free(&mut self, ptr: *mut c_void) -> bool {
log::trace!("hook_check__o_free");
self.allocator_mut().is_managed(ptr)
}
@ -992,7 +1066,8 @@ impl AsanRuntime {
_original: extern "C" fn(ptr: *mut c_void) -> usize,
ptr: *mut c_void,
) -> usize {
if ptr != std::ptr::null_mut() {
log::trace!("hook__o_free");
if ptr != ptr::null_mut() {
unsafe { self.allocator_mut().release(ptr) }
}
0
@ -1009,7 +1084,7 @@ impl AsanRuntime {
_original: extern "C" fn(ptr: *mut c_void) -> usize,
ptr: *mut c_void,
) -> usize {
if ptr != std::ptr::null_mut() {
if ptr != ptr::null_mut() {
unsafe { self.allocator_mut().release(ptr) }
}
0
@ -1023,6 +1098,7 @@ impl AsanRuntime {
alignment: usize,
size: usize,
) -> *mut c_void {
log::trace!("hook_memalign");
unsafe { self.allocator_mut().alloc(size, alignment) }
}
@ -1034,6 +1110,7 @@ impl AsanRuntime {
alignment: usize,
size: usize,
) -> i32 {
log::trace!("hook_posix_memalign");
unsafe {
*pptr = self.allocator_mut().alloc(size, alignment);
}
@ -1047,6 +1124,7 @@ impl AsanRuntime {
_original: extern "C" fn(ptr: *mut c_void) -> usize,
ptr: *mut c_void,
) -> usize {
log::trace!("hook_malloc_usable_size");
self.allocator_mut().get_usable_size(ptr)
}
#[inline]
@ -1219,6 +1297,7 @@ impl AsanRuntime {
_file_offset_low: u32,
size: usize,
) -> *const c_void {
log::trace!("hook_MapViewOfFile size {:?}", size);
let ret = original(
_handle,
_desired_access,
@ -1226,7 +1305,86 @@ impl AsanRuntime {
_file_offset_low,
size,
);
let mut size = size;
if size == 0 {
// The entire file is mapped starting from the offset
// We need to get the real size before unpoisoning it
// Use VirtualQuery to get the size of the mapped memory
let mut mem_info = MEMORY_BASIC_INFORMATION {
BaseAddress: ptr::null_mut(),
AllocationBase: ptr::null_mut(),
AllocationProtect: 0,
RegionSize: 0,
State: 0,
Protect: 0,
Type: 0,
};
let result = unsafe {
VirtualQuery(
ret as *const winapi::ctypes::c_void,
&mut mem_info,
size_of::<MEMORY_BASIC_INFORMATION>(),
)
};
if result == 0 {
log::error!("Failed to query virtual memory");
} else {
size = mem_info.RegionSize;
}
}
self.unpoison(ret as usize, size);
log::trace!("hook_MapViewOfFile returns {:p}", ret);
ret
}
#[inline]
#[expect(non_snake_case)]
#[cfg(windows)]
pub fn hook_UnmapViewOfFile(
&mut self,
original: extern "C" fn(ptr: *const c_void) -> bool,
ptr: *const c_void,
) -> bool {
log::info!("hook_UnmapViewOfFile {:p}", ptr);
let mut size = 0;
// We need to get the mapping size before poisoning it
// Use VirtualQuery to get the size of the mapped memory
let mut mem_info = MEMORY_BASIC_INFORMATION {
BaseAddress: ptr::null_mut(),
AllocationBase: ptr::null_mut(),
AllocationProtect: 0,
RegionSize: 0,
State: 0,
Protect: 0,
Type: 0,
};
let result = unsafe {
VirtualQuery(
ptr as *const winapi::ctypes::c_void,
&mut mem_info,
size_of::<MEMORY_BASIC_INFORMATION>(),
)
};
if result == 0 {
log::error!("Failed to query virtual memory for poisoning");
} else {
size = mem_info.RegionSize;
log::info!("Size of mapped memory: {} bytes", size);
}
let ret = original(ptr);
if size > 0 {
unsafe { self.poison(ptr as usize, size) };
}
ret
}
@ -1238,7 +1396,8 @@ impl AsanRuntime {
_original: extern "C" fn(ptr: *mut c_void) -> usize,
ptr: *mut c_void,
) -> usize {
if ptr != std::ptr::null_mut() {
log::trace!("delete[]");
if ptr != ptr::null_mut() {
unsafe { self.allocator_mut().release(ptr) }
}
0
@ -1253,7 +1412,8 @@ impl AsanRuntime {
ptr: *mut c_void,
_ulong: u64,
) -> usize {
if ptr != std::ptr::null_mut() {
log::trace!("delete[]");
if ptr != ptr::null_mut() {
unsafe { self.allocator_mut().release(ptr) }
}
0
@ -1269,7 +1429,8 @@ impl AsanRuntime {
_ulong: u64,
_alignment: usize,
) -> usize {
if ptr != std::ptr::null_mut() {
log::trace!("delete[](void*, std::size_t)");
if ptr != ptr::null_mut() {
unsafe { self.allocator_mut().release(ptr) }
}
0
@ -1284,7 +1445,8 @@ impl AsanRuntime {
ptr: *mut c_void,
_nothrow: *const c_void,
) -> usize {
if ptr != std::ptr::null_mut() {
log::trace!("delete[](void*, std::size_t, std::align_val_t)");
if ptr != ptr::null_mut() {
unsafe { self.allocator_mut().release(ptr) }
}
0
@ -1304,7 +1466,8 @@ impl AsanRuntime {
_alignment: usize,
_nothrow: *const c_void,
) -> usize {
if ptr != std::ptr::null_mut() {
log::trace!("delete[](void*, std::nothrow_t const&)");
if ptr != ptr::null_mut() {
unsafe { self.allocator_mut().release(ptr) }
}
0
@ -1319,7 +1482,8 @@ impl AsanRuntime {
ptr: *mut c_void,
_alignment: usize,
) -> usize {
if ptr != std::ptr::null_mut() {
log::trace!("delete[](void*, std::align_val_t)");
if ptr != ptr::null_mut() {
unsafe { self.allocator_mut().release(ptr) }
}
0
@ -1333,7 +1497,7 @@ impl AsanRuntime {
_original: extern "C" fn(ptr: *mut c_void) -> usize,
ptr: *mut c_void,
) -> usize {
if ptr != std::ptr::null_mut() {
if ptr != ptr::null_mut() {
unsafe { self.allocator_mut().release(ptr) }
}
0
@ -1348,7 +1512,8 @@ impl AsanRuntime {
ptr: *mut c_void,
_ulong: u64,
) -> usize {
if ptr != std::ptr::null_mut() {
log::trace!("delete(void*)");
if ptr != ptr::null_mut() {
unsafe { self.allocator_mut().release(ptr) }
}
0
@ -1364,7 +1529,8 @@ impl AsanRuntime {
_ulong: u64,
_alignment: usize,
) -> usize {
if ptr != std::ptr::null_mut() {
log::trace!("delete(void*)");
if ptr != ptr::null_mut() {
unsafe { self.allocator_mut().release(ptr) }
}
0
@ -1379,7 +1545,8 @@ impl AsanRuntime {
ptr: *mut c_void,
_nothrow: *const c_void,
) -> usize {
if ptr != std::ptr::null_mut() {
log::trace!("delete(void*)");
if ptr != ptr::null_mut() {
unsafe { self.allocator_mut().release(ptr) }
}
0
@ -1399,7 +1566,8 @@ impl AsanRuntime {
_alignment: usize,
_nothrow: *const c_void,
) -> usize {
if ptr != std::ptr::null_mut() {
log::trace!("delete(void*)");
if ptr != ptr::null_mut() {
unsafe { self.allocator_mut().release(ptr) }
}
0
@ -1414,7 +1582,8 @@ impl AsanRuntime {
ptr: *mut c_void,
_alignment: usize,
) -> usize {
if ptr != std::ptr::null_mut() {
log::trace!("delete(void*)");
if ptr != ptr::null_mut() {
unsafe { self.allocator_mut().release(ptr) }
}
0
@ -1439,6 +1608,7 @@ impl AsanRuntime {
fd: i32,
offset: usize,
) -> *mut c_void {
log::trace!("hook_mmap");
let res = original(addr, length, prot, flags, fd, offset);
if res != (-1_isize as *mut c_void) {
self.allocator_mut()
@ -1456,6 +1626,7 @@ impl AsanRuntime {
addr: *const c_void,
length: usize,
) -> i32 {
log::trace!("hook_munmap");
let res = original(addr, length);
if res != -1 {
Allocator::poison(self.allocator_mut().map_to_shadow(addr as usize), length);
@ -1472,6 +1643,7 @@ impl AsanRuntime {
buf: *const c_void,
count: usize,
) -> usize {
log::trace!("hook__write");
self.hook_write(original, fd, buf, count)
}
#[inline]
@ -1482,6 +1654,7 @@ impl AsanRuntime {
buf: *const c_void,
count: usize,
) -> usize {
log::trace!("hook_write");
if !self.allocator_mut().check_shadow(buf, count)
&& AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite((
"write".to_string(),
@ -1505,6 +1678,7 @@ impl AsanRuntime {
buf: *mut c_void,
count: usize,
) -> usize {
log::trace!("hook__read");
self.hook_read(original, fd, buf, count)
}
#[inline]
@ -1515,6 +1689,7 @@ impl AsanRuntime {
buf: *mut c_void,
count: usize,
) -> usize {
log::trace!("hook_read");
if !self.allocator_mut().check_shadow(buf, count)
&& AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead((
"read".to_string(),
@ -1537,6 +1712,7 @@ impl AsanRuntime {
size: u32,
stream: *mut c_void,
) -> *mut c_void {
log::trace!("hook_fgets");
if !self.allocator_mut().check_shadow(s, size as usize)
&& AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead((
"fgets".to_string(),
@ -1559,6 +1735,7 @@ impl AsanRuntime {
s2: *const c_void,
n: usize,
) -> i32 {
log::trace!("hook_memcmp");
if !self.allocator_mut().check_shadow(s1, n)
&& AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead((
"memcmp".to_string(),
@ -1592,6 +1769,7 @@ impl AsanRuntime {
src: *const c_void,
n: usize,
) -> *mut c_void {
log::trace!("hook_memcpy dest {dest:#?} src {src:#?} size {n}");
if !self.allocator_mut().check_shadow(dest, n)
&& AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite((
"memcpy".to_string(),
@ -1626,6 +1804,7 @@ impl AsanRuntime {
src: *const c_void,
n: usize,
) -> *mut c_void {
log::trace!("hook_mempcpy");
if !self.allocator_mut().check_shadow(dest, n)
&& AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite((
"mempcpy".to_string(),
@ -1659,6 +1838,7 @@ impl AsanRuntime {
src: *const c_void,
n: usize,
) -> *mut c_void {
log::trace!("hook_memmove");
if !self.allocator_mut().check_shadow(dest, n)
&& AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite((
"memmove".to_string(),
@ -1693,6 +1873,7 @@ impl AsanRuntime {
c: i32,
n: usize,
) -> *mut c_void {
log::trace!("hook_memset");
if !self.allocator_mut().check_shadow(dest, n)
&& AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite((
"memset".to_string(),
@ -1715,6 +1896,7 @@ impl AsanRuntime {
c: i32,
n: usize,
) -> *mut c_void {
log::trace!("hook_memchr");
if !self.allocator_mut().check_shadow(s, n)
&& AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead((
"memchr".to_string(),
@ -1738,6 +1920,7 @@ impl AsanRuntime {
c: i32,
n: usize,
) -> *mut c_void {
log::trace!("hook_memrchr");
if !self.allocator_mut().check_shadow(s, n)
&& AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead((
"memrchr".to_string(),
@ -1766,6 +1949,7 @@ impl AsanRuntime {
needle: *const c_void,
needlelen: usize,
) -> *mut c_void {
log::trace!("hook_memmem");
if !self.allocator_mut().check_shadow(haystack, haystacklen)
&& AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead((
"memmem".to_string(),
@ -1799,6 +1983,7 @@ impl AsanRuntime {
s: *mut c_void,
n: usize,
) -> usize {
log::trace!("hook_bzero");
if !self.allocator_mut().check_shadow(s, n)
&& AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite((
"bzero".to_string(),
@ -1821,6 +2006,7 @@ impl AsanRuntime {
s: *mut c_void,
n: usize,
) -> usize {
log::trace!("hook_explicit_bzero");
if !self.allocator_mut().check_shadow(s, n)
&& AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite((
"explicit_bzero".to_string(),
@ -1844,6 +2030,7 @@ impl AsanRuntime {
s2: *const c_void,
n: usize,
) -> i32 {
log::trace!("hook_bcmp");
if !self.allocator_mut().check_shadow(s1, n)
&& AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead((
"bcmp".to_string(),
@ -1880,6 +2067,7 @@ impl AsanRuntime {
fn strlen(s: *const c_char) -> usize;
}
log::trace!("hook_strchr");
if !self
.allocator_mut()
.check_shadow(s as *const c_void, unsafe { strlen(s) })
@ -1906,6 +2094,7 @@ impl AsanRuntime {
extern "system" {
fn strlen(s: *const c_char) -> usize;
}
log::trace!("hook_strrchr");
if !self
.allocator_mut()
.check_shadow(s as *const c_void, unsafe { strlen(s) })
@ -1932,6 +2121,7 @@ impl AsanRuntime {
extern "system" {
fn strlen(s: *const c_char) -> usize;
}
log::trace!("hook_strcasecmp");
if !self
.allocator_mut()
.check_shadow(s1 as *const c_void, unsafe { strlen(s1) })
@ -1969,6 +2159,7 @@ impl AsanRuntime {
s2: *const c_char,
n: usize,
) -> i32 {
log::trace!("hook_strncasecmp");
if !self.allocator_mut().check_shadow(s1 as *const c_void, n)
&& AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead((
"strncasecmp".to_string(),
@ -2004,6 +2195,7 @@ impl AsanRuntime {
extern "system" {
fn strlen(s: *const c_char) -> usize;
}
log::trace!("hook_strcat");
if !self
.allocator_mut()
.check_shadow(s1 as *const c_void, unsafe { strlen(s1) })
@ -2043,6 +2235,7 @@ impl AsanRuntime {
extern "system" {
fn strlen(s: *const c_char) -> usize;
}
log::trace!("hook_strcmp");
if !self
.allocator_mut()
.check_shadow(s1 as *const c_void, unsafe { strlen(s1) })
@ -2083,6 +2276,7 @@ impl AsanRuntime {
extern "system" {
fn strnlen(s: *const c_char, n: usize) -> usize;
}
log::trace!("hook_strncmp");
if !self
.allocator_mut()
.check_shadow(s1 as *const c_void, unsafe { strnlen(s1, n) })
@ -2122,6 +2316,7 @@ impl AsanRuntime {
extern "system" {
fn strlen(s: *const c_char) -> usize;
}
log::trace!("hook_strcpy");
if !self
.allocator_mut()
.check_shadow(dest as *const c_void, unsafe { strlen(src) })
@ -2162,6 +2357,7 @@ impl AsanRuntime {
extern "system" {
fn strlen(s: *const c_char) -> usize;
}
log::trace!("hook_strncpy");
if !self.allocator_mut().check_shadow(dest as *const c_void, n)
&& AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite((
"strncpy".to_string(),
@ -2198,6 +2394,7 @@ impl AsanRuntime {
extern "system" {
fn strlen(s: *const c_char) -> usize;
}
log::trace!("hook_stpcpy");
if !self
.allocator_mut()
.check_shadow(dest as *const c_void, unsafe { strlen(src) })
@ -2234,6 +2431,7 @@ impl AsanRuntime {
original: extern "C" fn(s: *const c_char) -> *mut c_char,
s: *const c_char,
) -> *mut c_char {
log::trace!("hook__strdup");
self.hook_strdup(original, s)
}
#[inline]
@ -2246,6 +2444,7 @@ impl AsanRuntime {
fn strlen(s: *const c_char) -> usize;
fn strcpy(dest: *mut c_char, src: *const c_char) -> *mut c_char;
}
log::trace!("hook_strdup");
let size = unsafe { strlen(s) + 1 };
if !self.allocator_mut().check_shadow(s as *const c_void, size)
&& AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead((
@ -2272,6 +2471,7 @@ impl AsanRuntime {
original: extern "C" fn(s: *const c_char) -> usize,
s: *const c_char,
) -> usize {
log::trace!("hook_strlen");
let size = original(s);
if !self.allocator_mut().check_shadow(s as *const c_void, size)
&& AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead((
@ -2294,6 +2494,7 @@ impl AsanRuntime {
s: *const c_char,
n: usize,
) -> usize {
log::trace!("hook_strnlen");
let size = original(s, n);
if !self.allocator_mut().check_shadow(s as *const c_void, size)
&& AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead((
@ -2319,6 +2520,7 @@ impl AsanRuntime {
extern "system" {
fn strlen(s: *const c_char) -> usize;
}
log::trace!("hook_strstr");
if !self
.allocator_mut()
.check_shadow(haystack as *const c_void, unsafe { strlen(haystack) })
@ -2358,6 +2560,7 @@ impl AsanRuntime {
extern "system" {
fn strlen(s: *const c_char) -> usize;
}
log::trace!("hook_strcasestr");
if !self
.allocator_mut()
.check_shadow(haystack as *const c_void, unsafe { strlen(haystack) })
@ -2396,6 +2599,7 @@ impl AsanRuntime {
extern "system" {
fn strlen(s: *const c_char) -> usize;
}
log::trace!("hook_atoi");
if !self
.allocator_mut()
.check_shadow(s as *const c_void, unsafe { strlen(s) })
@ -2422,6 +2626,7 @@ impl AsanRuntime {
extern "system" {
fn strlen(s: *const c_char) -> usize;
}
log::trace!("hook_atol");
if !self
.allocator_mut()
.check_shadow(s as *const c_void, unsafe { strlen(s) })
@ -2448,6 +2653,7 @@ impl AsanRuntime {
extern "system" {
fn strlen(s: *const c_char) -> usize;
}
log::trace!("hook_atoll");
if !self
.allocator_mut()
.check_shadow(s as *const c_void, unsafe { strlen(s) })
@ -2471,6 +2677,7 @@ impl AsanRuntime {
original: extern "C" fn(s: *const wchar_t) -> usize,
s: *const wchar_t,
) -> usize {
log::trace!("hook_wcslen");
let size = original(s);
if !self
.allocator_mut()
@ -2499,6 +2706,7 @@ impl AsanRuntime {
extern "system" {
fn wcslen(s: *const wchar_t) -> usize;
}
log::trace!("hook_wcscpy");
if !self
.allocator_mut()
.check_shadow(dest as *const c_void, unsafe { (wcslen(src) + 1) * 2 })
@ -2539,6 +2747,7 @@ impl AsanRuntime {
extern "system" {
fn wcslen(s: *const wchar_t) -> usize;
}
log::trace!("hook_wcscmp");
if !self
.allocator_mut()
.check_shadow(s1 as *const c_void, unsafe { (wcslen(s1) + 1) * 2 })

View File

@ -1,23 +1,19 @@
use core::fmt::{self, Debug, Formatter};
#[cfg(all(windows, not(test)))]
use std::process::abort;
use std::{ffi::c_void, marker::PhantomData};
use std::{cell::RefCell, ffi::c_void, marker::PhantomData, rc::Rc};
use frida_gum::{
stalker::{NoneEventSink, Stalker},
Gum, MemoryRange, NativePointer,
};
#[cfg(windows)]
use libafl::{
executors::{hooks::inprocess::InProcessHooks, inprocess::HasInProcessHooks},
inputs::Input,
state::{HasCurrentTestcase, HasSolutions},
};
use libafl::executors::{hooks::inprocess::InProcessHooks, inprocess::HasInProcessHooks};
use libafl::{
executors::{Executor, ExitKind, HasObservers, InProcessExecutor},
inputs::{NopTargetBytesConverter, TargetBytesConverter},
inputs::{Input, NopTargetBytesConverter, TargetBytesConverter},
observers::ObserversTuple,
state::HasExecutions,
state::{HasCurrentTestcase, HasExecutions, HasSolutions},
Error,
};
use libafl_bolts::{tuples::RefIndexable, AsSlice};
@ -29,38 +25,41 @@ use crate::helper::{FridaInstrumentationHelper, FridaRuntimeTuple};
use crate::windows_hooks::initialize;
/// The [`FridaInProcessExecutor`] is an [`Executor`] that executes the target in the same process, usinig [`frida`](https://frida.re/) for binary-only instrumentation.
pub struct FridaInProcessExecutor<'a, 'b, 'c, EM, H, I, OT, RT, S, TC, Z> {
pub struct FridaInProcessExecutor<'a, 'b, EM, H, I, OT, RT, S, TC, Z> {
base: InProcessExecutor<'a, EM, H, I, OT, S, Z>,
/// `thread_id` for the Stalker
thread_id: Option<u32>,
/// Frida's dynamic rewriting engine
stalker: Stalker,
/// User provided callback for instrumentation
helper: &'c mut FridaInstrumentationHelper<'b, RT>,
helper: Rc<RefCell<FridaInstrumentationHelper<'b, RT>>>,
target_bytes_converter: TC,
followed: bool,
_phantom: PhantomData<&'b u8>,
}
impl<EM, H, I, OT, RT, S, TC, Z> Debug
for FridaInProcessExecutor<'_, '_, '_, EM, H, I, OT, RT, S, TC, Z>
for FridaInProcessExecutor<'_, '_, EM, H, I, OT, RT, S, TC, Z>
where
OT: Debug,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("FridaInProcessExecutor")
.field("base", &self.base)
.field("helper", &self.helper)
.field("helper", &self.helper.borrow_mut())
.field("followed", &self.followed)
.finish_non_exhaustive()
}
}
impl<EM, H, I, OT, RT, S, TC, Z> Executor<EM, I, S, Z>
for FridaInProcessExecutor<'_, '_, '_, EM, H, I, OT, RT, S, TC, Z>
for FridaInProcessExecutor<'_, '_, EM, H, I, OT, RT, S, TC, Z>
where
H: FnMut(&I) -> ExitKind,
I: Input,
S: HasExecutions,
S: HasCurrentTestcase<I>,
S: HasSolutions<I>,
TC: TargetBytesConverter<I>,
OT: ObserversTuple<I, S>,
RT: FridaRuntimeTuple,
@ -75,13 +74,12 @@ where
input: &I,
) -> Result<ExitKind, Error> {
let target_bytes = self.target_bytes_converter.to_target_bytes(input);
self.helper.pre_exec(target_bytes.as_slice())?;
if self.helper.stalker_enabled() {
if self.followed {
self.stalker.activate(NativePointer(core::ptr::null_mut()));
} else {
self.helper.borrow_mut().pre_exec(target_bytes.as_slice())?;
if self.helper.borrow_mut().stalker_enabled() {
if !(self.followed) {
self.followed = true;
let transformer = self.helper.transformer();
let helper_binding = self.helper.borrow_mut();
let transformer = helper_binding.transformer();
if let Some(thread_id) = self.thread_id {
self.stalker.follow::<NoneEventSink>(
thread_id.try_into().unwrap(),
@ -90,11 +88,19 @@ where
);
} else {
self.stalker.follow_me::<NoneEventSink>(transformer, None);
self.stalker.deactivate();
}
}
// We removed the fuzzer from the stalked ranges,
// but we need to pass the harness entry point
// so that Stalker knows to pick it despite the module being excluded
let harness_fn_ref: &H = self.base.harness();
let ptr: *const H = harness_fn_ref as *const H;
log::info!("Activating Stalker for {:p}", ptr);
self.stalker.activate(NativePointer(ptr as *mut c_void));
}
let res = self.base.run_target(fuzzer, state, mgr, input);
if self.helper.stalker_enabled() {
if self.helper.borrow_mut().stalker_enabled() {
self.stalker.deactivate();
}
@ -107,13 +113,15 @@ where
abort();
}
}
self.helper.post_exec(target_bytes.as_slice())?;
self.helper
.borrow_mut()
.post_exec(target_bytes.as_slice())?;
res
}
}
impl<EM, H, I, OT, RT, S, TC, Z> HasObservers
for FridaInProcessExecutor<'_, '_, '_, EM, H, I, OT, RT, S, TC, Z>
for FridaInProcessExecutor<'_, '_, EM, H, I, OT, RT, S, TC, Z>
{
type Observers = OT;
#[inline]
@ -127,8 +135,8 @@ impl<EM, H, I, OT, RT, S, TC, Z> HasObservers
}
}
impl<'a, 'b, 'c, EM, H, I, OT, RT, S, Z>
FridaInProcessExecutor<'a, 'b, 'c, EM, H, I, OT, RT, S, NopTargetBytesConverter<I>, Z>
impl<'a, 'b, EM, H, I, OT, RT, S, Z>
FridaInProcessExecutor<'a, 'b, EM, H, I, OT, RT, S, NopTargetBytesConverter<I>, Z>
where
RT: FridaRuntimeTuple,
{
@ -136,7 +144,7 @@ where
pub fn new(
gum: &'a Gum,
base: InProcessExecutor<'a, EM, H, I, OT, S, Z>,
helper: &'c mut FridaInstrumentationHelper<'b, RT>,
helper: Rc<RefCell<FridaInstrumentationHelper<'b, RT>>>,
) -> Self {
FridaInProcessExecutor::with_target_bytes_converter(
gum,
@ -151,7 +159,7 @@ where
pub fn on_thread(
gum: &'a Gum,
base: InProcessExecutor<'a, EM, H, I, OT, S, Z>,
helper: &'c mut FridaInstrumentationHelper<'b, RT>,
helper: Rc<RefCell<FridaInstrumentationHelper<'b, RT>>>,
thread_id: u32,
) -> Self {
FridaInProcessExecutor::with_target_bytes_converter(
@ -164,8 +172,7 @@ where
}
}
impl<'a, 'b, 'c, EM, H, I, OT, RT, S, TC, Z>
FridaInProcessExecutor<'a, 'b, 'c, EM, H, I, OT, RT, S, TC, Z>
impl<'a, 'b, EM, H, I, OT, RT, S, TC, Z> FridaInProcessExecutor<'a, 'b, EM, H, I, OT, RT, S, TC, Z>
where
RT: FridaRuntimeTuple,
{
@ -173,31 +180,39 @@ where
pub fn with_target_bytes_converter(
gum: &'a Gum,
base: InProcessExecutor<'a, EM, H, I, OT, S, Z>,
helper: &'c mut FridaInstrumentationHelper<'b, RT>,
helper: Rc<RefCell<FridaInstrumentationHelper<'b, RT>>>,
thread_id: Option<u32>,
target_bytes_converter: TC,
) -> Self {
let mut stalker = Stalker::new(gum);
// 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();
let ranges = helper.borrow_mut().ranges().clone();
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)
< range.base_address().0 as u64 + range.size() as u64
{
ranges.insert(
range.base_address().0 as u64
..(range.base_address().0 as u64 + range.size() as u64),
(0xffff, "fuzzer".to_string()),
log::info!(
"Fuzzer range: {:x}-{:x}",
range.base_address().0 as u64,
range.base_address().0 as u64 + range.size() as u64
);
// Exclude the fuzzer from the stalked ranges, it is really unnecessary and harmfull.
// Otherwise, Stalker starts messing with our hooks and their callbacks
// wrecking havoc and causing deadlocks
stalker.exclude(&MemoryRange::new(
NativePointer(range.base_address().0),
range.size(),
));
break;
}
}
log::info!("disable_excludes: {:}", helper.disable_excludes);
if !helper.disable_excludes {
log::info!(
"disable_excludes: {:}",
helper.borrow_mut().disable_excludes
);
if !helper.borrow_mut().disable_excludes {
for range in ranges.gaps(&(0..u64::MAX)) {
log::info!("excluding range: {:x}-{:x}", range.start, range.end);
stalker.exclude(&MemoryRange::new(
@ -225,8 +240,8 @@ where
}
#[cfg(windows)]
impl<'a, 'b, 'c, EM, H, I, OT, RT, S, TC, Z> HasInProcessHooks<I, S>
for FridaInProcessExecutor<'a, 'b, 'c, EM, H, I, OT, RT, S, TC, Z>
impl<'a, 'b, EM, H, I, OT, RT, S, TC, Z> HasInProcessHooks<I, S>
for FridaInProcessExecutor<'a, 'b, EM, H, I, OT, RT, S, TC, Z>
where
H: FnMut(&I) -> ExitKind,
S: HasSolutions<I> + HasCurrentTestcase<I> + HasExecutions,

View File

@ -0,0 +1,97 @@
use std::{borrow::Cow, cell::RefCell, fmt, rc::Rc};
use libafl::{executors::ExitKind, inputs::HasTargetBytes, observers::Observer};
use libafl_bolts::{Error, Named};
use serde::{
de::{self, Deserialize, Deserializer, MapAccess, Visitor},
Serialize,
};
use crate::helper::{FridaInstrumentationHelper, FridaRuntimeTuple};
#[allow(clippy::unsafe_derive_deserialize)]
#[derive(Serialize, Debug)]
/// An observer that shuts down the Frida helper upon crash
/// This is necessary as we don't want to keep the instrumentation around when processing the crash
pub struct FridaHelperObserver<'a, RT> {
#[serde(skip)]
// helper: &'a RefCell<FridaInstrumentationHelper<'a, RT>>,
helper: Rc<RefCell<FridaInstrumentationHelper<'a, RT>>>,
}
impl<'a, RT> FridaHelperObserver<'a, RT>
where
RT: FridaRuntimeTuple + 'a,
{
/// Creates a new [`FridaHelperObserver`] with the given name.
#[must_use]
pub fn new(
// helper: &'a RefCell<FridaInstrumentationHelper<'a, RT>>,
helper: Rc<RefCell<FridaInstrumentationHelper<'a, RT>>>,
) -> Self {
Self { helper }
}
}
impl<'a, I, S, RT> Observer<I, S> for FridaHelperObserver<'a, RT>
where
// S: UsesInput,
// S::Input: HasTargetBytes,
RT: FridaRuntimeTuple + 'a,
I: HasTargetBytes,
{
fn post_exec(&mut self, _state: &mut S, input: &I, exit_kind: &ExitKind) -> Result<(), Error> {
if *exit_kind == ExitKind::Crash {
// Custom implementation logic for `FridaInProcessExecutor`
log::error!("Custom post_exec called for FridaInProcessExecutorHelper");
// Add any custom logic specific to FridaInProcessExecutor
return self.helper.borrow_mut().post_exec(&input.target_bytes());
}
Ok(())
}
}
impl<RT> Named for FridaHelperObserver<'_, RT> {
fn name(&self) -> &Cow<'static, str> {
static NAME: Cow<'static, str> = Cow::Borrowed("FridaHelperObserver");
&NAME
}
}
impl<'de, RT> Deserialize<'de> for FridaHelperObserver<'_, RT> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct FridaHelperObserverVisitor<'a, RT> {
// marker: std::marker::PhantomData<&'b mut FridaInstrumentationHelper<'a, RT>>,
marker: std::marker::PhantomData<&'a RT>,
}
impl<'de, 'a, RT> Visitor<'de> for FridaHelperObserverVisitor<'a, RT> {
type Value = FridaHelperObserver<'a, RT>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a FridaHelperObserver struct")
}
fn visit_map<M>(self, _map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
// Construct the struct without deserializing `helper`
Err(de::Error::custom(
"Cannot deserialize `FridaHelperObserver` with a mutable reference",
))
}
}
deserializer.deserialize_struct(
"FridaHelperObserver",
&[], // No fields to deserialize
FridaHelperObserverVisitor {
marker: std::marker::PhantomData,
},
)
}
}

View File

@ -451,18 +451,22 @@ impl FridaInstrumentationHelperBuilder {
start..(start + range.size() as u64),
(i as u16, module.path()),
);
}
for skip in skip_ranges {
match skip {
SkipRange::Absolute(range) => ranges
.borrow_mut()
.remove(range.start as u64..range.end as u64),
SkipRange::ModuleRelative { name, range } => {
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),
);
for skip in &skip_ranges {
match skip {
SkipRange::Absolute(range) => ranges
.borrow_mut()
.remove(range.start as u64..range.end as u64),
SkipRange::ModuleRelative { name, range } => {
if name.eq(&module.name()) {
log::trace!("Skipping {:?} {:?}", name, range);
let module_details = Module::load(gum, &name.to_string());
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),
);
}
}
}
}
}
@ -650,6 +654,7 @@ where
let mut first = true;
let mut basic_block_start = 0;
let mut basic_block_size = 0;
// let _guard = AsanInHookGuard::new(); // Ensure ASAN_IN_HOOK is set and reset
for instruction in basic_block {
let instr = instruction.instr();
let instr_size = instr.bytes().len();

View File

@ -61,7 +61,7 @@ pub mod pthread_hook;
#[cfg(feature = "cmplog")]
pub mod cmplog_rt;
/// The `LibAFL` firda helper
/// The `LibAFL` frida helper
pub mod helper;
pub mod drcov_rt;
@ -72,6 +72,9 @@ pub mod executor;
/// Utilities
pub mod utils;
/// The frida helper shutdown observer, needed to remove the instrumentation upon crashing
pub mod frida_helper_shutdown_observer;
// for parsing asan and cmplog cores
use libafl_bolts::core_affinity::{get_core_ids, CoreId, Cores};
@ -323,7 +326,7 @@ impl Default for FridaOptions {
#[cfg(test)]
mod tests {
use core::num::NonZero;
use std::sync::OnceLock;
use std::{cell::RefCell, rc::Rc, sync::OnceLock};
use clap::Parser;
use frida_gum::Gum;
@ -343,7 +346,6 @@ mod tests {
use libafl_bolts::{
cli::FuzzerOptions, rands::StdRand, tuples::tuple_list, AsSlice, SimpleStdoutLogger,
};
#[cfg(unix)]
use mimalloc::MiMalloc;
use crate::{
@ -353,16 +355,11 @@ mod tests {
},
coverage_rt::CoverageRuntime,
executor::FridaInProcessExecutor,
frida_helper_shutdown_observer::FridaHelperObserver,
helper::FridaInstrumentationHelper,
};
#[cfg(unix)]
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;
#[cfg(windows)]
use dlmalloc::GlobalDlmalloc;
#[cfg(windows)]
#[global_allocator]
static GLOBAL: GlobalDlmalloc = GlobalDlmalloc;
static GUM: OnceLock<Gum> = OnceLock::new();
@ -404,6 +401,14 @@ mod tests {
),
("malloc_heap_uaf_write", Some("heap use-after-free write")),
("malloc_heap_uaf_read", Some("heap use-after-free read")),
(
"heap_oob_memcpy_read",
Some("function arg resulting in bad read"),
),
(
"heap_oob_memcpy_write",
Some("function arg resulting in bad write"),
),
];
//NOTE: RTLD_NOW is required on linux as otherwise the hooks will NOT work
@ -420,11 +425,16 @@ mod tests {
let coverage = CoverageRuntime::new();
let asan = AsanRuntime::new(options);
let mut frida_helper = FridaInstrumentationHelper::new(
// let mut frida_helper = FridaInstrumentationHelper::new(
// GUM.get().expect("Gum uninitialized"),
// options,
// tuple_list!(coverage, asan),
// );
let frida_helper = Rc::new(RefCell::new(FridaInstrumentationHelper::new(
GUM.get().expect("Gum uninitialized"),
options,
tuple_list!(coverage, asan),
);
)));
// Run the tests for each function
for test in tests {
@ -442,6 +452,7 @@ mod tests {
let mut feedback = ConstFeedback::new(true);
let asan_obs = AsanErrorsObserver::from_static_asan_errors();
let frida_helper_observer = FridaHelperObserver::new(Rc::clone(&frida_helper));
// Feedbacks to recognize an input as solution
let mut objective = feedback_or_fast!(
@ -466,6 +477,7 @@ mod tests {
let mut fuzzer = StdFuzzer::new(StdScheduler::new(), feedback, objective);
let observers = tuple_list!(
frida_helper_observer,
asan_obs //,
);
@ -497,7 +509,8 @@ mod tests {
&mut event_manager,
)
.unwrap(),
&mut frida_helper,
// &mut frida_helper,
Rc::clone(&frida_helper),
);
let mutator = StdScheduledMutator::new(tuple_list!(BitFlipMutator::new()));
@ -523,7 +536,9 @@ mod tests {
}
}
frida_helper.deinit(GUM.get().expect("Gum uninitialized"));
frida_helper
.borrow_mut()
.deinit(GUM.get().expect("Gum uninitialized"));
}
#[test]
@ -545,8 +560,14 @@ mod tests {
SimpleStdoutLogger::set_logger().unwrap();
let out_dir = std::env::var_os("OUT_DIR").unwrap();
let out_dir = out_dir.to_string_lossy().to_string();
if let Ok(out_dir) = std::env::var("OUT_DIR") {
println!("OUT_DIR is set to: {out_dir}");
} else {
println!("OUT_DIR is not set!");
return;
}
let out_dir = std::env::var("OUT_DIR").unwrap();
// Check if the harness dynamic library is present, if not - skip the test
#[cfg(unix)]
let test_harness_name = "test_harness.so";

View File

@ -1,11 +1,15 @@
#include <stdint.h>
#include <stdlib.h>
#include <string>
#ifndef _MSC_VER
#include <string.h>
#endif
#ifdef _MSC_VER
#include <windows.h>
#include <winnt.h>
#include <winternl.h>
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call,
LPVOID lpReserved) {
(void)hModule;
@ -48,7 +52,7 @@ EXTERN int heap_oob_read(const uint8_t *_data, size_t _size) {
// OutputDebugStringA("heap_oob_read\n");
int *array = new int[100];
fprintf(stdout, "%d\n", array[100]);
fprintf(stdout, "heap_oob_read %d\n", array[100]);
delete[] array;
return 0;
}
@ -66,7 +70,7 @@ EXTERN int malloc_heap_uaf_read(const uint8_t *_data, size_t _size) {
(void)_size;
int *array = static_cast<int *>(malloc(100 * sizeof(int)));
free(array);
fprintf(stdout, "%d\n", array[5]);
fprintf(stdout, "malloc_heap_uaf_read %d\n", array[5]);
return 0;
}
@ -83,7 +87,7 @@ EXTERN int malloc_heap_oob_read(const uint8_t *_data, size_t _size) {
(void)_data;
(void)_size;
int *array = static_cast<int *>(malloc(100 * sizeof(int)));
fprintf(stdout, "%d\n", array[100]);
fprintf(stdout, "malloc_heap_oob_read %d\n", array[100]);
free(array);
return 0;
}
@ -163,6 +167,56 @@ EXTERN int malloc_heap_oob_write_0x17_int_at_0x13(const uint8_t *_data,
return 0;
}
EXTERN int heap_oob_memcpy_write(const uint8_t *_data, size_t _size) {
(void)_data;
(void)_size;
const size_t REAL_SIZE = 10;
const size_t LARGER_SIZE = REAL_SIZE + 1;
char *dest = new char[REAL_SIZE];
char *src = new char[LARGER_SIZE];
memcpy(dest, src, LARGER_SIZE);
delete[] dest;
delete[] src;
return 0;
}
EXTERN int heap_oob_memcpy_read(const uint8_t *_data, size_t _size) {
(void)_data;
(void)_size;
const size_t REAL_SIZE = 10;
const size_t LARGER_SIZE = REAL_SIZE + 1;
char *dest = new char[LARGER_SIZE];
char *src = new char[REAL_SIZE];
memcpy(dest, src, LARGER_SIZE);
delete[] dest;
delete[] src;
return 0;
}
EXTERN int heap_oob_memcpy_write_avx(const uint8_t *_data, size_t _size) {
(void)_data;
(void)_size;
// Using 127 bytes to make sure to fall on the AVX instruction in the
// optimized implementation
const size_t REAL_SIZE = 127;
const size_t LARGER_SIZE = REAL_SIZE + 1;
char *dest = new char[LARGER_SIZE];
char *src = new char[REAL_SIZE];
memcpy(dest, src, LARGER_SIZE);
delete[] dest;
delete[] src;
return 0;
}
EXTERN int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// abort();
(void)data;