create libafl_targets and start new structure for libfuzzer_libpng

This commit is contained in:
Andrea Fioraldi 2021-03-23 20:39:23 +01:00
parent 414a66382b
commit 1c8cdc76a8
21 changed files with 363 additions and 16 deletions

View File

@ -9,11 +9,15 @@ members = [
"libafl", "libafl",
"libafl_derive", "libafl_derive",
"libafl_cc", "libafl_cc",
"libafl_targets",
#example fuzzers #example fuzzers
"fuzzers/libfuzzer_libpng", #"fuzzers/libfuzzer_libpng",
"fuzzers/frida_libpng", "fuzzers/frida_libpng",
"fuzzers/libfuzzer_libmozjpeg", "fuzzers/libfuzzer_libmozjpeg",
"fuzzers/libfuzzer_libpng_cmpalloc", "fuzzers/libfuzzer_libpng_cmpalloc",
"fuzzers/libfuzzer_windows", "fuzzers/libfuzzer_windows",
] ]
exclude = [
"fuzzers/libfuzzer_libpng",
]

View File

@ -3,7 +3,6 @@ name = "libfuzzer_libpng"
version = "0.1.0" version = "0.1.0"
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"] authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"]
edition = "2018" edition = "2018"
build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -11,21 +10,21 @@ build = "build.rs"
default = ["std"] default = ["std"]
std = [] std = []
#[profile.release] [profile.release]
#lto = true lto = true
#codegen-units = 1 codegen-units = 1
#opt-level = 3 opt-level = 3
#debug = true debug = true
[build-dependencies]
cc = { version = "1.0", features = ["parallel"] }
num_cpus = "1.0"
[dependencies] [dependencies]
libafl = { path = "../../libafl/" } libafl = { path = "../../libafl/" }
# TODO Include it only when building cc
libafl_cc = { path = "../../libafl_cc/" }
[[example]] [lib]
name = "libfuzzer_libpng" name = "libfuzzer_libpng"
path = "./src/fuzzer.rs" path = "./src/fuzzer.rs"
test = false test = false
bench = false bench = false
crate-type = ["staticlib"]

View File

@ -0,0 +1,13 @@
use libafl_cc::{ClangWrapper, CompilerWrapper};
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
ClangWrapper::new("clang", "clang++")
.from_args(&args)
.unwrap()
.add_arg("-fsanitize=trace-pc-guard".into())
.unwrap()
.run()
.unwrap();
}

View File

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

View File

@ -0,0 +1,31 @@
[package]
name = "libfuzzer_libpng"
version = "0.1.0"
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"]
edition = "2018"
build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["std"]
std = []
#[profile.release]
#lto = true
#codegen-units = 1
#opt-level = 3
#debug = true
[build-dependencies]
cc = { version = "1.0", features = ["parallel"] }
num_cpus = "1.0"
[dependencies]
libafl = { path = "../../libafl/" }
[[example]]
name = "libfuzzer_libpng"
path = "./src/fuzzer.rs"
test = false
bench = false

View File

