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:
parent
dce0761b11
commit
19087f3dab
@ -24,12 +24,15 @@ tar = "0.4.37"
|
||||
reqwest = { version = "0.11.4", features = ["blocking"] }
|
||||
|
||||
[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/" }
|
||||
frida-gum = { version = "0.13.6", features = [ "auto-download", "event-sink", "invocation-listener"] }
|
||||
libafl_frida = { path = "../../libafl_frida", features = ["cmplog"] }
|
||||
libafl_targets = { path = "../../libafl_targets", features = ["sancov_cmplog"] }
|
||||
libloading = "0.7"
|
||||
mimalloc = { version = "*", default-features = false }
|
||||
dlmalloc ={version = "0.2.6", features = ["global"]}
|
||||
color-backtrace = "0.5"
|
||||
env_logger = "0.10.0"
|
||||
iced-x86 = { version = "1.20.0", features = ["code_asm"] }
|
||||
|
2
fuzzers/frida_gdiplus/cargo/.config
Normal file
2
fuzzers/frida_gdiplus/cargo/.config
Normal file
@ -0,0 +1,2 @@
|
||||
[build]
|
||||
target = "x86_64-pc-windows-msvc"
|
@ -21,8 +21,13 @@ ULONG_PTR gdiplusToken;
|
||||
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
|
||||
switch (fdwReason) {
|
||||
case DLL_PROCESS_ATTACH:
|
||||
LoadLibraryA("ole32.dll");
|
||||
LoadLibraryA("gdi32full.dll");
|
||||
LoadLibraryA("WindowsCodecs.dll");
|
||||
LoadLibraryA("shcore.dll");
|
||||
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
|
||||
LoadLibraryA("gdi32.dll");
|
||||
// DebugBreak();
|
||||
break;
|
||||
}
|
||||
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,
|
||||
size_t size) {
|
||||
static DWORD init = 0;
|
||||
if (!init) {
|
||||
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
|
||||
init = 1;
|
||||
}
|
||||
// if (!init) {
|
||||
// init = 1;
|
||||
// }
|
||||
|
||||
HGLOBAL m_hBuffer = ::GlobalAlloc(GMEM_MOVEABLE, size);
|
||||
if (m_hBuffer) {
|
||||
void *pBuffer = ::GlobalLock(m_hBuffer);
|
||||
if (pBuffer) {
|
||||
CopyMemory(pBuffer, data, size);
|
||||
memcpy(pBuffer, data, size);
|
||||
// CopyMemory(pBuffer, data, size);
|
||||
|
||||
IStream *pStream = NULL;
|
||||
if (::CreateStreamOnHGlobal(m_hBuffer, FALSE, &pStream) == S_OK) {
|
||||
|
@ -6,9 +6,16 @@
|
||||
//! 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.
|
||||
|
||||
#[cfg(unix)]
|
||||
use mimalloc::MiMalloc;
|
||||
#[cfg(unix)]
|
||||
#[global_allocator]
|
||||
static GLOBAL: MiMalloc = MiMalloc;
|
||||
#[cfg(windows)]
|
||||
use dlmalloc::GlobalDlmalloc;
|
||||
#[cfg(windows)]
|
||||
#[global_allocator]
|
||||
static GLOBAL: GlobalDlmalloc = GlobalDlmalloc;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
@ -17,8 +24,8 @@ use libafl::{
|
||||
corpus::{CachedOnDiskCorpus, Corpus, OnDiskCorpus},
|
||||
events::{launcher::Launcher, llmp::LlmpRestartingEventManager, EventConfig},
|
||||
executors::{inprocess::InProcessExecutor, ExitKind, ShadowExecutor},
|
||||
feedback_or, feedback_or_fast,
|
||||
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
|
||||
feedback_and_fast, feedback_or, feedback_or_fast,
|
||||
feedbacks::{ConstFeedback, CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
|
||||
fuzzer::{Fuzzer, StdFuzzer},
|
||||
inputs::{BytesInput, HasTargetBytes},
|
||||
monitors::MultiMonitor,
|
||||
@ -32,8 +39,6 @@ use libafl::{
|
||||
state::{HasCorpus, StdState},
|
||||
Error, HasMetadata,
|
||||
};
|
||||
#[cfg(unix)]
|
||||
use libafl::{feedback_and_fast, feedbacks::ConstFeedback};
|
||||
use libafl_bolts::{
|
||||
cli::{parse_args, FuzzerOptions},
|
||||
rands::StdRand,
|
||||
@ -41,11 +46,11 @@ use libafl_bolts::{
|
||||
tuples::{tuple_list, Merge},
|
||||
AsSlice,
|
||||
};
|
||||
#[cfg(unix)]
|
||||
use libafl_frida::asan::asan_rt::AsanRuntime;
|
||||
#[cfg(unix)]
|
||||
use libafl_frida::asan::errors::{AsanErrorsFeedback, AsanErrorsObserver};
|
||||
use libafl_frida::{
|
||||
asan::{
|
||||
asan_rt::AsanRuntime,
|
||||
errors::{AsanErrorsFeedback, AsanErrorsObserver},
|
||||
},
|
||||
cmplog_rt::CmpLogRuntime,
|
||||
coverage_rt::{CoverageRuntime, MAP_SIZE},
|
||||
executor::FridaInProcessExecutor,
|
||||
@ -55,6 +60,7 @@ use libafl_targets::cmplog::CmpLogObserver;
|
||||
|
||||
/// The main fn, usually parsing parameters, and starting the fuzzer
|
||||
pub fn main() {
|
||||
env_logger::init();
|
||||
color_backtrace::install();
|
||||
|
||||
let options = parse_args();
|
||||
@ -97,16 +103,11 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
|
||||
let gum = Gum::obtain();
|
||||
|
||||
let coverage = CoverageRuntime::new();
|
||||
#[cfg(unix)]
|
||||
let asan = AsanRuntime::new(options);
|
||||
let asan = AsanRuntime::new(&options);
|
||||
|
||||
#[cfg(unix)]
|
||||
let mut frida_helper =
|
||||
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
|
||||
let edges_observer = HitcountsMapObserver::new(StdMapObserver::from_mut_ptr(
|
||||
"edges",
|
||||
@ -118,7 +119,6 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
|
||||
// Create an observation channel to keep track of the execution time
|
||||
let time_observer = TimeObserver::new("time");
|
||||
|
||||
#[cfg(unix)]
|
||||
let asan_observer = AsanErrorsObserver::from_static_asan_errors();
|
||||
|
||||
// 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
|
||||
#[cfg(unix)]
|
||||
let mut objective = feedback_or_fast!(
|
||||
CrashFeedback::new(),
|
||||
TimeoutFeedback::new(),
|
||||
// TimeoutFeedback::new(),
|
||||
// true enables the AsanErrorFeedback
|
||||
feedback_and_fast!(
|
||||
ConstFeedback::from(true),
|
||||
AsanErrorsFeedback::new(&asan_observer)
|
||||
)
|
||||
);
|
||||
#[cfg(windows)]
|
||||
let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new());
|
||||
|
||||
// If not restarting, create a State from scratch
|
||||
let mut state = state.unwrap_or_else(|| {
|
||||
@ -183,20 +180,18 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
|
||||
// A fuzzer with feedbacks and a corpus scheduler
|
||||
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
|
||||
|
||||
#[cfg(unix)]
|
||||
let observers = tuple_list!(edges_observer, time_observer, asan_observer);
|
||||
#[cfg(windows)]
|
||||
let observers = tuple_list!(edges_observer, time_observer);
|
||||
let observers = tuple_list!(edges_observer, time_observer, asan_observer,);
|
||||
|
||||
// Create the executor for an in-process function with just one observer for edge coverage
|
||||
let mut executor = FridaInProcessExecutor::new(
|
||||
&gum,
|
||||
InProcessExecutor::new(
|
||||
InProcessExecutor::with_timeout(
|
||||
&mut frida_harness,
|
||||
observers,
|
||||
&mut fuzzer,
|
||||
&mut state,
|
||||
&mut mgr,
|
||||
options.timeout,
|
||||
)?,
|
||||
&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
|
||||
let time_observer = TimeObserver::new("time");
|
||||
#[cfg(unix)]
|
||||
let asan_observer = AsanErrorsObserver::from_static_asan_errors();
|
||||
|
||||
// Feedback to rate the interestingness of an input
|
||||
@ -249,7 +243,6 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
|
||||
TimeFeedback::new(&time_observer)
|
||||
);
|
||||
|
||||
#[cfg(unix)]
|
||||
let mut objective = feedback_or_fast!(
|
||||
CrashFeedback::new(),
|
||||
TimeoutFeedback::new(),
|
||||
@ -258,8 +251,6 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
|
||||
AsanErrorsFeedback::new(&asan_observer)
|
||||
)
|
||||
);
|
||||
#[cfg(windows)]
|
||||
let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new());
|
||||
|
||||
// If not restarting, create a State from scratch
|
||||
let mut state = state.unwrap_or_else(|| {
|
||||
@ -301,10 +292,7 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
|
||||
// A fuzzer with feedbacks and a corpus scheduler
|
||||
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
|
||||
|
||||
#[cfg(unix)]
|
||||
let observers = tuple_list!(edges_observer, time_observer, asan_observer);
|
||||
#[cfg(windows)]
|
||||
let observers = tuple_list!(edges_observer, time_observer,);
|
||||
|
||||
// Create the executor for an in-process function with just one observer for edge coverage
|
||||
let mut executor = FridaInProcessExecutor::new(
|
||||
@ -372,7 +360,6 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
|
||||
// Create an observation channel to keep track of the execution time
|
||||
let time_observer = TimeObserver::new("time");
|
||||
|
||||
#[cfg(unix)]
|
||||
let asan_observer = AsanErrorsObserver::from_static_asan_errors();
|
||||
|
||||
// Feedback to rate the interestingness of an input
|
||||
@ -384,7 +371,6 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
|
||||
TimeFeedback::new(&time_observer)
|
||||
);
|
||||
|
||||
#[cfg(unix)]
|
||||
let mut objective = feedback_or_fast!(
|
||||
CrashFeedback::new(),
|
||||
TimeoutFeedback::new(),
|
||||
@ -393,8 +379,6 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
|
||||
AsanErrorsFeedback::new(&asan_observer)
|
||||
)
|
||||
);
|
||||
#[cfg(windows)]
|
||||
let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new());
|
||||
|
||||
// If not restarting, create a State from scratch
|
||||
let mut state = state.unwrap_or_else(|| {
|
||||
@ -436,20 +420,18 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
|
||||
// A fuzzer with feedbacks and a corpus scheduler
|
||||
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
|
||||
|
||||
#[cfg(unix)]
|
||||
let observers = tuple_list!(edges_observer, time_observer, asan_observer);
|
||||
#[cfg(windows)]
|
||||
let observers = tuple_list!(edges_observer, time_observer);
|
||||
|
||||
// Create the executor for an in-process function with just one observer for edge coverage
|
||||
let mut executor = FridaInProcessExecutor::new(
|
||||
&gum,
|
||||
InProcessExecutor::new(
|
||||
InProcessExecutor::with_timeout(
|
||||
&mut frida_harness,
|
||||
observers,
|
||||
&mut fuzzer,
|
||||
&mut state,
|
||||
&mut mgr,
|
||||
options.timeout,
|
||||
)?,
|
||||
&mut frida_helper,
|
||||
);
|
||||
@ -466,7 +448,9 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
|
||||
|
||||
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(())
|
||||
})(state, mgr, core_id)
|
||||
|
@ -26,7 +26,8 @@ reqwest = { version = "0.11.4", features = ["blocking"] }
|
||||
|
||||
|
||||
[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/" }
|
||||
frida-gum = { version = "0.13.6", features = [ "auto-download", "event-sink", "invocation-listener"] }
|
||||
libafl_frida = { path = "../../libafl_frida", features = ["cmplog"] }
|
||||
@ -34,3 +35,5 @@ libafl_targets = { path = "../../libafl_targets", features = ["sancov_cmplog"] }
|
||||
libloading = "0.7"
|
||||
mimalloc = { version = "*", default-features = false }
|
||||
color-backtrace = "0.5"
|
||||
log = "0.4.20"
|
||||
env_logger = "0.10.0"
|
||||
|
@ -88,7 +88,7 @@ static char *allocation = NULL;
|
||||
__attribute__((noinline)) void func3(char *alloc) {
|
||||
// printf("func3\n");
|
||||
#ifdef _WIN32
|
||||
if (rand() == 0) {
|
||||
if ((rand() % 2) == 0) {
|
||||
alloc[0x1ff] = 0xde;
|
||||
printf("alloc[0x200]: %d\n", alloc[0x200]);
|
||||
}
|
||||
|
@ -50,8 +50,8 @@ static GLOBAL: MiMalloc = MiMalloc;
|
||||
|
||||
/// The main fn, usually parsing parameters, and starting the fuzzer
|
||||
pub fn main() {
|
||||
env_logger::init();
|
||||
color_backtrace::install();
|
||||
|
||||
let options = parse_args();
|
||||
|
||||
unsafe {
|
||||
@ -65,6 +65,8 @@ pub fn main() {
|
||||
/// The actual fuzzer
|
||||
#[allow(clippy::too_many_lines, clippy::too_many_arguments)]
|
||||
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
|
||||
let monitor = MultiMonitor::new(|s| println!("{s}"));
|
||||
|
||||
@ -97,7 +99,7 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
|
||||
|
||||
#[cfg(unix)]
|
||||
let mut frida_helper =
|
||||
FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage, asan));
|
||||
FridaInstrumentationHelper::new(&gum, options, tuple_list!(asan, coverage));
|
||||
#[cfg(windows)]
|
||||
let mut frida_helper =
|
||||
FridaInstrumentationHelper::new(&gum, &options, tuple_list!(coverage));
|
||||
|
@ -113,6 +113,8 @@ pub mod windows_exception_handler {
|
||||
sync::atomic::{compiler_fence, Ordering},
|
||||
};
|
||||
#[cfg(feature = "std")]
|
||||
use std::io::Write;
|
||||
#[cfg(feature = "std")]
|
||||
use std::panic;
|
||||
|
||||
use libafl_bolts::os::windows_exceptions::{
|
||||
@ -131,7 +133,7 @@ pub mod windows_exception_handler {
|
||||
},
|
||||
feedbacks::Feedback,
|
||||
fuzzer::HasObjective,
|
||||
inputs::UsesInput,
|
||||
inputs::{Input, UsesInput},
|
||||
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.
|
||||
if is_crash {
|
||||
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>(
|
||||
executor,
|
||||
state,
|
||||
|
@ -122,7 +122,7 @@ pub mod fs;
|
||||
#[cfg(feature = "alloc")]
|
||||
pub mod llmp;
|
||||
pub mod math;
|
||||
#[cfg(all(feature = "std", unix))]
|
||||
#[cfg(feature = "std")]
|
||||
pub mod minibsod;
|
||||
pub mod os;
|
||||
#[cfg(feature = "alloc")]
|
||||
|
@ -1,7 +1,5 @@
|
||||
//! Implements a mini-bsod generator.
|
||||
//! 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")]
|
||||
use core::mem::size_of;
|
||||
@ -9,6 +7,7 @@ use std::io::{BufWriter, Write};
|
||||
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
|
||||
use std::process::Command;
|
||||
|
||||
#[cfg(unix)]
|
||||
use libc::siginfo_t;
|
||||
#[cfg(target_vendor = "apple")]
|
||||
use mach::{
|
||||
@ -19,7 +18,10 @@ use mach::{
|
||||
vm_region::{vm_region_recurse_info_t, vm_region_submap_info_64},
|
||||
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};
|
||||
|
||||
/// Write the content of all important registers
|
||||
@ -391,7 +393,7 @@ pub fn dump_registers<W: Write>(
|
||||
write!(writer, "cs : {:#016x}, ", ucontext.sc_cs)?;
|
||||
Ok(())
|
||||
}
|
||||
///
|
||||
|
||||
/// Write the content of all important registers
|
||||
#[cfg(all(target_os = "openbsd", target_arch = "aarch64"))]
|
||||
#[allow(clippy::similar_names)]
|
||||
@ -452,6 +454,34 @@ pub fn dump_registers<W: Write>(
|
||||
}
|
||||
|
||||
/// 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"))]
|
||||
#[allow(clippy::similar_names)]
|
||||
pub fn dump_registers<W: Write>(
|
||||
@ -489,6 +519,7 @@ pub fn dump_registers<W: Write>(
|
||||
target_os = "dragonfly",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
windows,
|
||||
target_os = "haiku",
|
||||
any(target_os = "solaris", target_os = "illumos"),
|
||||
)))]
|
||||
@ -751,6 +782,7 @@ fn write_crash<W: Write>(
|
||||
target_os = "dragonfly",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
windows,
|
||||
target_os = "haiku",
|
||||
any(target_os = "solaris", target_os = "illumos"),
|
||||
)))]
|
||||
@ -765,6 +797,33 @@ fn write_crash<W: Write>(
|
||||
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"))]
|
||||
fn write_minibsod<W: Write>(writer: &mut BufWriter<W>) -> Result<(), std::io::Error> {
|
||||
match std::fs::read_to_string("/proc/self/maps") {
|
||||
@ -1023,6 +1082,30 @@ pub fn generate_minibsod<W: Write>(
|
||||
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)]
|
||||
mod tests {
|
||||
|
||||
|
@ -35,7 +35,7 @@ const EXCEPTION_CONTINUE_EXECUTION: c_long = -1;
|
||||
const EXCEPTION_CONTINUE_SEARCH: c_long = 0;
|
||||
|
||||
// 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
|
||||
pub const SIGINT: i32 = 2;
|
||||
|
@ -80,9 +80,13 @@ ahash = "0.8"
|
||||
paste = "1.0"
|
||||
log = "0.4.20"
|
||||
mmap-rs = "0.6.0"
|
||||
|
||||
bit_reverse = "0.1.8"
|
||||
yaxpeax-arch = "0.2.7"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winsafe = {version = "0.0.18", features = ["kernel"]}
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = { version = "3", default-features = false, features = ["logging"] }
|
||||
clap = {version = "4.5", features = ["derive"]}
|
||||
|
@ -7,34 +7,59 @@ fn main() {
|
||||
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++
|
||||
#[cfg(unix)]
|
||||
if target_family == "unix" {
|
||||
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
|
||||
// clang++ -shared -fPIC -O0 -o test_harness.so test_harness.cpp
|
||||
#[cfg(unix)]
|
||||
{
|
||||
// Check if we have clang++ installed
|
||||
let clangpp = std::process::Command::new("clang++")
|
||||
.arg("--version")
|
||||
.output();
|
||||
|
||||
match clangpp {
|
||||
Ok(_) => {
|
||||
std::process::Command::new("clang++")
|
||||
.arg("-shared")
|
||||
.arg("-fPIC")
|
||||
.arg("-O0")
|
||||
if target_family == "windows" {
|
||||
let compiler = cc::Build::new()
|
||||
.cpp(true)
|
||||
.file("test_harness.a")
|
||||
.get_compiler();
|
||||
let mut cmd = std::process::Command::new(compiler.path());
|
||||
let cmd = cmd
|
||||
.args(compiler.args())
|
||||
.arg("test_harness.cpp")
|
||||
.arg("/link");
|
||||
|
||||
#[cfg(unix)]
|
||||
let cmd = cmd
|
||||
.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")
|
||||
.arg("test_harness.cpp")
|
||||
.status()
|
||||
.expect("Failed to build test harness");
|
||||
}
|
||||
Err(_) => {
|
||||
println!("cargo:warning=clang++ not found, skipping test harness build");
|
||||
}
|
||||
}
|
||||
.expect("Failed to link test_harness");
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
#[cfg(any(
|
||||
windows,
|
||||
target_os = "linux",
|
||||
target_vendor = "apple",
|
||||
all(
|
||||
@ -13,6 +14,7 @@ use frida_gum::{PageProtection, RangeDetails};
|
||||
use hashbrown::HashMap;
|
||||
use libafl_bolts::cli::FuzzerOptions;
|
||||
#[cfg(any(
|
||||
windows,
|
||||
target_os = "linux",
|
||||
target_vendor = "apple",
|
||||
all(
|
||||
@ -20,8 +22,7 @@ use libafl_bolts::cli::FuzzerOptions;
|
||||
target_os = "android"
|
||||
)
|
||||
))]
|
||||
use mmap_rs::{MemoryAreas, MmapFlags, MmapMut, MmapOptions, ReservedMut};
|
||||
use nix::libc::memset;
|
||||
use mmap_rs::{MmapFlags, MmapMut, MmapOptions, ReservedMut};
|
||||
use rangemap::RangeSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@ -41,7 +42,9 @@ pub struct Allocator {
|
||||
/// The shadow bit
|
||||
shadow_bit: usize,
|
||||
/// 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
|
||||
allocations: HashMap<usize, AllocationMetadata>,
|
||||
/// All mappings
|
||||
@ -157,7 +160,6 @@ impl Allocator {
|
||||
pub unsafe fn alloc(&mut self, size: usize, _alignment: usize) -> *mut c_void {
|
||||
let mut is_malloc_zero = false;
|
||||
let size = if size == 0 {
|
||||
// log::warn!("zero-sized allocation!");
|
||||
is_malloc_zero = true;
|
||||
16
|
||||
} else {
|
||||
@ -179,7 +181,6 @@ impl Allocator {
|
||||
self.total_allocation_size += 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.size = size;
|
||||
if self.allocation_backtraces {
|
||||
@ -234,14 +235,13 @@ impl Allocator {
|
||||
let address = (metadata.address + self.page_size) as *mut c_void;
|
||||
|
||||
self.allocations.insert(address as usize, metadata);
|
||||
// log::trace!("serving address: {:?}, size: {:x}", address, size);
|
||||
log::trace!("serving address: {:?}, size: {:x}", address, size);
|
||||
address
|
||||
}
|
||||
|
||||
/// Releases the allocation at the given address.
|
||||
#[allow(clippy::missing_safety_doc)]
|
||||
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 {
|
||||
if !ptr.is_null() {
|
||||
AsanErrors::get_mut_blocking()
|
||||
@ -339,34 +339,30 @@ impl Allocator {
|
||||
}
|
||||
|
||||
fn unpoison(start: usize, size: usize) {
|
||||
// log::trace!("unpoisoning {:x} for {:x}", start, size / 8 + 1);
|
||||
unsafe {
|
||||
// log::trace!("memset: {:?}", start as *mut c_void);
|
||||
memset(start as *mut c_void, 0xff, size / 8);
|
||||
std::slice::from_raw_parts_mut(start as *mut u8, size / 8).fill(0xff);
|
||||
|
||||
let remainder = size % 8;
|
||||
if remainder > 0 {
|
||||
// log::trace!("remainder: {:x}, offset: {:x}", remainder, start + size / 8);
|
||||
memset(
|
||||
(start + size / 8) as *mut c_void,
|
||||
(0xff << (8 - remainder)) & 0xff,
|
||||
1,
|
||||
);
|
||||
let mut current_value = ((start + size / 8) as *const u8).read();
|
||||
current_value |= 0xff << (8 - remainder);
|
||||
((start + size / 8) as *mut u8).write(current_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Poisonn an area in memory
|
||||
pub fn poison(start: usize, size: usize) {
|
||||
// log::trace!("poisoning {:x} for {:x}", start, size / 8 + 1);
|
||||
unsafe {
|
||||
// log::trace!("memset: {:?}", start as *mut c_void);
|
||||
memset(start as *mut c_void, 0x00, size / 8);
|
||||
std::slice::from_raw_parts_mut(start as *mut u8, size / 8).fill(0x0);
|
||||
|
||||
let remainder = size % 8;
|
||||
if remainder > 0 {
|
||||
// log::trace!("remainder: {:x}, offset: {:x}", remainder, start + size / 8);
|
||||
memset((start + size / 8) as *mut c_void, 0x00, 1);
|
||||
let mask = !(0xff << (8 - remainder));
|
||||
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_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
|
||||
// different addresses.
|
||||
let shadow_end = self.round_up_to_page((end - start) / 8) + self.page_size + shadow_start;
|
||||
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();
|
||||
let shadow_end = self.round_up_to_page((end - start) / 8 + self.page_size + shadow_start);
|
||||
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)) {
|
||||
for ((pa_start, pa_end), shadow_mapping) in &mut self.pre_allocated_shadow_mappings
|
||||
{
|
||||
if *pa_start <= gap.start && gap.start < *pa_start + shadow_mapping.len() {
|
||||
log::trace!("pa_start: {:x}, pa_end {:x}, gap.start {:x}, shadow_mapping.ptr {:x}, shadow_mapping.len {:x}",
|
||||
*pa_start, *pa_end, gap.start, shadow_mapping.as_ptr() as usize, shadow_mapping.len());
|
||||
|
||||
// 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());
|
||||
let mut new_reserved_region = None;
|
||||
for reserved in &mut self.pre_allocated_shadow_mappings {
|
||||
if gap.start >= reserved.start() && gap.end <= reserved.end() {
|
||||
let mut to_be_commited =
|
||||
reserved.split_off(gap.start - reserved.start()).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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
.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 {
|
||||
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
|
||||
#[inline]
|
||||
#[must_use]
|
||||
@ -473,7 +536,7 @@ impl Allocator {
|
||||
#[inline]
|
||||
pub fn is_managed(&self, ptr: *mut c_void) -> bool {
|
||||
//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
|
||||
@ -488,17 +551,19 @@ impl Allocator {
|
||||
|
||||
/// Unpoison all the memory that is currently mapped with read/write permissions.
|
||||
pub fn unpoison_all_existing_memory(&mut self) {
|
||||
RangeDetails::enumerate_with_prot(PageProtection::NoAccess, &mut |range: &RangeDetails| {
|
||||
if range.protection() as u32 & PageProtection::ReadWrite as u32 != 0 {
|
||||
RangeDetails::enumerate_with_prot(
|
||||
PageProtection::Read,
|
||||
&mut |range: &RangeDetails| -> bool {
|
||||
let start = range.memory_range().base_address().0 as usize;
|
||||
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);
|
||||
}
|
||||
|
||||
true
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 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![];
|
||||
// max(userspace address) this is usually 0x8_0000_0000_0000 - 1 on x64 linux.
|
||||
#[cfg(unix)]
|
||||
let mut userspace_max: usize = 0;
|
||||
|
||||
// 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();
|
||||
|
||||
RangeDetails::enumerate_with_prot(
|
||||
PageProtection::Read,
|
||||
&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));
|
||||
// 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 {
|
||||
if end <= 2_usize.pow(48) && end > userspace_max {
|
||||
userspace_max = end;
|
||||
}
|
||||
|
||||
#[cfg(all(not(unix), target_arch = "x86_64"))]
|
||||
if (end >> 3) <= base.pow(44) && (end >> 3) > userspace_max {
|
||||
userspace_max = end >> 3;
|
||||
}
|
||||
//
|
||||
// #[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
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
if end <= base.pow(52) && end > userspace_max {
|
||||
if end <= 2_usize.pow(52) && end > userspace_max {
|
||||
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 {
|
||||
let base: usize = 2;
|
||||
if base.pow(power) > userspace_max {
|
||||
if 2_usize.pow(power) > userspace_max {
|
||||
maxbit = power;
|
||||
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 shadow_start = addr;
|
||||
let shadow_end = addr + addr + addr;
|
||||
let mut good_candidate = true;
|
||||
// check if the proposed shadow bit overlaps with 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) {
|
||||
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;
|
||||
break;
|
||||
}
|
||||
@ -573,7 +642,7 @@ impl Allocator {
|
||||
> shadow_end)
|
||||
{
|
||||
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;
|
||||
break;
|
||||
@ -582,33 +651,26 @@ impl Allocator {
|
||||
|
||||
if good_candidate {
|
||||
// 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()
|
||||
.with_flags(MmapFlags::NO_RESERVE)
|
||||
.with_address(addr)
|
||||
.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::trace!(
|
||||
"adding pre_allocated_shadow_mappings {:x} - {:x} with size {:}",
|
||||
addr,
|
||||
(addr + (1 << (shadow_bit + 1))),
|
||||
mapping.len()
|
||||
);
|
||||
|
||||
self.pre_allocated_shadow_mappings
|
||||
.insert((addr, (addr + (1 << (shadow_bit + 1)))), mapping);
|
||||
log::warn!("shadow_bit {shadow_bit:} is suitable");
|
||||
self.pre_allocated_shadow_mappings.push(mapping);
|
||||
self.using_pre_allocated_shadow_mapping = true;
|
||||
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);
|
||||
// attempt to pre-map the entire shadow-memory space
|
||||
log::warn!("shadow_bit: {shadow_bit}");
|
||||
assert!(shadow_bit != 0);
|
||||
|
||||
let addr: usize = 1 << shadow_bit;
|
||||
|
||||
@ -643,7 +705,8 @@ impl Default for Allocator {
|
||||
max_total_allocation: 1 << 32,
|
||||
allocation_backtraces: false,
|
||||
page_size,
|
||||
pre_allocated_shadow_mappings: HashMap::new(),
|
||||
pre_allocated_shadow_mappings: Vec::new(),
|
||||
using_pre_allocated_shadow_mapping: false,
|
||||
mappings: HashMap::new(),
|
||||
shadow_offset: 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
@ -91,7 +91,7 @@ pub(crate) enum AsanError {
|
||||
}
|
||||
|
||||
impl AsanError {
|
||||
fn description(&self) -> &str {
|
||||
pub fn description(&self) -> &str {
|
||||
match self {
|
||||
AsanError::OobRead(_) => "heap out-of-bounds read",
|
||||
AsanError::OobWrite(_) => "heap out-of-bounds write",
|
||||
@ -114,7 +114,7 @@ impl AsanError {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, SerdeAny)]
|
||||
pub struct AsanErrors {
|
||||
continue_on_error: bool,
|
||||
errors: Vec<AsanError>,
|
||||
pub(crate) errors: Vec<AsanError>,
|
||||
}
|
||||
|
||||
impl AsanErrors {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
#[cfg(all(unix, not(test)))]
|
||||
use core::borrow::Borrow;
|
||||
use core::fmt::{self, Debug, Formatter};
|
||||
#[cfg(windows)]
|
||||
use std::process::abort;
|
||||
use std::{ffi::c_void, marker::PhantomData};
|
||||
|
||||
use frida_gum::{
|
||||
@ -21,7 +21,7 @@ use libafl::{
|
||||
};
|
||||
use libafl_bolts::tuples::RefIndexable;
|
||||
|
||||
#[cfg(all(unix, not(test)))]
|
||||
#[cfg(not(test))]
|
||||
use crate::asan::errors::AsanErrors;
|
||||
use crate::helper::{FridaInstrumentationHelper, FridaRuntimeTuple};
|
||||
#[cfg(windows)]
|
||||
@ -106,11 +106,13 @@ where
|
||||
self.stalker.deactivate();
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(test)))]
|
||||
#[cfg(not(test))]
|
||||
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");
|
||||
libc::raise(libc::SIGABRT);
|
||||
#[cfg(windows)]
|
||||
abort();
|
||||
}
|
||||
}
|
||||
self.helper.post_exec(input)?;
|
||||
@ -206,6 +208,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("disable_excludes: {:}", helper.disable_excludes);
|
||||
if !helper.disable_excludes {
|
||||
for range in ranges.gaps(&(0..usize::MAX)) {
|
||||
log::info!("excluding range: {:x}-{:x}", range.start, range.end);
|
||||
|
@ -6,9 +6,8 @@ use std::{
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
use frida_gum::instruction_writer::InstructionWriter;
|
||||
use frida_gum::{
|
||||
instruction_writer::InstructionWriter,
|
||||
stalker::{StalkerIterator, StalkerOutput, Transformer},
|
||||
Gum, Module, ModuleDetails, ModuleMap, PageProtection,
|
||||
};
|
||||
@ -28,11 +27,9 @@ use yaxpeax_arm::armv8::a64::{ARMv8, InstDecoder};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use yaxpeax_x86::amd64::InstDecoder;
|
||||
|
||||
#[cfg(unix)]
|
||||
use crate::asan::asan_rt::AsanRuntime;
|
||||
#[cfg(feature = "cmplog")]
|
||||
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")]
|
||||
const ANONYMOUS_FLAG: MapFlags = MapFlags::MAP_ANON;
|
||||
@ -275,6 +272,11 @@ impl FridaInstrumentationHelperBuilder {
|
||||
|
||||
if stalker_enabled {
|
||||
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 start = range.base_address().0 as usize;
|
||||
ranges
|
||||
@ -330,7 +332,7 @@ impl Default for FridaInstrumentationHelperBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
stalker_enabled: true,
|
||||
disable_excludes: true,
|
||||
disable_excludes: false,
|
||||
instrument_module_predicate: None,
|
||||
skip_module_predicate: Box::new(|module| {
|
||||
// Skip the instrumentation module to avoid recursion.
|
||||
@ -445,7 +447,7 @@ where
|
||||
let runtimes = Rc::clone(runtimes);
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
let decoder = InstDecoder::minimal();
|
||||
let decoder = InstDecoder::default();
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
let decoder = <ARMv8 as Arch>::Decoder::default();
|
||||
@ -459,7 +461,7 @@ where
|
||||
basic_block: StalkerIterator,
|
||||
output: &StalkerOutput,
|
||||
ranges: &Rc<RefCell<RangeMap<usize, (u16, String)>>>,
|
||||
runtimes: &Rc<RefCell<RT>>,
|
||||
runtimes_unborrowed: &Rc<RefCell<RT>>,
|
||||
decoder: InstDecoder,
|
||||
) {
|
||||
let mut first = true;
|
||||
@ -469,10 +471,10 @@ where
|
||||
let instr = instruction.instr();
|
||||
let instr_size = instr.bytes().len();
|
||||
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)) {
|
||||
let mut runtimes = (*runtimes).borrow_mut();
|
||||
let mut runtimes = (*runtimes_unborrowed).borrow_mut();
|
||||
if first {
|
||||
first = false;
|
||||
// log::info!(
|
||||
@ -483,24 +485,29 @@ where
|
||||
if let Some(rt) = runtimes.match_first_type_mut::<CoverageRuntime>() {
|
||||
rt.emit_coverage_mapping(address, output);
|
||||
}
|
||||
|
||||
if let Some(_rt) = runtimes.match_first_type_mut::<DrCovRuntime>() {
|
||||
basic_block_start = address;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
let res = if let Some(_rt) = runtimes.match_first_type_mut::<AsanRuntime>() {
|
||||
AsanRuntime::asan_is_interesting_instruction(decoder, address, instr)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
#[cfg(all(target_arch = "x86_64", unix))]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
if let Some(details) = res {
|
||||
if let Some(rt) = runtimes.match_first_type_mut::<AsanRuntime>() {
|
||||
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>() {
|
||||
rt.add_stalked_address(
|
||||
output.writer().pc() as usize - instr_size,
|
||||
@ -556,7 +562,10 @@ where
|
||||
instruction.keep();
|
||||
}
|
||||
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}");
|
||||
rt.drcov_basic_blocks.push(DrCovBasicBlock::new(
|
||||
basic_block_start as usize,
|
||||
|
@ -64,10 +64,8 @@ Additional documentation is available in [the `LibAFL` book](https://aflplus.plu
|
||||
)]
|
||||
|
||||
/// The frida-asan allocator
|
||||
#[cfg(unix)]
|
||||
pub mod alloc;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub mod asan;
|
||||
|
||||
#[cfg(windows)]
|
||||
@ -369,7 +367,7 @@ mod tests {
|
||||
use crate::{
|
||||
asan::{
|
||||
asan_rt::AsanRuntime,
|
||||
errors::{AsanErrorsFeedback, AsanErrorsObserver},
|
||||
errors::{AsanErrors, AsanErrorsFeedback, AsanErrorsObserver},
|
||||
},
|
||||
coverage_rt::CoverageRuntime,
|
||||
executor::FridaInProcessExecutor,
|
||||
@ -378,20 +376,56 @@ mod tests {
|
||||
|
||||
static GUM: OnceLock<Gum> = OnceLock::new();
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
unsafe fn test_asan(options: &FuzzerOptions) {
|
||||
// The names of the functions to run
|
||||
let tests = vec![
|
||||
("LLVMFuzzerTestOneInput", 0),
|
||||
("heap_oob_read", 1),
|
||||
("heap_oob_write", 1),
|
||||
("heap_uaf_write", 1),
|
||||
("heap_uaf_read", 1),
|
||||
("malloc_heap_oob_read", 1),
|
||||
("malloc_heap_oob_write", 1),
|
||||
("malloc_heap_uaf_write", 1),
|
||||
("malloc_heap_uaf_read", 1),
|
||||
("LLVMFuzzerTestOneInput", None),
|
||||
("heap_oob_read", Some("heap out-of-bounds read")),
|
||||
("heap_oob_write", Some("heap out-of-bounds write")),
|
||||
("heap_uaf_write", Some("heap use-after-free write")),
|
||||
("heap_uaf_read", Some("heap use-after-free read")),
|
||||
("malloc_heap_oob_read", Some("heap out-of-bounds read")),
|
||||
("malloc_heap_oob_write", Some("heap out-of-bounds write")),
|
||||
(
|
||||
"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 coverage = CoverageRuntime::new();
|
||||
@ -404,7 +438,7 @@ mod tests {
|
||||
|
||||
// Run the tests for each function
|
||||
for test in tests {
|
||||
let (function_name, err_cnt) = test;
|
||||
let (function_name, expected_error) = test;
|
||||
log::info!("Testing with harness function {}", function_name);
|
||||
|
||||
let mut corpus = InMemoryCorpus::<BytesInput>::new();
|
||||
@ -415,7 +449,7 @@ mod tests {
|
||||
|
||||
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();
|
||||
|
||||
@ -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<
|
||||
unsafe extern "C" fn(data: *const u8, size: usize) -> i32,
|
||||
> = lib.get(function_name.as_bytes()).unwrap();
|
||||
@ -473,19 +513,25 @@ mod tests {
|
||||
let mutator = StdScheduledMutator::new(tuple_list!(BitFlipMutator::new()));
|
||||
let mut stages = tuple_list!(StdMutationalStage::with_max_iterations(mutator, 1));
|
||||
|
||||
// log::info!("Starting fuzzing!");
|
||||
log::info!("Starting fuzzing!");
|
||||
fuzzer
|
||||
.fuzz_one(&mut stages, &mut executor, &mut state, &mut event_manager)
|
||||
.unwrap_or_else(|_| panic!("Error in fuzz_one"));
|
||||
|
||||
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]
|
||||
#[cfg(unix)]
|
||||
fn run_test_asan() {
|
||||
// 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,
|
||||
@ -505,7 +551,10 @@ mod tests {
|
||||
SimpleStdoutLogger::set_logger().unwrap();
|
||||
|
||||
// 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!(
|
||||
std::path::Path::new(test_harness).exists(),
|
||||
"Skipping test, {test_harness} not found"
|
||||
|
@ -1,7 +1,8 @@
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use frida_gum::instruction_writer::Aarch64Register;
|
||||
#[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")]
|
||||
use num_traits::cast::FromPrimitive;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
@ -158,6 +159,30 @@ const X86_64_REGS: [(RegSpec, X86Register); 34] = [
|
||||
(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
|
||||
/// 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>
|
||||
@ -176,9 +201,25 @@ pub fn writer_register(reg: RegSpec) -> X86Register {
|
||||
}
|
||||
|
||||
/// Translates a frida instruction to a disassembled instruction.
|
||||
#[cfg(all(target_arch = "x86_64", unix))]
|
||||
pub(crate) fn frida_to_cs(decoder: InstDecoder, frida_insn: &frida_gum_sys::Insn) -> Instruction {
|
||||
decoder.decode_slice(frida_insn.bytes()).unwrap()
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub(crate) fn frida_to_cs(
|
||||
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")]
|
||||
@ -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)]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
/// What kind of memory access this instruction has
|
||||
|
@ -29,7 +29,7 @@ pub fn initialize(gum: &Gum) {
|
||||
NativePointer(is_processor_feature_present_detour as *mut c_void),
|
||||
NativePointer(std::ptr::null_mut()),
|
||||
)
|
||||
.unwrap();
|
||||
.unwrap_or_else(|_| NativePointer(std::ptr::null_mut()));
|
||||
|
||||
interceptor
|
||||
.replace(
|
||||
@ -37,7 +37,7 @@ pub fn initialize(gum: &Gum) {
|
||||
NativePointer(unhandled_exception_filter_detour as *mut c_void),
|
||||
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 {
|
||||
let result = match feature {
|
||||
|
@ -2,62 +2,132 @@
|
||||
#include <stdlib.h>
|
||||
#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];
|
||||
delete[] array;
|
||||
fprintf(stdout, "%d\n", array[5]);
|
||||
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];
|
||||
delete[] array;
|
||||
array[5] = 1;
|
||||
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];
|
||||
fprintf(stdout, "%d\n", array[100]);
|
||||
delete[] array;
|
||||
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];
|
||||
array[100] = 1;
|
||||
delete[] array;
|
||||
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)));
|
||||
free(array);
|
||||
fprintf(stdout, "%d\n", array[5]);
|
||||
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)));
|
||||
free(array);
|
||||
array[5] = 1;
|
||||
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)));
|
||||
fprintf(stdout, "%d\n", array[100]);
|
||||
free(array);
|
||||
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)));
|
||||
array[100] = 1;
|
||||
free(array);
|
||||
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();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef _MSC_VER
|
||||
}
|
||||
#endif
|
||||
|
@ -200,10 +200,11 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
let target_family = std::env::var("CARGO_CFG_TARGET_FAMILY").unwrap();
|
||||
|
||||
#[cfg(feature = "forkserver")]
|
||||
{
|
||||
#[cfg(unix)]
|
||||
{
|
||||
if target_family == "unix" {
|
||||
println!("cargo:rerun-if-changed=src/forkserver.c");
|
||||
|
||||
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");
|
||||
|
||||
cc::Build::new()
|
||||
|
@ -15,9 +15,17 @@ void *__libafl_asan_region_is_poisoned(void *beg, size_t size) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if defined(__clang__) && defined(_MSC_VER)
|
||||
void *__asan_region_is_poisoned(void *beg, size_t size) {
|
||||
(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__))
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user