Skip to content

Commit

Permalink
Add TargetBytesConverter to allow Nautilus for ForkserverExecutor (AF…
Browse files Browse the repository at this point in the history
…Lplusplus#2630)

* Add TargetBytesConverter to allow Nautilus for ForkserverExecutor

* ci

* ci

* More

* fmt
  • Loading branch information
domenukk authored and riesentoaster committed Dec 11, 2024
1 parent 6bf34cb commit 916bec0
Show file tree
Hide file tree
Showing 14 changed files with 519 additions and 43 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ jobs:
- ./fuzzers/structure_aware/baby_fuzzer_multi
- ./fuzzers/structure_aware/baby_fuzzer_custom_input
- ./fuzzers/structure_aware/baby_fuzzer_nautilus
- ./fuzzers/structure_aware/forkserver_simple_nautilus

# In-process
- ./fuzzers/fuzz_anything/cargo_fuzz
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ vendor

.DS_Store
.env
.vscode

*.test
*.tmp
Expand Down
18 changes: 8 additions & 10 deletions fuzzers/structure_aware/baby_fuzzer_nautilus/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,9 @@ pub fn main() {
)
.unwrap();

if state
.metadata_map()
.get::<NautilusChunksMetadata>()
.is_none()
{
state.add_metadata(NautilusChunksMetadata::new("/tmp/".into()));
}
let _ = state.metadata_or_insert_with::<NautilusChunksMetadata>(|| {
NautilusChunksMetadata::new("/tmp/".into())
});

// The Monitor trait define how the fuzzer stats are reported to the user
let monitor = SimpleMonitor::new(|s| println!("{s}"));
Expand Down Expand Up @@ -139,9 +135,11 @@ pub fn main() {
*/

// Generate 8 initial inputs
state
.generate_initial_inputs_forced(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8)
.expect("Failed to generate the initial corpus");
if state.must_load_initial_inputs() {
state
.generate_initial_inputs_forced(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8)
.expect("Failed to generate the initial corpus");
}

// Setup a mutational stage with a basic bytes mutator
let mutator = StdScheduledMutator::with_max_stack_pow(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
forkserver_simple
24 changes: 24 additions & 0 deletions fuzzers/structure_aware/forkserver_simple_nautilus/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "forkserver_simple"
version = "0.13.2"
authors = ["tokatoka <tokazerkje@outlook.com>"]
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"
lto = true
codegen-units = 1
opt-level = 3

[dependencies]
clap = { version = "4.5.18", features = ["derive"] }
env_logger = "0.11.5"
libafl = { path = "../../../libafl", features = ["std", "derive"] }
libafl_bolts = { path = "../../../libafl_bolts" }
log = { version = "0.4.22", features = ["release_max_level_info"] }
nix = { version = "0.29.0", features = ["signal"] }
13 changes: 13 additions & 0 deletions fuzzers/structure_aware/forkserver_simple_nautilus/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Simple Forkserver Fuzzer

This is a simple example fuzzer to fuzz a executable instrumented by afl-cc.
## Usage
You can build this example by `cargo build --release`.
This downloads AFLplusplus/AFLplusplus and compiles the example harness program in src/program.c with afl-cc

## Run
After you build it you can run
`cp ./target/release/forkserver_simple .` to copy the fuzzer into this directory,
and you can run
`taskset -c 1 ./forkserver_simple ./target/release/program ./corpus/ -t 1000` to run the fuzzer.
`taskset` binds this process to a specific core to improve the throughput.
59 changes: 59 additions & 0 deletions fuzzers/structure_aware/forkserver_simple_nautilus/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use std::{
env,
path::Path,
process::{exit, Command},
};

const AFL_URL: &str = "https://github.com/AFLplusplus/AFLplusplus";

fn main() {
if cfg!(windows) {
println!("cargo:warning=No support for windows yet.");
exit(0);
}

env::remove_var("DEBUG");
let cwd = env::current_dir().unwrap().to_string_lossy().to_string();

let afl = format!("{}/AFLplusplus", &cwd);
let afl_cc = format!("{}/AFLplusplus/afl-cc", &cwd);

let afl_path = Path::new(&afl);
let afl_cc_path = Path::new(&afl_cc);

if !afl_path.is_dir() {
println!("cargo:warning=AFL++ not found, downloading...");
Command::new("git")
.arg("clone")
.arg(AFL_URL)
.status()
.unwrap();
}

if !afl_cc_path.is_file() {
let mut afl_cc_make = Command::new("make");
afl_cc_make.arg("all").current_dir(afl_path);
if let Ok(llvm_config) = env::var("LLVM_CONFIG") {
if !llvm_config.is_empty() {
afl_cc_make.env("LLVM_CONFIG", llvm_config);
}
}
afl_cc_make.status().unwrap();
}

let mut compile_command = Command::new(afl_cc_path);
compile_command
.args(["src/program.c", "-o"])
.arg(format!("{cwd}/target/release/program"));

if let Ok(llvm_config) = env::var("LLVM_CONFIG") {
if !llvm_config.is_empty() {
compile_command.env("LLVM_CONFIG", llvm_config);
}
}

compile_command.status().unwrap();

println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=src/");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
aaa
227 changes: 227 additions & 0 deletions fuzzers/structure_aware/forkserver_simple_nautilus/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
use core::time::Duration;
use std::path::PathBuf;

use clap::Parser;
use libafl::{
corpus::{InMemoryCorpus, OnDiskCorpus},
events::SimpleEventManager,
executors::{forkserver::ForkserverExecutor, HasObservers},
feedback_and_fast, feedback_or,
feedbacks::{
CrashFeedback, MaxMapFeedback, NautilusChunksMetadata, NautilusFeedback, TimeFeedback,
},
fuzzer::{Fuzzer, StdFuzzer},
generators::{NautilusContext, NautilusGenerator},
inputs::{NautilusInput, NautilusTargetBytesConverter},
monitors::SimpleMonitor,
mutators::{
NautilusRandomMutator, NautilusRecursionMutator, NautilusSpliceMutator,
StdScheduledMutator, Tokens,
},
observers::{CanTrack, HitcountsMapObserver, StdMapObserver, TimeObserver},
schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler},
stages::mutational::StdMutationalStage,
state::StdState,
HasMetadata,
};
use libafl_bolts::{
current_nanos,
rands::StdRand,
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
tuples::{tuple_list, Handled},
AsSliceMut, Truncate,
};
use nix::sys::signal::Signal;

/// The commandline args this fuzzer accepts
#[derive(Debug, Parser)]
#[command(
name = "forkserver_simple",
about = "This is a simple example fuzzer to fuzz a executable instrumented by afl-cc, using Nautilus grammar.",
author = "tokatoka <tokazerkje@outlook.com>, dmnk <domenukk@gmail.com>"
)]
struct Opt {
#[arg(
help = "The instrumented binary we want to fuzz",
name = "EXEC",
required = true
)]
executable: String,

