Fuzzing Tools and Techniques

Fuzzing (fuzz testing) automatically generates and injects malformed or unexpected inputs to find bugs, crashes, and security vulnerabilities.

Overview

Fuzzing is an automated software testing technique that provides invalid, unexpected, or random data as inputs to a program.

Goals:

  • Find crashes and hangs
  • Discover memory corruption bugs
  • Identify security vulnerabilities
  • Test error handling
  • Improve code coverage

Types of Fuzzing

1. Black-Box Fuzzing

No knowledge of internal structure.

1Input → [Program] → Monitor for crashes

Tools: Radamsa, zzuf, Peach Fuzzer

2. White-Box Fuzzing

Uses program analysis and symbolic execution.

1Input → [Analyze Code] → Generate targeted inputs → Test

Tools: KLEE, Driller, Mayhem

3. Grey-Box Fuzzing

Uses lightweight instrumentation for feedback.

1Input → [Instrumented Program] → Coverage feedback → Mutate input

Tools: AFL, AFL++, LibFuzzer, Honggfuzz


AFL (American Fuzzy Lop)

Most popular coverage-guided fuzzer.

Installation

1# Install AFL++
2git clone https://github.com/AFLplusplus/AFLplusplus
3cd AFLplusplus
4make
5sudo make install
6
7# Or via package manager
8sudo apt-get install afl++  # Debian/Ubuntu
9brew install afl++          # macOS

Basic Usage

 1# 1. Compile target with AFL instrumentation
 2afl-gcc -o target target.c
 3# Or for C++
 4afl-g++ -o target target.cpp
 5
 6# 2. Create input directory with seed files
 7mkdir input
 8echo "test" > input/seed1.txt
 9echo "hello" > input/seed2.txt
10
11# 3. Create output directory
12mkdir output
13
14# 4. Run fuzzer
15afl-fuzz -i input -o output -- ./target @@
16# @@ is replaced with input filename
17
18# 5. Monitor results
19# AFL shows real-time stats in terminal
20# Crashes saved in output/crashes/
21# Hangs saved in output/hangs/

Example Target Program

 1// vulnerable.c
 2#include <stdio.h>
 3#include <stdlib.h>
 4#include <string.h>
 5
 6void process_input(char *data) {
 7    char buffer[16];
 8    
 9    // Vulnerability: no bounds checking
10    if (data[0] == 'A' && data[1] == 'B') {
11        strcpy(buffer, data);  // Buffer overflow!
12    }
13    
14    // Another bug: integer overflow
15    int len = strlen(data);
16    if (len > 0) {
17        char *ptr = malloc(len - 1);  // Can underflow!
18        free(ptr);
19    }
20}
21
22int main(int argc, char **argv) {
23    if (argc != 2) {
24        fprintf(stderr, "Usage: %s <input_file>\n", argv[0]);
25        return 1;
26    }
27    
28    FILE *fp = fopen(argv[1], "r");
29    if (!fp) {
30        perror("fopen");
31        return 1;
32    }
33    
34    char data[1024];
35    size_t len = fread(data, 1, sizeof(data) - 1, fp);
36    data[len] = '\0';
37    fclose(fp);
38    
39    process_input(data);
40    
41    return 0;
42}
1# Compile with AFL
2afl-gcc -o vulnerable vulnerable.c
3
4# Fuzz
5mkdir in out
6echo "test" > in/seed
7afl-fuzz -i in -o out -- ./vulnerable @@
8
9# AFL will find the buffer overflow when input starts with "AB"

Advanced AFL Options

 1# Multiple cores (parallel fuzzing)
 2afl-fuzz -i in -o out -M fuzzer1 -- ./target @@  # Master
 3afl-fuzz -i in -o out -S fuzzer2 -- ./target @@  # Slave
 4afl-fuzz -i in -o out -S fuzzer3 -- ./target @@  # Slave
 5
 6# With dictionary (for structured formats)
 7afl-fuzz -i in -o out -x dict.txt -- ./target @@
 8
 9# Dictionary example (dict.txt)
