diff --git a/docs/.gitignore b/docs/.gitignore index 7585238efe..f1d05dc5c9 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1 +1,2 @@ book +!listings/**/* diff --git a/docs/listings/baby_fuzzer/listing-01/Cargo.toml b/docs/listings/baby_fuzzer/listing-01/Cargo.toml new file mode 100644 index 0000000000..273da5b64d --- /dev/null +++ b/docs/listings/baby_fuzzer/listing-01/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "baby_fuzzer" +version = "0.1.0" +authors = ["Your Name "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/docs/listings/baby_fuzzer/listing-01/src/main.rs b/docs/listings/baby_fuzzer/listing-01/src/main.rs new file mode 100644 index 0000000000..e7a11a969c --- /dev/null +++ b/docs/listings/baby_fuzzer/listing-01/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/docs/listings/baby_fuzzer/listing-02/Cargo.toml b/docs/listings/baby_fuzzer/listing-02/Cargo.toml new file mode 100644 index 0000000000..a92d9c7aac --- /dev/null +++ b/docs/listings/baby_fuzzer/listing-02/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "baby_fuzzer" +version = "0.1.0" +authors = ["Your Name "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libafl = { path = "path/to/libafl/" } + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +lto = true +codegen-units = 1 +opt-level = 3 +debug = true diff --git a/docs/listings/baby_fuzzer/listing-02/src/main.rs b/docs/listings/baby_fuzzer/listing-02/src/main.rs new file mode 100644 index 0000000000..e7a11a969c --- /dev/null +++ b/docs/listings/baby_fuzzer/listing-02/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/docs/listings/baby_fuzzer/listing-03/Cargo.toml b/docs/listings/baby_fuzzer/listing-03/Cargo.toml new file mode 100644 index 0000000000..e39d872ecb --- /dev/null +++ b/docs/listings/baby_fuzzer/listing-03/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "baby_fuzzer" +version = "0.1.0" +authors = ["Your Name "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libafl = { path = "path/to/libafl/" } + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +lto = true +codegen-units = 1 +opt-level = 3 +debug = true + +[features] +panic = [] diff --git a/docs/listings/baby_fuzzer/listing-03/src/main.rs b/docs/listings/baby_fuzzer/listing-03/src/main.rs new file mode 100644 index 0000000000..b6396a4987 --- /dev/null +++ b/docs/listings/baby_fuzzer/listing-03/src/main.rs @@ -0,0 +1,25 @@ +extern crate libafl; +use libafl::{ + bolts::AsSlice, + executors::ExitKind, + inputs::{BytesInput, HasTargetBytes}, +}; + +fn main() { + let mut harness = |input: &BytesInput| { + let target = input.target_bytes(); + let buf = target.as_slice(); + if buf.len() > 0 && buf[0] == 'a' as u8 { + if buf.len() > 1 && buf[1] == 'b' as u8 { + if buf.len() > 2 && buf[2] == 'c' as u8 { + panic!("=)"); + } + } + } + ExitKind::Ok + }; + // To test the panic: + let input = BytesInput::new(Vec::from("abc")); + #[cfg(feature = "panic")] + harness(&input); +} diff --git a/docs/listings/baby_fuzzer/listing-04/Cargo.toml b/docs/listings/baby_fuzzer/listing-04/Cargo.toml new file mode 100644 index 0000000000..e39d872ecb --- /dev/null +++ b/docs/listings/baby_fuzzer/listing-04/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "baby_fuzzer" +version = "0.1.0" +authors = ["Your Name "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libafl = { path = "path/to/libafl/" } + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +lto = true +codegen-units = 1 +opt-level = 3 +debug = true + +[features] +panic = [] diff --git a/docs/listings/baby_fuzzer/listing-04/src/main.rs b/docs/listings/baby_fuzzer/listing-04/src/main.rs new file mode 100644 index 0000000000..62498db481 --- /dev/null +++ b/docs/listings/baby_fuzzer/listing-04/src/main.rs @@ -0,0 +1,85 @@ +/* ANCHOR: use */ +extern crate libafl; + +use libafl::{ + bolts::{current_nanos, rands::StdRand, AsSlice}, + corpus::{InMemoryCorpus, OnDiskCorpus}, + events::SimpleEventManager, + executors::{inprocess::InProcessExecutor, ExitKind}, + fuzzer::StdFuzzer, + generators::RandPrintablesGenerator, + inputs::{BytesInput, HasTargetBytes}, + monitors::SimpleMonitor, + schedulers::QueueScheduler, + state::StdState, +}; +use std::path::PathBuf; +/* ANCHOR_END: use */ + +fn main() { + let mut harness = |input: &BytesInput| { + let target = input.target_bytes(); + let buf = target.as_slice(); + if buf.len() > 0 && buf[0] == 'a' as u8 { + if buf.len() > 1 && buf[1] == 'b' as u8 { + if buf.len() > 2 && buf[2] == 'c' as u8 { + panic!("=)"); + } + } + } + ExitKind::Ok + }; + // To test the panic: + let input = BytesInput::new(Vec::from("abc")); + #[cfg(feature = "panic")] + harness(&input); + + /* ANCHOR: state */ + // 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(), + &mut (), + &mut (), + ) + .unwrap(); + /* ANCHOR_END: state */ + + /* ANCHOR: event_manager */ + // The Monitor trait defines how the fuzzer stats are displayed to the user + let mon = SimpleMonitor::new(|s| println!("{s}")); + + // The event manager handles 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(mon); + /* ANCHOR_END: event_manager */ + + /* ANCHOR: scheduler_fuzzer */ + // 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, (), ()); + /* ANCHOR_END: scheduler_fuzzer */ + + /* ANCHOR: executor */ + // Create the executor for an in-process function + let mut executor = InProcessExecutor::new(&mut harness, (), &mut fuzzer, &mut state, &mut mgr) + .expect("Failed to create the Executor"); + /* ANCHOR_END: executor */ + + /* ANCHOR: generator */ + // Generator of printable bytearrays of max size 32 + let mut generator = RandPrintablesGenerator::new(32); + + // Generate 8 initial inputs + state + .generate_initial_inputs(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8) + .expect("Failed to generate the initial corpus"); + /* ANCHOR_END: generator */ +} diff --git a/docs/listings/baby_fuzzer/listing-05/Cargo.toml b/docs/listings/baby_fuzzer/listing-05/Cargo.toml new file mode 100644 index 0000000000..e39d872ecb --- /dev/null +++ b/docs/listings/baby_fuzzer/listing-05/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "baby_fuzzer" +version = "0.1.0" +authors = ["Your Name "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libafl = { path = "path/to/libafl/" } + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +lto = true +codegen-units = 1 +opt-level = 3 +debug = true + +[features] +panic = [] diff --git a/docs/listings/baby_fuzzer/listing-05/src/main.rs b/docs/listings/baby_fuzzer/listing-05/src/main.rs new file mode 100644 index 0000000000..930cebd0be --- /dev/null +++ b/docs/listings/baby_fuzzer/listing-05/src/main.rs @@ -0,0 +1,115 @@ +/* ANCHOR: use */ +extern crate libafl; + +use libafl::{ + bolts::{current_nanos, rands::StdRand, tuples::tuple_list, AsSlice}, + corpus::{InMemoryCorpus, OnDiskCorpus}, + events::SimpleEventManager, + executors::{inprocess::InProcessExecutor, ExitKind}, + feedbacks::{CrashFeedback, MaxMapFeedback}, + fuzzer::StdFuzzer, + generators::RandPrintablesGenerator, + inputs::{BytesInput, HasTargetBytes}, + monitors::SimpleMonitor, + observers::StdMapObserver, + schedulers::QueueScheduler, + state::StdState, +}; +use std::path::PathBuf; +/* ANCHOR_END: use */ + +/* ANCHOR: signals */ +// Coverage map with explicit assignments due to the lack of instrumentation +static mut SIGNALS: [u8; 16] = [0; 16]; + +fn signals_set(idx: usize) { + unsafe { SIGNALS[idx] = 1 }; +} + +fn main() { + // The closure that we want to fuzz + let mut harness = |input: &BytesInput| { + let target = input.target_bytes(); + let buf = target.as_slice(); + signals_set(0); // set SIGNALS[0] + if buf.len() > 0 && buf[0] == 'a' as u8 { + signals_set(1); // set SIGNALS[1] + if buf.len() > 1 && buf[1] == 'b' as u8 { + signals_set(2); // set SIGNALS[2] + if buf.len() > 2 && buf[2] == 'c' as u8 { + panic!("=)"); + } + } + } + ExitKind::Ok + }; + /* ANCHOR_END: signals */ + // To test the panic: + let input = BytesInput::new(Vec::from("abc")); + #[cfg(feature = "panic")] + harness(&input); + + /* ANCHOR: observer */ + // Create an observation channel using the signals map + let observer = unsafe { StdMapObserver::new("signals", &mut SIGNALS) }; + /* ANCHOR_END: observer */ + + /* ANCHOR: state_with_feedback_and_objective */ + // Feedback to rate the interestingness of an input + let mut feedback = MaxMapFeedback::new(&observer); + + // 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(), + &mut feedback, + &mut objective, + ) + .unwrap(); + /* ANCHOR_END: state_with_feedback_and_objective */ + + // The Monitor trait defines how the fuzzer stats are displayed to the user + let mon = SimpleMonitor::new(|s| println!("{s}")); + + // The event manager handles 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(mon); + + // A queue policy to get testcasess from the corpus + let scheduler = QueueScheduler::new(); + /* ANCHOR: state_with_feedback_and_objective */ + + // A fuzzer with feedbacks and a corpus scheduler + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + /* ANCHOR_END: state_with_feedback_and_objective */ + + /* ANCHOR: executor_with_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"); + /* ANCHOR_END: executor_with_observer */ + + // Generator of printable bytearrays of max size 32 + let mut generator = RandPrintablesGenerator::new(32); + + // Generate 8 initial inputs + state + .generate_initial_inputs(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8) + .expect("Failed to generate the initial corpus"); + /* ANCHOR: signals */ +} +/* ANCHOR_END: signals */ diff --git a/docs/listings/baby_fuzzer/listing-06/Cargo.toml b/docs/listings/baby_fuzzer/listing-06/Cargo.toml new file mode 100644 index 0000000000..e39d872ecb --- /dev/null +++ b/docs/listings/baby_fuzzer/listing-06/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "baby_fuzzer" +version = "0.1.0" +authors = ["Your Name "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libafl = { path = "path/to/libafl/" } + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +lto = true +codegen-units = 1 +opt-level = 3 +debug = true + +[features] +panic = [] diff --git a/docs/listings/baby_fuzzer/listing-06/src/main.rs b/docs/listings/baby_fuzzer/listing-06/src/main.rs new file mode 100644 index 0000000000..16819e24f4 --- /dev/null +++ b/docs/listings/baby_fuzzer/listing-06/src/main.rs @@ -0,0 +1,115 @@ +/* ANCHOR: use */ +extern crate libafl; + +use libafl::{ + bolts::{current_nanos, rands::StdRand, tuples::tuple_list, AsSlice}, + corpus::{InMemoryCorpus, OnDiskCorpus}, + events::SimpleEventManager, + executors::{inprocess::InProcessExecutor, ExitKind}, + feedbacks::{CrashFeedback, MaxMapFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + generators::RandPrintablesGenerator, + inputs::{BytesInput, HasTargetBytes}, + monitors::SimpleMonitor, + mutators::scheduled::{havoc_mutations, StdScheduledMutator}, + observers::StdMapObserver, + schedulers::QueueScheduler, + stages::mutational::StdMutationalStage, + state::StdState, +}; +use std::path::PathBuf; +/* ANCHOR_END: use */ + +// Coverage map with explicit assignments due to the lack of instrumentation +static mut SIGNALS: [u8; 16] = [0; 16]; + +fn signals_set(idx: usize) { + unsafe { SIGNALS[idx] = 1 }; +} + +fn main() { + // The closure that we want to fuzz + let mut harness = |input: &BytesInput| { + let target = input.target_bytes(); + let buf = target.as_slice(); + signals_set(0); // set SIGNALS[0] + if buf.len() > 0 && buf[0] == 'a' as u8 { + signals_set(1); // set SIGNALS[1] + if buf.len() > 1 && buf[1] == 'b' as u8 { + signals_set(2); // set SIGNALS[2] + if buf.len() > 2 && buf[2] == 'c' as u8 { + panic!("=)"); + } + } + } + ExitKind::Ok + }; + // To test the panic: + let input = BytesInput::new(Vec::from("abc")); + #[cfg(feature = "panic")] + harness(&input); + + // Create an observation channel using the signals map + let observer = unsafe { StdMapObserver::new("signals", &mut SIGNALS) }; + + // Feedback to rate the interestingness of an input + let mut feedback = MaxMapFeedback::new(&observer); + + // 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(), + &mut feedback, + &mut objective, + ) + .unwrap(); + + // The Monitor trait defines how the fuzzer stats are displayed to the user + let mon = SimpleMonitor::new(|s| println!("{s}")); + + // The event manager handles 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(mon); + + // 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); + + // 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"); + + // Generator of printable bytearrays of max size 32 + let mut generator = RandPrintablesGenerator::new(32); + + // Generate 8 initial inputs + state + .generate_initial_inputs(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8) + .expect("Failed to generate the initial corpus"); + + /* ANCHOR: mutational_stage */ + // Setup a mutational stage with a basic bytes mutator + let mutator = StdScheduledMutator::new(havoc_mutations()); + let mut stages = tuple_list!(StdMutationalStage::new(mutator)); + + fuzzer + .fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr) + .expect("Error in the fuzzing loop"); + /* ANCHOR_END: mutational_stage */ +} diff --git a/docs/src/baby_fuzzer/baby_fuzzer.md b/docs/src/baby_fuzzer/baby_fuzzer.md index 9adf45e4a8..50a7f515eb 100644 --- a/docs/src/baby_fuzzer/baby_fuzzer.md +++ b/docs/src/baby_fuzzer/baby_fuzzer.md @@ -17,7 +17,7 @@ You can find a complete version of this tutorial as an example fuzzer in [`fuzze We use cargo to create a new Rust project with LibAFL as a dependency. -```sh +```console $ cargo new baby_fuzzer $ cd baby_fuzzer ``` @@ -25,18 +25,11 @@ $ cd baby_fuzzer The generated `Cargo.toml` looks like the following: ```toml -[package] -name = "baby_fuzzer" -version = "0.1.0" -authors = ["Your Name "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] +{{#include ../../listings/baby_fuzzer/listing-01/Cargo.toml}} ``` In order to use LibAFl we must add it as dependency adding `libafl = { path = "path/to/libafl/" }` under `[dependencies]`. +That path actually needs to point to the `libafl` directory within the cloned repo, not the root of the repo itself. You can use the LibAFL version from [crates.io](https://crates.io/crates/libafl) if you want, in this case, you have to use `libafl = "*"` to get the latest version (or set it to the current version). As we are going to fuzz Rust code, we want that a panic does not simply cause the program to exit, but raise an `abort` that can then be caught by the fuzzer. @@ -47,28 +40,10 @@ Alongside this setting, we add some optimization flags for the compilation, when The final `Cargo.toml` should look similar to the following: ```toml -[package] -name = "baby_fuzzer" -version = "0.1.0" -authors = ["Your Name "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -libafl = { path = "path/to/libafl/" } - -[profile.dev] -panic = "abort" - -[profile.release] -panic = "abort" -lto = true -codegen-units = 1 -opt-level = 3 -debug = true +{{#include ../../listings/baby_fuzzer/listing-02/Cargo.toml}} ``` + ## The function under test Opening `src/main.rs`, we have an empty `main` function. @@ -76,52 +51,32 @@ To start, we create the closure that we want to fuzz. It takes a buffer as input `ExitKind` is used to inform the fuzzer about the harness' exit status. ```rust -extern crate libafl; -use libafl::{ - bolts::AsSlice, - inputs::{BytesInput, HasTargetBytes}, - executors::ExitKind, -}; - -fn main(){ - let mut harness = |input: &BytesInput| { - let target = input.target_bytes(); - let buf = target.as_slice(); - if buf.len() > 0 && buf[0] == 'a' as u8 { - if buf.len() > 1 && buf[1] == 'b' as u8 { - if buf.len() > 2 && buf[2] == 'c' as u8 { - panic!("=)"); - } - } - } - ExitKind::Ok - }; - // To test the panic: - let input = BytesInput::new(Vec::from("abc")); - #[cfg(feature = "panic")] - harness(&input); -} +{{#rustdoc_include ../../listings/baby_fuzzer/listing-03/src/main.rs}} ``` +To test the crash manually, you can add a feature in `Cargo.toml` that enables the call that triggers the panic: + +```toml +{{#include ../../listings/baby_fuzzer/listing-03/Cargo.toml:22:23}} +``` + +And then run the program with that feature activated: + +```console +$ cargo run -F panic +``` + +And you should see the program crash as expected. + ## Generating and running some tests One of the main components that a LibAFL-based fuzzer uses is the State, a container of the data that will evolve during the fuzzing process. It includes all state, such as the Corpus of inputs, the current RNG state, and potential Metadata for the testcases and run. In our `main` we create a basic State instance like the following: -```rust,ignore -// 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(), - &mut (), - &mut () -).unwrap(); + +```rust +{{#rustdoc_include ../../listings/baby_fuzzer/listing-04/src/main.rs:state}} ``` - The first parameter is a random number generator, that is part of the fuzzer state, in this case, we use the default one `StdRand`, but you can choose a different one. We seed it with the current nanoseconds. @@ -134,38 +89,21 @@ let mut state = StdState::new( Another required component is the **EventManager**. It handles some events such as the addition of a testcase to the corpus during the fuzzing process. For our purpose, we use the simplest one that just displays the information about these events to the user using a `Monitor` instance. -```rust,ignore -// The Monitor trait defines how the fuzzer stats are displayed to the user -let mon = 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(mon); +```rust +{{#rustdoc_include ../../listings/baby_fuzzer/listing-04/src/main.rs:event_manager}} ``` In addition, we have the **Fuzzer**, an entity that contains some actions that alter the State. One of these actions is the scheduling of the testcases to the fuzzer using a **Scheduler**. We create it as `QueueScheduler`, a scheduler that serves testcases to the fuzzer in a FIFO fashion. -```rust,ignore -// 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, (), ()); +```rust +{{#rustdoc_include ../../listings/baby_fuzzer/listing-04/src/main.rs:scheduler_fuzzer}} ``` Last but not least, we need an **Executor** that is the entity responsible to run our program under test. In this example, we want to run the harness function in-process (without forking off a child, for example), and so we use the `InProcessExecutor`. -```rust,ignore -// Create the executor for an in-process function -let mut executor = InProcessExecutor::new( - &mut harness, - (), - &mut fuzzer, - &mut state, - &mut mgr, -) -.expect("Failed to create the Executor"); +```rust +{{#rustdoc_include ../../listings/baby_fuzzer/listing-04/src/main.rs:executor}} ``` It takes a reference to the harness, the state, and the event manager. We will discuss the second parameter later. @@ -175,41 +113,19 @@ Now we have the 4 major entities ready for running our tests, but we still canno For this purpose, we use a **Generator**, `RandPrintablesGenerator` that generates a string of printable bytes. -```rust,ignore -use libafl::generators::RandPrintablesGenerator; - -// Generator of printable bytearrays of max size 32 -let mut generator = RandPrintablesGenerator::new(32); - -// Generate 8 initial inputs -state - .generate_initial_inputs(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8) - .expect("Failed to generate the initial corpus".into()); +```rust +{{#rustdoc_include ../../listings/baby_fuzzer/listing-04/src/main.rs:generator}} ``` Now you can prepend the necessary `use` directives to your main.rs and compile the fuzzer. ```rust -extern crate libafl; - -use std::path::PathBuf; -use libafl::{ - bolts::{AsSlice, current_nanos, rands::StdRand}, - corpus::{InMemoryCorpus, OnDiskCorpus}, - events::SimpleEventManager, - executors::{inprocess::InProcessExecutor, ExitKind}, - fuzzer::StdFuzzer, - generators::RandPrintablesGenerator, - inputs::{BytesInput, HasTargetBytes}, - monitors::SimpleMonitor, - schedulers::QueueScheduler, - state::StdState, -}; +{{#rustdoc_include ../../listings/baby_fuzzer/listing-04/src/main.rs:use}} ``` When running, you should see something similar to: -```sh +```console $ cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.04s Running `target/debug/baby_fuzzer` @@ -228,57 +144,19 @@ We represent such map as a `static mut` variable. As we don't rely on any instrumentation engine, we have to manually track the satisfied conditions by `signals_set` in our harness: ```rust -extern crate libafl; -use libafl::{ - bolts::AsSlice, - inputs::{BytesInput, HasTargetBytes}, - executors::ExitKind, -}; - -// Coverage map with explicit assignments due to the lack of instrumentation -static mut SIGNALS: [u8; 16] = [0; 16]; - -fn signals_set(idx: usize) { - unsafe { SIGNALS[idx] = 1 }; -} - -// The closure that we want to fuzz -let mut harness = |input: &BytesInput| { - let target = input.target_bytes(); - let buf = target.as_slice(); - signals_set(0); // set SIGNALS[0] - if buf.len() > 0 && buf[0] == 'a' as u8 { - signals_set(1); // set SIGNALS[1] - if buf.len() > 1 && buf[1] == 'b' as u8 { - signals_set(2); // set SIGNALS[2] - if buf.len() > 2 && buf[2] == 'c' as u8 { - panic!("=)"); - } - } - } - ExitKind::Ok -}; +{{#rustdoc_include ../../listings/baby_fuzzer/listing-05/src/main.rs:signals}} ``` The observer can be created directly from the `SIGNALS` map, in the following way: -```rust,ignore -// Create an observation channel using the signals map -let observer = StdMapObserver::new("signals", unsafe { &mut SIGNALS }); +```rust +{{#rustdoc_include ../../listings/baby_fuzzer/listing-05/src/main.rs:observer}} ``` The observers are usually kept in the corresponding executor as they keep track of information that is valid for just one run. We have then to modify our InProcessExecutor creation to include the observer as follows: -```rust,ignore -// 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".into()); +```rust +{{#rustdoc_include ../../listings/baby_fuzzer/listing-05/src/main.rs:executor_with_observer}} ``` Now that the fuzzer can observe which condition is satisfied, we need a way to rate an input as interesting (i.e. worth of addition to the corpus) based on this observation. Here comes the notion of Feedback. @@ -291,45 +169,19 @@ We use `MaxMapFeedback`, a feedback that implements a novelty search over the ma We need to update our State creation including the feedback state and the Fuzzer including the feedback and the objective: -```rust,ignore -extern crate libafl; -use libafl::{ - bolts::{current_nanos, rands::StdRand, tuples::tuple_list}, - corpus::{InMemoryCorpus, OnDiskCorpus}, - feedbacks::{MaxMapFeedback, CrashFeedback}, - fuzzer::StdFuzzer, - state::StdState, - observers::StdMapObserver, -}; - -// Feedback to rate the interestingness of an input -let mut feedback = MaxMapFeedback::new(&observer); - -// 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(), - &mut feedback, - &mut objective -).unwrap(); - -// ... - -// A fuzzer with feedbacks and a corpus scheduler -let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); +```rust +{{#rustdoc_include ../../listings/baby_fuzzer/listing-05/src/main.rs:state_with_feedback_and_objective}} +``` + +Once again, you need to add the necessary `use` directives for this to work properly: + +```rust +{{#rustdoc_include ../../listings/baby_fuzzer/listing-05/src/main.rs:use}} ``` ## The actual fuzzing -Now, after including the correct `use`, we can run the program, but the outcome is not so different from the previous one as the random generator does not take into account what we save as interesting in the corpus. To do that, we need to plug a Mutator. +Now, we can run the program, but the outcome is not so different from the previous one as the random generator does not take into account what we save as interesting in the corpus. To do that, we need to plug a Mutator. **Stages** perform actions on individual inputs, taken from the corpus. For instance, the `MutationalStage` executes the harness several times in a row, every time with mutated inputs. @@ -337,28 +189,20 @@ For instance, the `MutationalStage` executes the harness several times in a row, As the last step, we create a MutationalStage that uses a mutator inspired by the havoc mutator of AFL. ```rust,ignore -use libafl::{ - mutators::scheduled::{havoc_mutations, StdScheduledMutator}, - stages::mutational::StdMutationalStage, - fuzzer::Fuzzer, -}; +{{#rustdoc_include ../../listings/baby_fuzzer/listing-06/src/main.rs:mutational_stage}} +``` -// ... +`fuzz_loop` will request a testcase for each iteration to the fuzzer using the scheduler and then it will invoke the stage. -// Setup a mutational stage with a basic bytes mutator -let mutator = StdScheduledMutator::new(havoc_mutations()); -let mut stages = tuple_list!(StdMutationalStage::new(mutator)); +Again, we need to add the new `use` directives: -fuzzer - .fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr) - .expect("Error in the fuzzing loop"); +```rust,ignore +{{#rustdoc_include ../../listings/baby_fuzzer/listing-06/src/main.rs:use}} ``` -`fuzz_loop` will request a testcase for each iteration to the fuzzer using the scheduler and then it will invoke the stage. - After adding this code, we have a proper fuzzer, that can run and find the input that panics the function in less than a second. -```text +```console $ cargo run Compiling baby_fuzzer v0.1.0 (/home/andrea/Desktop/baby_fuzzer) Finished dev [unoptimized + debuginfo] target(s) in 1.56s