[Windows] Add libfuzzer example for windows with ASAN (#934)
* Add libfuzzer example for window with ASAN * Fix formatting * Add link * Fix cpp format * Skip windows fuzzers * Fix format * Fix testing fuzzer * Fix taks name Co-authored-by: Dongjia "toka" Zhang <tokazerkje@outlook.com> Co-authored-by: Andrea Fioraldi <andreafioraldi@gmail.com> Co-authored-by: Dominik Maier <domenukk@gmail.com>
This commit is contained in:
parent
3c7dcac41d
commit
4d8b566a87
1
fuzzers/libfuzzer_windows_asan/.gitignore
vendored
Normal file
1
fuzzers/libfuzzer_windows_asan/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
libfuzzer_windows_asan*
|
26
fuzzers/libfuzzer_windows_asan/Cargo.toml
Normal file
26
fuzzers/libfuzzer_windows_asan/Cargo.toml
Normal file
@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "libfuzzer_windows_asan"
|
||||
version = "0.8.2"
|
||||
authors = ["Max Ammann <max@maxammann.org>"]
|
||||
edition = "2021"
|
||||
categories = ["development-tools::testing"]
|
||||
|
||||
[features]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
||||
[dependencies]
|
||||
libafl = { path = "../../libafl/" }
|
||||
libafl_targets = { path = "../../libafl_targets/", features = ["libfuzzer", "sancov_pcguard_edges"] }
|
||||
libafl_cc = { path = "../../libafl_cc/" }
|
||||
|
||||
[build-dependencies]
|
||||
cc = { version = "1.0" }
|
||||
|
||||
[lib]
|
||||
name = "libfuzzer_windows_asan"
|
||||
crate-type = ["staticlib"]
|
98
fuzzers/libfuzzer_windows_asan/Makefile.toml
Normal file
98
fuzzers/libfuzzer_windows_asan/Makefile.toml
Normal file
@ -0,0 +1,98 @@
|
||||
# Variables
|
||||
[env]
|
||||
FUZZER_NAME='libfuzzer_windows_asan'
|
||||
CARGO_TARGET_DIR = { value = "./target", condition = { env_not_set = ["CARGO_TARGET_DIR"] } }
|
||||
|
||||
[tasks.unsupported]
|
||||
script_runner="@shell"
|
||||
script='''
|
||||
echo "Cargo-make not integrated yet on this"
|
||||
'''
|
||||
|
||||
# Compilers
|
||||
[tasks.cxx]
|
||||
linux_alias = "unsupported"
|
||||
mac_alias = "unsupported"
|
||||
windows_alias = "cxx_unix"
|
||||
|
||||
[tasks.cxx_unix]
|
||||
command = "cargo"
|
||||
args = ["build" , "--release"]
|
||||
|
||||
[tasks.cc]
|
||||
linux_alias = "cc_unix"
|
||||
mac_alias = "cc_unix"
|
||||
windows_alias = "cc_unix"
|
||||
|
||||
[tasks.cc_unix]
|
||||
command = "cargo"
|
||||
args = ["build" , "--release"]
|
||||
|
||||
[tasks.crash_cxx]
|
||||
linux_alias = "unsupported"
|
||||
mac_alias = "unsupported"
|
||||
windows_alias = "unsupported"
|
||||
|
||||
[tasks.crash_cc]
|
||||
linux_alias = "unsupported"
|
||||
mac_alias = "unsupported"
|
||||
windows_alias = "unsupported"
|
||||
|
||||
# Library
|
||||
[tasks.lib]
|
||||
linux_alias = "unsupported"
|
||||
mac_alias = "unsupported"
|
||||
windows_alias = "lib_unix"
|
||||
|
||||
[tasks.lib_unix]
|
||||
dependencies = [ "cxx", "cc" ]
|
||||
|
||||
# Harness
|
||||
[tasks.fuzzer]
|
||||
linux_alias = "unsupported"
|
||||
mac_alias = "unsupported"
|
||||
windows_alias = "fuzzer_windows"
|
||||
|
||||
[tasks.fuzzer_windows]
|
||||
command = "${CARGO_TARGET_DIR}/release/libafl_cxx"
|
||||
args = ["./harness.cpp", "-o", "${FUZZER_NAME}.exe"]
|
||||
dependencies = [ "lib", "cxx", "cc" ]
|
||||
|
||||
# Run the fuzzer
|
||||
[tasks.run]
|
||||
linux_alias = "unsupported"
|
||||
mac_alias = "unsupported"
|
||||
windows_alias = "run_windows" # TODO
|
||||
|
||||
[tasks.run_windows]
|
||||
script_runner = "@shell"
|
||||
script='''
|
||||
'''
|
||||
dependencies = [ "fuzzer" ]
|
||||
|
||||
# Test
|
||||
[tasks.test]
|
||||
linux_alias = "unsupported"
|
||||
mac_alias = "unsupported"
|
||||
windows_alias = "test_windows" # TODO
|
||||
|
||||
[tasks.test_windows]
|
||||
script_runner = "@shell"
|
||||
script='''
|
||||
'''
|
||||
dependencies = [ "fuzzer" ]
|
||||
|
||||
# Clean up
|
||||
[tasks.clean]
|
||||
linux_alias = "unsupported"
|
||||
mac_alias = "unsupported"
|
||||
windows_alias = "clean_windows"
|
||||
|
||||
[tasks.clean_windows]
|
||||
# Disable default `clean` definition
|
||||
clear = true
|
||||
script_runner="@shell"
|
||||
script='''
|
||||
del ./${FUZZER_NAME}
|
||||
cargo clean
|
||||
'''
|
49
fuzzers/libfuzzer_windows_asan/README.md
Normal file
49
fuzzers/libfuzzer_windows_asan/README.md
Normal file
@ -0,0 +1,49 @@
|
||||
# LibFuzzer Example for Windows with ASAN
|
||||
|
||||
This folder contains an example fuzzer for Windows which also uses ASAN.
|
||||
|
||||
We are initializing LibAFL to be compatible with ASAN.
|
||||
|
||||
## Setup
|
||||
|
||||
We are currently using Clang on Windows. Make sure to install Clang through the Visual Studio installer.
|
||||
|
||||
We recommend using Powershell and enabling the Visual Studio environment using this script:
|
||||
|
||||
```powershell
|
||||
Push-Location "C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build\"
|
||||
& "C:\\Windows\System32\cmd.exe" /c "vcvars64.bat & set" |
|
||||
ForEach-Object {
|
||||
if ($_ -match "=") {
|
||||
$v = $_.split("=", 2); set-item -force -path "ENV:\$($v[0])" -value "$($v[1])"
|
||||
}
|
||||
}
|
||||
Pop-Location
|
||||
Write-Host "`nVisual Studio 2022 Command Prompt variables set." -ForegroundColor Yellow
|
||||
````
|
||||
|
||||
After that clang should be available in the PATH.
|
||||
|
||||
## Build
|
||||
|
||||
To build the fuzzer and link against the `harness.cpp` in this example run:
|
||||
|
||||
```
|
||||
cargo make fuzzer
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
```
|
||||
.\libfuzzer_windows_asan.exe
|
||||
```
|
||||
|
||||
## Note on MSVC
|
||||
|
||||
The MSVC compiler (`cl.exe`) will work in the future. Currently, it is blocked because of a [bug](https://developercommunity.visualstudio.com/t/__sanitizer_cov_trace_pc_guard_init-neve/10218995) with coverage.
|
||||
|
||||
### Note on ASAN
|
||||
|
||||
Using ASAN on Windows with MSVC is not trivial as of 2022. Depending on the harness and fuzzing target, the required compilation flags differ. Most notably, the usage of `/MT` and `/MD` for the CRT is important. All compilation artifacts should use the same config for the CRT (either all `/MT` or all `/MD`). [Rust uses as of 2022](https://rust-lang.github.io/rfcs/1721-crt-static.html) `/MD` as default. So compile everything with `/MD`.
|
||||
|
||||
Depending on the linking mode different ASAN libraries get linked. Definitely read [this](https://devblogs.microsoft.com/cppblog/addresssanitizer-asan-for-windows-with-msvc/) blog post by Microsoft.
|
1
fuzzers/libfuzzer_windows_asan/corpus/hello_world
Normal file
1
fuzzers/libfuzzer_windows_asan/corpus/hello_world
Normal file
@ -0,0 +1 @@
|
||||
Hello World
|
16
fuzzers/libfuzzer_windows_asan/harness.cpp
Normal file
16
fuzzers/libfuzzer_windows_asan/harness.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
|
||||
void asan_crash() {
|
||||
int *array = new int[100];
|
||||
delete[] array;
|
||||
array[5] += 1;
|
||||
fprintf(stdout, "%d\n", array[5]);
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
// abort();
|
||||
asan_crash();
|
||||
return 0;
|
||||
}
|
43
fuzzers/libfuzzer_windows_asan/src/bin/libafl_cc.rs
Normal file
43
fuzzers/libfuzzer_windows_asan/src/bin/libafl_cc.rs
Normal file
@ -0,0 +1,43 @@
|
||||
use std::env;
|
||||
|
||||
use libafl_cc::{ClangWrapper, CompilerWrapper};
|
||||
|
||||
pub fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() > 1 {
|
||||
let mut dir = env::current_exe().unwrap();
|
||||
let wrapper_name = dir
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.replace(".exe", "");
|
||||
|
||||
let is_cpp = match wrapper_name[wrapper_name.len()-2..].to_lowercase().as_str() {
|
||||
"cc" => false,
|
||||
"++" | "pp" | "xx" => true,
|
||||
_ => panic!("Could not figure out if c or c++ warpper was called. Expected {:?} to end with c or cxx", dir),
|
||||
};
|
||||
|
||||
dir.pop();
|
||||
|
||||
let mut cc = ClangWrapper::new();
|
||||
if let Some(code) = cc
|
||||
.cpp(is_cpp)
|
||||
// silence the compiler wrapper output, needed for some configure scripts.
|
||||
.silence(true)
|
||||
.parse_args(&args)
|
||||
.expect("Failed to parse the command line")
|
||||
.link_staticlib(&dir, "libfuzzer_windows_asan")
|
||||
.add_arg("-lOleAut32.lib")
|
||||
.add_arg("-fsanitize-coverage=trace-pc-guard")
|
||||
.add_arg("-fsanitize=address")
|
||||
.run()
|
||||
.expect("Failed to run the wrapped compiler")
|
||||
{
|
||||
std::process::exit(code);
|
||||
}
|
||||
} else {
|
||||
panic!("LibAFL CC: No Arguments given");
|
||||
}
|
||||
}
|
5
fuzzers/libfuzzer_windows_asan/src/bin/libafl_cxx.rs
Normal file
5
fuzzers/libfuzzer_windows_asan/src/bin/libafl_cxx.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub mod libafl_cc;
|
||||
|
||||
fn main() {
|
||||
libafl_cc::main()
|
||||
}
|
187
fuzzers/libfuzzer_windows_asan/src/lib.rs
Normal file
187
fuzzers/libfuzzer_windows_asan/src/lib.rs
Normal file
@ -0,0 +1,187 @@
|
||||
use core::time::Duration;
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
use libafl::{
|
||||
bolts::{
|
||||
current_nanos,
|
||||
rands::StdRand,
|
||||
tuples::{tuple_list, Merge},
|
||||
AsSlice,
|
||||
},
|
||||
corpus::{Corpus, InMemoryCorpus, OnDiskCorpus},
|
||||
events::{setup_restarting_mgr_std, EventConfig, EventRestarter},
|
||||
executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor},
|
||||
feedback_or, feedback_or_fast,
|
||||
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
|
||||
fuzzer::{Fuzzer, StdFuzzer},
|
||||
inputs::{BytesInput, HasTargetBytes},
|
||||
monitors::MultiMonitor,
|
||||
mutators::{
|
||||
scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator},
|
||||
token_mutations::Tokens,
|
||||
},
|
||||
observers::{HitcountsMapObserver, StdMapObserver, TimeObserver},
|
||||
schedulers::{
|
||||
powersched::PowerSchedule, IndexesLenTimeMinimizerScheduler, StdWeightedScheduler,
|
||||
},
|
||||
stages::{calibrate::CalibrationStage, power::StdPowerMutationalStage},
|
||||
state::{HasCorpus, HasMetadata, StdState},
|
||||
Error,
|
||||
};
|
||||
use libafl_targets::{libfuzzer_initialize, libfuzzer_test_one_input, EDGES_MAP, MAX_EDGES_NUM};
|
||||
|
||||
#[no_mangle]
|
||||
pub fn libafl_main() {
|
||||
println!(
|
||||
"Workdir: {:?}",
|
||||
env::current_dir().unwrap().to_string_lossy().to_string()
|
||||
);
|
||||
fuzz(
|
||||
&[PathBuf::from("./corpus")],
|
||||
PathBuf::from("./crashes"),
|
||||
1337,
|
||||
)
|
||||
.expect("An error occurred while fuzzing");
|
||||
}
|
||||
|
||||
/// The actual fuzzer
|
||||
#[cfg(not(test))]
|
||||
fn fuzz(corpus_dirs: &[PathBuf], objective_dir: PathBuf, broker_port: u16) -> Result<(), Error> {
|
||||
// 'While the stats are state, they are usually used in the broker - which is likely never restarted
|
||||
let monitor = MultiMonitor::new(|s| println!("{}", s));
|
||||
|
||||
// The restarting state will spawn the same process again as child, then restarted it each time it crashes.
|
||||
let (state, mut restarting_mgr) =
|
||||
match setup_restarting_mgr_std(monitor, broker_port, EventConfig::AlwaysUnique) {
|
||||
Ok(res) => res,
|
||||
Err(err) => match err {
|
||||
Error::ShuttingDown => {
|
||||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
panic!("Failed to setup the restarter: {}", err);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Create an observation channel using the coverage map
|
||||
let edges = unsafe { &mut EDGES_MAP[0..MAX_EDGES_NUM] };
|
||||
let edges_observer = HitcountsMapObserver::new(StdMapObserver::new("edges", edges));
|
||||
|
||||
// Create an observation channel to keep track of the execution time
|
||||
let time_observer = TimeObserver::new("time");
|
||||
|
||||
let map_feedback = MaxMapFeedback::new_tracking(&edges_observer, true, false);
|
||||
|
||||
let calibration = CalibrationStage::new(&map_feedback);
|
||||
|
||||
// Feedback to rate the interestingness of an input
|
||||
// This one is composed by two Feedbacks in OR
|
||||
let mut feedback = feedback_or!(
|
||||
// New maximization map feedback linked to the edges observer and the feedback state
|
||||
map_feedback,
|
||||
// Time feedback, this one does not need a feedback state
|
||||
TimeFeedback::new_with_observer(&time_observer)
|
||||
);
|
||||
|
||||
// A feedback to choose if an input is a solution or not
|
||||
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(|| {
|
||||
StdState::new(
|
||||
// RNG
|
||||
StdRand::with_seed(current_nanos()),
|
||||
// Corpus that will be evolved, we keep it in memory for performance
|
||||
InMemoryCorpus::new(),
|
||||
// Corpus in which we store solutions (crashes in this example),
|
||||
// on disk so the user can get them after stopping the fuzzer
|
||||
OnDiskCorpus::new(objective_dir).unwrap(),
|
||||
// States of the feedbacks.
|
||||
// The feedbacks can report the data that should persist in the State.
|
||||
&mut feedback,
|
||||
// Same for objective feedbacks
|
||||
&mut objective,
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
println!("We're a client, let's fuzz :)");
|
||||
|
||||
// Setup a basic mutator with a mutational stage
|
||||
|
||||
let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations()));
|
||||
|
||||
let power = StdPowerMutationalStage::new(mutator, &edges_observer);
|
||||
|
||||
let mut stages = tuple_list!(calibration, power);
|
||||
|
||||
// A minimization+queue policy to get testcasess from the corpus
|
||||
let scheduler = IndexesLenTimeMinimizerScheduler::new(StdWeightedScheduler::with_schedule(
|
||||
PowerSchedule::FAST,
|
||||
));
|
||||
|
||||
// A fuzzer with feedbacks and a corpus scheduler
|
||||
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
|
||||
|
||||
// The wrapped harness function, calling out to the LLVM-style harness
|
||||
let mut harness = |input: &BytesInput| {
|
||||
let target = input.target_bytes();
|
||||
let buf = target.as_slice();
|
||||
libfuzzer_test_one_input(buf);
|
||||
ExitKind::Ok
|
||||
};
|
||||
|
||||
// Create the executor for an in-process function with one observer for edge coverage and one for the execution time
|
||||
let mut executor = TimeoutExecutor::new(
|
||||
InProcessExecutor::new(
|
||||
&mut harness,
|
||||
tuple_list!(edges_observer, time_observer),
|
||||
&mut fuzzer,
|
||||
&mut state,
|
||||
&mut restarting_mgr,
|
||||
)?,
|
||||
// 10 seconds timeout
|
||||
Duration::new(10, 0),
|
||||
);
|
||||
|
||||
// Initialize ASAN, call this before any ASAN crashes can occur (directly after initializing executor e.g.)
|
||||
#[cfg(windows)]
|
||||
unsafe {
|
||||
libafl_targets::setup_asan_callback(&executor, &restarting_mgr, &fuzzer);
|
||||
}
|
||||
|
||||
// The actual target run starts here.
|
||||
// Call LLVMFUzzerInitialize() if present.
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if libfuzzer_initialize(&args) == -1 {
|
||||
println!("Warning: LLVMFuzzerInitialize failed with -1")
|
||||
}
|
||||
|
||||
// In case the corpus is empty (on first run), reset
|
||||
if state.corpus().count() < 1 {
|
||||
state
|
||||
.load_initial_inputs(&mut fuzzer, &mut executor, &mut restarting_mgr, corpus_dirs)
|
||||
.unwrap_or_else(|_| panic!("Failed to load initial corpus at {:?}", &corpus_dirs));
|
||||
println!("We imported {} inputs from disk.", state.corpus().count());
|
||||
}
|
||||
|
||||
// This fuzzer restarts after 1 mio `fuzz_one` executions.
|
||||
// Each fuzz_one will internally do many executions of the target.
|
||||
// If your target is very instable, setting a low count here may help.
|
||||
// However, you will lose a lot of performance that way.
|
||||
let iters = 1_000_000;
|
||||
fuzzer.fuzz_loop_for(
|
||||
&mut stages,
|
||||
&mut executor,
|
||||
&mut state,
|
||||
&mut restarting_mgr,
|
||||
iters,
|
||||
)?;
|
||||
|
||||
// It's important, that we store the state before restarting!
|
||||
// Else, the parent will not respawn a new child and quit.
|
||||
restarting_mgr.on_restart(&mut state)?;
|
||||
|
||||
Ok(())
|
||||
}
|
@ -10,7 +10,7 @@ typedef long double max_align_t;
|
||||
#endif
|
||||
|
||||
#if LLVM_VERSION_MAJOR >= 7 /* use new pass manager */
|
||||
//#define USE_NEW_PM 1
|
||||
// #define USE_NEW_PM 1
|
||||
#endif
|
||||
|
||||
/* #if LLVM_VERSION_STRING >= "4.0.1" */
|
||||
|
@ -22,6 +22,15 @@ EXT_FUNC_IMPL(main, int, (int argc, char** argv), false) {
|
||||
libafl_main();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
// If we do not add the main, the MSVC linker fails with:
|
||||
// LINK : fatal error LNK1561: entry point must be defined
|
||||
int main(int argc, char** argv) {
|
||||
libafl_main();
|
||||
}
|
||||
#endif
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
EXPORT_FN int libafl_targets_has_libfuzzer_init() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user