Use the new bolts::cli with the frida_libpng sample (#541)

* Use the new bolts::cli with the frida_libpng sample

* Fix comment and add must_use

* Fix windows

* Fix windows more

* Fix windows more, more

* Fix windows more, more, more

* Remove comma

* fmt
This commit is contained in:
s1341 2022-03-01 12:25:11 +02:00 committed by GitHub
parent bf9d2b4c57
commit f4c4d9044f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 468 additions and 310 deletions

View File

@ -28,7 +28,7 @@ reqwest = { version = "0.11.4", features = ["blocking"] }
[dependencies] [dependencies]
libafl = { path = "../../libafl/", features = [ "std", "llmp_compression", "llmp_bind_public" ] } #, "llmp_small_maps", "llmp_debug"]} libafl = { path = "../../libafl/", features = [ "std", "llmp_compression", "llmp_bind_public", "frida_cli" ] } #, "llmp_small_maps", "llmp_debug"]}
capstone = "0.10.0" capstone = "0.10.0"
frida-gum = { version = "0.6.3", features = [ "auto-download", "event-sink", "invocation-listener"] } frida-gum = { version = "0.6.3", features = [ "auto-download", "event-sink", "invocation-listener"] }
libafl_frida = { path = "../../libafl_frida", features = ["cmplog"] } libafl_frida = { path = "../../libafl_frida", features = ["cmplog"] }

View File

@ -4,19 +4,14 @@ use mimalloc::MiMalloc;
#[global_allocator] #[global_allocator]
static GLOBAL: MiMalloc = MiMalloc; static GLOBAL: MiMalloc = MiMalloc;
use clap::{self, StructOpt};
use frida_gum::Gum; use frida_gum::Gum;
use std::{ use std::path::PathBuf;
env,
net::SocketAddr,
path::{Path, PathBuf},
};
use libafl::{ use libafl::{
bolts::{ bolts::{
cli::{parse_args, FuzzerOptions},
current_nanos, current_nanos,
launcher::Launcher, launcher::Launcher,
os::Cores,
rands::StdRand, rands::StdRand,
shmem::{ShMemProvider, StdShMemProvider}, shmem::{ShMemProvider, StdShMemProvider},
tuples::{tuple_list, Merge}, tuples::{tuple_list, Merge},
@ -46,7 +41,7 @@ use libafl::{
use libafl_frida::{ use libafl_frida::{
coverage_rt::CoverageRuntime, coverage_rt::MAP_SIZE, executor::FridaInProcessExecutor, coverage_rt::CoverageRuntime, coverage_rt::MAP_SIZE, executor::FridaInProcessExecutor,
helper::FridaInstrumentationHelper, FridaOptions, helper::FridaInstrumentationHelper,
}; };
#[cfg(unix)] #[cfg(unix)]
@ -55,114 +50,16 @@ use libafl_targets::cmplog::{CmpLogObserver, CMPLOG_MAP};
#[cfg(unix)] #[cfg(unix)]
use libafl_frida::asan::errors::{AsanErrorsFeedback, AsanErrorsObserver, ASAN_ERRORS}; use libafl_frida::asan::errors::{AsanErrorsFeedback, AsanErrorsObserver, ASAN_ERRORS};
use libafl_frida::cmplog_rt::CmpLogRuntime;
#[derive(Debug, StructOpt)]
#[clap(
name = "libafl_frida",
version = "0.1.0",
about = "A frida-based binary-only libfuzzer-style fuzzer for with llmp-multithreading support",
author = "s1341 <github@shmarya.net>,
Dongjia Zhang <toka@aflplus.plus>, Andrea Fioraldi <andreafioraldi@gmail.com>, Dominik Maier <domenukk@gmail.com>"
)]
struct Opt {
#[clap(
short,
long,
parse(try_from_str = Cores::from_cmdline),
help = "Spawn a client in each of the provided cores. Broker runs in the 0th core. 'all' to select all available cores. 'none' to run a client without binding to any core. eg: '1,2-4,6' selects the cores 1,2,3,4,6.",
name = "CORES"
)]
cores: Cores,
#[clap(
short = 'p',
long,
help = "Choose the broker TCP port, default is 1337",
name = "PORT",
default_value = "1337"
)]
broker_port: u16,
#[clap(
parse(try_from_str),
short = 'a',
long,
help = "Specify a remote broker",
name = "REMOTE"
)]
remote_broker_addr: Option<SocketAddr>,
#[clap(
parse(try_from_str),
short,
long,
help = "Set an initial corpus directory",
name = "INPUT"
)]
input: Vec<PathBuf>,
#[clap(
short,
long,
parse(try_from_str),
help = "Set the output directory, default is ./out",
name = "OUTPUT",
default_value = "./out"
)]
output: PathBuf,
#[clap(
long,
help = "The configuration this fuzzer runs with, for multiprocessing",
name = "CONF",
default_value = "default launcher"
)]
configuration: String,
#[clap(
long,
help = "The file to redirect stdout input to (/dev/null if unset)"
)]
stdout_file: Option<String>,
#[clap(help = "The harness")]
harness: String,
#[clap(help = "The symbol name to look up and hook")]
symbol: String,
#[clap(help = "The modules to instrument, separated by colons")]
modules_to_instrument: String,
}
/// The main fn, usually parsing parameters, and starting the fuzzer /// The main fn, usually parsing parameters, and starting the fuzzer
pub fn main() { pub fn main() {
// Registry the metadata types used in this fuzzer
// Needed only on no_std
//RegistryBuilder::register::<Tokens>();
let opt = Opt::parse();
color_backtrace::install(); color_backtrace::install();
println!( let options = parse_args();
"Workdir: {:?}",
env::current_dir().unwrap().to_string_lossy().to_string()
);
unsafe { unsafe {
match fuzz( match fuzz(options) {
&opt.harness,
&opt.symbol,
&opt.modules_to_instrument.split(':').collect::<Vec<_>>(),
//modules_to_instrument,
&opt.input,
&opt.output,
opt.broker_port,
&opt.cores,
opt.stdout_file.as_deref(),
opt.remote_broker_addr,
opt.configuration,
) {
Ok(()) | Err(Error::ShuttingDown) => println!("\nFinished fuzzing. Good bye."), Ok(()) | Err(Error::ShuttingDown) => println!("\nFinished fuzzing. Good bye."),
Err(e) => panic!("Error during fuzzing: {:?}", e), Err(e) => panic!("Error during fuzzing: {:?}", e),
} }
@ -171,34 +68,19 @@ pub fn main() {
/// The actual fuzzer /// The actual fuzzer
#[allow(clippy::too_many_lines, clippy::too_many_arguments)] #[allow(clippy::too_many_lines, clippy::too_many_arguments)]
unsafe fn fuzz( unsafe fn fuzz(options: FuzzerOptions) -> Result<(), Error> {
module_name: &str,
symbol_name: &str,
modules_to_instrument: &[&str],
corpus_dirs: &[PathBuf],
objective_dir: &Path,
broker_port: u16,
cores: &Cores,
stdout_file: Option<&str>,
broker_addr: Option<SocketAddr>,
configuration: String,
) -> Result<(), Error> {
// 'While the stats are state, they are usually used in the broker - which is likely never restarted // 'While the stats are state, they are usually used in the broker - which is likely never restarted
let monitor = MultiMonitor::new(|s| println!("{}", s)); let monitor = MultiMonitor::new(|s| println!("{}", s));
let shmem_provider = StdShMemProvider::new()?; let shmem_provider = StdShMemProvider::new()?;
let mut run_client = |state: Option<StdState<_, _, _, _, _>>, let mut run_client = |state: Option<StdState<_, _, _, _, _>>,
mut mgr: LlmpRestartingEventManager<_, _, _, _>, mgr: LlmpRestartingEventManager<_, _, _, _>,
_core_id| { core_id| {
// The restarting state will spawn the same process again as child, then restarted it each time it crashes. let lib = libloading::Library::new(options.clone().harness.unwrap()).unwrap();
// println!("{:?}", mgr.mgr_id());
let lib = libloading::Library::new(module_name).unwrap();
let target_func: libloading::Symbol< let target_func: libloading::Symbol<
unsafe extern "C" fn(data: *const u8, size: usize) -> i32, unsafe extern "C" fn(data: *const u8, size: usize) -> i32,
> = lib.get(symbol_name.as_bytes()).unwrap(); > = lib.get(options.harness_function.as_bytes()).unwrap();
let mut frida_harness = |input: &BytesInput| { let mut frida_harness = |input: &BytesInput| {
let target = input.target_bytes(); let target = input.target_bytes();
@ -207,168 +89,403 @@ unsafe fn fuzz(
ExitKind::Ok ExitKind::Ok
}; };
let gum = Gum::obtain(); if options.asan && options.asan_cores.contains(core_id) {
let frida_options = FridaOptions::parse_env_options(); (|state: Option<StdState<_, _, _, _, _>>,
let coverage = CoverageRuntime::new(); mut mgr: LlmpRestartingEventManager<_, _, _, _>,
// let asan = AsanRuntime::new(frida_options.clone()); _core_id| {
let mut frida_helper = FridaInstrumentationHelper::new( let gum = unsafe { Gum::obtain() };
&gum,
&frida_options,
module_name,
modules_to_instrument,
tuple_list!(coverage),
);
// Create an observation channel using the coverage map let coverage = CoverageRuntime::new();
let edges_observer = HitcountsMapObserver::new(StdMapObserver::new_from_ptr( #[cfg(unix)]
"edges", let asan = AsanRuntime::new(options.clone());
frida_helper.map_ptr_mut().unwrap(),
MAP_SIZE,
));
// Create an observation channel to keep track of the execution time #[cfg(unix)]
let time_observer = TimeObserver::new("time"); 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));
let feedback_state = MapFeedbackState::with_observer(&edges_observer); // Create an observation channel using the coverage map
// Feedback to rate the interestingness of an input let edges_observer = HitcountsMapObserver::new(unsafe {
// This one is composed by two Feedbacks in OR StdMapObserver::new_from_ptr(
let feedback = feedback_or!( "edges",
// New maximization map feedback linked to the edges observer and the feedback state frida_helper.map_ptr_mut().unwrap(),
MaxMapFeedback::new_tracking(&feedback_state, &edges_observer, true, false), MAP_SIZE,
// Time feedback, this one does not need a feedback state )
TimeFeedback::new_with_observer(&time_observer) });
);
// Feedbacks to recognize an input as solution // Create an observation channel to keep track of the execution time
let time_observer = TimeObserver::new("time");
#[cfg(unix)] let feedback_state = MapFeedbackState::with_observer(&edges_observer);
let objective = feedback_or_fast!(
CrashFeedback::new(),
TimeoutFeedback::new(),
AsanErrorsFeedback::new()
);
#[cfg(windows)] // Feedback to rate the interestingness of an input
let objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()); // This one is composed by two Feedbacks in OR
let feedback = feedback_or!(
// New maximization map feedback linked to the edges observer and the feedback state
MaxMapFeedback::new_tracking(&feedback_state, &edges_observer, true, false),
// Time feedback, this one does not need a feedback state
TimeFeedback::new_with_observer(&time_observer)
);
// If not restarting, create a State from scratch // Feedbacks to recognize an input as solution
let mut state = state.unwrap_or_else(|| { #[cfg(unix)]
StdState::new( let objective = feedback_or_fast!(
// RNG CrashFeedback::new(),
StdRand::with_seed(current_nanos()), TimeoutFeedback::new(),
// Corpus that will be evolved, we keep it in memory for performance AsanErrorsFeedback::new()
CachedOnDiskCorpus::new(PathBuf::from("./corpus_discovered"), 64).unwrap(), );
// Corpus in which we store solutions (crashes in this example), #[cfg(windows)]
// on disk so the user can get them after stopping the fuzzer let objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new());
OnDiskCorpus::new_save_meta(
objective_dir.to_path_buf(),
Some(OnDiskMetadataFormat::JsonPretty),
)
.unwrap(),
// States of the feedbacks.
// They are the data related to the feedbacks that you want to persist in the State.
tuple_list!(feedback_state),
)
});
println!("We're a client, let's fuzz :)"); // 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
CachedOnDiskCorpus::new(PathBuf::from("./corpus_discovered"), 64).unwrap(),
// Corpus in which we store solutions (crashes in this example),
// on disk so the user can get them after stopping the fuzzer
OnDiskCorpus::new_save_meta(
options.output.to_path_buf(),
Some(OnDiskMetadataFormat::JsonPretty),
)
.unwrap(),
// States of the feedbacks.
// They are the data related to the feedbacks that you want to persist in the State.
tuple_list!(feedback_state),
)
});
// Create a PNG dictionary if not existing println!("We're a client, let's fuzz :)");
if state.metadata().get::<Tokens>().is_none() {
state.add_metadata(Tokens::from([
vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header
b"IHDR".to_vec(),
b"IDAT".to_vec(),
b"PLTE".to_vec(),
b"IEND".to_vec(),
]));
}
// Setup a basic mutator with a mutational stage // Create a PNG dictionary if not existing
let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations())); if state.metadata().get::<Tokens>().is_none() {
state.add_metadata(Tokens::from([
vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header
b"IHDR".to_vec(),
b"IDAT".to_vec(),
b"PLTE".to_vec(),
b"IEND".to_vec(),
]));
}
// A minimization+queue policy to get testcasess from the corpus // Setup a basic mutator with a mutational stage
let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new()); let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations()));
// A fuzzer with feedbacks and a corpus scheduler // A minimization+queue policy to get testcasess from the corpus
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); let scheduler =
IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
// Create the executor for an in-process function with just one observer for edge coverage // A fuzzer with feedbacks and a corpus scheduler
#[cfg(unix)] let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
let mut executor = FridaInProcessExecutor::new(
&gum, #[cfg(unix)]
InProcessExecutor::new( let observers = tuple_list!(
&mut frida_harness,
tuple_list!(
edges_observer, edges_observer,
time_observer, time_observer,
AsanErrorsObserver::new(&ASAN_ERRORS) AsanErrorsObserver::new(unsafe { &ASAN_ERRORS })
), );
&mut fuzzer, #[cfg(windows)]
&mut state, let observers = tuple_list!(edges_observer, time_observer);
&mut mgr,
)?,
&mut frida_helper,
);
#[cfg(windows)] // Create the executor for an in-process function with just one observer for edge coverage
let mut executor = FridaInProcessExecutor::new( let mut executor = FridaInProcessExecutor::new(
&gum, &gum,
InProcessExecutor::new( InProcessExecutor::new(
&mut frida_harness, &mut frida_harness,
tuple_list!(edges_observer, time_observer,), observers,
&mut fuzzer, &mut fuzzer,
&mut state, &mut state,
&mut mgr, &mut mgr,
)?, )?,
&mut frida_helper, &mut frida_helper,
); );
// In case the corpus is empty (on first run), reset // In case the corpus is empty (on first run), reset
if state.corpus().count() < 1 { if state.corpus().count() < 1 {
state state
.load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, corpus_dirs) .load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &options.input)
.unwrap_or_else(|_| panic!("Failed to load initial corpus at {:?}", &corpus_dirs)); .unwrap_or_else(|_| {
println!("We imported {} inputs from disk.", state.corpus().count()); panic!("Failed to load initial corpus at {:?}", &options.input)
} });
println!("We imported {} inputs from disk.", state.corpus().count());
}
if frida_options.cmplog_enabled() { let mut stages = tuple_list!(StdMutationalStage::new(mutator));
// Create an observation channel using cmplog map
let cmplog_observer = CmpLogObserver::new("cmplog", &mut CMPLOG_MAP, true);
let mut executor = ShadowExecutor::new(executor, tuple_list!(cmplog_observer)); fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?;
let tracing = ShadowTracingStage::new(&mut executor); Ok(())
})(state, mgr, core_id)
} else if options.cmplog && options.cmplog_cores.contains(core_id) {
(|state: Option<StdState<_, _, _, _, _>>,
mut mgr: LlmpRestartingEventManager<_, _, _, _>,
_core_id| {
let gum = unsafe { Gum::obtain() };
// Setup a randomic Input2State stage let coverage = CoverageRuntime::new();
let i2s = StdMutationalStage::new(StdScheduledMutator::new(tuple_list!( let cmplog = CmpLogRuntime::new();
I2SRandReplace::new()
)));
// Setup a basic mutator let mut frida_helper =
let mutational = StdMutationalStage::new(mutator); FridaInstrumentationHelper::new(&gum, &options, tuple_list!(coverage, cmplog));
// The order of the stages matter! // Create an observation channel using the coverage map
let mut stages = tuple_list!(tracing, i2s, mutational); let edges_observer = HitcountsMapObserver::new(unsafe {
StdMapObserver::new_from_ptr(
"edges",
frida_helper.map_ptr_mut().unwrap(),
MAP_SIZE,
)
});
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; // Create an observation channel to keep track of the execution time
let time_observer = TimeObserver::new("time");
let feedback_state = MapFeedbackState::with_observer(&edges_observer);
// Feedback to rate the interestingness of an input
// This one is composed by two Feedbacks in OR
let feedback = feedback_or!(
// New maximization map feedback linked to the edges observer and the feedback state
MaxMapFeedback::new_tracking(&feedback_state, &edges_observer, true, false),
// Time feedback, this one does not need a feedback state
TimeFeedback::new_with_observer(&time_observer)
);
// Feedbacks to recognize an input as solution
let 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
CachedOnDiskCorpus::new(PathBuf::from("./corpus_discovered"), 64).unwrap(),
// Corpus in which we store solutions (crashes in this example),
// on disk so the user can get them after stopping the fuzzer
OnDiskCorpus::new_save_meta(
options.output.to_path_buf(),
Some(OnDiskMetadataFormat::JsonPretty),
)
.unwrap(),
// States of the feedbacks.
// They are the data related to the feedbacks that you want to persist in the State.
tuple_list!(feedback_state),
)
});
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::from([
vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header
b"IHDR".to_vec(),
b"IDAT".to_vec(),
b"PLTE".to_vec(),
b"IEND".to_vec(),
]));
}
// Setup a basic mutator with a mutational stage
let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations()));
// A minimization+queue policy to get testcasess from the corpus
let scheduler =
IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
// 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,
AsanErrorsObserver::new(unsafe { &ASAN_ERRORS })
);
#[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(
&mut frida_harness,
observers,
&mut fuzzer,
&mut state,
&mut mgr,
)?,
&mut frida_helper,
);
// In case the corpus is empty (on first run), reset
if state.corpus().count() < 1 {
state
.load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &options.input)
.unwrap_or_else(|_| {
panic!("Failed to load initial corpus at {:?}", &options.input)
});
println!("We imported {} inputs from disk.", state.corpus().count());
}
// Create an observation channel using cmplog map
let cmplog_observer =
CmpLogObserver::new("cmplog", unsafe { &mut CMPLOG_MAP }, true);
let mut executor = ShadowExecutor::new(executor, tuple_list!(cmplog_observer));
let tracing = ShadowTracingStage::new(&mut executor);
// Setup a randomic Input2State stage
let i2s = StdMutationalStage::new(StdScheduledMutator::new(tuple_list!(
I2SRandReplace::new()
)));
// Setup a basic mutator
let mutational = StdMutationalStage::new(mutator);
// The order of the stages matter!
let mut stages = tuple_list!(tracing, i2s, mutational);
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?;
Ok(())
})(state, mgr, core_id)
} else { } else {
let mut stages = tuple_list!(StdMutationalStage::new(mutator)); (|state: Option<StdState<_, _, _, _, _>>,
mut mgr: LlmpRestartingEventManager<_, _, _, _>,
_core_id| {
let gum = unsafe { Gum::obtain() };
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; let coverage = CoverageRuntime::new();
};
Ok(()) let mut frida_helper =
FridaInstrumentationHelper::new(&gum, &options, tuple_list!(coverage));
// Create an observation channel using the coverage map
let edges_observer = HitcountsMapObserver::new(unsafe {
StdMapObserver::new_from_ptr(
"edges",
frida_helper.map_ptr_mut().unwrap(),
MAP_SIZE,
)
});
// Create an observation channel to keep track of the execution time
let time_observer = TimeObserver::new("time");
let feedback_state = MapFeedbackState::with_observer(&edges_observer);
// Feedback to rate the interestingness of an input
// This one is composed by two Feedbacks in OR
let feedback = feedback_or!(
// New maximization map feedback linked to the edges observer and the feedback state
MaxMapFeedback::new_tracking(&feedback_state, &edges_observer, true, false),
// Time feedback, this one does not need a feedback state
TimeFeedback::new_with_observer(&time_observer)
);
// Feedbacks to recognize an input as solution
let 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
CachedOnDiskCorpus::new(PathBuf::from("./corpus_discovered"), 64).unwrap(),
// Corpus in which we store solutions (crashes in this example),
// on disk so the user can get them after stopping the fuzzer
OnDiskCorpus::new_save_meta(
options.output.to_path_buf(),
Some(OnDiskMetadataFormat::JsonPretty),
)
.unwrap(),
// States of the feedbacks.
// They are the data related to the feedbacks that you want to persist in the State.
tuple_list!(feedback_state),
)
});
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::from([
vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header
b"IHDR".to_vec(),
b"IDAT".to_vec(),
b"PLTE".to_vec(),
b"IEND".to_vec(),
]));
}
// Setup a basic mutator with a mutational stage
let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations()));
// A minimization+queue policy to get testcasess from the corpus
let scheduler =
IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
// 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,
AsanErrorsObserver::new(unsafe { &ASAN_ERRORS })
);
#[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(
&mut frida_harness,
observers,
&mut fuzzer,
&mut state,
&mut mgr,
)?,
&mut frida_helper,
);
// In case the corpus is empty (on first run), reset
if state.corpus().count() < 1 {
state
.load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &options.input)
.unwrap_or_else(|_| {
panic!("Failed to load initial corpus at {:?}", &options.input)
});
println!("We imported {} inputs from disk.", state.corpus().count());
}
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?;
Ok(())
})(state, mgr, core_id)
}
}; };
Launcher::builder() Launcher::builder()
.configuration(EventConfig::from_name(&configuration)) .configuration(EventConfig::AlwaysUnique)
.shmem_provider(shmem_provider) .shmem_provider(shmem_provider)
.monitor(monitor) .monitor(monitor)
.run_client(&mut run_client) .run_client(&mut run_client)
.cores(cores) .cores(&options.cores)
.broker_port(broker_port) .broker_port(options.broker_port)
.stdout_file(stdout_file) .stdout_file(Some(&options.stdout))
.remote_broker_addr(broker_addr) .remote_broker_addr(options.remote_broker_addr)
.build() .build()
.launch() .launch()
} }