10# keyword_http="GET"
11# keyword_http="POST"
12# keyword_http="HTTP/1.1"
13
14# Persistent mode (faster)
15# Requires modifying target to use __AFL_LOOP
16afl-fuzz -i in -o out -- ./target_persistent
17
18# QEMU mode (no source code needed)
19afl-fuzz -Q -i in -o out -- ./binary @@

Analyzing Crashes

 1# Reproduce crash
 2./target output/crashes/id:000000,sig:11,src:000000,op:havoc,rep:2
 3
 4# With Address Sanitizer for better diagnostics
 5afl-gcc -fsanitize=address -o target_asan target.c
 6./target_asan output/crashes/id:000000*
 7
 8# Minimize crashing input
 9afl-tmin -i crash_input -o minimized -- ./target @@
10
11# Get crash statistics
12afl-whatsup output/

LibFuzzer

LLVM's in-process, coverage-guided fuzzer.

Basic Example

 1// fuzz_target.cpp
 2#include <stdint.h>
 3#include <stddef.h>
 4#include <string.h>
 5
 6// Vulnerable function
 7void process_data(const uint8_t *data, size_t size) {
 8    if (size >= 4) {
 9        if (data[0] == 'F' &&
10            data[1] == 'U' &&
11            data[2] == 'Z' &&
12            data[3] == 'Z') {
13            // Trigger crash
14            char *ptr = nullptr;
15            *ptr = 0;  // Null pointer dereference
16        }
17    }
18}
19
20// LibFuzzer entry point
21extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
22    process_data(data, size);
23    return 0;
24}
 1# Compile with LibFuzzer
 2clang++ -g -fsanitize=fuzzer,address fuzz_target.cpp -o fuzz_target
 3
 4# Run fuzzer
 5./fuzz_target
 6
 7# With corpus directory
 8mkdir corpus
 9./fuzz_target corpus/
10
11# With options
12./fuzz_target -max_len=1024 -timeout=10 corpus/
13
14# Reproduce crash
15./fuzz_target crash-file

Advanced LibFuzzer

 1// Custom mutator
 2extern "C" size_t LLVMFuzzerCustomMutator(
 3    uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed) {
 4    // Custom mutation logic
 5    return Size;
 6}
 7
 8// Custom crossover
 9extern "C" size_t LLVMFuzzerCustomCrossOver(
10    const uint8_t *Data1, size_t Size1,
11    const uint8_t *Data2, size_t Size2,
12    uint8_t *Out, size_t MaxOutSize, unsigned int Seed) {
13    // Custom crossover logic
14    return 0;
15}
16
17// Initialize (runs once)
18extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) {
19    // Setup code
20    return 0;
21}

Honggfuzz

Security-oriented fuzzer with hardware-assisted feedback.

Installation

1# Install
2git clone https://github.com/google/honggfuzz
3cd honggfuzz
4make
5sudo make install

Usage

 1# Compile target
 2hfuzz-gcc target.c -o target
 3# Or
 4hfuzz-clang target.c -o target
 5
 6# Fuzz
 7honggfuzz -i input_corpus -o output -- ./target ___FILE___
 8
 9# With sanitizers
10hfuzz-clang -fsanitize=address target.c -o target
11honggfuzz -i input -o output -- ./target ___FILE___
12
13# Persistent mode
14honggfuzz -i input -o output -P -- ./target

Protocol Fuzzing

HTTP Fuzzing with Wfuzz

 1# Install
 2pip install wfuzz
 3
 4# Fuzz URL parameters
 5wfuzz -z file,wordlist.txt http://target.com/page?param=FUZZ
 6
 7# Fuzz POST data
 8wfuzz -z file,payloads.txt -d "username=admin&password=FUZZ" \
 9      http://target.com/login
