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:
parent
f9715392af
commit
b3fe744e57
2
.gitignore
vendored
2
.gitignore
vendored
@ -75,3 +75,5 @@ harness
|
||||
program
|
||||
fuzzer_libpng*
|
||||
forkserver_simple
|
||||
|
||||
*.patch
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
|
@ -0,0 +1,2 @@
|
||||
[build]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
@ -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",
|
||||
|
@ -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`
|
||||
|
@ -1,2 +0,0 @@
|
||||
[build]
|
||||
target = "x86_64-pc-windows-msvc"
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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 }
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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]
|
||||
|
@ -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 {
|
||||
|
@ -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" }
|
||||
|
@ -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()
|
||||
));
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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),*)
|
||||
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 {
|
||||
(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),*)
|
||||
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 {
|
||||
(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,40 +699,44 @@ 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),*)
|
||||
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 {
|
||||
let ret = if $always_enabled && this.[<hook_check_ $name>]($($param),*) {
|
||||
this.[<hook_ $name>](*original, $($param),*)
|
||||
} else {
|
||||
(original)($($param),*)
|
||||
};
|
||||
ret
|
||||
}
|
||||
} 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;
|
||||
let _ = interceptor.replace(
|
||||
@ -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),*)
|
||||
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 {
|
||||
let ret = if $always_enabled && this.[<hook_check_ $name>]($($param),*) {
|
||||
this.[<hook_ $name>](*original, $($param),*)
|
||||
} else {
|
||||
(original)($($param),*)
|
||||
};
|
||||
ret
|
||||
}
|
||||
} 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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 })
|
||||
|
@ -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,
|
||||
|
97
libafl_frida/src/frida_helper_shutdown_observer.rs
Normal file
97
libafl_frida/src/frida_helper_shutdown_observer.rs
Normal 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,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
@ -451,21 +451,25 @@ impl FridaInstrumentationHelperBuilder {
|
||||
start..(start + range.size() as u64),
|
||||
(i as u16, module.path()),
|
||||
);
|
||||
}
|
||||
for skip in skip_ranges {
|
||||
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);
|
||||
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),
|
||||
(lib_start + range.start as u64)
|
||||
..(lib_start + range.end as u64),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
runtimes
|
||||
.borrow_mut()
|
||||
.init_all(gum, &ranges.borrow(), &module_map);
|
||||
@ -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();
|
||||
|
@ -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";
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user