Windows frida support (#1607)

* WIP: windows frida

* frida-windows: fix hooks not present on windows

* windows: allow building using cargo xwin

* frida-windows: fmrt

* frida-windows: cleanup and allow asan/drcov on windows

* frida-windows: fmt

* frida-windows: fix clippy

* frida-windows: handle unknown exceptions gracefully

* frida-windows: rework shadow mapping algo

* frida-windows: add hook functions

* frida-windows: hook functions; fix stack register

* minibsod: enable for windows

* check_shadow: fix edge casees

* asan_rt: rework and add hooks for windows

* inprocess: add minibsod on windows

* Fix warnings

* minibsod: disable test on windows

* WIP: HookRuntime

* Cleanup after merge

* Bump frida-gum version

* Fix conflict marker; update frida

* Make winsafe windows-specific

* Fmt

* Format

* Better detection of clang++ (using cc)

* Make AsanErrors crate public so we can use it in tests

* Add helper to get immediate of operand

* Use HookRuntime to hook asan functions

Tests now passing

* fmt

* Implement recurisve jmp resolve

* Fix reversed logic

* windows_hooks: Don't die if functions are already replaced

* Allow utils to work on windows

* Enable allocator hooking on windows

* Warnings; add trace to free

* Make ASAN tests run windows (with cargo xwin compilation)

* Fmt

* clang-format

* clang-format

* Add more tests

* Fix partial range access bug in unpoisoning/shadow_check

* Merge main

* Fix check_shadow and implement unit tests

* Fix hooking and PC retrieval

* WIP: Working gdiplus fuzzing with frida-ASAN, no false positives

* LibAFL Frida asan_rt and hook_rt fixes for frida_windows (#2095)

* Introduce aarch64

* MacOS fix - MemoryAreas is broken on MacOS and just loops

* Introduce working aarch64 ASAN check

* Implement large blob

* Fix hook_rt for arm64

* Fix poison/unpoison

* Fix shadow check

* Update x86-64

* Fix aarch64 unused import

* Remove extraneous println statement

* merge main

* Fixes

* alloc: add tests, pass the tests

* HookRuntime before AsanRuntime, and don't Asan if Hooked

* hook_rt: Fixes

* Frida windows check shadow fix (#2159)

* Fix check_shadow and add additional tests

* add some additional documentation

* Revert to Interceptor based hooks

* fixes

* format

* Get rid of hook_rt; fixes

* clang-format

* clang-format

* Fix with_threshold

* fixes

* fix build.rs

* fmt

* Fix offset to RDI on stack

* Fix clippy

* Fix build.rs

* clippy

* hook MapViewOfFile

* fmt

* fix

* clippy

* clippy

* Missing brace

* fix

* Clippy

* fomrrat

* fix i64 cast

* clippy exclude

* too many lines

* Undo merge fails

* fmt

* move debug print

* Fix some frida things

* Remove unused frida_to_cs fn for aarch64

* name

* Don't touch libafl_qemu

---------

Co-authored-by: Dongjia "toka" Zhang <tokazerkje@outlook.com>
Co-authored-by: Sharad Khanna <sharad@mineo333.dev>
Co-authored-by: Dominik Maier <domenukk@gmail.com>
Co-authored-by: Dominik Maier <dmnk@google.com>
This commit is contained in:
s1341 2024-05-14 11:45:56 +03:00 committed by GitHub
parent dce0761b11
commit 19087f3dab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 2286 additions and 1216 deletions

View File

@ -24,12 +24,15 @@ tar = "0.4.37"
reqwest = { version = "0.11.4", features = ["blocking"] } reqwest = { version = "0.11.4", features = ["blocking"] }
[dependencies] [dependencies]
libafl = { path = "../../libafl/", features = [ "std", "llmp_compression", "llmp_bind_public", "frida_cli" ] } #, "llmp_small_maps", "llmp_debug"]} libafl = { path = "../../libafl/", features = [ "std", "llmp_compression",
"llmp_bind_public", "frida_cli", "errors_backtrace" ] } #, "llmp_small_maps", "llmp_debug"]}
libafl_bolts = { path = "../../libafl_bolts/" } libafl_bolts = { path = "../../libafl_bolts/" }
frida-gum = { version = "0.13.6", features = [ "auto-download", "event-sink", "invocation-listener"] } frida-gum = { version = "0.13.6", features = [ "auto-download", "event-sink", "invocation-listener"] }
libafl_frida = { path = "../../libafl_frida", features = ["cmplog"] } libafl_frida = { path = "../../libafl_frida", features = ["cmplog"] }
libafl_targets = { path = "../../libafl_targets", features = ["sancov_cmplog"] } libafl_targets = { path = "../../libafl_targets", features = ["sancov_cmplog"] }
libloading = "0.7" libloading = "0.7"
mimalloc = { version = "*", default-features = false } mimalloc = { version = "*", default-features = false }
dlmalloc ={version = "0.2.6", features = ["global"]}
color-backtrace = "0.5" color-backtrace = "0.5"
env_logger = "0.10.0"
iced-x86 = { version = "1.20.0", features = ["code_asm"] } iced-x86 = { version = "1.20.0", features = ["code_asm"] }

View File

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

View File

@ -21,8 +21,13 @@ ULONG_PTR gdiplusToken;
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
switch (fdwReason) { switch (fdwReason) {
case DLL_PROCESS_ATTACH: case DLL_PROCESS_ATTACH:
LoadLibraryA("ole32.dll");
LoadLibraryA("gdi32full.dll"); LoadLibraryA("gdi32full.dll");
LoadLibraryA("WindowsCodecs.dll"); LoadLibraryA("WindowsCodecs.dll");
LoadLibraryA("shcore.dll");
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
LoadLibraryA("gdi32.dll");
// DebugBreak();
break; break;
} }
return TRUE; return TRUE;
@ -31,16 +36,16 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
extern "C" __declspec(dllexport) int LLVMFuzzerTestOneInput(const uint8_t *data, extern "C" __declspec(dllexport) int LLVMFuzzerTestOneInput(const uint8_t *data,
size_t size) { size_t size) {
static DWORD init = 0; static DWORD init = 0;
if (!init) { // if (!init) {
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); // init = 1;
init = 1; // }
}
HGLOBAL m_hBuffer = ::GlobalAlloc(GMEM_MOVEABLE, size); HGLOBAL m_hBuffer = ::GlobalAlloc(GMEM_MOVEABLE, size);
if (m_hBuffer) { if (m_hBuffer) {
void *pBuffer = ::GlobalLock(m_hBuffer); void *pBuffer = ::GlobalLock(m_hBuffer);
if (pBuffer) { if (pBuffer) {
CopyMemory(pBuffer, data, size); memcpy(pBuffer, data, size);
// CopyMemory(pBuffer, data, size);
IStream *pStream = NULL; IStream *pStream = NULL;
if (::CreateStreamOnHGlobal(m_hBuffer, FALSE, &pStream) == S_OK) { if (::CreateStreamOnHGlobal(m_hBuffer, FALSE, &pStream) == S_OK) {

View File

@ -6,9 +6,16 @@
//! going to make it compilable only for Windows, don't forget to modify the //! going to make it compilable only for Windows, don't forget to modify the
//! `scripts/test_fuzzer.sh` to opt-out this fuzzer from that test. //! `scripts/test_fuzzer.sh` to opt-out this fuzzer from that test.
#[cfg(unix)]
use mimalloc::MiMalloc; use mimalloc::MiMalloc;
#[cfg(unix)]
#[global_allocator] #[global_allocator]
static GLOBAL: MiMalloc = MiMalloc; static GLOBAL: MiMalloc = MiMalloc;
#[cfg(windows)]
use dlmalloc::GlobalDlmalloc;
#[cfg(windows)]
#[global_allocator]
static GLOBAL: GlobalDlmalloc = GlobalDlmalloc;
use std::path::PathBuf; use std::path::PathBuf;
@ -17,8 +24,8 @@ use libafl::{
corpus::{CachedOnDiskCorpus, Corpus, OnDiskCorpus}, corpus::{CachedOnDiskCorpus, Corpus, OnDiskCorpus},
events::{launcher::Launcher, llmp::LlmpRestartingEventManager, EventConfig}, events::{launcher::Launcher, llmp::LlmpRestartingEventManager, EventConfig},
executors::{inprocess::InProcessExecutor, ExitKind, ShadowExecutor}, executors::{inprocess::InProcessExecutor, ExitKind, ShadowExecutor},
feedback_or, feedback_or_fast, feedback_and_fast, feedback_or, feedback_or_fast,
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, feedbacks::{ConstFeedback, CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
fuzzer::{Fuzzer, StdFuzzer}, fuzzer::{Fuzzer, StdFuzzer},
inputs::{BytesInput, HasTargetBytes}, inputs::{BytesInput, HasTargetBytes},
monitors::MultiMonitor, monitors::MultiMonitor,
@ -32,8 +39,6 @@ use libafl::{
state::{HasCorpus, StdState}, state::{HasCorpus, StdState},
Error, HasMetadata, Error, HasMetadata,
}; };
#[cfg(unix)]
use libafl::{feedback_and_fast, feedbacks::ConstFeedback};
use libafl_bolts::{ use libafl_bolts::{
cli::{parse_args, FuzzerOptions}, cli::{parse_args, FuzzerOptions},
rands::StdRand, rands::StdRand,
@ -41,11 +46,11 @@ use libafl_bolts::{
tuples::{tuple_list, Merge}, tuples::{tuple_list, Merge},
AsSlice, AsSlice,
}; };
#[cfg(unix)]
use libafl_frida::asan::asan_rt::AsanRuntime;
#[cfg(unix)]
use libafl_frida::asan::errors::{AsanErrorsFeedback, AsanErrorsObserver};
use libafl_frida::{ use libafl_frida::{
asan::{
asan_rt::AsanRuntime,
errors::{AsanErrorsFeedback, AsanErrorsObserver},
},
cmplog_rt::CmpLogRuntime, cmplog_rt::CmpLogRuntime,
coverage_rt::{CoverageRuntime, MAP_SIZE}, coverage_rt::{CoverageRuntime, MAP_SIZE},
executor::FridaInProcessExecutor, executor::FridaInProcessExecutor,
@ -55,6 +60,7 @@ use libafl_targets::cmplog::CmpLogObserver;
/// The main fn, usually parsing parameters, and starting the fuzzer /// The main fn, usually parsing parameters, and starting the fuzzer
pub fn main() { pub fn main() {
env_logger::init();
color_backtrace::install(); color_backtrace::install();
let options = parse_args(); let options = parse_args();
@ -97,16 +103,11 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
let gum = Gum::obtain(); let gum = Gum::obtain();
let coverage = CoverageRuntime::new(); let coverage = CoverageRuntime::new();
#[cfg(unix)] let asan = AsanRuntime::new(&options);
let asan = AsanRuntime::new(options);
#[cfg(unix)]
let mut frida_helper = let mut frida_helper =
FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage, asan)); FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage, asan));
#[cfg(windows)] //
let mut frida_helper =
FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage));
// Create an observation channel using the coverage map // Create an observation channel using the coverage map
let edges_observer = HitcountsMapObserver::new(StdMapObserver::from_mut_ptr( let edges_observer = HitcountsMapObserver::new(StdMapObserver::from_mut_ptr(
"edges", "edges",
@ -118,7 +119,6 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
// Create an observation channel to keep track of the execution time // Create an observation channel to keep track of the execution time
let time_observer = TimeObserver::new("time"); let time_observer = TimeObserver::new("time");
#[cfg(unix)]
let asan_observer = AsanErrorsObserver::from_static_asan_errors(); let asan_observer = AsanErrorsObserver::from_static_asan_errors();
// Feedback to rate the interestingness of an input // Feedback to rate the interestingness of an input
@ -131,18 +131,15 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
); );
// Feedbacks to recognize an input as solution // Feedbacks to recognize an input as solution
#[cfg(unix)]
let mut objective = feedback_or_fast!( let mut objective = feedback_or_fast!(
CrashFeedback::new(), CrashFeedback::new(),
TimeoutFeedback::new(), // TimeoutFeedback::new(),
// true enables the AsanErrorFeedback // true enables the AsanErrorFeedback
feedback_and_fast!( feedback_and_fast!(
ConstFeedback::from(true), ConstFeedback::from(true),
AsanErrorsFeedback::new(&asan_observer) AsanErrorsFeedback::new(&asan_observer)
) )
); );
#[cfg(windows)]
let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new());
// If not restarting, create a State from scratch // If not restarting, create a State from scratch
let mut state = state.unwrap_or_else(|| { let mut state = state.unwrap_or_else(|| {
@ -183,20 +180,18 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
// A fuzzer with feedbacks and a corpus scheduler // A fuzzer with feedbacks and a corpus scheduler
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
#[cfg(unix)] let observers = tuple_list!(edges_observer, time_observer, asan_observer,);
let observers = tuple_list!(edges_observer, time_observer, asan_observer);
#[cfg(windows)]
let observers = tuple_list!(edges_observer, time_observer);
// Create the executor for an in-process function with just one observer for edge coverage // Create the executor for an in-process function with just one observer for edge coverage
let mut executor = FridaInProcessExecutor::new( let mut executor = FridaInProcessExecutor::new(
&gum, &gum,
InProcessExecutor::new( InProcessExecutor::with_timeout(
&mut frida_harness, &mut frida_harness,
observers, observers,
&mut fuzzer, &mut fuzzer,
&mut state, &mut state,
&mut mgr, &mut mgr,
options.timeout,
)?, )?,
&mut frida_helper, &mut frida_helper,
); );
@ -237,7 +232,6 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
// Create an observation channel to keep track of the execution time // Create an observation channel to keep track of the execution time
let time_observer = TimeObserver::new("time"); let time_observer = TimeObserver::new("time");
#[cfg(unix)]
let asan_observer = AsanErrorsObserver::from_static_asan_errors(); let asan_observer = AsanErrorsObserver::from_static_asan_errors();
// Feedback to rate the interestingness of an input // Feedback to rate the interestingness of an input
@ -249,7 +243,6 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
TimeFeedback::new(&time_observer) TimeFeedback::new(&time_observer)
); );
#[cfg(unix)]
let mut objective = feedback_or_fast!( let mut objective = feedback_or_fast!(
CrashFeedback::new(), CrashFeedback::new(),
TimeoutFeedback::new(), TimeoutFeedback::new(),
@ -258,8 +251,6 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
AsanErrorsFeedback::new(&asan_observer) AsanErrorsFeedback::new(&asan_observer)
) )
); );
#[cfg(windows)]
let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new());
// If not restarting, create a State from scratch // If not restarting, create a State from scratch
let mut state = state.unwrap_or_else(|| { let mut state = state.unwrap_or_else(|| {
@ -301,10 +292,7 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
// A fuzzer with feedbacks and a corpus scheduler // A fuzzer with feedbacks and a corpus scheduler
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
#[cfg(unix)]
let observers = tuple_list!(edges_observer, time_observer, asan_observer); let observers = tuple_list!(edges_observer, time_observer, asan_observer);
#[cfg(windows)]
let observers = tuple_list!(edges_observer, time_observer,);
// Create the executor for an in-process function with just one observer for edge coverage // Create the executor for an in-process function with just one observer for edge coverage
let mut executor = FridaInProcessExecutor::new( let mut executor = FridaInProcessExecutor::new(
@ -372,7 +360,6 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
// Create an observation channel to keep track of the execution time // Create an observation channel to keep track of the execution time
let time_observer = TimeObserver::new("time"); let time_observer = TimeObserver::new("time");
#[cfg(unix)]
let asan_observer = AsanErrorsObserver::from_static_asan_errors(); let asan_observer = AsanErrorsObserver::from_static_asan_errors();
// Feedback to rate the interestingness of an input // Feedback to rate the interestingness of an input
@ -384,7 +371,6 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
TimeFeedback::new(&time_observer) TimeFeedback::new(&time_observer)
); );
#[cfg(unix)]
let mut objective = feedback_or_fast!( let mut objective = feedback_or_fast!(
CrashFeedback::new(), CrashFeedback::new(),
TimeoutFeedback::new(), TimeoutFeedback::new(),
@ -393,8 +379,6 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
AsanErrorsFeedback::new(&asan_observer) AsanErrorsFeedback::new(&asan_observer)
) )
); );
#[cfg(windows)]
let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new());
// If not restarting, create a State from scratch // If not restarting, create a State from scratch
let mut state = state.unwrap_or_else(|| { let mut state = state.unwrap_or_else(|| {
@ -436,20 +420,18 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
// A fuzzer with feedbacks and a corpus scheduler // A fuzzer with feedbacks and a corpus scheduler
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
#[cfg(unix)]
let observers = tuple_list!(edges_observer, time_observer, asan_observer); let observers = tuple_list!(edges_observer, time_observer, asan_observer);
#[cfg(windows)]
let observers = tuple_list!(edges_observer, time_observer);
// Create the executor for an in-process function with just one observer for edge coverage // Create the executor for an in-process function with just one observer for edge coverage
let mut executor = FridaInProcessExecutor::new( let mut executor = FridaInProcessExecutor::new(
&gum, &gum,
InProcessExecutor::new( InProcessExecutor::with_timeout(
&mut frida_harness, &mut frida_harness,
observers, observers,
&mut fuzzer, &mut fuzzer,
&mut state, &mut state,
&mut mgr, &mut mgr,
options.timeout,
)?, )?,
&mut frida_helper, &mut frida_helper,
); );
@ -466,7 +448,9 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
let mut stages = tuple_list!(StdMutationalStage::new(mutator)); let mut stages = tuple_list!(StdMutationalStage::new(mutator));
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; fuzzer
.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)
.unwrap();
Ok(()) Ok(())
})(state, mgr, core_id) })(state, mgr, core_id)

View File

@ -26,7 +26,8 @@ reqwest = { version = "0.11.4", features = ["blocking"] }
[dependencies] [dependencies]
libafl = { path = "../../libafl/", features = [ "std", "llmp_compression", "llmp_bind_public", "frida_cli", "errors_backtrace" ] } #, "llmp_small_maps", "llmp_debug"]} libafl = { path = "../../libafl/", features = [ "std", "llmp_compression",
"llmp_bind_public", "frida_cli", "errors_backtrace" ] } #, "llmp_small_maps", "llmp_debug"]}
libafl_bolts = { path = "../../libafl_bolts/" } libafl_bolts = { path = "../../libafl_bolts/" }
frida-gum = { version = "0.13.6", features = [ "auto-download", "event-sink", "invocation-listener"] } frida-gum = { version = "0.13.6", features = [ "auto-download", "event-sink", "invocation-listener"] }
libafl_frida = { path = "../../libafl_frida", features = ["cmplog"] } libafl_frida = { path = "../../libafl_frida", features = ["cmplog"] }
@ -34,3 +35,5 @@ libafl_targets = { path = "../../libafl_targets", features = ["sancov_cmplog"] }
libloading = "0.7" libloading = "0.7"
mimalloc = { version = "*", default-features = false } mimalloc = { version = "*", default-features = false }
color-backtrace = "0.5" color-backtrace = "0.5"
log = "0.4.20"
env_logger = "0.10.0"

View File

@ -88,7 +88,7 @@ static char *allocation = NULL;
__attribute__((noinline)) void func3(char *alloc) { __attribute__((noinline)) void func3(char *alloc) {
// printf("func3\n"); // printf("func3\n");
#ifdef _WIN32 #ifdef _WIN32
if (rand() == 0) { if ((rand() % 2) == 0) {
alloc[0x1ff] = 0xde; alloc[0x1ff] = 0xde;
printf("alloc[0x200]: %d\n", alloc[0x200]); printf("alloc[0x200]: %d\n", alloc[0x200]);
} }

View File

@ -50,8 +50,8 @@ static GLOBAL: MiMalloc = MiMalloc;
/// The main fn, usually parsing parameters, and starting the fuzzer /// The main fn, usually parsing parameters, and starting the fuzzer
pub fn main() { pub fn main() {
env_logger::init();
color_backtrace::install(); color_backtrace::install();
let options = parse_args(); let options = parse_args();
unsafe { unsafe {
@ -65,6 +65,8 @@ pub fn main() {
/// The actual fuzzer /// The actual fuzzer
#[allow(clippy::too_many_lines, clippy::too_many_arguments)] #[allow(clippy::too_many_lines, clippy::too_many_arguments)]
unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
log::info!("Frida fuzzer starting up.");
// 'While the stats are state, they are usually used in the broker - which is likely never restarted // 'While the stats are state, they are usually used in the broker - which is likely never restarted
let monitor = MultiMonitor::new(|s| println!("{s}")); let monitor = MultiMonitor::new(|s| println!("{s}"));
@ -97,7 +99,7 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
#[cfg(unix)] #[cfg(unix)]
let mut frida_helper = let mut frida_helper =
FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage, asan)); FridaInstrumentationHelper::new(&gum, options, tuple_list!(asan, coverage));
#[cfg(windows)] #[cfg(windows)]
let mut frida_helper = let mut frida_helper =
FridaInstrumentationHelper::new(&gum, &options, tuple_list!(coverage)); FridaInstrumentationHelper::new(&gum, &options, tuple_list!(coverage));

View File

@ -113,6 +113,8 @@ pub mod windows_exception_handler {
sync::atomic::{compiler_fence, Ordering}, sync::atomic::{compiler_fence, Ordering},
}; };
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::io::Write;
#[cfg(feature = "std")]
use std::panic; use std::panic;
use libafl_bolts::os::windows_exceptions::{ use libafl_bolts::os::windows_exceptions::{
@ -131,7 +133,7 @@ pub mod windows_exception_handler {
}, },
feedbacks::Feedback, feedbacks::Feedback,
fuzzer::HasObjective, fuzzer::HasObjective,
inputs::UsesInput, inputs::{Input, UsesInput},
state::{HasCorpus, HasExecutions, HasSolutions, State}, state::{HasCorpus, HasExecutions, HasSolutions, State},
}; };
@ -394,7 +396,17 @@ pub mod windows_exception_handler {
// Make sure we don't crash in the crash handler forever. // Make sure we don't crash in the crash handler forever.
if is_crash { if is_crash {
let input = data.take_current_input::<<E::State as UsesInput>::Input>(); let input = data.take_current_input::<<E::State as UsesInput>::Input>();
{
let mut bsod = Vec::new();
{
let mut writer = std::io::BufWriter::new(&mut bsod);
writeln!(writer, "input: {:?}", input.generate_name(0)).unwrap();
libafl_bolts::minibsod::generate_minibsod(&mut writer, exception_pointers)
.unwrap();
writer.flush().unwrap();
}
log::error!("{}", std::str::from_utf8(&bsod).unwrap());
}
run_observers_and_save_state::<E, EM, OF, Z>( run_observers_and_save_state::<E, EM, OF, Z>(
executor, executor,
state, state,

View File

@ -122,7 +122,7 @@ pub mod fs;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub mod llmp; pub mod llmp;
pub mod math; pub mod math;
#[cfg(all(feature = "std", unix))] #[cfg(feature = "std")]
pub mod minibsod; pub mod minibsod;
pub mod os; pub mod os;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]

View File

@ -1,7 +1,5 @@
//! Implements a mini-bsod generator. //! Implements a mini-bsod generator.
//! It dumps all important registers and prints a stacktrace. //! It dumps all important registers and prints a stacktrace.
//! You may use the [`crate::os::unix_signals::ucontext`]
//! function to get a [`ucontext_t`].
#[cfg(target_vendor = "apple")] #[cfg(target_vendor = "apple")]
use core::mem::size_of; use core::mem::size_of;
@ -9,6 +7,7 @@ use std::io::{BufWriter, Write};
#[cfg(any(target_os = "solaris", target_os = "illumos"))] #[cfg(any(target_os = "solaris", target_os = "illumos"))]
use std::process::Command; use std::process::Command;
#[cfg(unix)]
use libc::siginfo_t; use libc::siginfo_t;
#[cfg(target_vendor = "apple")] #[cfg(target_vendor = "apple")]
use mach::{ use mach::{
@ -19,7 +18,10 @@ use mach::{
vm_region::{vm_region_recurse_info_t, vm_region_submap_info_64}, vm_region::{vm_region_recurse_info_t, vm_region_submap_info_64},
vm_types::{mach_vm_address_t, mach_vm_size_t, natural_t}, vm_types::{mach_vm_address_t, mach_vm_size_t, natural_t},
}; };
#[cfg(windows)]
use windows::Win32::System::Diagnostics::Debug::{CONTEXT, EXCEPTION_POINTERS};
#[cfg(unix)]
use crate::os::unix_signals::{ucontext_t, Signal}; use crate::os::unix_signals::{ucontext_t, Signal};
/// Write the content of all important registers /// Write the content of all important registers
@ -391,7 +393,7 @@ pub fn dump_registers<W: Write>(
write!(writer, "cs : {:#016x}, ", ucontext.sc_cs)?; write!(writer, "cs : {:#016x}, ", ucontext.sc_cs)?;
Ok(()) Ok(())
} }
///
/// Write the content of all important registers /// Write the content of all important registers
#[cfg(all(target_os = "openbsd", target_arch = "aarch64"))] #[cfg(all(target_os = "openbsd", target_arch = "aarch64"))]
#[allow(clippy::similar_names)] #[allow(clippy::similar_names)]
@ -452,6 +454,34 @@ pub fn dump_registers<W: Write>(
} }
/// Write the content of all important registers /// Write the content of all important registers
#[cfg(windows)]
#[allow(clippy::similar_names)]
pub fn dump_registers<W: Write>(
writer: &mut BufWriter<W>,
context: &CONTEXT,
) -> Result<(), std::io::Error> {
write!(writer, "r8 : {:#016x}, ", context.R8)?;
write!(writer, "r9 : {:#016x}, ", context.R9)?;
write!(writer, "r10: {:#016x}, ", context.R10)?;
writeln!(writer, "r11: {:#016x}, ", context.R11)?;
write!(writer, "r12: {:#016x}, ", context.R12)?;
write!(writer, "r13: {:#016x}, ", context.R13)?;
write!(writer, "r14: {:#016x}, ", context.R14)?;
writeln!(writer, "r15: {:#016x}, ", context.R15)?;
write!(writer, "rdi: {:#016x}, ", context.Rdi)?;
write!(writer, "rsi: {:#016x}, ", context.Rsi)?;
write!(writer, "rbp: {:#016x}, ", context.Rbp)?;
writeln!(writer, "rbx: {:#016x}, ", context.Rbx)?;
write!(writer, "rdx: {:#016x}, ", context.Rdx)?;
write!(writer, "rax: {:#016x}, ", context.Rax)?;
write!(writer, "rcx: {:#016x}, ", context.Rcx)?;
writeln!(writer, "rsp: {:#016x}, ", context.Rsp)?;
write!(writer, "rip: {:#016x}, ", context.Rip)?;
writeln!(writer, "efl: {:#016x}, ", context.EFlags)?;
Ok(())
}
#[cfg(all(target_os = "haiku", target_arch = "x86_64"))] #[cfg(all(target_os = "haiku", target_arch = "x86_64"))]
#[allow(clippy::similar_names)] #[allow(clippy::similar_names)]
pub fn dump_registers<W: Write>( pub fn dump_registers<W: Write>(
@ -489,6 +519,7 @@ pub fn dump_registers<W: Write>(
target_os = "dragonfly", target_os = "dragonfly",
target_os = "netbsd", target_os = "netbsd",
target_os = "openbsd", target_os = "openbsd",
windows,
target_os = "haiku", target_os = "haiku",
any(target_os = "solaris", target_os = "illumos"), any(target_os = "solaris", target_os = "illumos"),
)))] )))]
@ -751,6 +782,7 @@ fn write_crash<W: Write>(
target_os = "dragonfly", target_os = "dragonfly",
target_os = "openbsd", target_os = "openbsd",
target_os = "netbsd", target_os = "netbsd",
windows,
target_os = "haiku", target_os = "haiku",
any(target_os = "solaris", target_os = "illumos"), any(target_os = "solaris", target_os = "illumos"),
)))] )))]
@ -765,6 +797,33 @@ fn write_crash<W: Write>(
Ok(()) Ok(())
} }
#[cfg(windows)]
fn write_crash<W: Write>(
writer: &mut BufWriter<W>,
exception_pointers: *mut EXCEPTION_POINTERS,
) -> Result<(), std::io::Error> {
// TODO add fault addr for other platforms.
unsafe {
writeln!(
writer,
"Received exception {:0x} at address {:x}",
(*exception_pointers)
.ExceptionRecord
.as_mut()
.unwrap()
.ExceptionCode
.0,
(*exception_pointers)
.ExceptionRecord
.as_mut()
.unwrap()
.ExceptionAddress as usize
)
}?;
Ok(())
}
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
fn write_minibsod<W: Write>(writer: &mut BufWriter<W>) -> Result<(), std::io::Error> { fn write_minibsod<W: Write>(writer: &mut BufWriter<W>) -> Result<(), std::io::Error> {
match std::fs::read_to_string("/proc/self/maps") { match std::fs::read_to_string("/proc/self/maps") {
@ -1023,6 +1082,30 @@ pub fn generate_minibsod<W: Write>(
write_minibsod(writer) write_minibsod(writer)
} }
/// Generates a mini-BSOD given an `EXCEPTION_POINTERS` structure.
#[cfg(windows)]
#[allow(
clippy::non_ascii_literal,
clippy::too_many_lines,
clippy::not_unsafe_ptr_arg_deref
)]
pub fn generate_minibsod<W: Write>(
writer: &mut BufWriter<W>,
exception_pointers: *mut EXCEPTION_POINTERS,
) -> Result<(), std::io::Error> {
writeln!(writer, "{:━^100}", " CRASH ")?;
write_crash(writer, exception_pointers)?;
writeln!(writer, "{:━^100}", " REGISTERS ")?;
dump_registers(writer, unsafe {
(*exception_pointers).ContextRecord.as_mut().unwrap()
})?;
writeln!(writer, "{:━^100}", " BACKTRACE ")?;
writeln!(writer, "{:?}", backtrace::Backtrace::new())?;
writeln!(writer, "{:━^100}", " MAPS ")?;
write_minibsod(writer)
}
#[cfg(unix)]
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View File

@ -35,7 +35,7 @@ const EXCEPTION_CONTINUE_EXECUTION: c_long = -1;
const EXCEPTION_CONTINUE_SEARCH: c_long = 0; const EXCEPTION_CONTINUE_SEARCH: c_long = 0;
// For SEH // For SEH
//const EXCEPTION_EXECUTE_HANDLER: c_long = 1; // const EXCEPTION_EXECUTE_HANDLER: c_long = 1;
// From https://github.com/Alexpux/mingw-w64/blob/master/mingw-w64-headers/crt/signal.h // From https://github.com/Alexpux/mingw-w64/blob/master/mingw-w64-headers/crt/signal.h
pub const SIGINT: i32 = 2; pub const SIGINT: i32 = 2;

View File

@ -80,9 +80,13 @@ ahash = "0.8"
paste = "1.0" paste = "1.0"
log = "0.4.20" log = "0.4.20"
mmap-rs = "0.6.0" mmap-rs = "0.6.0"
bit_reverse = "0.1.8"
yaxpeax-arch = "0.2.7" yaxpeax-arch = "0.2.7"
[target.'cfg(windows)'.dependencies]
winsafe = {version = "0.0.18", features = ["kernel"]}
[dev-dependencies] [dev-dependencies]
serial_test = { version = "3", default-features = false, features = ["logging"] } serial_test = { version = "3", default-features = false, features = ["logging"] }
clap = {version = "4.5", features = ["derive"]} clap = {version = "4.5", features = ["derive"]}

View File

@ -7,34 +7,59 @@ fn main() {
cc::Build::new().file("src/gettls.c").compile("libgettls.a"); cc::Build::new().file("src/gettls.c").compile("libgettls.a");
} }
let target_family = std::env::var("CARGO_CFG_TARGET_FAMILY").unwrap();
// Force linking against libc++ // Force linking against libc++
#[cfg(unix)] if target_family == "unix" {
println!("cargo:rustc-link-lib=dylib=c++"); println!("cargo:rustc-link-lib=dylib=c++");
}
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=test_harness.cpp");
println!("cargo:rerun-if-changed=src/gettls.c");
// Build the test harness // Build the test harness
// clang++ -shared -fPIC -O0 -o test_harness.so test_harness.cpp // clang++ -shared -fPIC -O0 -o test_harness.so test_harness.cpp
#[cfg(unix)] // Check if we have clang++ installed
{
// Check if we have clang++ installed
let clangpp = std::process::Command::new("clang++")
.arg("--version")
.output();
match clangpp { if target_family == "windows" {
Ok(_) => { let compiler = cc::Build::new()
std::process::Command::new("clang++") .cpp(true)
.arg("-shared") .file("test_harness.a")
.arg("-fPIC") .get_compiler();
.arg("-O0") let mut cmd = std::process::Command::new(compiler.path());
.arg("-o") let cmd = cmd
.arg("test_harness.so") .args(compiler.args())
.arg("test_harness.cpp") .arg("test_harness.cpp")
.status() .arg("/link");
.expect("Failed to build test harness");
} #[cfg(unix)]
Err(_) => { let cmd = cmd
println!("cargo:warning=clang++ not found, skipping test harness build"); .arg(format!(
} "/libpath:{}/.cache/cargo-xwin/xwin/crt/lib/x86_64/",
} std::env::var("HOME").unwrap()
))
.arg(format!(
"/libpath:{}/.cache/cargo-xwin/xwin/sdk/lib/ucrt/x86_64/",
std::env::var("HOME").unwrap()
))
.arg(format!(
"/libpath:{}/.cache/cargo-xwin/xwin/sdk/lib/um/x86_64/",
std::env::var("HOME").unwrap()
));
cmd.arg("/dll").arg("/OUT:test_harness.dll");
cmd.status().expect("Failed to link test_harness.dll");
} else {
let compiler = cc::Build::new()
.cpp(true)
.opt_level(0)
.shared_flag(true)
.get_compiler();
let clangpp = compiler.path();
let mut cmd = std::process::Command::new(clangpp);
cmd.args(compiler.args())
.arg("test_harness.cpp")
.arg("-o")
.arg("test_harness.so")
.status()
.expect("Failed to link test_harness");
} }
} }