10
11# Fuzz headers
12wfuzz -z file,xss.txt -H "User-Agent: FUZZ" http://target.com/
13
14# Multiple injection points
15wfuzz -z file,users.txt -z file,passes.txt \
16      -d "user=FUZZ&pass=FUZ2Z" http://target.com/login
17
18# Filter responses
19wfuzz -z range,1-1000 --hc 404 http://target.com/page?id=FUZZ
20
21# Common options:
22# --hc: Hide responses with code
23# --hl: Hide responses with lines
24# --hw: Hide responses with words
25# --hh: Hide responses with chars

Ffuf (Fast Web Fuzzer)

 1# Install
 2go install github.com/ffuf/ffuf@latest
 3
 4# Directory fuzzing
 5ffuf -w wordlist.txt -u http://target.com/FUZZ
 6
 7# Virtual host fuzzing
 8ffuf -w vhosts.txt -u http://target.com -H "Host: FUZZ.target.com"
 9
10# POST data fuzzing
11ffuf -w wordlist.txt -X POST -d "username=admin&password=FUZZ" \
12     -u http://target.com/login
13
14# Recursive fuzzing
15ffuf -w wordlist.txt -u http://target.com/FUZZ -recursion
16
17# Match/filter responses
18ffuf -w wordlist.txt -u http://target.com/FUZZ \
19     -mc 200,301,302 \
20     -fs 1234  # Filter by size
21
22# Rate limiting
23ffuf -w wordlist.txt -u http://target.com/FUZZ -rate 100

Boofuzz (Network Protocol Fuzzing)

 1# Install
 2pip install boofuzz
 3
 4# Example: Fuzz HTTP server
 5from boofuzz import *
 6
 7def main():
 8    session = Session(
 9        target=Target(
10            connection=TCPSocketConnection("127.0.0.1", 80)
11        ),
12    )
13    
14    # Define HTTP request
15    s_initialize("http_get")
16    s_string("GET", fuzzable=False)
17    s_delim(" ", fuzzable=False)
18    s_string("/", fuzzable=True)
19    s_delim(" ", fuzzable=False)
20    s_string("HTTP/1.1", fuzzable=False)
21    s_static("\r\n")
22    s_string("Host:", fuzzable=False)
23    s_delim(" ", fuzzable=False)
24    s_string("localhost", fuzzable=True)
25    s_static("\r\n\r\n")
26    
27    session.connect(s_get("http_get"))
28    session.fuzz()
29
30if __name__ == "__main__":
31    main()

Radamsa (General-Purpose Fuzzer)

 1# Install
 2git clone https://gitlab.com/akihe/radamsa.git
 3cd radamsa
 4make
 5sudo make install
 6
 7# Generate fuzzed inputs
 8echo "GET / HTTP/1.1" | radamsa
 9
10# Generate multiple
11echo "test input" | radamsa -n 100 -o fuzz-%n.txt
12
13# Fuzz file
14radamsa input.txt -o output-%n.txt -n 1000
15
16# Use in pipeline
17cat valid_request.txt | radamsa | nc target.com 80

Structure-Aware Fuzzing

Protobuf Fuzzing

1// proto_fuzzer.cpp
2#include <libprotobuf-mutator/libfuzzer/libfuzzer_macro.h>
3#include "message.pb.h"
4
5DEFINE_PROTO_FUZZER(const MyMessage& message) {
6    // Process protobuf message
7    ProcessMessage(message);
8}
1# Compile
2clang++ -fsanitize=fuzzer,address proto_fuzzer.cpp \
3        message.pb.cc -lprotobuf -o proto_fuzzer
4
5# Run
6./proto_fuzzer

JSON Fuzzing

 1# json_fuzzer.py
 2import json
 3import atheris
 4import sys
 5
 6@atheris.instrument_func
 7def test_json_parser(data):
 8    try:
 9        parsed = json.loads(data)
10        # Process parsed JSON
11        process_json(parsed)
12    except (json.JSONDecodeError, UnicodeDecodeError):
13        pass
14
15def main():
16    atheris.Setup(sys.argv, test_json_parser)
17    atheris.Fuzz()
18
19if __name__ == "__main__":
20    main()

