Fuzzing
One of the use cases of concolic execution, which is demonstrated to be effective, is hybrid fuzzing, in which fuzzing is aided with solver-found inputs generated by symbolic execution to take certain paths inside the program that other techniques are inefficient to find.
Leaf is aimed to be suitable for this purpose and comes with a built-in support for LibAFL,
a customizable fuzzing framework with modern architecture written in Rust.
Crate libafl_leaf
contains facilities for using Leaf-instrumented programs with
the fuzzers written using this library.
Hybrid Fuzzing for libFuzzer
In an abstract manner, hybrid fuzzing for LibAFL-based fuzzers is achievable using a stage that generates diverging inputs from the current test case. This stage should perform the concolic execution using the current test case to derive the diverging inputs and offer them to the fuzzer for evaluation. Thus, the following steps are presumable for an execution-based concolic executor like Leaf.
- Build an executable equivalent to the fuzz target, which is suitable for concolic execution.
- Define a mutator stage that runs the built executable and obtains new inputs.
- Add the stage to the fuzzer.
The mentioned ingredients are provided by Leaf;
leafc
instruments your target program,
leafo_onetime
helps with collecting the diverging inputs,
and libafl_leaf
provides the stage.
As libFuzzer
(through cargo-fuzz
) is one the most-used tools to perform fuzzing
for Rust projects, a rudimentary support is also provided for harnesses
written based on libfuzzer-sys
to upgrade them to a hybrid fuzzer.
It is developed as an extension of LibAFL's implementation of libFuzzer
, so
the same instructions and options
apply.
Recipe
With an understanding of the general procedure above, you can follow the instruction below to upgrade your existing fuzzer to a hybrid one.
- Prerequisites
-
The one-time orchestrator (
leafo_onetime
) is installed in your environment. If not, install it similarly toleafc
using the following command in Leaf's root folder.leaf$ cargo install --path ./orchestrator
-
You have a fuzzer written using
libfuzzer-sys
. We assume it is namedfuzz_target_1
and has the following template.#![allow(unused)] #![no_main] fn main() { use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { // fuzzed code goes here }); }
-
-
Replace
libfuzzer-sys
source to Leaf's implementation inCargo.toml
of your fuzz project.# From libfuzzer-sys = { version = "...", features = ["your", "features", "here"] } # To libfuzzer-sys = { git = "https://github.com/sfu-rsl/leaf.git", package = "libafl_libfuzzer", features = ["your", "features", "here"]}
-
Change
fuzz_target
macro invocation tohybrid_fuzz_target
, and makeno_main
attribute conditional based on compilation withleafc
.#![allow(unused)] #![cfg_attr(not(leafc), no_main)] fn main() { use libfuzzer_sys::hybrid_fuzz_target; hybrid_fuzz_target!(|data: &[u8]| { // fuzzed code goes here }); }
(
hybrid_fuzz_target
additionally writes a program with amain
function that reads the whole standard input, marks it as symbolic, and passes to the closure.) -
Build your fuzz target with
leafc
in a separate cargo target directory like below.fuzz$ RUSTC=leafc cargo build --bin fuzz_target_1 --target-dir ./target/leaf
-
Build your fuzzer normally, e.g.,
fuzz$ cargo fuzz build fuzz_target_1
-
Run your fuzzer with the additional argument
conc_program
which points to the instrumented executable built usingleafc
.fuzz$ cargo fuzz run fuzz_target_1 -- -conc_program=./target/leaf/debug/fuzz_target_1
Please refer to the technical documentation for further details about the components and steps mentioned above.