View File

@ -64,6 +64,7 @@
//!``` //!```
use clap::{App, AppSettings, IntoApp, Parser}; use clap::{App, AppSettings, IntoApp, Parser};
use serde::{Deserialize, Serialize};
#[cfg(feature = "frida_cli")] #[cfg(feature = "frida_cli")]
use std::error; use std::error;
use std::net::SocketAddr; use std::net::SocketAddr;
@ -102,7 +103,7 @@ fn parse_instrumentation_location(
} }
/// Top-level container for cli options/arguments/subcommands /// Top-level container for cli options/arguments/subcommands
#[derive(Parser, Debug)] #[derive(Parser, Clone, Debug, Serialize, Deserialize)]
#[clap( #[clap(
setting(AppSettings::ArgRequiredElseHelp), setting(AppSettings::ArgRequiredElseHelp),
setting(AppSettings::SubcommandPrecedenceOverArg), setting(AppSettings::SubcommandPrecedenceOverArg),
@ -122,10 +123,21 @@ pub struct FuzzerOptions {
#[clap(short, long, default_value = "/dev/null")] #[clap(short, long, default_value = "/dev/null")]
pub stdout: String, pub stdout: String,
/// the name of the configuration to use
#[clap(short, long, default_value = "default configuration")]
pub configuration: String,
/// enable Address Sanitizer (ASAN) /// enable Address Sanitizer (ASAN)
#[clap(short = 'A', long, help_heading = "Fuzz Options")] #[clap(short = 'A', long, help_heading = "Fuzz Options")]
pub asan: bool, pub asan: bool,
/// Enable ASAN on each of the provided cores. Use 'all' to select all available
/// cores. 'none' to run a client without binding to any core.
/// ex: '1,2-4,6' selects the cores 1, 2, 3, 4, and 6.
#[cfg(feature = "frida_cli")]
#[clap(short, long, default_value = "0", parse(try_from_str = Cores::from_cmdline), help_heading = "ASAN Options")]
pub asan_cores: Cores,
/// number of fuzz iterations to perform /// number of fuzz iterations to perform
#[clap(short = 'I', long, help_heading = "Fuzz Options", default_value = "0")] #[clap(short = 'I', long, help_heading = "Fuzz Options", default_value = "0")]
pub iterations: usize, pub iterations: usize,
@ -139,6 +151,21 @@ pub struct FuzzerOptions {
#[clap(last = true, name = "HARNESS_ARGS")] #[clap(last = true, name = "HARNESS_ARGS")]
pub harness_args: Vec<String>, pub harness_args: Vec<String>,
/// harness function to call
#[cfg(feature = "frida_cli")]
#[clap(
short = 'F',
long,
default_value = "LLVMFuzzerTestOneInput",
help_heading = "Frida Options"
)]
pub harness_function: String,
/// additional libraries to instrument
#[cfg(feature = "frida_cli")]
#[clap(short, long, help_heading = "Frida Options")]
pub libs_to_instrument: Vec<String>,
/// enable CmpLog instrumentation /// enable CmpLog instrumentation
#[cfg_attr( #[cfg_attr(
feature = "frida_cli", feature = "frida_cli",
@ -150,6 +177,13 @@ pub struct FuzzerOptions {
)] )]
pub cmplog: bool, pub cmplog: bool,
/// Enable CmpLog on each of the provided cores. Use 'all' to select all available
/// cores. 'none' to run a client without binding to any core.
/// ex: '1,2-4,6' selects the cores 1, 2, 3, 4, and 6.
#[cfg(feature = "frida_cli")]
#[clap(short, long, default_value = "0", parse(try_from_str = Cores::from_cmdline), help_heading = "Frida Options")]
pub cmplog_cores: Cores,
/// enable ASAN leak detection /// enable ASAN leak detection
#[cfg(feature = "frida_cli")] #[cfg(feature = "frida_cli")]
#[clap(short, long, help_heading = "ASAN Options")] #[clap(short, long, help_heading = "ASAN Options")]