@ -0,0 +1,25 @@
# 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 --example libfuzzer_libpng --release`.
This will call (the build.rs)[./builld.rs], which in turn downloads a libpng archive from the web.
Then, it will link (the fuzzer)[./src/fuzzer.rs] against (the C++ harness)[./harness.cc] and the instrumented `libpng`.
Afterwards, the fuzzer will be ready to run, from `../../target/examples/libfuzzer_libpng`.
## 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).
For convenience, you may just run `./test.sh` in this folder to test it.

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,179 @@
//! A libfuzzer-like fuzzer with llmp-multithreading support and restarts
//! The example harness is built for libpng.
#[cfg(unix)]
use core::time::Duration;
use std::{env, path::PathBuf};
#[cfg(unix)]
use libafl::{
bolts::{shmem::UnixShMem, tuples::tuple_list},
corpus::{
Corpus, InMemoryCorpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus,
QueueCorpusScheduler,
},
events::setup_restarting_mgr,
executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor},
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer},
mutators::{scheduled::HavocBytesMutator, token_mutations::Tokens},
observers::{HitcountsMapObserver, StdMapObserver, TimeObserver},
stages::mutational::StdMutationalStage,
state::{HasCorpus, HasMetadata, State},
stats::SimpleStats,
utils::{current_nanos, StdRand},
Error,
};
/// We will interact with a C++ target, so use external c functionality
#[cfg(unix)]
extern "C" {
/// int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
fn LLVMFuzzerTestOneInput(data: *const u8, size: usize) -> i32;
// afl_libfuzzer_init calls LLVMFUzzerInitialize()
fn afl_libfuzzer_init() -> i32;
static __lafl_edges_map: *mut u8;
static __lafl_cmp_map: *mut u8;
static __lafl_max_edges_size: u32;
}
/// The main fn, usually parsing parameters, and starting the fuzzer
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");
}
/// Not supported on windows right now
#[cfg(windows)]
fn fuzz(_corpus_dirs: Vec<PathBuf>, _objective_dir: PathBuf, _broker_port: u16) -> Result<(), ()> {
todo!("Example not supported on Windows");
}
/// The actual fuzzer
#[cfg(unix)]
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::<_, _, UnixShMem, _>(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_from_ptr("edges", __lafl_edges_map, __lafl_max_edges_size as usize)
});
// 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(), TimeoutFeedback::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 = HavocBytesMutator::default();
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));
// The wrapped harness function, calling out to the LLVM-style harness
let mut harness = |buf: &[u8]| {
unsafe { LLVMFuzzerTestOneInput(buf.as_ptr(), buf.len()) };
ExitKind::Ok
};
// 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)",
&mut harness,
tuple_list!(edges_observer, TimeObserver::new("time")),
&mut state,
&mut restarting_mgr,
)?,
// 10 seconds timeout
Duration::new(10, 0),
);
// The actual target run starts here.
// Call LLVMFUzzerInitialize() if present.
unsafe {
if afl_libfuzzer_init() == -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,
fuzzer.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)?;
// Never reached
Ok(())
}

View File

@ -1,3 +0,0 @@
fn main() {
todo!("libafl-cc");
}

View File

@ -75,7 +75,7 @@ impl CompilerWrapper for ClangWrapper {
let mut linking = true; let mut linking = true;
// Detect stray -v calls from ./configure scripts. // Detect stray -v calls from ./configure scripts.
if args.len() == 1 && args[1] == "-v" { if args.len() > 1 && args[1] == "-v" {
linking = false; linking = false;
} }

15
libafl_targets/Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "libafl_targets"
version = "0.1.0"
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>"]
edition = "2018"
[features]
default = ["sancov", "libfuzzer_compatibility"]
sancov = []
libfuzzer_compatibility = []
[build-dependencies]
cc = { version = "1.0", features = ["parallel"] }
[dependencies]

22
libafl_targets/build.rs Normal file
View File

@ -0,0 +1,22 @@
// 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();
//let out_dir_path = Path::new(&out_dir);
#[cfg(feature = "libfuzzer_compatibility")]
{
println!("cargo:rerun-if-changed=libfuzzer_compatibility.c");
cc::Build::new()
.file("libfuzzer_compatibility.c")
.compile("libfuzzer-compatibility");
}
println!("cargo:rustc-link-search=native={}", &out_dir);
println!("cargo:rerun-if-changed=build.rs");
}

View File

@ -0,0 +1,25 @@
static int orig_argc;
static char **orig_argv;
static char **orig_envp;
static void save_main_args(int argc, char** argv, char** envp) {
orig_argc = argc;
orig_argv = argv;
orig_envp = envp;
}
__attribute__((section(".init_array")))
void (*p_libafl_targets_save_main_args)(int, char*[], char*[]) = &save_main_args;
__attribute__((weak))
int LLVMFuzzerInitialize(int *argc, char ***argv);
int libafl_targets_libfuzzer_init() {
if (LLVMFuzzerInitialize) {
return LLVMFuzzerInitialize(&orig_argc, &orig_argv);
} else {
return 0;
}
}

10
libafl_targets/src/lib.rs Normal file
View File

@ -0,0 +1,10 @@
#[cfg(feature = "sancov")]
pub mod sancov;
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

View File

@ -0,0 +1,26 @@
// 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 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;
}
#[no_mangle]
pub unsafe extern "C" fn __sanitizer_cov_trace_pc_guard_init(mut start: *mut u32, stop: *mut u32) {
if start == stop || *start != 0 {
return;
}
while start < stop {
MAX_EDGES_NUM += 1;
*start = (MAX_EDGES_NUM & (MAP_SIZE - 1)) as u32;
start = start.offset(1);
}
}