Introduction

Welcome to the user guide for fuzzer. This guide is designed to help you understand how to install, configure, and effectively use the fuzzer to test your applications for bugs and vulnerabilities.

The fuzzer is built with flexibility and extensibility in mind, supporting a variety of fuzzing modes, input formats, and advanced features such as coverage-guided fuzzing, corpus management, and comparison logging. Whether you're new to fuzzing or an experienced security researcher, this guide will provide you with the knowledge to utilize the fuzzer to its full potential.

Let's begin by setting up the fuzzer and getting it running on your system.

Installation

Prerequisites

Before installing the fuzzer, ensure that you have the following prerequisites installed on your system:

  • Rust and Cargo: Install Rust and Cargo via rustup.

Installing the Fuzzer

To include the fuzzer in your Rust project, add it as a dependency in your Cargo.toml file:

[dependencies]
fuzzer = { path = "https://github.com/0xb-s/fuzzer" }

Getting Started

This section will guide you through setting up and running the fuzzer for the first time. We'll cover the basic steps needed to configure the fuzzer and execute a simple fuzzing session.

Overview

To get started with the fuzzer, you'll need to:

  1. Configure the Fuzzer: Set up the FuzzerConfig with your desired options.
  2. Define Target Functions: Create one or more functions that the fuzzer will test.
  3. Initialize the Fuzzer: Create an instance of the Fuzzer and add your target functions.
  4. Run the Fuzzer: Execute the fuzzing session and monitor progress.

In the next section, we'll walk through a quick start example that demonstrates these steps.

Quick Start Example

Let's dive into a simple example to illustrate how to use the fuzzer in your project.

Step 1: Set Up the Project

Create a new Rust project or use an existing one. Ensure that the fuzzer is added to your Cargo.toml dependencies as described in the Installation section.

Step 2: Configure the Fuzzer

use fuzzer::mutator_options::{MutationType, MutatorOptions};
use fuzzer::utils::{FuzzMode, InputFormat};
use fuzzer::{Fuzzer, FuzzerConfig, FuzzerError, TargetFunction};
use std::future::Future;
use std::pin::Pin;
use std::time::Duration;

#[tokio::main]
async fn main() {
    let mutator_options = MutatorOptions {
        mutation_rate: 0.1,
        max_mutations: 5,
        mutation_types: vec![
            MutationType::BitFlip,
            MutationType::ByteFlip,
            MutationType::BlockMutation,
        ],

        ..Default::default()
    };

    let config = FuzzerConfig::builder()
        .input_format(InputFormat::Text)
        .fuzz_mode(FuzzMode::Mutation)
        .timeout(Duration::from_secs(1))
        .max_iterations(1000)
        .seed(42)
        .mutator_options(mutator_options)
        .stop_on_first_crash(false)
        .stats_interval(100)
        .max_input_size(256)
        .min_input_size(1)
        .build();

    let mut fuzzer = Fuzzer::new(config);

    let from_utf8_target =
        |input: &[u8]| -> Pin<Box<dyn Future<Output = Result<(), FuzzerError>> + Send>> {
            let input_owned = input.to_owned();

            Box::pin(async move {
                match std::str::from_utf8(&input_owned) {
                    Ok(valid_str) => {
                        println!("Valid UTF-8 string: {}", valid_str);
                        Ok(())
                    }
                    Err(e) => Err(FuzzerError::ExecutionError(format!(
                        "from_utf8 error: {}",
                        e
                    ))),
                }
            })
        };

    let target = TargetFunction::new_async("FromUtf8", from_utf8_target);

    fuzzer.add_target(target);

    if let Err(e) = fuzzer.run().await {
        eprintln!("Fuzzer encountered an error: {}", e);
    }
}

Step 3: Run the Fuzzer

Execute your program:


cargo run

The fuzzer will start running, generating inputs, mutating them, and feeding them to your target function. It will report progress and any crashes it encounters.

Configuration

Configuring the fuzzer allows you to customize its behavior and optimize it for specific testing needs. This section will explain the options available for setting up the fuzzer, including general configuration options, mutator options, and sanitizers.

To configure the fuzzer, you'll use the FuzzerConfig builder pattern. This allows you to chain multiple configuration options and create a customized FuzzerConfig object that controls the behavior of the fuzzing process.

Setting Up the Configuration

Here's a basic example of how to create and apply configuration settings to the fuzzer:

#![allow(unused)]
fn main() {
use fuzzer::FuzzerConfig;
use fuzzer::utils::{FuzzMode, InputFormat};
use std::time::Duration;

let config = FuzzerConfig::builder()
    .input_format(InputFormat::JSON)
    .fuzz_mode(FuzzMode::Mutation)
    .timeout(Duration::from_secs(2))
    .max_iterations(5000)
    .seed(12345)
    .stop_on_first_crash(true)
    .enable_logging(true)
    .save_crashes(true)
    .thread_count(4)
    .build();
}

Mutator Options

The MutatorOptions structure allows you to control how inputs are mutated during the fuzzing process. These options help define the types and frequency of mutations that the fuzzer will apply, enabling you to customize the way inputs evolve and explore potential vulnerabilities in the target function.

Setting Up Mutator Options

To configure mutation behaviors, create a MutatorOptions instance and pass it to your fuzzer configuration:

#![allow(unused)]
fn main() {
use fuzzer::mutator_options::{MutatorOptions, MutationType};

let mutator_options = MutatorOptions {
    mutation_rate: 0.2,
    max_mutations: 10,
    mutation_types: vec![MutationType::BitFlip, MutationType::ByteFlip],
    enable_crossover: true,
    crossover_rate: 0.05,
    block_mutation_size: 16,
    arithmetics_range: 10,
    interesting_values: vec![vec![0x00], vec![0xFF], vec![0x7F]],
    ..Default::default()
};
}

After defining the options, pass mutator_options into the FuzzerConfig:

#![allow(unused)]
fn main() {
let config = FuzzerConfig::builder()
    .mutator_options(mutator_options)
    .build();
}

Sanitizer Options

Sanitizers are tools that help detect various types of bugs and vulnerabilities during runtime by instrumenting code to catch errors such as memory corruption, data races, and undefined behavior. The SanitizerOptions structure lets you enable or disable specific sanitizers to improve the effectiveness of your fuzzing process.

Enabling Sanitizers

To activate sanitizers, set sanitizer_enabled to true in your fuzzer configuration. Then, use SanitizerOptions to specify which sanitizers to enable. Here’s an example:

#![allow(unused)]
fn main() {
use fuzzer::FuzzerConfig;
use fuzzer::sanitizer_options::SanitizerOptions;

let sanitizer_options = SanitizerOptions {
    address: true,
    thread: false,
    memory: false,
    undefined_behavior: true,
    leak: true,
};

let config = FuzzerConfig::builder()
    .sanitizer_enabled(true)
    .sanitizer_options(sanitizer_options)
    .build();
}