
* SerdeAny MapFeedbackState * Fix macro syntax * alloc * fix * Metadata calibrate and map feedback * metadata feedback states * compile * fmt * Register common generic types * tests * sugar * no_std * fix book * alloc * fix fuzzers * fix * fmt * disable python bindings for libafl * clippy * fmt * fixes * fmt * fix * fix * fix * fix * fix * release autofix * fix * fix * fix * fmt * fix * fix * name * fix Co-authored-by: Dominik Maier <dmnk@google.com>
176 lines
5.7 KiB
Rust
176 lines
5.7 KiB
Rust
use std::io::Read;
|
|
use std::{fs, path::PathBuf};
|
|
|
|
#[cfg(windows)]
|
|
use std::ptr::write_volatile;
|
|
|
|
use libafl::{
|
|
bolts::{current_nanos, rands::StdRand, tuples::tuple_list, AsSlice},
|
|
corpus::{InMemoryCorpus, OnDiskCorpus},
|
|
events::SimpleEventManager,
|
|
executors::{inprocess::InProcessExecutor, ExitKind},
|
|
feedbacks::{CrashFeedback, MaxMapFeedback},
|
|
fuzzer::{Evaluator, Fuzzer, StdFuzzer},
|
|
inputs::{GeneralizedInput, HasTargetBytes},
|
|
monitors::SimpleMonitor,
|
|
mutators::{
|
|
havoc_mutations, scheduled::StdScheduledMutator, GrimoireExtensionMutator,
|
|
GrimoireRandomDeleteMutator, GrimoireRecursiveReplacementMutator,
|
|
GrimoireStringReplacementMutator, Tokens,
|
|
},
|
|
observers::StdMapObserver,
|
|
schedulers::QueueScheduler,
|
|
stages::{mutational::StdMutationalStage, GeneralizationStage},
|
|
state::{HasMetadata, StdState},
|
|
};
|
|
|
|
/// Coverage map with explicit assignments due to the lack of instrumentation
|
|
static mut SIGNALS: [u8; 16] = [0; 16];
|
|
|
|
/// Assign a signal to the signals map
|
|
fn signals_set(idx: usize) {
|
|
unsafe { SIGNALS[idx] = 1 };
|
|
}
|
|
|
|
fn is_sub<T: PartialEq>(mut haystack: &[T], needle: &[T]) -> bool {
|
|
if needle.is_empty() {
|
|
return true;
|
|
}
|
|
while !haystack.is_empty() {
|
|
if haystack.starts_with(needle) {
|
|
return true;
|
|
}
|
|
haystack = &haystack[1..];
|
|
}
|
|
false
|
|
}
|
|
|
|
#[allow(clippy::similar_names)]
|
|
pub fn main() {
|
|
let mut initial_inputs = vec![];
|
|
for entry in fs::read_dir("./corpus").unwrap() {
|
|
let path = entry.unwrap().path();
|
|
let attr = fs::metadata(&path);
|
|
if attr.is_err() {
|
|
continue;
|
|
}
|
|
let attr = attr.unwrap();
|
|
|
|
if attr.is_file() && attr.len() > 0 {
|
|
println!("Loading file {:?} ...", &path);
|
|
let mut file = fs::File::open(path).expect("no file found");
|
|
let mut buffer = vec![];
|
|
file.read_to_end(&mut buffer).expect("buffer overflow");
|
|
let input = GeneralizedInput::new(buffer);
|
|
initial_inputs.push(input);
|
|
}
|
|
}
|
|
|
|
// The closure that we want to fuzz
|
|
let mut harness = |input: &GeneralizedInput| {
|
|
let target_bytes = input.target_bytes();
|
|
let bytes = target_bytes.as_slice();
|
|
|
|
if is_sub(bytes, "fn".as_bytes()) {
|
|
signals_set(2);
|
|
}
|
|
|
|
if is_sub(bytes, "pippopippo".as_bytes()) {
|
|
signals_set(3);
|
|
}
|
|
|
|
unsafe {
|
|
if input.grimoire_mutated {
|
|
// println!(">>> {:?}", input.generalized());
|
|
println!(">>> {:?}", std::str::from_utf8_unchecked(bytes));
|
|
}
|
|
}
|
|
signals_set(1);
|
|
ExitKind::Ok
|
|
};
|
|
|
|
// Create an observation channel using the signals map
|
|
let observer = StdMapObserver::new("signals", unsafe { &mut SIGNALS });
|
|
|
|
// Feedback to rate the interestingness of an input
|
|
let mut feedback = MaxMapFeedback::new_tracking(&observer, false, true);
|
|
|
|
// A feedback to choose if an input is a solution or not
|
|
let mut objective = CrashFeedback::new();
|
|
|
|
// create a State from scratch
|
|
let mut state = 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(PathBuf::from("./crashes")).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();
|
|
|
|
if state.metadata().get::<Tokens>().is_none() {
|
|
state.add_metadata(Tokens::from([b"FOO".to_vec(), b"BAR".to_vec()]));
|
|
}
|
|
|
|
// The Monitor trait define how the fuzzer stats are reported to the user
|
|
let monitor = SimpleMonitor::new(|s| println!("{}", s));
|
|
|
|
// The event manager handle the various events generated during the fuzzing loop
|
|
// such as the notification of the addition of a new item to the corpus
|
|
let mut mgr = SimpleEventManager::new(monitor);
|
|
|
|
// A queue policy to get testcasess from the corpus
|
|
let scheduler = QueueScheduler::new();
|
|
|
|
// A fuzzer with feedbacks and a corpus scheduler
|
|
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
|
|
|
|
let generalization = GeneralizationStage::new(&observer);
|
|
|
|
// Create the executor for an in-process function with just one observer
|
|
let mut executor = InProcessExecutor::new(
|
|
&mut harness,
|
|
tuple_list!(observer),
|
|
&mut fuzzer,
|
|
&mut state,
|
|
&mut mgr,
|
|
)
|
|
.expect("Failed to create the Executor");
|
|
|
|
// Setup a mutational stage with a basic bytes mutator
|
|
let mutator = StdScheduledMutator::with_max_stack_pow(havoc_mutations(), 2);
|
|
let grimoire_mutator = StdScheduledMutator::with_max_stack_pow(
|
|
tuple_list!(
|
|
GrimoireExtensionMutator::new(),
|
|
GrimoireRecursiveReplacementMutator::new(),
|
|
GrimoireStringReplacementMutator::new(),
|
|
// give more probability to avoid large inputs
|
|
GrimoireRandomDeleteMutator::new(),
|
|
GrimoireRandomDeleteMutator::new(),
|
|
),
|
|
3,
|
|
);
|
|
let mut stages = tuple_list!(
|
|
generalization,
|
|
StdMutationalStage::new(mutator),
|
|
StdMutationalStage::new(grimoire_mutator)
|
|
);
|
|
|
|
for input in initial_inputs {
|
|
fuzzer
|
|
.evaluate_input(&mut state, &mut executor, &mut mgr, input)
|
|
.unwrap();
|
|
}
|
|
|
|
fuzzer
|
|
.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)
|
|
.expect("Error in the fuzzing loop");
|
|
}
|