#[arg(
help = "Timeout for each individual execution, in milliseconds",
short = 't',
long = "timeout",
default_value = "1200"
)]
timeout: u64,

#[arg(
help = "If not set, the child's stdout and stderror will be redirected to /dev/null",
short = 'd',
long = "debug-child",
default_value = "false"
)]
debug_child: bool,

#[arg(
help = "Arguments passed to the target",
name = "arguments",
num_args(1..),
allow_hyphen_values = true,
)]
arguments: Vec<String>,

#[arg(
help = "Signal used to stop child",
short = 's',
long = "signal",
value_parser = str::parse::<Signal>,
default_value = "SIGKILL"
)]
signal: Signal,

#[arg(help = "The nautilus grammar file", short)]
grammar: PathBuf,
}

#[allow(clippy::similar_names)]
pub fn main() {
env_logger::init();
const MAP_SIZE: usize = 65536;

let opt = Opt::parse();

let mut shmem_provider = UnixShMemProvider::new().unwrap();

// The coverage map shared between observer and executor
let mut shmem = shmem_provider.new_shmem(MAP_SIZE).unwrap();
// let the forkserver know the shmid
shmem.write_to_env("__AFL_SHM_ID").unwrap();
let shmem_buf = shmem.as_slice_mut();

// Create an observation channel using the signals map
let edges_observer = unsafe {
HitcountsMapObserver::new(StdMapObserver::new("shared_mem", shmem_buf)).track_indices()
};

// Create an observation channel to keep track of the execution time
let time_observer = TimeObserver::new("time");

let context = NautilusContext::from_file(15, opt.grammar);

// Feedback to rate the interestingness of an input
// This one is composed by two Feedbacks in OR
let mut feedback = feedback_or!(
// New maximization map feedback linked to the edges observer and the feedback state
MaxMapFeedback::new(&edges_observer),
// Time feedback, this one does not need a feedback state
TimeFeedback::new(&time_observer),
// Nautilus context
NautilusFeedback::new(&context),
);

// A feedback to choose if an input is a solution or not
// We want to do the same crash deduplication that AFL does
let mut objective = feedback_and_fast!(
// Must be a crash
CrashFeedback::new(),
// Take it only if trigger new coverage over crashes
// Uses `with_name` to create a different history from the `MaxMapFeedback` in `feedback` above
MaxMapFeedback::with_name("mapfeedback_metadata_objective", &edges_observer)
);

// 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::<NautilusInput>::new(),
// Corpus in which we store solutions (crashes in this example),
// on disk so the user can get them after stopping the fuzzer
OnDiskCorpus::new(PathBuf::from("./crashes")).unwrap(),
// States of the feedbacks.
// The feedbacks can report the data that should persist in the State.
&mut feedback,
// Same for objective feedbacks
&mut objective,
)
.unwrap();