Fuzzing Best Practices

1. Seed Corpus

 1# Good seed corpus:
 2# - Valid inputs that exercise different code paths
 3# - Minimal but diverse
 4# - Cover edge cases
 5
 6# Example for HTTP fuzzer
 7mkdir corpus
 8echo "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n" > corpus/get.txt
 9echo "POST / HTTP/1.1\r\nContent-Length: 0\r\n\r\n" > corpus/post.txt
10echo "OPTIONS / HTTP/1.1\r\n\r\n" > corpus/options.txt

2. Sanitizers

 1# Address Sanitizer (memory errors)
 2clang -fsanitize=address -g target.c -o target
 3
 4# Undefined Behavior Sanitizer
 5clang -fsanitize=undefined -g target.c -o target
 6
 7# Memory Sanitizer (uninitialized memory)
 8clang -fsanitize=memory -g target.c -o target
 9
10# Thread Sanitizer (data races)
11clang -fsanitize=thread -g target.c -o target
12
13# Combine multiple
14clang -fsanitize=address,undefined -g target.c -o target

3. Coverage Analysis

 1# Generate coverage report with AFL
 2afl-cov -d output/ --live --coverage-cmd \
 3        "cat AFL_FILE | ./target" \
 4        --code-dir .
 5
 6# With LLVM coverage
 7clang -fprofile-instr-generate -fcoverage-mapping target.c -o target
 8LLVM_PROFILE_FILE="target.profraw" ./target input
 9llvm-profdata merge -sparse target.profraw -o target.profdata
10llvm-cov show ./target -instr-profile=target.profdata

4. Continuous Fuzzing

 1# .github/workflows/fuzzing.yml
 2name: Continuous Fuzzing
 3
 4on:
 5  push:
 6    branches: [ main ]
 7  schedule:
 8    - cron: '0 0 * * *'  # Daily
 9
10jobs:
11  fuzz:
12    runs-on: ubuntu-latest
13    steps:
14      - uses: actions/checkout@v2
15      
16      - name: Install AFL++
17        run: |
18          sudo apt-get update
19          sudo apt-get install -y afl++          
20      
21      - name: Build
22        run: |
23                    afl-gcc -o target target.c
24      
25      - name: Fuzz
26        run: |
27          mkdir -p in out
28          echo "seed" > in/seed
29          timeout 3600 afl-fuzz -i in -o out -- ./target @@          
30      
31      - name: Upload crashes
32        if: always()
33        uses: actions/upload-artifact@v2
34        with:
35          name: crashes
36          path: out/crashes/

Fuzzing Frameworks Comparison

ToolTypeSpeedLearning CurveBest For
AFL++Grey-boxFastMediumC/C++ programs
LibFuzzerGrey-boxVery FastLowIn-process fuzzing
HonggfuzzGrey-boxFastMediumSecurity research
BoofuzzBlack-boxSlowLowNetwork protocols
RadamsaBlack-boxFastVery LowQuick tests
OSS-FuzzPlatformN/AHighOpen source projects

Common Vulnerabilities Found

Buffer Overflows

1// AFL will find this
2void vulnerable(char *input) {
3    char buf[16];
4    strcpy(buf, input);  // No bounds check
5}

Integer Overflows

1// Fuzzer can trigger with large values
2void allocate(size_t size) {
3    if (size > 0) {
4        char *buf = malloc(size - 1);  // Underflow!
5    }
6}

Format String Bugs

1// Fuzzer finds with %n%n%n...
2void log_message(char *msg) {
3    printf(msg);  // Should be printf("%s", msg)
4}

Use-After-Free

1// Fuzzer triggers with specific input sequence
2void process(char *input) {
3    char *ptr = malloc(100);
4    free(ptr);
5    if (input[0] == 'X') {
6        strcpy(ptr, input);  // Use after free!
7    }
8}

Further Reading

Related Snippets