libfuzzer_stb_image with build.rs and win32 fixes

This commit is contained in:
andreafioraldi 2021-03-26 10:39:02 +01:00
parent 40fe286cf9
commit 7564ce1e87
24 changed files with 8149 additions and 54 deletions

View File

@ -12,7 +12,6 @@ members = [
"libafl_targets",
#example fuzzers
#"fuzzers/libfuzzer_libpng",
"fuzzers/frida_libpng",
"fuzzers/libfuzzer_libmozjpeg",
"fuzzers/libfuzzer_libpng_cmpalloc",
@ -20,4 +19,5 @@ members = [
]
exclude = [
"fuzzers/libfuzzer_libpng",
"fuzzers/libfuzzer_stb_image",
]

View File

@ -18,7 +18,7 @@ debug = true
[dependencies]
libafl = { path = "../../libafl/" }
libafl_targets = { path = "../../libafl_targets/", features = ["sancov", "libfuzzer_compatibility"] }
libafl_targets = { path = "../../libafl_targets/", features = ["pcguard_hitcounts", "libfuzzer"] }
# TODO Include it only when building cc
libafl_cc = { path = "../../libafl_cc/" }

View File

@ -6,18 +6,26 @@ fn main() {
if args.len() > 1 {
let mut dir = env::current_exe().unwrap();
dir.pop();
ClangWrapper::new("clang", "clang++")
.from_args(&args)
let mut cc = ClangWrapper::new("clang", "clang++");
cc.from_args(&args)
.unwrap()
.add_arg("-fsanitize-coverage=trace-pc-guard".into())
.unwrap()
.add_link_arg(
dir.join(format!("{}libfuzzer_libpng.{}", LIB_PREFIX, LIB_EXT))
dir.join(format!("{}libfuzzer_stb_image.{}", LIB_PREFIX, LIB_EXT))
.display()
.to_string(),
)
.unwrap()
.run()
.unwrap();
// Libraries needed by libafl on Windows
#[cfg(windows)]
cc.add_link_arg("-lws2_32".into())
.unwrap()
.add_link_arg("-lBcrypt".into())
.unwrap()
.add_link_arg("-lAdvapi32".into())
.unwrap();
cc.run().unwrap();
}
}

View File

@ -6,19 +6,27 @@ fn main() {
if args.len() > 1 {
let mut dir = env::current_exe().unwrap();
dir.pop();
ClangWrapper::new("clang", "clang++")
.is_cpp()
let mut cc = ClangWrapper::new("clang", "clang++");
cc.is_cpp()
.from_args(&args)
.unwrap()
.add_arg("-fsanitize-coverage=trace-pc-guard".into())
.unwrap()
.add_link_arg(
dir.join(format!("{}libfuzzer_libpng.{}", LIB_PREFIX, LIB_EXT))
dir.join(format!("{}libfuzzer_stb_image.{}", LIB_PREFIX, LIB_EXT))
.display()
.to_string(),
)
.unwrap()
.run()
.unwrap();
// Libraries needed by libafl on Windows
#[cfg(windows)]
cc.add_link_arg("-lws2_32".into())
.unwrap()
.add_link_arg("-lBcrypt".into())
.unwrap()
.add_link_arg("-lAdvapi32".into())
.unwrap();
cc.run().unwrap();
}
}

View File

@ -107,9 +107,11 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
let stage = StdMutationalStage::new(mutator);
// A fuzzer with just one stage and a minimization+queue policy to get testcasess from the corpus
let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
let mut fuzzer = StdFuzzer::new(tuple_list!(stage));
// A minimization+queue policy to get testcasess from the corpus
let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
// The wrapped harness function, calling out to the LLVM-style harness
let mut harness = |buf: &[u8]| {
libfuzzer_test_one_input(buf);
@ -119,7 +121,7 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
// Create the executor for an in-process function with just one observer for edge coverage
let mut executor = TimeoutExecutor::new(
InProcessExecutor::new(
"in-process(edges)",
"in-process(edges,time)",
&mut harness,
tuple_list!(edges_observer, TimeObserver::new("time")),
&mut state,

View File

@ -0,0 +1 @@
libpng-*

View File

@ -0,0 +1,24 @@
[package]
name = "libfuzzer_stb_image"
version = "0.1.0"
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"]
edition = "2018"
build = "build.rs"
[features]
default = ["std"]
std = []
[profile.release]
lto = true
codegen-units = 1
opt-level = 3
debug = true
[dependencies]
libafl = { path = "../../libafl/" }
libafl_targets = { path = "../../libafl_targets/", features = ["pcguard_edges", "libfuzzer"] }
[build-dependencies]
cc = { version = "1.0", features = ["parallel"] }
num_cpus = "1.0"

View File

@ -0,0 +1,43 @@
# Libfuzzer for libpng
This folder contains an example fuzzer for libpng, using LLMP for fast multi-process fuzzing and crash detection.
To show off crash detection, we added a ud2 instruction to the harness, edit harness.cc if you want a non-crashing example.
It has been tested on Linux.
## Build
To build this example, run `cargo build --release`.
This will build the library with the fuzzer (src/lib.rs) with the libfuzzer compatibility layer and the SanitizerCoverage runtime functions for coverage feedback.
In addition, it will build also two C and C++ compiler wrappers (bin/c(c/xx).rs) that you must use to compile the target.
Then download libpng from https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz and unpack the archive.
Now compile it with:
```
cd libpng-1.6.37
./configure
make CC=/path/to/libfuzzer_libpng/target/release/cc -j `nproc`
```
You can find the static lib at `libpng-1.6.37/.libs/libpng16.a`.
Now, we have to build the libfuzzer harness and link all togheter to create our fuzzer binary.
```
/path/to/libfuzzer_libpng/target/debug/cxx /path/to/libfuzzer_libpng/harness.cc libpng-1.6.37/.libs/libpng16.a -I libpng-1.6.37/ -o fuzzer -lz -lm
```
Afterwards, the fuzzer will be ready to run simply executing `./fuzzer`.
## Run
The first time you run the binary, the broker will open a tcp port (currently on port `1337`), waiting for fuzzer clients to connect. This port is local and only used for the initial handshake. All further communication happens via shared map, to be independent of the kernel.
Each following execution will run a fuzzer client.
As this example uses in-process fuzzing, we added a Restarting Event Manager (`setup_restarting_mgr`).
This means each client will start itself again to listen for crashes and timeouts.
By restarting the actual fuzzer, it can recover from these exit conditions.
In any real-world scenario, you should use `taskset` to pin each client to an empty CPU core, the lib does not pick an empty core automatically (yet).

View File

@ -0,0 +1,27 @@
// build.rs
use std::env;
fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
let out_dir = out_dir.to_string_lossy().to_string();
println!("cargo:rerun-if-changed=harness.c");
// Enforce clang for its -fsanitize-coverage support.
std::env::set_var("CC", "clang");
std::env::set_var("CXX", "clang++");
cc::Build::new()
// Use sanitizer coverage to track the edges in the PUT
.flag("-fsanitize-coverage=trace-pc-guard")
// Take advantage of LTO (needs lld-link set in your cargo config)
//.flag("-flto=thin")
.flag("-Wno-sign-compare")
.file("./harness.c")
.compile("harness");
println!("cargo:rustc-link-search=native={}", &out_dir);
println!("cargo:rerun-if-changed=build.rs");
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

View File

@ -0,0 +1,61 @@
#include <stdint.h>
#define STBI_ASSERT(x)
#define STBI_NO_SIMD
#define STBI_NO_LINEAR
#define STBI_NO_STDIO
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
int target_func(const uint8_t *buf, size_t size) {
/*printf("BUF (%ld): ", size);
for (int i = 0; i < size; i++) {
printf("%02X", buf[i]);
}
printf("\n");*/
if (size == 0) return 0;
switch (buf[0]) {
case 1:
if (buf[1] == 0x44) {
//__builtin_trap();
return 8;
}
break;
case 0xff:
if (buf[2] == 0xff) {
if (buf[1] == 0x44) {
//*(char *)(0xdeadbeef) = 1;
return 9;
}
}
break;
default:
break;
}
return 1;
}
int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{return target_func(data, size);
int x, y, channels;
if(!stbi_info_from_memory(data, size, &x, &y, &channels)) return 0;
/* exit if the image is larger than ~80MB */
if(y && x > (80000000 / 4) / y) return 0;
unsigned char *img = stbi_load_from_memory(data, size, &x, &y, &channels, 4);
free(img);
return 0;
}

View File

@ -0,0 +1,150 @@
//! A libfuzzer-like fuzzer with llmp-multithreading support and restarts
//! The example harness is built for stb_image.
use std::{env, path::PathBuf};
use libafl::{
bolts::{shmem::StdShMem, tuples::tuple_list},
corpus::{
Corpus, InMemoryCorpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus,
QueueCorpusScheduler,
},
events::setup_restarting_mgr,
executors::{inprocess::InProcessExecutor, ExitKind},
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
fuzzer::{Fuzzer, StdFuzzer},
mutators::scheduled::{havoc_mutations, StdScheduledMutator},
mutators::token_mutations::Tokens,
observers::{HitcountsMapObserver, StdMapObserver, TimeObserver},
stages::mutational::StdMutationalStage,
state::{HasCorpus, HasMetadata, State},
stats::SimpleStats,
utils::{current_nanos, StdRand},
Error,
};
use libafl_targets::{libfuzzer_initialize, libfuzzer_test_one_input, EDGES_MAP, MAX_EDGES_NUM};
/// The main fn, no_mangle as it is a C main
pub fn main() {
// Registry the metadata types used in this fuzzer
// Needed only on no_std
//RegistryBuilder::register::<Tokens>();
println!(
"Workdir: {:?}",
env::current_dir().unwrap().to_string_lossy().to_string()
);
fuzz(
vec![PathBuf::from("./corpus")],
PathBuf::from("./crashes"),
1337,
)
.expect("An error occurred while fuzzing");
}
/// The actual fuzzer
fn fuzz(corpus_dirs: Vec<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 stats = SimpleStats::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::<_, _, StdShMem, _>(stats, broker_port) {
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_observer = HitcountsMapObserver::new(unsafe {
StdMapObserver::new("edges", &mut EDGES_MAP, MAX_EDGES_NUM)
});
// If not restarting, create a State from scratch
let mut state = state.unwrap_or_else(|| {
State::new(
// RNG
StdRand::with_seed(current_nanos()),
// Corpus that will be evolved, we keep it in memory for performance
InMemoryCorpus::new(),
// Feedbacks to rate the interestingness of an input
tuple_list!(
MaxMapFeedback::new_with_observer_track(&edges_observer, true, false),
TimeFeedback::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(),
// Feedbacks to recognize an input as solution
tuple_list!(CrashFeedback::new()),
)
});
println!("We're a client, let's fuzz :)");
// Create a PNG dictionary if not existing
if state.metadata().get::<Tokens>().is_none() {
state.add_metadata(Tokens::new(vec![
vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header
"IHDR".as_bytes().to_vec(),
"IDAT".as_bytes().to_vec(),
"PLTE".as_bytes().to_vec(),
"IEND".as_bytes().to_vec(),
]));
}
// Setup a basic mutator with a mutational stage
let mutator = StdScheduledMutator::new(havoc_mutations());
let stage = StdMutationalStage::new(mutator);
// A fuzzer with just one stage and a minimization+queue policy to get testcasess from the corpus
let mut fuzzer = StdFuzzer::new(tuple_list!(stage));
// A minimization+queue policy to get testcasess from the corpus
let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
// The wrapped harness function, calling out to the LLVM-style harness
let mut harness = |buf: &[u8]| {
libfuzzer_test_one_input(buf);
ExitKind::Ok
};
// Create the executor for an in-process function with just one observer for edge coverage
let mut executor = InProcessExecutor::new(
"in-process(edges,time)",
&mut harness,
tuple_list!(edges_observer, TimeObserver::new("time")),
&mut state,
&mut restarting_mgr,
)?;
// 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 executor, &mut restarting_mgr, &scheduler, &corpus_dirs)
.expect(&format!(
"Failed to load initial corpus at {:?}",
&corpus_dirs
));
println!("We imported {} inputs from disk.", state.corpus().count());
}
fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr, &scheduler)?;
// Never reached
Ok(())
}

File diff suppressed because it is too large Load Diff

View File

@ -219,13 +219,13 @@ int load_stbi(const uint8_t *data, int size)
extern "C"
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
//return target_func(Data, Size);
return target_func(Data, Size);
if(Size > 0x4000) return 0;
int size = Size;
const unsigned char * data = Data;
//return load_stbi(data, size);
return parse_pe(data, size);
return load_stbi(data, size);
//return parse_pe(data, size);
}

View File

@ -14,8 +14,9 @@ use libafl::{
events::setup_restarting_mgr,
executors::{inprocess::InProcessExecutor, ExitKind},
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer},
mutators::{scheduled::HavocBytesMutator, token_mutations::Tokens},
fuzzer::{Fuzzer, StdFuzzer},
mutators::scheduled::{havoc_mutations, StdScheduledMutator},
mutators::token_mutations::Tokens,
observers::{HitcountsMapObserver, StdMapObserver, TimeObserver},
stages::mutational::StdMutationalStage,
state::{HasCorpus, HasMetadata, State},
@ -133,12 +134,12 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
}
// Setup a basic mutator with a mutational stage
let mutator = HavocBytesMutator::default();
let mutator = StdScheduledMutator::new(havoc_mutations());
let stage = StdMutationalStage::new(mutator);
// A fuzzer with just one stage and a minimization+queue policy to get testcasess from the corpus
let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
let fuzzer = StdFuzzer::new(scheduler, tuple_list!(stage));
let mut fuzzer = StdFuzzer::new(tuple_list!(stage));
// Create the executor for an in-process function with just one observer for edge coverage
let mut executor = InProcessExecutor::new(
@ -160,12 +161,7 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
// In case the corpus is empty (on first run), reset
if state.corpus().count() < 1 {
state
.load_initial_inputs(
&mut executor,
&mut restarting_mgr,
fuzzer.scheduler(),
&corpus_dirs,
)
.load_initial_inputs(&mut executor, &mut restarting_mgr, &scheduler, &corpus_dirs)
.expect(&format!(
"Failed to load initial corpus at {:?}",
&corpus_dirs
@ -173,7 +169,7 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
println!("We imported {} inputs from disk.", state.corpus().count());
}
fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr)?;
fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr, &scheduler)?;
// Never reached
Ok(())

View File

@ -6,8 +6,10 @@ edition = "2018"
[features]
default = []
sancov = []
libfuzzer_compatibility = []
pcguard_edges = []
pcguard_hitcounts = []
libfuzzer = []
pcguard = ["pcguard_hitcounts"]
[build-dependencies]
cc = { version = "1.0", features = ["parallel"] }

View File

@ -7,13 +7,16 @@ fn main() {
let out_dir = out_dir.to_string_lossy().to_string();
//let out_dir_path = Path::new(&out_dir);
#[cfg(feature = "libfuzzer_compatibility")]
std::env::set_var("CC", "clang");
std::env::set_var("CXX", "clang++");
#[cfg(feature = "libfuzzer")]
{
println!("cargo:rerun-if-changed=libfuzzer_compatibility.c");
cc::Build::new()
.file("libfuzzer_compatibility.c")
.compile("libfuzzer-compatibility");
.compile("libfuzzer_compatibility");
}
println!("cargo:rustc-link-search=native={}", &out_dir);

View File

@ -13,6 +13,8 @@
#define LIBFUZZER_MSVC 0
#endif // _MSC_VER
#define EXPORT_FN __declspec(dllexport)
// From Libfuzzer
// Intermediate macro to ensure the parameter is expanded before stringified.
#define STRINGIFY_(A) #A
@ -32,7 +34,7 @@
__pragma(comment(linker, "/alternatename:" WIN_SYM_PREFIX STRINGIFY( \
Name) "=" WIN_SYM_PREFIX STRINGIFY(Default)))
#define CHECK_WEAK_FN(Name) (Name != &Name##Def)
#define CHECK_WEAK_FN(Name) ((void*)Name != (void*)&Name##Def)
#else
// Declare external functions as weak to allow them to default to a specified
// function if not defined explicitly. We must use weak symbols because clang's
@ -49,6 +51,8 @@
EXTERNAL_FUNC(NAME, NAME##Def) RETURN_TYPE NAME FUNC_SIG
#else
#define EXPORT_FN
// Declare these symbols as weak to allow them to be optionally defined.
#define EXT_FUNC(NAME, RETURN_TYPE, FUNC_SIG, WARN) \
__attribute__((weak, visibility("default"))) RETURN_TYPE NAME FUNC_SIG
@ -68,11 +72,11 @@ EXT_FUNC(LLVMFuzzerCustomCrossOver, size_t,
#undef EXT_FUNC
int libafl_targets_has_libfuzzer_init() {
EXPORT_FN int libafl_targets_has_libfuzzer_init() {
return CHECK_WEAK_FN(LLVMFuzzerInitialize);
}
int libafl_targets_libfuzzer_init(int *argc, char ***argv) {
EXPORT_FN int libafl_targets_libfuzzer_init(int *argc, char ***argv) {
if (libafl_targets_has_libfuzzer_init()) {
return LLVMFuzzerInitialize(argc, argv);
} else {

View File

@ -1,17 +1,9 @@
#[cfg(feature = "sancov")]
pub mod sancov;
#[cfg(feature = "sancov")]
pub use sancov::*;
#[cfg(any(feature = "pcguard_edges", feature = "pcguard_hitcounts"))]
pub mod pcguard;
#[cfg(any(feature = "pcguard_edges", feature = "pcguard_hitcounts"))]
pub use pcguard::*;
#[cfg(feature = "libfuzzer_compatibility")]
pub mod libfuzzer_compatibility;
#[cfg(feature = "libfuzzer_compatibility")]
pub use libfuzzer_compatibility::*;
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
#[cfg(feature = "libfuzzer")]
pub mod libfuzzer;
#[cfg(feature = "libfuzzer")]
pub use libfuzzer::*;

View File

@ -1,15 +1,27 @@
#[cfg(all(feature = "pcguard_edges", feature = "pcguard_hitcounts"))]
compile_error!(
"the libafl_targets `pcguard_edges` and `pcguard_hitcounts` features are mutually exclusive."
);
// TODO compile time flag
pub const MAP_SIZE: usize = 65536;
pub static mut EDGES_MAP: [u8; MAP_SIZE] = [0; MAP_SIZE];
pub static mut CMP_MAP: [u8; MAP_SIZE] = [0; MAP_SIZE];
//pub static mut CMP_MAP: [u8; MAP_SIZE] = [0; MAP_SIZE];
pub static mut MAX_EDGES_NUM: usize = 0;
#[no_mangle]
pub unsafe extern "C" fn __sanitizer_cov_trace_pc_guard(guard: *mut u32) {
let pos = *guard as usize;
let val = (EDGES_MAP[pos] as u8).wrapping_add(1);
EDGES_MAP[pos] = val;
#[cfg(feature = "pcguard_edges")]
{
*EDGES_MAP.get_unchecked_mut(pos) = 1;
}
#[cfg(feature = "pcguard_hitcounts")]
{
let val = (*EDGES_MAP.get_unchecked(pos) as u8).wrapping_add(1);
*EDGES_MAP.get_unchecked_mut(pos) = val;
}
}
#[no_mangle]