let _ = state.metadata_or_insert_with::<NautilusChunksMetadata>(|| {
NautilusChunksMetadata::new("/tmp/".into())
});

// The Monitor trait define how the fuzzer stats are reported to the user
let monitor = SimpleMonitor::new(|s| println!("{s}"));

// The event manager handle the various events generated during the fuzzing loop
// such as the notification of the addition of a new item to the corpus
let mut mgr = SimpleEventManager::new(monitor);

// A minimization+queue policy to get testcasess from the corpus
let scheduler = IndexesLenTimeMinimizerScheduler::new(&edges_observer, QueueScheduler::new());

// A fuzzer with feedbacks and a corpus scheduler
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);

// If we should debug the child
let debug_child = opt.debug_child;

// Create the executor for the forkserver
let args = opt.arguments;

let observer_ref = edges_observer.handle();

let mut tokens = Tokens::new();
let mut executor = ForkserverExecutor::builder()
.program(opt.executable)
.debug_child(debug_child)
.shmem_provider(&mut shmem_provider)
.autotokens(&mut tokens)
.parse_afl_cmdline(args)
.coverage_map_size(MAP_SIZE)
.timeout(Duration::from_millis(opt.timeout))
.kill_signal(opt.signal)
.target_bytes_converter(NautilusTargetBytesConverter::new(&context))
.build(tuple_list!(time_observer, edges_observer))
.unwrap();

if let Some(dynamic_map_size) = executor.coverage_map_size() {
executor.observers_mut()[&observer_ref]
.as_mut()
.truncate(dynamic_map_size);
}

let mut generator = NautilusGenerator::new(&context);

if state.must_load_initial_inputs() {
state
.generate_initial_inputs(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8)
.expect("Failed to generate inputs");
}

state.add_metadata(tokens);

// Setup a mutational stage with a basic bytes mutator
let mutator = StdScheduledMutator::with_max_stack_pow(
tuple_list!(
NautilusRandomMutator::new(&context),
NautilusRandomMutator::new(&context),
NautilusRandomMutator::new(&context),
NautilusRandomMutator::new(&context),
NautilusRandomMutator::new(&context),
NautilusRandomMutator::new(&context),
NautilusRecursionMutator::new(&context),
NautilusSpliceMutator::new(&context),
NautilusSpliceMutator::new(&context),
NautilusSpliceMutator::new(&context),
),
2,
);
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");
}
Loading

0 comments on commit 916bec0

Please sign in to comment.