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:
parent
bf9d2b4c57
commit
f4c4d9044f
@ -28,7 +28,7 @@ reqwest = { version = "0.11.4", features = ["blocking"] }
|
||||
|
||||
|
||||
[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"
|
||||
frida-gum = { version = "0.6.3", features = [ "auto-download", "event-sink", "invocation-listener"] }
|
||||
libafl_frida = { path = "../../libafl_frida", features = ["cmplog"] }
|
||||
|
@ -4,19 +4,14 @@ use mimalloc::MiMalloc;
|
||||
#[global_allocator]
|
||||
static GLOBAL: MiMalloc = MiMalloc;
|
||||
|
||||
use clap::{self, StructOpt};
|
||||
use frida_gum::Gum;
|
||||
use std::{
|
||||
env,
|
||||
net::SocketAddr,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use libafl::{
|
||||
bolts::{
|
||||
cli::{parse_args, FuzzerOptions},
|
||||
current_nanos,
|
||||
launcher::Launcher,
|
||||
os::Cores,
|
||||
rands::StdRand,
|
||||
shmem::{ShMemProvider, StdShMemProvider},
|
||||
tuples::{tuple_list, Merge},
|
||||
@ -46,7 +41,7 @@ use libafl::{
|
||||
|
||||
use libafl_frida::{
|
||||
coverage_rt::CoverageRuntime, coverage_rt::MAP_SIZE, executor::FridaInProcessExecutor,
|
||||
helper::FridaInstrumentationHelper, FridaOptions,
|
||||
helper::FridaInstrumentationHelper,
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
@ -55,114 +50,16 @@ use libafl_targets::cmplog::{CmpLogObserver, CMPLOG_MAP};
|
||||
|
||||
#[cfg(unix)]
|
||||
use libafl_frida::asan::errors::{AsanErrorsFeedback, AsanErrorsObserver, ASAN_ERRORS};
|
||||
|
||||
#[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,
|
||||
}
|
||||
use libafl_frida::cmplog_rt::CmpLogRuntime;
|
||||
|
||||
/// 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>();
|
||||
|
||||
let opt = Opt::parse();
|
||||
color_backtrace::install();
|
||||
|
||||
println!(
|
||||
"Workdir: {:?}",
|
||||
env::current_dir().unwrap().to_string_lossy().to_string()
|
||||
);
|
||||
let options = parse_args();
|
||||
|
||||
unsafe {
|
||||
match fuzz(
|
||||
&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,
|
||||
) {
|
||||
match fuzz(options) {
|
||||
Ok(()) | Err(Error::ShuttingDown) => println!("\nFinished fuzzing. Good bye."),
|
||||
Err(e) => panic!("Error during fuzzing: {:?}", e),
|
||||
}
|
||||
@ -171,34 +68,19 @@ pub fn main() {
|
||||
|
||||
/// The actual fuzzer
|
||||
#[allow(clippy::too_many_lines, clippy::too_many_arguments)]
|
||||
unsafe fn fuzz(
|
||||
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> {
|
||||
unsafe fn fuzz(options: FuzzerOptions) -> 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));
|
||||
|
||||
let shmem_provider = StdShMemProvider::new()?;
|
||||
|
||||
let mut run_client = |state: Option<StdState<_, _, _, _, _>>,
|
||||
mut mgr: LlmpRestartingEventManager<_, _, _, _>,
|
||||
_core_id| {
|
||||
// The restarting state will spawn the same process again as child, then restarted it each time it crashes.
|
||||
|
||||
// println!("{:?}", mgr.mgr_id());
|
||||
|
||||
let lib = libloading::Library::new(module_name).unwrap();
|
||||
mgr: LlmpRestartingEventManager<_, _, _, _>,
|
||||
core_id| {
|
||||
let lib = libloading::Library::new(options.clone().harness.unwrap()).unwrap();
|
||||
let target_func: libloading::Symbol<
|
||||
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 target = input.target_bytes();
|
||||
@ -207,29 +89,37 @@ unsafe fn fuzz(
|
||||
ExitKind::Ok
|
||||
};
|
||||
|
||||
let gum = Gum::obtain();
|
||||
let frida_options = FridaOptions::parse_env_options();
|
||||
if options.asan && options.asan_cores.contains(core_id) {
|
||||
(|state: Option<StdState<_, _, _, _, _>>,
|
||||
mut mgr: LlmpRestartingEventManager<_, _, _, _>,
|
||||
_core_id| {
|
||||
let gum = unsafe { Gum::obtain() };
|
||||
|
||||
let coverage = CoverageRuntime::new();
|
||||
// let asan = AsanRuntime::new(frida_options.clone());
|
||||
let mut frida_helper = FridaInstrumentationHelper::new(
|
||||
&gum,
|
||||
&frida_options,
|
||||
module_name,
|
||||
modules_to_instrument,
|
||||
tuple_list!(coverage),
|
||||
);
|
||||
#[cfg(unix)]
|
||||
let asan = AsanRuntime::new(options.clone());
|
||||
|
||||
#[cfg(unix)]
|
||||
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));
|
||||
|
||||
// Create an observation channel using the coverage map
|
||||
let edges_observer = HitcountsMapObserver::new(StdMapObserver::new_from_ptr(
|
||||
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!(
|
||||
@ -240,14 +130,12 @@ unsafe fn fuzz(
|
||||
);
|
||||
|
||||
// Feedbacks to recognize an input as solution
|
||||
|
||||
#[cfg(unix)]
|
||||
let objective = feedback_or_fast!(
|
||||
CrashFeedback::new(),
|
||||
TimeoutFeedback::new(),
|
||||
AsanErrorsFeedback::new()
|
||||
);
|
||||
|
||||
#[cfg(windows)]
|
||||
let objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new());
|
||||
|
||||
@ -261,7 +149,7 @@ unsafe fn fuzz(
|
||||
// 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(
|
||||
objective_dir.to_path_buf(),
|
||||
options.output.to_path_buf(),
|
||||
Some(OnDiskMetadataFormat::JsonPretty),
|
||||
)
|
||||
.unwrap(),
|
||||
@ -288,35 +176,27 @@ unsafe fn fuzz(
|
||||
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());
|
||||
let scheduler =
|
||||
IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
|
||||
|
||||
// A fuzzer with feedbacks and a corpus scheduler
|
||||
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
|
||||
|
||||
// Create the executor for an in-process function with just one observer for edge coverage
|
||||
#[cfg(unix)]
|
||||
let mut executor = FridaInProcessExecutor::new(
|
||||
&gum,
|
||||
InProcessExecutor::new(
|
||||
&mut frida_harness,
|
||||
tuple_list!(
|
||||
let observers = tuple_list!(
|
||||
edges_observer,
|
||||
time_observer,
|
||||
AsanErrorsObserver::new(&ASAN_ERRORS)
|
||||
),
|
||||
&mut fuzzer,
|
||||
&mut state,
|
||||
&mut mgr,
|
||||
)?,
|
||||
&mut frida_helper,
|
||||
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,
|
||||
tuple_list!(edges_observer, time_observer,),
|
||||
observers,
|
||||
&mut fuzzer,
|
||||
&mut state,
|
||||
&mut mgr,
|
||||
@ -327,14 +207,135 @@ unsafe fn fuzz(
|
||||
// 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, corpus_dirs)
|
||||
.unwrap_or_else(|_| panic!("Failed to load initial corpus at {:?}", &corpus_dirs));
|
||||
.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)
|
||||
} else if options.cmplog && options.cmplog_cores.contains(core_id) {
|
||||
(|state: Option<StdState<_, _, _, _, _>>,
|
||||
mut mgr: LlmpRestartingEventManager<_, _, _, _>,
|
||||
_core_id| {
|
||||
let gum = unsafe { Gum::obtain() };
|
||||
|
||||
let coverage = CoverageRuntime::new();
|
||||
let cmplog = CmpLogRuntime::new();
|
||||
|
||||
let mut frida_helper =
|
||||
FridaInstrumentationHelper::new(&gum, &options, tuple_list!(coverage, cmplog));
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
if frida_options.cmplog_enabled() {
|
||||
// Create an observation channel using cmplog map
|
||||
let cmplog_observer = CmpLogObserver::new("cmplog", &mut CMPLOG_MAP, true);
|
||||
let cmplog_observer =
|
||||
CmpLogObserver::new("cmplog", unsafe { &mut CMPLOG_MAP }, true);
|
||||
|
||||
let mut executor = ShadowExecutor::new(executor, tuple_list!(cmplog_observer));
|
||||
|
||||
@ -352,23 +353,139 @@ unsafe fn fuzz(
|
||||
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 {
|
||||
(|state: Option<StdState<_, _, _, _, _>>,
|
||||
mut mgr: LlmpRestartingEventManager<_, _, _, _>,
|
||||
_core_id| {
|
||||
let gum = unsafe { Gum::obtain() };
|
||||
|
||||
let coverage = CoverageRuntime::new();
|
||||
|
||||
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()
|
||||
.configuration(EventConfig::from_name(&configuration))
|
||||
.configuration(EventConfig::AlwaysUnique)
|
||||
.shmem_provider(shmem_provider)
|
||||
.monitor(monitor)
|
||||
.run_client(&mut run_client)
|
||||
.cores(cores)
|
||||
.broker_port(broker_port)
|
||||
.stdout_file(stdout_file)
|
||||
.remote_broker_addr(broker_addr)
|
||||
.cores(&options.cores)
|
||||
.broker_port(options.broker_port)
|
||||
.stdout_file(Some(&options.stdout))
|
||||
.remote_broker_addr(options.remote_broker_addr)
|
||||
.build()
|
||||
.launch()
|
||||
}
|
||||
|
@ -64,6 +64,7 @@
|
||||
//!```
|
||||
|
||||
use clap::{App, AppSettings, IntoApp, Parser};
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "frida_cli")]
|
||||
use std::error;
|
||||
use std::net::SocketAddr;
|
||||
@ -102,7 +103,7 @@ fn parse_instrumentation_location(
|
||||
}
|
||||
|
||||
/// Top-level container for cli options/arguments/subcommands
|
||||
#[derive(Parser, Debug)]
|
||||
#[derive(Parser, Clone, Debug, Serialize, Deserialize)]
|
||||
#[clap(
|
||||
setting(AppSettings::ArgRequiredElseHelp),
|
||||
setting(AppSettings::SubcommandPrecedenceOverArg),
|
||||
@ -122,10 +123,21 @@ pub struct FuzzerOptions {
|
||||
#[clap(short, long, default_value = "/dev/null")]
|
||||
pub stdout: String,
|
||||
|
||||
/// the name of the configuration to use
|
||||
#[clap(short, long, default_value = "default configuration")]
|
||||
pub configuration: String,
|
||||
|
||||
/// enable Address Sanitizer (ASAN)
|
||||
#[clap(short = 'A', long, help_heading = "Fuzz Options")]
|
||||
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
|
||||
#[clap(short = 'I', long, help_heading = "Fuzz Options", default_value = "0")]
|
||||
pub iterations: usize,
|
||||
@ -139,6 +151,21 @@ pub struct FuzzerOptions {
|
||||
#[clap(last = true, name = "HARNESS_ARGS")]
|
||||
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
|
||||
#[cfg_attr(
|
||||
feature = "frida_cli",
|
||||
@ -150,6 +177,13 @@ pub struct FuzzerOptions {
|
||||
)]
|
||||
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
|
||||
#[cfg(feature = "frida_cli")]
|
||||
#[clap(short, long, help_heading = "ASAN Options")]
|
||||
|
@ -5,6 +5,7 @@ use alloc::{
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(any(unix, all(windows, feature = "std")))]
|
||||
use crate::Error;
|
||||
@ -106,7 +107,7 @@ pub fn dup2(fd: i32, device: i32) -> Result<(), Error> {
|
||||
}
|
||||
|
||||
/// Core ID
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct CoreId {
|
||||
/// The id of this core
|
||||
pub id: usize,
|
||||
@ -155,7 +156,7 @@ impl From<core_affinity::CoreId> for CoreId {
|
||||
}
|
||||
|
||||
/// A list of [`CoreId`] to use for fuzzing
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Cores {
|
||||
/// The original commandline used during parsing
|
||||
pub cmdline: String,
|
||||
@ -218,6 +219,13 @@ impl 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 {
|
||||
|
@ -19,7 +19,7 @@ cmplog = []
|
||||
cc = { version = "1.0", features = ["parallel"] }
|
||||
|
||||
[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"] }
|
||||
|
||||
nix = "0.23"
|
||||
|
@ -1,5 +1,6 @@
|
||||
use frida_gum::{PageProtection, RangeDetails};
|
||||
use hashbrown::HashMap;
|
||||
use libafl::bolts::cli::FuzzerOptions;
|
||||
use nix::{
|
||||
libc::memset,
|
||||
sys::mman::{mmap, MapFlags, ProtFlags},
|
||||
@ -22,17 +23,14 @@ use serde::{Deserialize, Serialize};
|
||||
use std::io;
|
||||
use std::{collections::BTreeMap, ffi::c_void};
|
||||
|
||||
use crate::{
|
||||
asan::errors::{AsanError, AsanErrors},
|
||||
FridaOptions,
|
||||
};
|
||||
use crate::asan::errors::{AsanError, AsanErrors};
|
||||
|
||||
/// An allocator wrapper with binary-only address sanitization
|
||||
#[derive(Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct Allocator {
|
||||
#[allow(dead_code)]
|
||||
options: FridaOptions,
|
||||
options: FuzzerOptions,
|
||||
page_size: usize,
|
||||
shadow_offset: usize,
|
||||
shadow_bit: usize,
|
||||
@ -78,7 +76,7 @@ impl Allocator {
|
||||
all(target_arch = "aarch64", target_os = "android")
|
||||
)))]
|
||||
#[must_use]
|
||||
pub fn new(_: FridaOptions) -> Self {
|
||||
pub fn new(_: FuzzerOptions) -> Self {
|
||||
todo!("Shadow region not yet supported for this platform!");
|
||||
}
|
||||
|
||||
@ -90,7 +88,7 @@ impl Allocator {
|
||||
))]
|
||||
#[must_use]
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn new(options: FridaOptions) -> Self {
|
||||
pub fn new(options: FuzzerOptions) -> Self {
|
||||
let ret = unsafe { sysconf(_SC_PAGESIZE) };
|
||||
assert!(
|
||||
ret >= 0,
|
||||
@ -259,9 +257,9 @@ impl Allocator {
|
||||
} else {
|
||||
size
|
||||
};
|
||||
if size > self.options.asan_max_allocation() {
|
||||
if size > self.options.max_allocation {
|
||||
#[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);
|
||||
}
|
||||
|
||||
@ -269,7 +267,7 @@ impl Allocator {
|
||||
}
|
||||
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();
|
||||
}
|
||||
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);
|
||||
metadata.is_malloc_zero = is_malloc_zero;
|
||||
metadata.size = size;
|
||||
if self.options.enable_asan_allocation_backtraces {
|
||||
if self.options.allocation_backtraces {
|
||||
metadata.allocation_site_backtrace = Some(Backtrace::new_unresolved());
|
||||
}
|
||||
metadata
|
||||
@ -311,7 +309,7 @@ impl Allocator {
|
||||
actual_size: rounded_up_size,
|
||||
..AllocationMetadata::default()
|
||||
};
|
||||
if self.options.enable_asan_allocation_backtraces {
|
||||
if self.options.allocation_backtraces {
|
||||
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);
|
||||
|
||||
metadata.freed = true;
|
||||
if self.options.enable_asan_allocation_backtraces {
|
||||
if self.options.allocation_backtraces {
|
||||
metadata.release_site_backtrace = Some(Backtrace::new_unresolved());
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ use core::{
|
||||
};
|
||||
use frida_gum::{ModuleDetails, NativePointer, RangeDetails};
|
||||
use hashbrown::HashMap;
|
||||
use libafl::bolts::AsSlice;
|
||||
use libafl::bolts::{cli::FuzzerOptions, AsSlice};
|
||||
use nix::sys::mman::{mmap, MapFlags, ProtFlags};
|
||||
use rangemap::RangeMap;
|
||||
|
||||
@ -58,7 +58,6 @@ use crate::{
|
||||
asan::errors::{AsanError, AsanErrors, AsanReadWriteError, ASAN_ERRORS},
|
||||
helper::FridaRuntime,
|
||||
utils::writer_register,
|
||||
FridaOptions,
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
@ -135,7 +134,7 @@ pub struct AsanRuntime {
|
||||
blob_check_mem_48bytes: Option<Box<[u8]>>,
|
||||
blob_check_mem_64bytes: Option<Box<[u8]>>,
|
||||
stalked_addresses: HashMap<usize, usize>,
|
||||
options: FridaOptions,
|
||||
options: FuzzerOptions,
|
||||
module_map: Option<ModuleMap>,
|
||||
suppressed_addresses: Vec<usize>,
|
||||
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.module_map = Some(ModuleMap::new_from_names(modules_to_instrument));
|
||||
if let Some(suppressed_specifiers) = self.options.dont_instrument_locations() {
|
||||
for (module_name, offset) in suppressed_specifiers {
|
||||
if !self.options.dont_instrument.is_empty() {
|
||||
for (module_name, offset) in self.options.dont_instrument.clone() {
|
||||
let module_details = ModuleDetails::with_name(module_name).unwrap();
|
||||
let lib_start = module_details.range().base_address().0 as usize;
|
||||
self.suppressed_addresses.push(lib_start + offset);
|
||||
@ -288,9 +287,9 @@ impl FridaRuntime for AsanRuntime {
|
||||
impl AsanRuntime {
|
||||
/// Create a new `AsanRuntime`
|
||||
#[must_use]
|
||||
pub fn new(options: FridaOptions) -> AsanRuntime {
|
||||
pub fn new(options: FuzzerOptions) -> AsanRuntime {
|
||||
Self {
|
||||
check_for_leaks_enabled: options.asan_detect_leaks(),
|
||||
check_for_leaks_enabled: options.detect_leaks,
|
||||
current_report_impl: 0,
|
||||
allocator: Allocator::new(options.clone()),
|
||||
regs: [0; ASAN_SAVE_REGISTER_COUNT],
|
||||
|
@ -8,7 +8,7 @@ use color_backtrace::{default_output_stream, BacktracePrinter, Verbosity};
|
||||
use frida_gum::interceptor::Interceptor;
|
||||
use frida_gum::ModuleDetails;
|
||||
use libafl::{
|
||||
bolts::{ownedref::OwnedPtr, tuples::Named},
|
||||
bolts::{cli::FuzzerOptions, ownedref::OwnedPtr, tuples::Named},
|
||||
corpus::Testcase,
|
||||
events::EventFirer,
|
||||
executors::ExitKind,
|
||||
@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::io::Write;
|
||||
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)]
|
||||
pub(crate) struct AsanReadWriteError {
|
||||
@ -94,14 +94,14 @@ impl AsanError {
|
||||
#[allow(clippy::unsafe_derive_deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, SerdeAny)]
|
||||
pub struct AsanErrors {
|
||||
options: FridaOptions,
|
||||
options: FuzzerOptions,
|
||||
errors: Vec<AsanError>,
|
||||
}
|
||||
|
||||
impl AsanErrors {
|
||||
/// Creates a new `AsanErrors` struct
|
||||
#[must_use]
|
||||
pub fn new(options: FridaOptions) -> Self {
|
||||
pub fn new(options: FuzzerOptions) -> Self {
|
||||
Self {
|
||||
options,
|
||||
errors: Vec::new(),
|
||||
@ -534,7 +534,7 @@ impl AsanErrors {
|
||||
};
|
||||
|
||||
#[allow(clippy::manual_assert)]
|
||||
if !self.options.asan_continue_after_error() {
|
||||
if !self.options.continue_on_error {
|
||||
panic!("ASAN: Crashing target!");
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use libafl::{
|
||||
bolts::tuples::MatchFirstType,
|
||||
bolts::{cli::FuzzerOptions, tuples::MatchFirstType},
|
||||
inputs::{HasTargetBytes, Input},
|
||||
Error,
|
||||
};
|
||||
@ -10,10 +10,8 @@ use libafl_targets::drcov::DrCovBasicBlock;
|
||||
#[cfg(all(feature = "cmplog", target_arch = "aarch64"))]
|
||||
use crate::cmplog_rt::CmpLogRuntime;
|
||||
use crate::coverage_rt::CoverageRuntime;
|
||||
#[cfg(windows)]
|
||||
use crate::FridaOptions;
|
||||
#[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")]
|
||||
use capstone::{
|
||||
arch::{self, BuildsCapstone},
|
||||
@ -124,7 +122,7 @@ pub struct FridaInstrumentationHelper<'a, RT> {
|
||||
capstone: Capstone,
|
||||
ranges: RangeMap<usize, (u16, String)>,
|
||||
module_map: ModuleMap,
|
||||
options: &'a FridaOptions,
|
||||
options: &'a FuzzerOptions,
|
||||
runtimes: RT,
|
||||
}
|
||||
|
||||
@ -170,13 +168,7 @@ where
|
||||
/// Constructor function to create a new [`FridaInstrumentationHelper`], given a `module_name`.
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
gum: &'a Gum,
|
||||
options: &'a FridaOptions,
|
||||
_harness_module_name: &str,
|
||||
modules_to_instrument: &'a [&str],
|
||||
runtimes: RT,
|
||||
) -> Self {
|
||||
pub fn new(gum: &'a Gum, options: &'a FuzzerOptions, runtimes: RT) -> Self {
|
||||
// workaround frida's frida-gum-allocate-near bug:
|
||||
#[cfg(unix)]
|
||||
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 {
|
||||
transformer: None,
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
@ -219,12 +221,12 @@ where
|
||||
.build()
|
||||
.expect("Failed to create Capstone object"),
|
||||
ranges: RangeMap::new(),
|
||||
module_map: ModuleMap::new_from_names(modules_to_instrument),
|
||||
module_map: ModuleMap::new_from_names(&modules_to_instrument),
|
||||
options,
|
||||
runtimes,
|
||||
};
|
||||
|
||||
if helper.options().stalker_enabled() {
|
||||
if options.cmplog || options.asan || !options.disable_coverage {
|
||||
for (i, module) in helper.module_map.values().iter().enumerate() {
|
||||
let range = module.range();
|
||||
let start = range.base_address().0 as usize;
|
||||
@ -233,8 +235,8 @@ where
|
||||
.ranges
|
||||
.insert(start..(start + range.size()), (i as u16, module.path()));
|
||||
}
|
||||
if let Some(suppressed_specifiers) = helper.options().dont_instrument_locations() {
|
||||
for (module_name, offset) in suppressed_specifiers {
|
||||
if !options.dont_instrument.is_empty() {
|
||||
for (module_name, offset) in options.dont_instrument.clone() {
|
||||
let module_details = ModuleDetails::with_name(module_name).unwrap();
|
||||
let lib_start = module_details.range().base_address().0 as usize;
|
||||
// println!("removing address: {:#x}", lib_start + offset);
|
||||
@ -354,7 +356,7 @@ where
|
||||
helper.transformer = Some(transformer);
|
||||
helper
|
||||
.runtimes
|
||||
.init_all(gum, &helper.ranges, modules_to_instrument);
|
||||
.init_all(gum, &helper.ranges, &modules_to_instrument);
|
||||
}
|
||||
helper
|
||||
}
|
||||
@ -402,7 +404,7 @@ where
|
||||
|
||||
/// If stalker is enabled
|
||||
pub fn stalker_enabled(&self) -> bool {
|
||||
self.options.stalker_enabled()
|
||||
self.options.cmplog || self.options.asan || !self.options.disable_coverage
|
||||
}
|
||||
|
||||
/// Pointer to coverage map
|
||||
@ -423,7 +425,7 @@ where
|
||||
|
||||
/// Return the ref to options
|
||||
#[inline]
|
||||
pub fn options(&self) -> &FridaOptions {
|
||||
pub fn options(&self) -> &FuzzerOptions {
|
||||
self.options
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user