View File

@ -5,6 +5,7 @@ use alloc::{
string::{String, ToString}, string::{String, ToString},
vec::Vec, vec::Vec,
}; };
use serde::{Deserialize, Serialize};
#[cfg(any(unix, all(windows, feature = "std")))] #[cfg(any(unix, all(windows, feature = "std")))]
use crate::Error; use crate::Error;
@ -106,7 +107,7 @@ pub fn dup2(fd: i32, device: i32) -> Result<(), Error> {
} }
/// Core ID /// Core ID
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct CoreId { pub struct CoreId {
/// The id of this core /// The id of this core
pub id: usize, pub id: usize,
@ -155,7 +156,7 @@ impl From<core_affinity::CoreId> for CoreId {
} }
/// A list of [`CoreId`] to use for fuzzing /// A list of [`CoreId`] to use for fuzzing
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Cores { pub struct Cores {
/// The original commandline used during parsing /// The original commandline used during parsing
pub cmdline: String, pub cmdline: String,
@ -218,6 +219,13 @@ impl Cores {
ids: cores, ids: cores,
}) })
} }
/// Checks if this [`Cores`] instance contains a given ``core_id``
#[must_use]
pub fn contains(&self, core_id: usize) -> bool {
let core_id = CoreId::from(core_id);
self.ids.contains(&core_id)
}
} }
impl From<&[usize]> for Cores { impl From<&[usize]> for Cores {

View File

@ -19,7 +19,7 @@ cmplog = []
cc = { version = "1.0", features = ["parallel"] } cc = { version = "1.0", features = ["parallel"] }
[dependencies] [dependencies]
libafl = { path = "../libafl", version = "0.7.1", features = ["std", "libafl_derive"] } libafl = { path = "../libafl", version = "0.7.1", features = ["std", "libafl_derive", "frida_cli"] }
libafl_targets = { path = "../libafl_targets", version = "0.7.1", features = ["std", "sancov_cmplog"] } libafl_targets = { path = "../libafl_targets", version = "0.7.1", features = ["std", "sancov_cmplog"] }
nix = "0.23" nix = "0.23"

View File

@ -1,5 +1,6 @@
use frida_gum::{PageProtection, RangeDetails}; use frida_gum::{PageProtection, RangeDetails};
use hashbrown::HashMap; use hashbrown::HashMap;
use libafl::bolts::cli::FuzzerOptions;
use nix::{ use nix::{
libc::memset, libc::memset,
sys::mman::{mmap, MapFlags, ProtFlags}, sys::mman::{mmap, MapFlags, ProtFlags},
@ -22,17 +23,14 @@ use serde::{Deserialize, Serialize};
use std::io; use std::io;
use std::{collections::BTreeMap, ffi::c_void}; use std::{collections::BTreeMap, ffi::c_void};
use crate::{ use crate::asan::errors::{AsanError, AsanErrors};
asan::errors::{AsanError, AsanErrors},
FridaOptions,
};
/// An allocator wrapper with binary-only address sanitization /// An allocator wrapper with binary-only address sanitization
#[derive(Debug)] #[derive(Debug)]
#[allow(missing_docs)] #[allow(missing_docs)]
pub struct Allocator { pub struct Allocator {
#[allow(dead_code)] #[allow(dead_code)]
options: FridaOptions, options: FuzzerOptions,
page_size: usize, page_size: usize,
shadow_offset: usize, shadow_offset: usize,
shadow_bit: usize, shadow_bit: usize,
@ -78,7 +76,7 @@ impl Allocator {
all(target_arch = "aarch64", target_os = "android") all(target_arch = "aarch64", target_os = "android")
)))] )))]
#[must_use] #[must_use]
pub fn new(_: FridaOptions) -> Self { pub fn new(_: FuzzerOptions) -> Self {
todo!("Shadow region not yet supported for this platform!"); todo!("Shadow region not yet supported for this platform!");
} }
@ -90,7 +88,7 @@ impl Allocator {
))] ))]
#[must_use] #[must_use]
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
pub fn new(options: FridaOptions) -> Self { pub fn new(options: FuzzerOptions) -> Self {
let ret = unsafe { sysconf(_SC_PAGESIZE) }; let ret = unsafe { sysconf(_SC_PAGESIZE) };
assert!( assert!(
ret >= 0, ret >= 0,
@ -259,9 +257,9 @@ impl Allocator {
} else { } else {
size size
}; };
if size > self.options.asan_max_allocation() { if size > self.options.max_allocation {
#[allow(clippy::manual_assert)] #[allow(clippy::manual_assert)]
if self.options.asan_max_allocation_panics() { if self.options.max_allocation_panics {
panic!("ASAN: Allocation is too large: 0x{:x}", size); panic!("ASAN: Allocation is too large: 0x{:x}", size);
} }
@ -269,7 +267,7 @@ impl Allocator {
} }
let rounded_up_size = self.round_up_to_page(size) + 2 * self.page_size; let rounded_up_size = self.round_up_to_page(size) + 2 * self.page_size;
if self.total_allocation_size + rounded_up_size > self.options.asan_max_total_allocation() { if self.total_allocation_size + rounded_up_size > self.options.max_total_allocation {
return std::ptr::null_mut(); return std::ptr::null_mut();
} }
self.total_allocation_size += rounded_up_size; self.total_allocation_size += rounded_up_size;
@ -278,7 +276,7 @@ impl Allocator {
//println!("reusing allocation at {:x}, (actual mapping starts at {:x}) size {:x}", metadata.address, metadata.address - self.page_size, size); //println!("reusing allocation at {:x}, (actual mapping starts at {:x}) size {:x}", metadata.address, metadata.address - self.page_size, size);
metadata.is_malloc_zero = is_malloc_zero; metadata.is_malloc_zero = is_malloc_zero;
metadata.size = size; metadata.size = size;
if self.options.enable_asan_allocation_backtraces { if self.options.allocation_backtraces {
metadata.allocation_site_backtrace = Some(Backtrace::new_unresolved()); metadata.allocation_site_backtrace = Some(Backtrace::new_unresolved());
} }
metadata metadata
@ -311,7 +309,7 @@ impl Allocator {
actual_size: rounded_up_size, actual_size: rounded_up_size,
..AllocationMetadata::default() ..AllocationMetadata::default()
}; };
if self.options.enable_asan_allocation_backtraces { if self.options.allocation_backtraces {
metadata.allocation_site_backtrace = Some(Backtrace::new_unresolved()); metadata.allocation_site_backtrace = Some(Backtrace::new_unresolved());
} }
@ -356,7 +354,7 @@ impl Allocator {
let shadow_mapping_start = map_to_shadow!(self, ptr as usize); let shadow_mapping_start = map_to_shadow!(self, ptr as usize);
metadata.freed = true; metadata.freed = true;
if self.options.enable_asan_allocation_backtraces { if self.options.allocation_backtraces {
metadata.release_site_backtrace = Some(Backtrace::new_unresolved()); metadata.release_site_backtrace = Some(Backtrace::new_unresolved());
} }

View File

@ -13,7 +13,7 @@ use core::{
}; };
use frida_gum::{ModuleDetails, NativePointer, RangeDetails}; use frida_gum::{ModuleDetails, NativePointer, RangeDetails};
use hashbrown::HashMap; use hashbrown::HashMap;
use libafl::bolts::AsSlice; use libafl::bolts::{cli::FuzzerOptions, AsSlice};
use nix::sys::mman::{mmap, MapFlags, ProtFlags}; use nix::sys::mman::{mmap, MapFlags, ProtFlags};
use rangemap::RangeMap; use rangemap::RangeMap;
@ -58,7 +58,6 @@ use crate::{
asan::errors::{AsanError, AsanErrors, AsanReadWriteError, ASAN_ERRORS}, asan::errors::{AsanError, AsanErrors, AsanReadWriteError, ASAN_ERRORS},
helper::FridaRuntime, helper::FridaRuntime,
utils::writer_register, utils::writer_register,
FridaOptions,
}; };
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
@ -135,7 +134,7 @@ pub struct AsanRuntime {
blob_check_mem_48bytes: Option<Box<[u8]>>, blob_check_mem_48bytes: Option<Box<[u8]>>,
blob_check_mem_64bytes: Option<Box<[u8]>>, blob_check_mem_64bytes: Option<Box<[u8]>>,
stalked_addresses: HashMap<usize, usize>, stalked_addresses: HashMap<usize, usize>,
options: FridaOptions, options: FuzzerOptions,
module_map: Option<ModuleMap>, module_map: Option<ModuleMap>,
suppressed_addresses: Vec<usize>, suppressed_addresses: Vec<usize>,
shadow_check_func: Option<extern "C" fn(*const c_void, usize) -> bool>, shadow_check_func: Option<extern "C" fn(*const c_void, usize) -> bool>,
@ -172,8 +171,8 @@ impl FridaRuntime for AsanRuntime {
self.unpoison_all_existing_memory(); self.unpoison_all_existing_memory();
self.module_map = Some(ModuleMap::new_from_names(modules_to_instrument)); self.module_map = Some(ModuleMap::new_from_names(modules_to_instrument));
if let Some(suppressed_specifiers) = self.options.dont_instrument_locations() { if !self.options.dont_instrument.is_empty() {
for (module_name, offset) in suppressed_specifiers { for (module_name, offset) in self.options.dont_instrument.clone() {
let module_details = ModuleDetails::with_name(module_name).unwrap(); let module_details = ModuleDetails::with_name(module_name).unwrap();
let lib_start = module_details.range().base_address().0 as usize; let lib_start = module_details.range().base_address().0 as usize;
self.suppressed_addresses.push(lib_start + offset); self.suppressed_addresses.push(lib_start + offset);
@ -288,9 +287,9 @@ impl FridaRuntime for AsanRuntime {
impl AsanRuntime { impl AsanRuntime {
/// Create a new `AsanRuntime` /// Create a new `AsanRuntime`
#[must_use] #[must_use]
pub fn new(options: FridaOptions) -> AsanRuntime { pub fn new(options: FuzzerOptions) -> AsanRuntime {
Self { Self {
check_for_leaks_enabled: options.asan_detect_leaks(), check_for_leaks_enabled: options.detect_leaks,
current_report_impl: 0, current_report_impl: 0,
allocator: Allocator::new(options.clone()), allocator: Allocator::new(options.clone()),
regs: [0; ASAN_SAVE_REGISTER_COUNT], regs: [0; ASAN_SAVE_REGISTER_COUNT],

View File

@ -8,7 +8,7 @@ use color_backtrace::{default_output_stream, BacktracePrinter, Verbosity};
use frida_gum::interceptor::Interceptor; use frida_gum::interceptor::Interceptor;
use frida_gum::ModuleDetails; use frida_gum::ModuleDetails;
use libafl::{ use libafl::{
bolts::{ownedref::OwnedPtr, tuples::Named}, bolts::{cli::FuzzerOptions, ownedref::OwnedPtr, tuples::Named},
corpus::Testcase, corpus::Testcase,
events::EventFirer, events::EventFirer,
executors::ExitKind, executors::ExitKind,
@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize};
use std::io::Write; use std::io::Write;
use termcolor::{Color, ColorSpec, WriteColor}; use termcolor::{Color, ColorSpec, WriteColor};
use crate::{alloc::AllocationMetadata, asan::asan_rt::ASAN_SAVE_REGISTER_COUNT, FridaOptions}; use crate::{alloc::AllocationMetadata, asan::asan_rt::ASAN_SAVE_REGISTER_COUNT};
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct AsanReadWriteError { pub(crate) struct AsanReadWriteError {
@ -94,14 +94,14 @@ impl AsanError {
#[allow(clippy::unsafe_derive_deserialize)] #[allow(clippy::unsafe_derive_deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, SerdeAny)] #[derive(Debug, Clone, Serialize, Deserialize, SerdeAny)]
pub struct AsanErrors { pub struct AsanErrors {
options: FridaOptions, options: FuzzerOptions,
errors: Vec<AsanError>, errors: Vec<AsanError>,
} }
impl AsanErrors { impl AsanErrors {
/// Creates a new `AsanErrors` struct /// Creates a new `AsanErrors` struct
#[must_use] #[must_use]
pub fn new(options: FridaOptions) -> Self { pub fn new(options: FuzzerOptions) -> Self {
Self { Self {
options, options,
errors: Vec::new(), errors: Vec::new(),
@ -534,7 +534,7 @@ impl AsanErrors {
}; };
#[allow(clippy::manual_assert)] #[allow(clippy::manual_assert)]
if !self.options.asan_continue_after_error() { if !self.options.continue_on_error {
panic!("ASAN: Crashing target!"); panic!("ASAN: Crashing target!");
} }
} }

View File

@ -1,5 +1,5 @@
use libafl::{ use libafl::{
bolts::tuples::MatchFirstType, bolts::{cli::FuzzerOptions, tuples::MatchFirstType},
inputs::{HasTargetBytes, Input}, inputs::{HasTargetBytes, Input},
Error, Error,
}; };
@ -10,10 +10,8 @@ use libafl_targets::drcov::DrCovBasicBlock;
#[cfg(all(feature = "cmplog", target_arch = "aarch64"))] #[cfg(all(feature = "cmplog", target_arch = "aarch64"))]
use crate::cmplog_rt::CmpLogRuntime; use crate::cmplog_rt::CmpLogRuntime;
use crate::coverage_rt::CoverageRuntime; use crate::coverage_rt::CoverageRuntime;
#[cfg(windows)]
use crate::FridaOptions;
#[cfg(unix)] #[cfg(unix)]
use crate::{asan::asan_rt::AsanRuntime, drcov_rt::DrCovRuntime, FridaOptions}; use crate::{asan::asan_rt::AsanRuntime, drcov_rt::DrCovRuntime};
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
use capstone::{ use capstone::{
arch::{self, BuildsCapstone}, arch::{self, BuildsCapstone},
@ -124,7 +122,7 @@ pub struct FridaInstrumentationHelper<'a, RT> {
capstone: Capstone, capstone: Capstone,
ranges: RangeMap<usize, (u16, String)>, ranges: RangeMap<usize, (u16, String)>,
module_map: ModuleMap, module_map: ModuleMap,
options: &'a FridaOptions, options: &'a FuzzerOptions,
runtimes: RT, runtimes: RT,
} }
@ -170,13 +168,7 @@ where
/// Constructor function to create a new [`FridaInstrumentationHelper`], given a `module_name`. /// Constructor function to create a new [`FridaInstrumentationHelper`], given a `module_name`.
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
#[must_use] #[must_use]
pub fn new( pub fn new(gum: &'a Gum, options: &'a FuzzerOptions, runtimes: RT) -> Self {
gum: &'a Gum,
options: &'a FridaOptions,
_harness_module_name: &str,
modules_to_instrument: &'a [&str],
runtimes: RT,
) -> Self {
// workaround frida's frida-gum-allocate-near bug: // workaround frida's frida-gum-allocate-near bug:
#[cfg(unix)] #[cfg(unix)]
unsafe { unsafe {
@ -202,6 +194,16 @@ where
} }
} }
let mut modules_to_instrument = vec![options
.harness
.as_ref()
.unwrap()
.to_string_lossy()
.to_string()];
modules_to_instrument.append(&mut options.libs_to_instrument.clone());
let modules_to_instrument: Vec<&str> =
modules_to_instrument.iter().map(AsRef::as_ref).collect();
let mut helper = Self { let mut helper = Self {
transformer: None, transformer: None,
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
@ -219,12 +221,12 @@ where
.build() .build()
.expect("Failed to create Capstone object"), .expect("Failed to create Capstone object"),
ranges: RangeMap::new(), ranges: RangeMap::new(),
module_map: ModuleMap::new_from_names(modules_to_instrument), module_map: ModuleMap::new_from_names(&modules_to_instrument),
options, options,
runtimes, runtimes,
}; };
if helper.options().stalker_enabled() { if options.cmplog || options.asan || !options.disable_coverage {
for (i, module) in helper.module_map.values().iter().enumerate() { for (i, module) in helper.module_map.values().iter().enumerate() {
let range = module.range(); let range = module.range();
let start = range.base_address().0 as usize; let start = range.base_address().0 as usize;
@ -233,8 +235,8 @@ where
.ranges .ranges
.insert(start..(start + range.size()), (i as u16, module.path())); .insert(start..(start + range.size()), (i as u16, module.path()));
} }
if let Some(suppressed_specifiers) = helper.options().dont_instrument_locations() { if !options.dont_instrument.is_empty() {
for (module_name, offset) in suppressed_specifiers { for (module_name, offset) in options.dont_instrument.clone() {
let module_details = ModuleDetails::with_name(module_name).unwrap(); let module_details = ModuleDetails::with_name(module_name).unwrap();
let lib_start = module_details.range().base_address().0 as usize; let lib_start = module_details.range().base_address().0 as usize;
// println!("removing address: {:#x}", lib_start + offset); // println!("removing address: {:#x}", lib_start + offset);
@ -354,7 +356,7 @@ where
helper.transformer = Some(transformer); helper.transformer = Some(transformer);
helper helper
.runtimes .runtimes
.init_all(gum, &helper.ranges, modules_to_instrument); .init_all(gum, &helper.ranges, &modules_to_instrument);
} }
helper helper
} }
@ -402,7 +404,7 @@ where
/// If stalker is enabled /// If stalker is enabled
pub fn stalker_enabled(&self) -> bool { pub fn stalker_enabled(&self) -> bool {
self.options.stalker_enabled() self.options.cmplog || self.options.asan || !self.options.disable_coverage
} }
/// Pointer to coverage map /// Pointer to coverage map
@ -423,7 +425,7 @@ where
/// Return the ref to options /// Return the ref to options
#[inline] #[inline]
pub fn options(&self) -> &FridaOptions { pub fn options(&self) -> &FuzzerOptions {
self.options self.options
} }
} }