View File

@ -1,4 +1,5 @@
#[cfg(any( #[cfg(any(
windows,
target_os = "linux", target_os = "linux",
target_vendor = "apple", target_vendor = "apple",
all( all(
@ -13,6 +14,7 @@ use frida_gum::{PageProtection, RangeDetails};
use hashbrown::HashMap; use hashbrown::HashMap;
use libafl_bolts::cli::FuzzerOptions; use libafl_bolts::cli::FuzzerOptions;
#[cfg(any( #[cfg(any(
windows,
target_os = "linux", target_os = "linux",
target_vendor = "apple", target_vendor = "apple",
all( all(
@ -20,8 +22,7 @@ use libafl_bolts::cli::FuzzerOptions;
target_os = "android" target_os = "android"
) )
))] ))]
use mmap_rs::{MemoryAreas, MmapFlags, MmapMut, MmapOptions, ReservedMut}; use mmap_rs::{MmapFlags, MmapMut, MmapOptions, ReservedMut};
use nix::libc::memset;
use rangemap::RangeSet; use rangemap::RangeSet;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -41,7 +42,9 @@ pub struct Allocator {
/// The shadow bit /// The shadow bit
shadow_bit: usize, shadow_bit: usize,
/// The reserved (pre-allocated) shadow mapping /// The reserved (pre-allocated) shadow mapping
pre_allocated_shadow_mappings: HashMap<(usize, usize), ReservedMut>, pre_allocated_shadow_mappings: Vec<ReservedMut>,
/// Whether we've pre allocated a shadow mapping:
using_pre_allocated_shadow_mapping: bool,
/// All tracked allocations /// All tracked allocations
allocations: HashMap<usize, AllocationMetadata>, allocations: HashMap<usize, AllocationMetadata>,
/// All mappings /// All mappings
@ -157,7 +160,6 @@ impl Allocator {
pub unsafe fn alloc(&mut self, size: usize, _alignment: usize) -> *mut c_void { pub unsafe fn alloc(&mut self, size: usize, _alignment: usize) -> *mut c_void {
let mut is_malloc_zero = false; let mut is_malloc_zero = false;
let size = if size == 0 { let size = if size == 0 {
// log::warn!("zero-sized allocation!");
is_malloc_zero = true; is_malloc_zero = true;
16 16
} else { } else {
@ -179,7 +181,6 @@ impl Allocator {
self.total_allocation_size += rounded_up_size; self.total_allocation_size += rounded_up_size;
let metadata = if let Some(mut metadata) = self.find_smallest_fit(rounded_up_size) { let metadata = if let Some(mut metadata) = self.find_smallest_fit(rounded_up_size) {
//log::trace!("reusing allocation at {:x}, (actual mapping starts at {:x}) size {:x}", metadata.address, metadata.address - self.page_size, size);
metadata.is_malloc_zero = is_malloc_zero; metadata.is_malloc_zero = is_malloc_zero;
metadata.size = size; metadata.size = size;
if self.allocation_backtraces { if self.allocation_backtraces {
@ -234,14 +235,13 @@ impl Allocator {
let address = (metadata.address + self.page_size) as *mut c_void; let address = (metadata.address + self.page_size) as *mut c_void;
self.allocations.insert(address as usize, metadata); self.allocations.insert(address as usize, metadata);
// log::trace!("serving address: {:?}, size: {:x}", address, size); log::trace!("serving address: {:?}, size: {:x}", address, size);
address address
} }
/// Releases the allocation at the given address. /// Releases the allocation at the given address.
#[allow(clippy::missing_safety_doc)] #[allow(clippy::missing_safety_doc)]
pub unsafe fn release(&mut self, ptr: *mut c_void) { pub unsafe fn release(&mut self, ptr: *mut c_void) {
//log::trace!("freeing address: {:?}", ptr);
let Some(metadata) = self.allocations.get_mut(&(ptr as usize)) else { let Some(metadata) = self.allocations.get_mut(&(ptr as usize)) else {
if !ptr.is_null() { if !ptr.is_null() {
AsanErrors::get_mut_blocking() AsanErrors::get_mut_blocking()
@ -339,34 +339,30 @@ impl Allocator {
} }
fn unpoison(start: usize, size: usize) { fn unpoison(start: usize, size: usize) {
// log::trace!("unpoisoning {:x} for {:x}", start, size / 8 + 1);
unsafe { unsafe {
// log::trace!("memset: {:?}", start as *mut c_void); std::slice::from_raw_parts_mut(start as *mut u8, size / 8).fill(0xff);
memset(start as *mut c_void, 0xff, size / 8);
let remainder = size % 8; let remainder = size % 8;
if remainder > 0 { if remainder > 0 {
// log::trace!("remainder: {:x}, offset: {:x}", remainder, start + size / 8); let mut current_value = ((start + size / 8) as *const u8).read();
memset( current_value |= 0xff << (8 - remainder);
(start + size / 8) as *mut c_void, ((start + size / 8) as *mut u8).write(current_value);
(0xff << (8 - remainder)) & 0xff,
1,
);
} }
} }
} }
/// Poisonn an area in memory /// Poisonn an area in memory
pub fn poison(start: usize, size: usize) { pub fn poison(start: usize, size: usize) {
// log::trace!("poisoning {:x} for {:x}", start, size / 8 + 1);
unsafe { unsafe {
// log::trace!("memset: {:?}", start as *mut c_void); std::slice::from_raw_parts_mut(start as *mut u8, size / 8).fill(0x0);
memset(start as *mut c_void, 0x00, size / 8);
let remainder = size % 8; let remainder = size % 8;
if remainder > 0 { if remainder > 0 {
// log::trace!("remainder: {:x}, offset: {:x}", remainder, start + size / 8); let mask = !(0xff << (8 - remainder));
memset((start + size / 8) as *mut c_void, 0x00, 1); let mut current_value = ((start + size / 8) as *const u8).read();
current_value &= mask;
((start + size / 8) as *mut u8).write(current_value);
} }
} }
} }
@ -381,87 +377,154 @@ impl Allocator {
let shadow_mapping_start = map_to_shadow!(self, start); let shadow_mapping_start = map_to_shadow!(self, start);
let shadow_start = self.round_down_to_page(shadow_mapping_start); let shadow_start = self.round_down_to_page(shadow_mapping_start);
// I'm not sure this works as planned. The same address appearing as start and end is mapped to let shadow_end = self.round_up_to_page((end - start) / 8 + self.page_size + shadow_start);
// different addresses. if self.using_pre_allocated_shadow_mapping {
let shadow_end = self.round_up_to_page((end - start) / 8) + self.page_size + shadow_start; let mut newly_committed_regions = Vec::new();
log::trace!(
"map_shadow_for_region start: {:x}, end {:x}, size {:x}, shadow {:x}-{:x}",
start,
end,
end - start,
shadow_start,
shadow_end
);
if self.pre_allocated_shadow_mappings.is_empty() {
for range in self.shadow_pages.gaps(&(shadow_start..shadow_end)) {
/*
log::trace!(
"range: {:x}-{:x}, pagesize: {}",
range.start, range.end, self.page_size
);
*/
let mapping = MmapOptions::new(range.end - range.start - 1)
.unwrap()
.with_address(range.start)
.map_mut()
.expect("An error occurred while mapping shadow memory");
self.mappings.insert(range.start, mapping);
}
log::trace!("adding shadow pages {:x} - {:x}", shadow_start, shadow_end);
self.shadow_pages.insert(shadow_start..shadow_end);
} else {
let mut new_shadow_mappings = Vec::new();
for gap in self.shadow_pages.gaps(&(shadow_start..shadow_end)) { for gap in self.shadow_pages.gaps(&(shadow_start..shadow_end)) {
for ((pa_start, pa_end), shadow_mapping) in &mut self.pre_allocated_shadow_mappings let mut new_reserved_region = None;
{ for reserved in &mut self.pre_allocated_shadow_mappings {
if *pa_start <= gap.start && gap.start < *pa_start + shadow_mapping.len() { if gap.start >= reserved.start() && gap.end <= reserved.end() {
log::trace!("pa_start: {:x}, pa_end {:x}, gap.start {:x}, shadow_mapping.ptr {:x}, shadow_mapping.len {:x}", let mut to_be_commited =
*pa_start, *pa_end, gap.start, shadow_mapping.as_ptr() as usize, shadow_mapping.len()); reserved.split_off(gap.start - reserved.start()).unwrap();
// Split the preallocated mapping into two parts, keeping the
// part before the gap and returning the part starting with the gap as a new mapping
let mut start_mapping =
shadow_mapping.split_off(gap.start - *pa_start).unwrap();
// Split the new mapping into two parts,
// keeping the part holding the gap and returning the part starting after the gap as a new mapping
let end_mapping = start_mapping.split_off(gap.end - gap.start).unwrap();
//Push the new after-the-gap mapping to the list of mappings to be added
new_shadow_mappings.push(((gap.end, *pa_end), end_mapping));
// Insert the new gap mapping into the list of mappings
self.mappings
.insert(gap.start, start_mapping.try_into().unwrap());
if to_be_commited.end() > gap.end {
let upper = to_be_commited
.split_off(gap.end - to_be_commited.start())
.unwrap();
new_reserved_region = Some(upper);
}
let commited: MmapMut = to_be_commited
.try_into()
.expect("Failed to commit reserved shadow memory");
newly_committed_regions.push(commited);
break; break;
} }
} }
}
for new_shadow_mapping in new_shadow_mappings {
log::trace!(
"adding pre_allocated_shadow_mappings and shadow pages {:x} - {:x}",
new_shadow_mapping.0 .0,
new_shadow_mapping.0 .1
);
self.pre_allocated_shadow_mappings
.insert(new_shadow_mapping.0, new_shadow_mapping.1);
if let Some(new_reserved_region) = new_reserved_region {
self.pre_allocated_shadow_mappings.push(new_reserved_region);
}
}
for newly_committed_region in newly_committed_regions {
self.shadow_pages self.shadow_pages
.insert(new_shadow_mapping.0 .0..new_shadow_mapping.0 .1); .insert(newly_committed_region.start()..newly_committed_region.end());
self.mappings
.insert(newly_committed_region.start(), newly_committed_region);
} }
} }
// log::trace!("shadow_mapping_start: {:x}, shadow_size: {:x}", shadow_mapping_start, (end - start) / 8);
if unpoison { if unpoison {
Self::unpoison(shadow_mapping_start, end - start); Self::unpoison(shadow_mapping_start, end - start);
} }
(shadow_mapping_start, (end - start) / 8) (shadow_mapping_start, (end - start) / 8 + 1)
} }
#[inline]
#[must_use]
fn check_shadow_aligned(&mut self, address: *const c_void, size: usize) -> bool {
assert_eq!(
(address as usize) & 7,
0,
"check_shadow_aligned used when address is not aligned. Use check_shadow"
);
assert_eq!(
size & 7,
0,
"check_shadow_aligned used when size is not aligned. Use check_shadow"
);
if size == 0 {
return true;
}
let shadow_addr = map_to_shadow!(self, (address as usize));
let shadow_size = size >> 3;
let buf = unsafe { std::slice::from_raw_parts_mut(shadow_addr as *mut u8, shadow_size) };
let (prefix, aligned, suffix) = unsafe { buf.align_to::<u128>() };
if !prefix.iter().all(|&x| x == 0xff)
|| !suffix.iter().all(|&x| x == 0xff)
|| !aligned
.iter()
.all(|&x| x == 0xffffffffffffffffffffffffffffffffu128)
{
return false;
}
true
}
/// Checks whether the given address up till size is valid unpoisoned shadow memory.
/// TODO: check edge cases
#[inline]
#[must_use]
pub fn check_shadow(&mut self, address: *const c_void, size: usize) -> bool {
//the algorithm for check_shadow is as follows:
//1. we first check if its managed. if is not then exit
//2. we check if it is aligned. this should be 99% of accesses. If it is do an aligned check and leave
//3. if it is not split the check into 3 parts: the pre-aligment bytes, the aligned portion, and the post alignment posts
//3. The prealignment bytes are the unaligned bytes (if any) located in the qword preceding the aligned portion. Perform a specialied check to ensure that the bytes from [start, align(start, 8)) are valid. In this case align(start,8) aligns start to the next 8 byte boundary.
//4. The aligned check is where the address and the size is 8 byte aligned. Use check_shadow_aligned to check it
//5. The post-alignment is the same as pre-alignment except it is the qword following the aligned portion. Use a specialized check to ensure that [end & ~7, end) is valid.
if size == 0
/*|| !self.is_managed(address as *mut c_void)*/
{
return true;
}
if !self.is_managed(address as *mut c_void) {
log::trace!("unmanaged address to check_shadow: {:?}, {size:x}", address);
return true;
}
//fast path. most buffers are likely 8 byte aligned in size and address
if (address as usize).trailing_zeros() >= 3 && size.trailing_zeros() >= 3 {
return self.check_shadow_aligned(address, size);
}
//slow path. check everything
let start_address = address as usize;
let end_address = start_address + size;
//8 byte align the start/end so we can use check_shadow_aligned for the majority of it
//in the case of subqword accesses (i.e,, the entire access is located within 1 qword), aligned_start > aligned_end naturally
let aligned_start = (start_address + 7) & !7;
let aligned_end = end_address & !7;
let start_offset = start_address & 7;
let end_offset = end_address & 7;
//if the start is unaligned
if start_address != aligned_start {
let start_shadow = map_to_shadow!(self, start_address);
let start_mask: u8 = 0xff << (8 - start_offset);
if unsafe { (start_shadow as *const u8).read() } & start_mask != start_mask {
return false;
}
}
//if this is not true then it must be a subqword access as the start will be larger than the end
if aligned_start <= aligned_end {
if !self
.check_shadow_aligned(aligned_start as *const c_void, aligned_end - aligned_start)
{
return false;
}
if end_address != aligned_end {
let end_shadow = map_to_shadow!(self, end_address);
let end_mask = 0xff << (8 - end_offset); //we want to check from the beginning of the qword to the offset
if unsafe { (end_shadow as *const u8).read() } & end_mask != end_mask {
return false;
}
}
}
// self.map_shadow_for_region(address, address + size, false);
true
}
/// Maps the address to a shadow address /// Maps the address to a shadow address
#[inline] #[inline]
#[must_use] #[must_use]
@ -473,7 +536,7 @@ impl Allocator {
#[inline] #[inline]
pub fn is_managed(&self, ptr: *mut c_void) -> bool { pub fn is_managed(&self, ptr: *mut c_void) -> bool {
//self.allocations.contains_key(&(ptr as usize)) //self.allocations.contains_key(&(ptr as usize))
self.base_mapping_addr <= ptr as usize && (ptr as usize) < self.current_mapping_addr self.shadow_offset <= ptr as usize && (ptr as usize) < self.current_mapping_addr
} }
/// Checks if any of the allocations has not been freed /// Checks if any of the allocations has not been freed
@ -488,17 +551,19 @@ impl Allocator {
/// Unpoison all the memory that is currently mapped with read/write permissions. /// Unpoison all the memory that is currently mapped with read/write permissions.
pub fn unpoison_all_existing_memory(&mut self) { pub fn unpoison_all_existing_memory(&mut self) {
RangeDetails::enumerate_with_prot(PageProtection::NoAccess, &mut |range: &RangeDetails| { RangeDetails::enumerate_with_prot(
if range.protection() as u32 & PageProtection::ReadWrite as u32 != 0 { PageProtection::Read,
&mut |range: &RangeDetails| -> bool {
let start = range.memory_range().base_address().0 as usize; let start = range.memory_range().base_address().0 as usize;
let end = start + range.memory_range().size(); let end = start + range.memory_range().size();
if !self.pre_allocated_shadow_mappings.is_empty() && start == 1 << self.shadow_bit {
return true; if !self.is_managed(start as *mut c_void) {
self.map_shadow_for_region(start, end, true);
} }
self.map_shadow_for_region(start, end, true);
} true
true },
}); );
} }
/// Initialize the allocator, making sure a valid shadow bit is selected. /// Initialize the allocator, making sure a valid shadow bit is selected.
@ -512,57 +577,61 @@ impl Allocator {
let mut occupied_ranges: Vec<(usize, usize)> = vec![]; let mut occupied_ranges: Vec<(usize, usize)> = vec![];
// max(userspace address) this is usually 0x8_0000_0000_0000 - 1 on x64 linux. // max(userspace address) this is usually 0x8_0000_0000_0000 - 1 on x64 linux.
#[cfg(unix)]
let mut userspace_max: usize = 0; let mut userspace_max: usize = 0;
// Enumerate memory ranges that are already occupied. // Enumerate memory ranges that are already occupied.
for area in MemoryAreas::open(None).unwrap() {
let start = area.as_ref().unwrap().start();
let end = area.unwrap().end();
occupied_ranges.push((start, end));
// log::trace!("Occupied {:x} {:x}", start, end);
let base: usize = 2;
// On x64, if end > 2**48, then that's in vsyscall or something.
#[cfg(all(unix, target_arch = "x86_64"))]
if end <= base.pow(48) && end > userspace_max {
userspace_max = end;
}
#[cfg(all(not(unix), target_arch = "x86_64"))] RangeDetails::enumerate_with_prot(
if (end >> 3) <= base.pow(44) && (end >> 3) > userspace_max { PageProtection::Read,
userspace_max = end >> 3; &mut |range: &RangeDetails| -> bool {
} let start = range.memory_range().base_address().0 as usize;
let end = start + range.memory_range().size();
occupied_ranges.push((start, end));
// On x64, if end > 2**48, then that's in vsyscall or something.
#[cfg(all(unix, target_arch = "x86_64"))]
if end <= 2_usize.pow(48) && end > userspace_max {
userspace_max = end;
}
//
// #[cfg(all(not(unix), target_arch = "x86_64"))]
// if end <= 2_usize.pow(64) && end > userspace_max {
// userspace_max = end;
// }
// On aarch64, if end > 2**52, then range is not in userspace // On aarch64, if end > 2**52, then range is not in userspace
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
if end <= base.pow(52) && end > userspace_max { if end <= 2_usize.pow(52) && end > userspace_max {
userspace_max = end; userspace_max = end;
} }
}
let mut maxbit = 0; true
},
);
#[cfg(unix)]
let mut maxbit = 63;
#[cfg(windows)]
let maxbit = 63;
#[cfg(unix)]
for power in 1..64 { for power in 1..64 {
let base: usize = 2; if 2_usize.pow(power) > userspace_max {
if base.pow(power) > userspace_max {
maxbit = power; maxbit = power;
break; break;
} }
} }
{ {
for try_shadow_bit in &[maxbit, maxbit - 4, maxbit - 3, maxbit - 2] { for try_shadow_bit in 44..maxbit {
let addr: usize = 1 << try_shadow_bit; let addr: usize = 1 << try_shadow_bit;
let shadow_start = addr; let shadow_start = addr;
let shadow_end = addr + addr + addr; let shadow_end = addr + addr + addr;
let mut good_candidate = true; let mut good_candidate = true;
// check if the proposed shadow bit overlaps with occupied ranges. // check if the proposed shadow bit overlaps with occupied ranges.
for (start, end) in &occupied_ranges { for (start, end) in &occupied_ranges {
// log::trace!("{:x} {:x}, {:x} {:x} -> {:x} - {:x}", shadow_start, shadow_end, start, end,
// shadow_start + ((start >> 3) & ((1 << (try_shadow_bit + 1)) - 1)),
// shadow_start + ((end >> 3) & ((1 << (try_shadow_bit + 1)) - 1))
// );
if (shadow_start <= *end) && (*start <= shadow_end) { if (shadow_start <= *end) && (*start <= shadow_end) {
log::trace!("{:x} {:x}, {:x} {:x}", shadow_start, shadow_end, start, end); log::trace!("{:x} {:x}, {:x} {:x}", shadow_start, shadow_end, start, end);
log::warn!("shadow_bit {try_shadow_bit:x} is not suitable"); log::warn!("shadow_bit {try_shadow_bit:} is not suitable");
good_candidate = false; good_candidate = false;
break; break;
} }
@ -573,7 +642,7 @@ impl Allocator {
> shadow_end) > shadow_end)
{ {
log::warn!( log::warn!(
"shadow_bit {try_shadow_bit:x} is not suitable (shadow out of range)" "shadow_bit {try_shadow_bit:} is not suitable (shadow out of range)"
); );
good_candidate = false; good_candidate = false;
break; break;
@ -582,33 +651,26 @@ impl Allocator {
if good_candidate { if good_candidate {
// We reserve the shadow memory space of size addr*2, but don't commit it. // We reserve the shadow memory space of size addr*2, but don't commit it.
if let Ok(mapping) = MmapOptions::new(1 << (*try_shadow_bit + 1)) if let Ok(mapping) = MmapOptions::new(1 << (try_shadow_bit + 1))
.unwrap() .unwrap()
.with_flags(MmapFlags::NO_RESERVE) .with_flags(MmapFlags::NO_RESERVE)
.with_address(addr) .with_address(addr)
.reserve_mut() .reserve_mut()
{ {
shadow_bit = (*try_shadow_bit).try_into().unwrap(); shadow_bit = (try_shadow_bit).try_into().unwrap();
log::warn!("shadow_bit {shadow_bit:x} is suitable"); log::warn!("shadow_bit {shadow_bit:} is suitable");
log::trace!( self.pre_allocated_shadow_mappings.push(mapping);
"adding pre_allocated_shadow_mappings {:x} - {:x} with size {:}", self.using_pre_allocated_shadow_mapping = true;
addr,
(addr + (1 << (shadow_bit + 1))),
mapping.len()
);
self.pre_allocated_shadow_mappings
.insert((addr, (addr + (1 << (shadow_bit + 1)))), mapping);
break; break;
} }
log::warn!("shadow_bit {try_shadow_bit:x} is not suitable - failed to allocate shadow memory"); log::warn!("shadow_bit {try_shadow_bit:} is not suitable - failed to allocate shadow memory");
} }
} }
} }
// assert!(shadow_bit != 0); log::warn!("shadow_bit: {shadow_bit}");
// attempt to pre-map the entire shadow-memory space assert!(shadow_bit != 0);
let addr: usize = 1 << shadow_bit; let addr: usize = 1 << shadow_bit;
@ -643,7 +705,8 @@ impl Default for Allocator {
max_total_allocation: 1 << 32, max_total_allocation: 1 << 32,
allocation_backtraces: false, allocation_backtraces: false,
page_size, page_size,
pre_allocated_shadow_mappings: HashMap::new(), pre_allocated_shadow_mappings: Vec::new(),
using_pre_allocated_shadow_mapping: false,
mappings: HashMap::new(), mappings: HashMap::new(),
shadow_offset: 0, shadow_offset: 0,
shadow_bit: 0, shadow_bit: 0,
@ -657,3 +720,61 @@ impl Default for Allocator {
} }
} }
} }
#[test]
fn check_shadow() {
let mut allocator = Allocator::default();
allocator.init();
let allocation = unsafe { allocator.alloc(8, 8) };
assert!(!allocation.is_null());
assert!(allocator.check_shadow(allocation, 1));
assert!(allocator.check_shadow(allocation, 2));
assert!(allocator.check_shadow(allocation, 3));
assert!(allocator.check_shadow(allocation, 4));
assert!(allocator.check_shadow(allocation, 5));
assert!(allocator.check_shadow(allocation, 6));
assert!(allocator.check_shadow(allocation, 7));
assert!(allocator.check_shadow(allocation, 8));
assert!(!allocator.check_shadow(allocation, 9));
assert!(!allocator.check_shadow(allocation, 10));
assert!(allocator.check_shadow(unsafe { allocation.offset(1) }, 7));
assert!(allocator.check_shadow(unsafe { allocation.offset(2) }, 6));
assert!(allocator.check_shadow(unsafe { allocation.offset(3) }, 5));
assert!(allocator.check_shadow(unsafe { allocation.offset(4) }, 4));
assert!(allocator.check_shadow(unsafe { allocation.offset(5) }, 3));
assert!(allocator.check_shadow(unsafe { allocation.offset(6) }, 2));
assert!(allocator.check_shadow(unsafe { allocation.offset(7) }, 1));
assert!(allocator.check_shadow(unsafe { allocation.offset(8) }, 0));
assert!(!allocator.check_shadow(unsafe { allocation.offset(9) }, 1));
assert!(!allocator.check_shadow(unsafe { allocation.offset(9) }, 8));
assert!(!allocator.check_shadow(unsafe { allocation.offset(1) }, 9));
assert!(!allocator.check_shadow(unsafe { allocation.offset(1) }, 8));
assert!(!allocator.check_shadow(unsafe { allocation.offset(2) }, 8));
assert!(!allocator.check_shadow(unsafe { allocation.offset(3) }, 8));
let allocation = unsafe { allocator.alloc(0xc, 0) };
assert!(allocator.check_shadow(unsafe { allocation.offset(4) }, 8));
//subqword access
assert!(allocator.check_shadow(unsafe { allocation.offset(3) }, 2));
//unaligned access
assert!(allocator.check_shadow(unsafe { allocation.offset(3) }, 8));
let allocation = unsafe { allocator.alloc(0x20, 0) };
//access with unaligned parts at the beginning and end
assert!(allocator.check_shadow(unsafe { allocation.offset(10) }, 21));
//invalid, unaligned access
assert!(!allocator.check_shadow(unsafe { allocation.offset(10) }, 29));
let allocation = unsafe { allocator.alloc(4, 0) };
assert!(!allocation.is_null());
assert!(allocator.check_shadow(allocation, 1));
assert!(allocator.check_shadow(allocation, 2));
assert!(allocator.check_shadow(allocation, 3));
assert!(allocator.check_shadow(allocation, 4));
assert!(!allocator.check_shadow(allocation, 5));
assert!(!allocator.check_shadow(allocation, 6));
assert!(!allocator.check_shadow(allocation, 7));
assert!(!allocator.check_shadow(allocation, 8));
let allocation = unsafe { allocator.alloc(0xc, 0) };
assert!(allocator.check_shadow(unsafe { allocation.offset(4) }, 8));
let allocation = unsafe { allocator.alloc(0x3c, 0) };
assert!(allocator.check_shadow(unsafe { allocation.offset(0x3a) }, 2));
}

File diff suppressed because it is too large Load Diff

View File

@ -91,7 +91,7 @@ pub(crate) enum AsanError {
} }
impl AsanError { impl AsanError {
fn description(&self) -> &str { pub fn description(&self) -> &str {
match self { match self {
AsanError::OobRead(_) => "heap out-of-bounds read", AsanError::OobRead(_) => "heap out-of-bounds read",
AsanError::OobWrite(_) => "heap out-of-bounds write", AsanError::OobWrite(_) => "heap out-of-bounds write",
@ -114,7 +114,7 @@ impl AsanError {
#[derive(Debug, Clone, Serialize, Deserialize, SerdeAny)] #[derive(Debug, Clone, Serialize, Deserialize, SerdeAny)]
pub struct AsanErrors { pub struct AsanErrors {
continue_on_error: bool, continue_on_error: bool,
errors: Vec<AsanError>, pub(crate) errors: Vec<AsanError>,
} }
impl AsanErrors { impl AsanErrors {

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
#[cfg(all(unix, not(test)))]
use core::borrow::Borrow;
use core::fmt::{self, Debug, Formatter}; use core::fmt::{self, Debug, Formatter};
#[cfg(windows)]
use std::process::abort;
use std::{ffi::c_void, marker::PhantomData}; use std::{ffi::c_void, marker::PhantomData};
use frida_gum::{ use frida_gum::{
@ -21,7 +21,7 @@ use libafl::{
}; };
use libafl_bolts::tuples::RefIndexable; use libafl_bolts::tuples::RefIndexable;
#[cfg(all(unix, not(test)))] #[cfg(not(test))]
use crate::asan::errors::AsanErrors; use crate::asan::errors::AsanErrors;
use crate::helper::{FridaInstrumentationHelper, FridaRuntimeTuple}; use crate::helper::{FridaInstrumentationHelper, FridaRuntimeTuple};
#[cfg(windows)] #[cfg(windows)]
@ -106,11 +106,13 @@ where
self.stalker.deactivate(); self.stalker.deactivate();
} }
#[cfg(all(unix, not(test)))] #[cfg(not(test))]
unsafe { unsafe {
if !AsanErrors::get_mut_blocking().borrow().is_empty() { if !AsanErrors::get_mut_blocking().is_empty() {
log::error!("Crashing target as it had ASan errors"); log::error!("Crashing target as it had ASan errors");
libc::raise(libc::SIGABRT); libc::raise(libc::SIGABRT);
#[cfg(windows)]
abort();
} }
} }
self.helper.post_exec(input)?; self.helper.post_exec(input)?;
@ -206,6 +208,7 @@ where
} }
} }
log::info!("disable_excludes: {:}", helper.disable_excludes);
if !helper.disable_excludes { if !helper.disable_excludes {
for range in ranges.gaps(&(0..usize::MAX)) { for range in ranges.gaps(&(0..usize::MAX)) {
log::info!("excluding range: {:x}-{:x}", range.start, range.end); log::info!("excluding range: {:x}-{:x}", range.start, range.end);

View File

@ -6,9 +6,8 @@ use std::{
rc::Rc, rc::Rc,
}; };
#[cfg(unix)]
use frida_gum::instruction_writer::InstructionWriter;
use frida_gum::{ use frida_gum::{
instruction_writer::InstructionWriter,
stalker::{StalkerIterator, StalkerOutput, Transformer}, stalker::{StalkerIterator, StalkerOutput, Transformer},
Gum, Module, ModuleDetails, ModuleMap, PageProtection, Gum, Module, ModuleDetails, ModuleMap, PageProtection,
}; };
@ -28,11 +27,9 @@ use yaxpeax_arm::armv8::a64::{ARMv8, InstDecoder};
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
use yaxpeax_x86::amd64::InstDecoder; use yaxpeax_x86::amd64::InstDecoder;
#[cfg(unix)]
use crate::asan::asan_rt::AsanRuntime;
#[cfg(feature = "cmplog")] #[cfg(feature = "cmplog")]
use crate::cmplog_rt::CmpLogRuntime; use crate::cmplog_rt::CmpLogRuntime;
use crate::{coverage_rt::CoverageRuntime, drcov_rt::DrCovRuntime}; use crate::{asan::asan_rt::AsanRuntime, coverage_rt::CoverageRuntime, drcov_rt::DrCovRuntime};
#[cfg(target_vendor = "apple")] #[cfg(target_vendor = "apple")]
const ANONYMOUS_FLAG: MapFlags = MapFlags::MAP_ANON; const ANONYMOUS_FLAG: MapFlags = MapFlags::MAP_ANON;
@ -275,6 +272,11 @@ impl FridaInstrumentationHelperBuilder {
if stalker_enabled { if stalker_enabled {
for (i, module) in module_map.values().iter().enumerate() { for (i, module) in module_map.values().iter().enumerate() {
log::trace!(
"module: {:?} {:x}",
module.name(),
module.range().base_address().0 as usize
);
let range = module.range(); let range = module.range();
let start = range.base_address().0 as usize; let start = range.base_address().0 as usize;
ranges ranges
@ -330,7 +332,7 @@ impl Default for FridaInstrumentationHelperBuilder {
fn default() -> Self { fn default() -> Self {
Self { Self {
stalker_enabled: true, stalker_enabled: true,
disable_excludes: true, disable_excludes: false,
instrument_module_predicate: None, instrument_module_predicate: None,
skip_module_predicate: Box::new(|module| { skip_module_predicate: Box::new(|module| {
// Skip the instrumentation module to avoid recursion. // Skip the instrumentation module to avoid recursion.
@ -445,7 +447,7 @@ where
let runtimes = Rc::clone(runtimes); let runtimes = Rc::clone(runtimes);
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
let decoder = InstDecoder::minimal(); let decoder = InstDecoder::default();
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
let decoder = <ARMv8 as Arch>::Decoder::default(); let decoder = <ARMv8 as Arch>::Decoder::default();
@ -459,7 +461,7 @@ where
basic_block: StalkerIterator, basic_block: StalkerIterator,
output: &StalkerOutput, output: &StalkerOutput,
ranges: &Rc<RefCell<RangeMap<usize, (u16, String)>>>, ranges: &Rc<RefCell<RangeMap<usize, (u16, String)>>>,
runtimes: &Rc<RefCell<RT>>, runtimes_unborrowed: &Rc<RefCell<RT>>,
decoder: InstDecoder, decoder: InstDecoder,
) { ) {
let mut first = true; let mut first = true;
@ -469,10 +471,10 @@ where
let instr = instruction.instr(); let instr = instruction.instr();
let instr_size = instr.bytes().len(); let instr_size = instr.bytes().len();
let address = instr.address(); let address = instr.address();
// log::trace!("block @ {:x} transformed to {:x}", address, output.writer().pc()); // log::trace!("x - block @ {:x} transformed to {:x}", address, output.writer().pc());
//the ASAN check needs to be done before the hook_rt check due to x86 insns such as call [mem]
if ranges.borrow().contains_key(&(address as usize)) { if ranges.borrow().contains_key(&(address as usize)) {
let mut runtimes = (*runtimes).borrow_mut(); let mut runtimes = (*runtimes_unborrowed).borrow_mut();
if first { if first {
first = false; first = false;
// log::info!( // log::info!(
@ -483,24 +485,29 @@ where
if let Some(rt) = runtimes.match_first_type_mut::<CoverageRuntime>() { if let Some(rt) = runtimes.match_first_type_mut::<CoverageRuntime>() {
rt.emit_coverage_mapping(address, output); rt.emit_coverage_mapping(address, output);
} }
if let Some(_rt) = runtimes.match_first_type_mut::<DrCovRuntime>() { if let Some(_rt) = runtimes.match_first_type_mut::<DrCovRuntime>() {
basic_block_start = address; basic_block_start = address;
} }
} }
#[cfg(unix)]
let res = if let Some(_rt) = runtimes.match_first_type_mut::<AsanRuntime>() { let res = if let Some(_rt) = runtimes.match_first_type_mut::<AsanRuntime>() {
AsanRuntime::asan_is_interesting_instruction(decoder, address, instr) AsanRuntime::asan_is_interesting_instruction(decoder, address, instr)
} else { } else {
None None
}; };
#[cfg(all(target_arch = "x86_64", unix))] #[cfg(target_arch = "x86_64")]
if let Some(details) = res { if let Some(details) = res {
if let Some(rt) = runtimes.match_first_type_mut::<AsanRuntime>() { if let Some(rt) = runtimes.match_first_type_mut::<AsanRuntime>() {
rt.emit_shadow_check( rt.emit_shadow_check(
address, output, details.0, details.1, details.2, details.3, details.4, address,
output,
instr.bytes().len(),
details.0,
details.1,
details.2,
details.3,
details.4,
); );
} }
} }
@ -541,7 +548,6 @@ where
} }
} }
#[cfg(unix)]
if let Some(rt) = runtimes.match_first_type_mut::<AsanRuntime>() { if let Some(rt) = runtimes.match_first_type_mut::<AsanRuntime>() {
rt.add_stalked_address( rt.add_stalked_address(
output.writer().pc() as usize - instr_size, output.writer().pc() as usize - instr_size,
@ -556,7 +562,10 @@ where
instruction.keep(); instruction.keep();
} }
if basic_block_size != 0 { if basic_block_size != 0 {
if let Some(rt) = runtimes.borrow_mut().match_first_type_mut::<DrCovRuntime>() { if let Some(rt) = runtimes_unborrowed
.borrow_mut()
.match_first_type_mut::<DrCovRuntime>()
{
log::trace!("{basic_block_start:#016X}:{basic_block_size:X}"); log::trace!("{basic_block_start:#016X}:{basic_block_size:X}");
rt.drcov_basic_blocks.push(DrCovBasicBlock::new( rt.drcov_basic_blocks.push(DrCovBasicBlock::new(
basic_block_start as usize, basic_block_start as usize,

View File

@ -64,10 +64,8 @@ Additional documentation is available in [the `LibAFL` book](https://aflplus.plu
)] )]
/// The frida-asan allocator /// The frida-asan allocator
#[cfg(unix)]
pub mod alloc; pub mod alloc;
#[cfg(unix)]
pub mod asan; pub mod asan;
#[cfg(windows)] #[cfg(windows)]
@ -369,7 +367,7 @@ mod tests {
use crate::{ use crate::{
asan::{ asan::{
asan_rt::AsanRuntime, asan_rt::AsanRuntime,
errors::{AsanErrorsFeedback, AsanErrorsObserver}, errors::{AsanErrors, AsanErrorsFeedback, AsanErrorsObserver},
}, },
coverage_rt::CoverageRuntime, coverage_rt::CoverageRuntime,
executor::FridaInProcessExecutor, executor::FridaInProcessExecutor,
@ -378,20 +376,56 @@ mod tests {
static GUM: OnceLock<Gum> = OnceLock::new(); static GUM: OnceLock<Gum> = OnceLock::new();
#[allow(clippy::too_many_lines)]
unsafe fn test_asan(options: &FuzzerOptions) { unsafe fn test_asan(options: &FuzzerOptions) {
// The names of the functions to run // The names of the functions to run
let tests = vec![ let tests = vec![
("LLVMFuzzerTestOneInput", 0), ("LLVMFuzzerTestOneInput", None),
("heap_oob_read", 1), ("heap_oob_read", Some("heap out-of-bounds read")),
("heap_oob_write", 1), ("heap_oob_write", Some("heap out-of-bounds write")),
("heap_uaf_write", 1), ("heap_uaf_write", Some("heap use-after-free write")),
("heap_uaf_read", 1), ("heap_uaf_read", Some("heap use-after-free read")),
("malloc_heap_oob_read", 1), ("malloc_heap_oob_read", Some("heap out-of-bounds read")),
("malloc_heap_oob_write", 1), ("malloc_heap_oob_write", Some("heap out-of-bounds write")),
("malloc_heap_uaf_write", 1), (
("malloc_heap_uaf_read", 1), "malloc_heap_oob_write_0x12",
Some("heap out-of-bounds write"),
),
(
"malloc_heap_oob_write_0x14",
Some("heap out-of-bounds write"),
),
(
"malloc_heap_oob_write_0x17",
Some("heap out-of-bounds write"),
),
(
"malloc_heap_oob_write_0x17_int_at_0x16",
Some("heap out-of-bounds write"),
),
(
"malloc_heap_oob_write_0x17_int_at_0x15",
Some("heap out-of-bounds write"),
),
("malloc_heap_oob_write_0x17_int_at_0x13", None),
(
"malloc_heap_oob_write_0x17_int_at_0x14",
Some("heap out-of-bounds write"),
),
("malloc_heap_uaf_write", Some("heap use-after-free write")),
("malloc_heap_uaf_read", Some("heap use-after-free read")),
]; ];
//NOTE: RTLD_NOW is required on linux as otherwise the hooks will NOT work
#[cfg(target_os = "linux")]
let lib = libloading::os::unix::Library::open(
Some(options.clone().harness.unwrap()),
libloading::os::unix::RTLD_NOW,
)
.unwrap();
#[cfg(not(target_os = "linux"))]
let lib = libloading::Library::new(options.clone().harness.unwrap()).unwrap(); let lib = libloading::Library::new(options.clone().harness.unwrap()).unwrap();
let coverage = CoverageRuntime::new(); let coverage = CoverageRuntime::new();
@ -404,7 +438,7 @@ mod tests {
// Run the tests for each function // Run the tests for each function
for test in tests { for test in tests {
let (function_name, err_cnt) = test; let (function_name, expected_error) = test;
log::info!("Testing with harness function {}", function_name); log::info!("Testing with harness function {}", function_name);
let mut corpus = InMemoryCorpus::<BytesInput>::new(); let mut corpus = InMemoryCorpus::<BytesInput>::new();
@ -415,7 +449,7 @@ mod tests {
let rand = StdRand::with_seed(0); let rand = StdRand::with_seed(0);
let mut feedback = ConstFeedback::new(false); let mut feedback = ConstFeedback::new(true);
let asan_obs = AsanErrorsObserver::from_static_asan_errors(); let asan_obs = AsanErrorsObserver::from_static_asan_errors();
@ -446,6 +480,12 @@ mod tests {
); );
{ {
#[cfg(target_os = "linux")]
let target_func: libloading::os::unix::Symbol<
unsafe extern "C" fn(data: *const u8, size: usize) -> i32,
> = lib.get(function_name.as_bytes()).unwrap();
#[cfg(not(target_os = "linux"))]
let target_func: libloading::Symbol< let target_func: libloading::Symbol<
unsafe extern "C" fn(data: *const u8, size: usize) -> i32, unsafe extern "C" fn(data: *const u8, size: usize) -> i32,
> = lib.get(function_name.as_bytes()).unwrap(); > = lib.get(function_name.as_bytes()).unwrap();
@ -473,19 +513,25 @@ mod tests {
let mutator = StdScheduledMutator::new(tuple_list!(BitFlipMutator::new())); let mutator = StdScheduledMutator::new(tuple_list!(BitFlipMutator::new()));
let mut stages = tuple_list!(StdMutationalStage::with_max_iterations(mutator, 1)); let mut stages = tuple_list!(StdMutationalStage::with_max_iterations(mutator, 1));
// log::info!("Starting fuzzing!"); log::info!("Starting fuzzing!");
fuzzer fuzzer
.fuzz_one(&mut stages, &mut executor, &mut state, &mut event_manager) .fuzz_one(&mut stages, &mut executor, &mut state, &mut event_manager)
.unwrap_or_else(|_| panic!("Error in fuzz_one")); .unwrap_or_else(|_| panic!("Error in fuzz_one"));
log::info!("Done fuzzing! Got {} solutions", state.solutions().count()); log::info!("Done fuzzing! Got {} solutions", state.solutions().count());
if let Some(expected_error) = expected_error {
assert_eq!(state.solutions().count(), 1);
if let Some(error) = AsanErrors::get_mut_blocking().errors.first() {
assert_eq!(error.description(), expected_error);
}
} else {
assert_eq!(state.solutions().count(), 0);
}
} }
assert_eq!(state.solutions().count(), err_cnt);
} }
} }
#[test] #[test]
#[cfg(unix)]
fn run_test_asan() { fn run_test_asan() {
// Read RUST_LOG from the environment and set the log level accordingly (not using env_logger) // Read RUST_LOG from the environment and set the log level accordingly (not using env_logger)
// Note that in cargo test, the output of successfull tests is suppressed by default, // Note that in cargo test, the output of successfull tests is suppressed by default,
@ -505,7 +551,10 @@ mod tests {
SimpleStdoutLogger::set_logger().unwrap(); SimpleStdoutLogger::set_logger().unwrap();
// Check if the harness dynamic library is present, if not - skip the test // Check if the harness dynamic library is present, if not - skip the test
let test_harness = "test_harness.so"; #[cfg(unix)]
let test_harness = "./test_harness.so";
#[cfg(windows)]
let test_harness = ".\\test_harness.dll";
assert!( assert!(
std::path::Path::new(test_harness).exists(), std::path::Path::new(test_harness).exists(),
"Skipping test, {test_harness} not found" "Skipping test, {test_harness} not found"

View File

@ -1,7 +1,8 @@
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
use frida_gum::instruction_writer::Aarch64Register; use frida_gum::instruction_writer::Aarch64Register;
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
use frida_gum::instruction_writer::X86Register; use frida_gum::{instruction_writer::X86Register, CpuContext};
use libafl::Error;
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
use num_traits::cast::FromPrimitive; use num_traits::cast::FromPrimitive;
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
@ -158,6 +159,30 @@ const X86_64_REGS: [(RegSpec, X86Register); 34] = [
(RegSpec::rip(), X86Register::Rip), (RegSpec::rip(), X86Register::Rip),
]; ];
/// Get the value of a register given a context
#[cfg(target_arch = "x86_64")]
pub fn get_register(context: &CpuContext, reg: X86Register) -> u64 {
match reg {
X86Register::Rax => context.rax(),
X86Register::Rbx => context.rbx(),
X86Register::Rcx => context.rcx(),
X86Register::Rdx => context.rdx(),
X86Register::Rdi => context.rdi(),
X86Register::Rsi => context.rsi(),
X86Register::Rsp => context.rsp(),
X86Register::Rbp => context.rbp(),
X86Register::R8 => context.r8(),
X86Register::R9 => context.r9(),
X86Register::R10 => context.r10(),
X86Register::R11 => context.r11(),
X86Register::R12 => context.r12(),
X86Register::R13 => context.r13(),
X86Register::R14 => context.r14(),
X86Register::R15 => context.r15(),
_ => 0,
}
}
/// The writer registers /// The writer registers
/// frida registers: <https://docs.rs/frida-gum/latest/frida_gum/instruction_writer/enum.X86Register.html> /// frida registers: <https://docs.rs/frida-gum/latest/frida_gum/instruction_writer/enum.X86Register.html>
/// capstone registers: <https://docs.rs/capstone-sys/latest/capstone_sys/x86_reg/index.html> /// capstone registers: <https://docs.rs/capstone-sys/latest/capstone_sys/x86_reg/index.html>
@ -176,9 +201,25 @@ pub fn writer_register(reg: RegSpec) -> X86Register {
} }
/// Translates a frida instruction to a disassembled instruction. /// Translates a frida instruction to a disassembled instruction.
#[cfg(all(target_arch = "x86_64", unix))] #[cfg(target_arch = "x86_64")]
pub(crate) fn frida_to_cs(decoder: InstDecoder, frida_insn: &frida_gum_sys::Insn) -> Instruction { pub(crate) fn frida_to_cs(
decoder.decode_slice(frida_insn.bytes()).unwrap() decoder: InstDecoder,
frida_insn: &frida_gum_sys::Insn,
) -> Result<Instruction, Error> {
match decoder.decode_slice(frida_insn.bytes()) {
Ok(result) => Ok(result),
Err(error) => {
log::error!(
"{:?}: {:x}: {:?}",
error,
frida_insn.address(),
frida_insn.bytes()
);
Err(Error::illegal_state(
"Instruction did not disassemble properly",
))
}
}
} }
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
@ -225,6 +266,23 @@ pub fn operand_details(operand: &Operand) -> Option<(X86Register, X86Register, u
} }
} }
#[cfg(target_arch = "x86_64")]
/// Get the immediate value of the operand
pub fn immediate_value(operand: &Operand) -> Option<i64> {
match operand {
Operand::ImmediateI8(v) => Some(i64::from(*v)),
Operand::ImmediateU8(v) => Some(i64::from(*v)),
Operand::ImmediateI16(v) => Some(i64::from(*v)),
Operand::ImmediateI32(v) => Some(i64::from(*v)),
Operand::ImmediateU16(v) => Some(i64::from(*v)),
Operand::ImmediateU32(v) => Some(i64::from(*v)),
Operand::ImmediateI64(v) => Some(*v),
#[allow(clippy::cast_possible_wrap)]
Operand::ImmediateU64(v) => Some(*v as i64),
_ => None,
}
}
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
/// What kind of memory access this instruction has /// What kind of memory access this instruction has

View File

@ -29,7 +29,7 @@ pub fn initialize(gum: &Gum) {
NativePointer(is_processor_feature_present_detour as *mut c_void), NativePointer(is_processor_feature_present_detour as *mut c_void),
NativePointer(std::ptr::null_mut()), NativePointer(std::ptr::null_mut()),
) )
.unwrap(); .unwrap_or_else(|_| NativePointer(std::ptr::null_mut()));
interceptor interceptor
.replace( .replace(
@ -37,7 +37,7 @@ pub fn initialize(gum: &Gum) {
NativePointer(unhandled_exception_filter_detour as *mut c_void), NativePointer(unhandled_exception_filter_detour as *mut c_void),
NativePointer(std::ptr::null_mut()), NativePointer(std::ptr::null_mut()),
) )
.unwrap(); .unwrap_or_else(|_| NativePointer(std::ptr::null_mut()));
unsafe extern "C" fn is_processor_feature_present_detour(feature: u32) -> bool { unsafe extern "C" fn is_processor_feature_present_detour(feature: u32) -> bool {
let result = match feature { let result = match feature {

View File

@ -2,62 +2,132 @@
#include <stdlib.h> #include <stdlib.h>
#include <string> #include <string>
extern "C" int heap_uaf_read(const uint8_t *_data, size_t _size) { #ifdef _MSC_VER
#include <windows.h>
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call,
LPVOID lpReserved) {
return TRUE;
}
#define EXTERN __declspec(dllexport) extern "C"
#else
#define EXTERN
extern "C" {
#endif
EXTERN int heap_uaf_read(const uint8_t *_data, size_t _size) {
int *array = new int[100]; int *array = new int[100];
delete[] array; delete[] array;
fprintf(stdout, "%d\n", array[5]); fprintf(stdout, "%d\n", array[5]);
return 0; return 0;
} }
extern "C" int heap_uaf_write(const uint8_t *_data, size_t _size) { EXTERN int heap_uaf_write(const uint8_t *_data, size_t _size) {
int *array = new int[100]; int *array = new int[100];
delete[] array; delete[] array;
array[5] = 1; array[5] = 1;
return 0; return 0;
} }
extern "C" int heap_oob_read(const uint8_t *_data, size_t _size) { EXTERN int heap_oob_read(const uint8_t *_data, size_t _size) {
int *array = new int[100]; int *array = new int[100];
fprintf(stdout, "%d\n", array[100]); fprintf(stdout, "%d\n", array[100]);
delete[] array; delete[] array;
return 0; return 0;
} }
extern "C" int heap_oob_write(const uint8_t *_data, size_t _size) { EXTERN int heap_oob_write(const uint8_t *_data, size_t _size) {
int *array = new int[100]; int *array = new int[100];
array[100] = 1; array[100] = 1;
delete[] array; delete[] array;
return 0; return 0;
} }
extern "C" int malloc_heap_uaf_read(const uint8_t *_data, size_t _size) { EXTERN int malloc_heap_uaf_read(const uint8_t *_data, size_t _size) {
int *array = static_cast<int *>(malloc(100 * sizeof(int))); int *array = static_cast<int *>(malloc(100 * sizeof(int)));
free(array); free(array);
fprintf(stdout, "%d\n", array[5]); fprintf(stdout, "%d\n", array[5]);
return 0; return 0;
} }
extern "C" int malloc_heap_uaf_write(const uint8_t *_data, size_t _size) { EXTERN int malloc_heap_uaf_write(const uint8_t *_data, size_t _size) {
int *array = static_cast<int *>(malloc(100 * sizeof(int))); int *array = static_cast<int *>(malloc(100 * sizeof(int)));
free(array); free(array);
array[5] = 1; array[5] = 1;
return 0; return 0;
} }
extern "C" int malloc_heap_oob_read(const uint8_t *_data, size_t _size) { EXTERN int malloc_heap_oob_read(const uint8_t *_data, size_t _size) {
int *array = static_cast<int *>(malloc(100 * sizeof(int))); int *array = static_cast<int *>(malloc(100 * sizeof(int)));
fprintf(stdout, "%d\n", array[100]); fprintf(stdout, "%d\n", array[100]);
free(array); free(array);
return 0; return 0;
} }
extern "C" int malloc_heap_oob_write(const uint8_t *_data, size_t _size) { EXTERN int malloc_heap_oob_write(const uint8_t *_data, size_t _size) {
int *array = static_cast<int *>(malloc(100 * sizeof(int))); int *array = static_cast<int *>(malloc(100 * sizeof(int)));
array[100] = 1; array[100] = 1;
free(array); free(array);
return 0; return 0;
} }
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { EXTERN int malloc_heap_oob_write_0x12(const uint8_t *_data, size_t _size) {
char *array = static_cast<char *>(malloc(0x12));
array[0x12] = 1;
free(array);
return 0;
}
EXTERN int malloc_heap_oob_write_0x14(const uint8_t *_data, size_t _size) {
char *array = static_cast<char *>(malloc(0x14));
array[0x14] = 1;
free(array);
return 0;
}
EXTERN int malloc_heap_oob_write_0x17(const uint8_t *_data, size_t _size) {
char *array = static_cast<char *>(malloc(0x17));
array[0x17] = 1;
free(array);
return 0;
}
EXTERN int malloc_heap_oob_write_0x17_int_at_0x16(const uint8_t *_data,
size_t _size) {
char *array = static_cast<char *>(malloc(0x17));
*(int *)(&array[0x16]) = 1;
free(array);
return 0;
}
EXTERN int malloc_heap_oob_write_0x17_int_at_0x15(const uint8_t *_data,
size_t _size) {
char *array = static_cast<char *>(malloc(0x17));
*(int *)(&array[0x15]) = 1;
free(array);
return 0;
}
EXTERN int malloc_heap_oob_write_0x17_int_at_0x14(const uint8_t *_data,
size_t _size) {
char *array = static_cast<char *>(malloc(0x17));
*(int *)(&array[0x14]) = 1;
free(array);
return 0;
}
EXTERN int malloc_heap_oob_write_0x17_int_at_0x13(const uint8_t *_data,
size_t _size) {
char *array = static_cast<char *>(malloc(0x17));
*(int *)(&array[0x13]) = 1;
free(array);
return 0;
}
EXTERN int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// abort(); // abort();
return 0; return 0;
} }
#ifndef _MSC_VER
}
#endif

View File

@ -200,10 +200,11 @@ fn main() {
} }
} }
let target_family = std::env::var("CARGO_CFG_TARGET_FAMILY").unwrap();
#[cfg(feature = "forkserver")] #[cfg(feature = "forkserver")]
{ {
#[cfg(unix)] if target_family == "unix" {
{
println!("cargo:rerun-if-changed=src/forkserver.c"); println!("cargo:rerun-if-changed=src/forkserver.c");
cc::Build::new() cc::Build::new()
@ -212,8 +213,8 @@ fn main() {
} }
} }
#[cfg(all(feature = "windows_asan", windows))] #[cfg(feature = "windows_asan")]
{ if target_family == "windows" {
println!("cargo:rerun-if-changed=src/windows_asan.c"); println!("cargo:rerun-if-changed=src/windows_asan.c");
cc::Build::new() cc::Build::new()

View File

@ -15,9 +15,17 @@ void *__libafl_asan_region_is_poisoned(void *beg, size_t size) {
return NULL; return NULL;
} }
#pragma comment( \ #if defined(__clang__) && defined(_MSC_VER)
linker, \ void *__asan_region_is_poisoned(void *beg, size_t size) {
"/alternatename:__asan_region_is_poisoned=__libafl_asan_region_is_poisoned") (void)beg;
(void)size;
return NULL;
}
#else
#pragma comment( \
linker, \
"/alternatename:__asan_region_is_poisoned=__libafl_asan_region_is_poisoned")
#endif
#elif defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) #elif defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))