Skip to content

Commit

Permalink
Windows frida support (#1607)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
Co-authored-by: Sharad Khanna <[email protected]>
Co-authored-by: Dominik Maier <[email protected]>
Co-authored-by: Dominik Maier <[email protected]>
  • Loading branch information
5 people authored May 14, 2024
1 parent dce0761 commit 19087f3
Show file tree
Hide file tree
Showing 25 changed files with 2,281 additions and 1,211 deletions.
5 changes: 4 additions & 1 deletion fuzzers/frida_gdiplus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 2 additions & 0 deletions fuzzers/frida_gdiplus/cargo/.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build]
target = "x86_64-pc-windows-msvc"
15 changes: 10 additions & 5 deletions fuzzers/frida_gdiplus/harness.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand Down
66 changes: 25 additions & 41 deletions fuzzers/frida_gdiplus/src/fuzzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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,
Expand All @@ -32,20 +39,18 @@ 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,
shmem::{ShMemProvider, StdShMemProvider},
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,
Expand All @@ -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();
Expand Down Expand Up @@ -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",
Expand All @@ -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
Expand All @@ -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(|| {
Expand Down Expand Up @@ -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,
);
Expand Down Expand Up @@ -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
Expand All @@ -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(),
Expand All @@ -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(|| {
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand All @@ -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(),
Expand All @@ -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(|| {
Expand Down Expand Up @@ -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,
);
Expand All @@ -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)
Expand Down
5 changes: 4 additions & 1 deletion fuzzers/frida_libpng/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@ 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"] }
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"
2 changes: 1 addition & 1 deletion fuzzers/frida_libpng/harness.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
}
Expand Down
6 changes: 4 additions & 2 deletions fuzzers/frida_libpng/src/fuzzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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}"));

Expand Down Expand Up @@ -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));
Expand Down
16 changes: 14 additions & 2 deletions libafl/src/executors/hooks/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -131,7 +133,7 @@ pub mod windows_exception_handler {
},
feedbacks::Feedback,
fuzzer::HasObjective,
inputs::UsesInput,
inputs::{Input, UsesInput},
state::{HasCorpus, HasExecutions, HasSolutions, State},
};

Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion libafl_bolts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
Loading

0 comments on commit 19087f3

Please sign in to comment.