🧐 What is fuzzing?

Fuzzing or fuzz testing is an effective way to find bigs or vulnerabilities in a software or a library. The program which is used to fuzz is called the fuzzer (for example: AFL, HongFuzz or wfuzz) and the program being fuzzed is our target. A fuzzer typically starts feeding the target program with random inputs while observing its behaviour. Whenever the target crashes, the fuzzer reports the input which caused the crash to the user as a bug or a crash.

This method has proven its value many times in vulnerabilities and bugs research, as shown in these different articles and videos:

So a few weeks ago, I immersed myself into this new world, and after spending a lot of time playing with AFL++, HongFuzz and Atheris on a lot of programs, I felt like writing my own fuzzer, first for learning purposes, then for finding vulnerabilities. A few nights of code later, Frelatage was born.

🏭 Frelatage

Frelatage is a coverage-based Python fuzzing library which can be used to fuzz python code.

-> It is a mutation-based fuzzer. Meaning, Frelatage generates new inputs by slightly modifying a seed input, using different method such as duplicating a part of the input, or modifying a segment of it.

-> Frelatage is also a greybox fuzzer (not blackbox nor whitebox). Meaning, Frelatage leverages coverage-feedback to learn how to reach deeper into the program. It is not entirely blackbox because Frelatage leverages at least some program analysis. It is not entirely whitebox either because Frelatage does not build on heavyweight program analysis or constraint solving. Instead, Frelatage uses lightweight program instrumentation to glean some information about the coverage of a generated input. If a generated input increases coverage, it is used afterwards for further fuzzing.

📕 Influences

The development of Frelatage was inspired by various other fuzzers, including AFL/AFL++, Atheris and PythonFuzz.The main purpose of the project is to take advantage of the best features of these fuzzers and gather them together into a new tool in order to efficiently fuzz python applications and libraries.

  1. 🕵️ How it works
  2. 👍 Setting up our fuzzing environment
    1. ⚙️ Installing Frelatage
    2. 📝 Creation of a corpus
    3. 📙 Dictionary-based optimization
  3. ✍️ Writing the fuzzing harness
  4. 🦘 Running the fuzzer
  5. 💥 Triaging the crashes reports
  6. 👉 Upcoming features

🕵️ How it works

The idea behind the design of Frelatage is the usage of a genetic algorithm to generate mutations that will cover as much code as possible (frelatage is a coverage-based fuzzer). The process of a fuzzing cycle can be roughly summarized with this diagram:

graph TB m1(Mutation 1) --> |input| function(Fuzzed function) m2(Mutation 2) --> |input| function(Fuzzed function) mplus(Mutation ...) --> |input| function(Fuzzed function) mn(Mutation n) --> |input| function(Fuzzed function) function --> generate_reports(Generate reports) generate_reports --> rank_reports(Rank reports) rank_reports --> select(Select n best reports) select --> |mutate| nm1(Mutation 1) & nm2(Mutation 2) & nmplus(Mutation ...) & nmn(Mutation n) subgraph Cycle mutations direction LR m1 m2 mplus mn end subgraph Next cycle mutations direction LR nm1 nm2 nmplus nmn end style function fill:#5388e8,stroke:white,stroke-width:4px

👍 Setting up our fuzzing environment

⚙️ Installing Frelatage

The package is available on PyPi and can be installed simply with pip:

pip3 install frelatage

🏗️ The project structure

# Create the main folder
mkdir pillow_fuzz
cd pillow_fuzz

# Create the input file folder
mkdir in
# Create the dictionary folder
mkdir dict
# Create the fuzzing harness file
touch pillow_fuzzer.py

Once these operations are done, we have a tree structure of this form:

  ├── pillow_fuzzer.py
  │   ├── dict
  │   ├── in

📝 Creation of a corpus

A corpus is a set of inputs for a fuzz target. In most contexts, it refers to a set of minimal test inputs that generate maximal code coverage.

Pillow being an image processing library, we will need a corpus of images to reach a satisfactory code coverage and help the fuzzer to find interesting paths. The official documentation of the library informs us that the following formats are fully supported:

  • BMP
  • DDS
  • DIB
  • EPS
  • GIF
  • ICNS
  • ICO
  • IM
  • JPEG
  • JPEG 2000
  • MSP
  • PCX
  • PNG
  • Saving
  • PPM
  • SGI
  • SPIDER
  • TGA
  • TIFF
  • WebP
  • XBM

In order to optimize the efficiency of the fuzzer, it may be relevant to write a different fuzzing harness for each supported format. This includes the creation of a dictionary specific to each format, as well as a dedicated corpus for each fuzzing harness.

What we call a fuzzing harness is a test case or a particular test target.

For this article, we will focus on the JPEG format, on the one hand because it is one of the most widespread formats, and on the other hand because it is the format for which it is the easiest to find a corpus (with PNG and GIF).

There are already pre-made corpus freely available, for the needs of our work we will use this one

# Download the corpus
svn export https://github.com/strongcourage/fuzzing-corpus/trunk/jpg

# Merge all subfolders into the ./in folder
cp jpg/*/* ./in
rm -rf jpg

📙 Dictionary-based optimizations

A JPEG image consists of a sequence of segments, each beginning with a marker, each of which begins with a 0xFF byte, followed by a byte indicating what kind of marker it is.

space-1.jpg
representation of a JPEG file

We will use this dictionary that was written for AFL by Michal Zalewski.

#
# AFL dictionary for JPEG images
# ------------------------------
#
# Created by Michal Zalewski
#

header_jfif="JFIF\x00"
header_jfxx="JFXX\x00"

section_ffc0="\xff\xc0"
section_ffc2="\xff\xc2"
section_ffc4="\xff\xc4"
section_ffd0="\xff\xd0"
section_ffd8="\xff\xd8"
section_ffd9="\xff\xd9"
section_ffda="\xff\xda"
section_ffdb="\xff\xdb"
section_ffdd="\xff\xdd"
section_ffe0="\xff\xe0"
section_ffe1="\xff\xe1"
section_fffe="\xff\xfe"

it will be saved in the ./dict folder (default folder for dictionaries)

✍️ Writing the fuzzing harness

Our fuzzing harness will be in pillow_fuzzer.py.

import frelatage
from PIL import Image
from PIL import ImageFile

# Allow PIL to load truncated images
ImageFile.LOAD_TRUNCATED_IMAGES = True

# The function we want to fuzz
def jpeg_fuzz_pillow(image):
    # We fuzz the "open" method
    Image.open(image)
    return

# Load the corpus
jpeg_corpus = frelatage.load_corpus(directory="./")
# Initialize the fuzzer
f = frelatage.Fuzzer(jpeg_fuzz_pillow, [jpeg_corpus])
# Launch the fuzzing process
f.fuzz()

🦘 Running the fuzzer

I have developed frelatage to be highly configurable, so it is possible to change several constants through environment variables:

ENV Variable Description Possible Values Default Value
FRELATAGE_DICTIONARY_ENABLE Enable the use of mutations based on dictionary elements 1 to enable, 0 otherwise 1
FRELATAGE_TIMEOUT_DELAY Delay in seconds after which a function will return a TimeoutError 1 - 20 2
FRELATAGE_INPUT_FILE_TMP_DIR Temporary folder where input files are stored absolute path to a folder, e.g. /tmp/custom_dir /tmp/frelatage
FRELATAGE_INPUT_MAX_LEN Maximum size of an input variable in bytes 4 - 1000000 4094
FRELATAGE_MAX_THREADS Maximum number of simultaneous threads 8 - 50 8
FRELATAGE_MAX_CYCLES_WITHOUT_NEW_PATHS Number of cycles without new paths found after which we go to the next stage 10 - 50000 5000
FRELATAGE_INPUT_DIR Directory containing the initial input files. It needs to be a relative path (to the path of the fuzzing file) relative path to a folder, e.g. ./in ./in
FRELATAGE_DICTIONARY_DIR Default directory for dictionaries. It needs to be a relative path (to the path of the fuzzing file) relative path to a folder, e.g. ./dict ./dict

So we start by configuring our fuzzer:

export FRELATAGE_DICTIONARY_ENABLE=1 &&
export FRELATAGE_TIMEOUT_DELAY=2 &&
export FRELATAGE_INPUT_FILE_TMP_DIR="/tmp/frelatage" &&
export FRELATAGE_INPUT_MAX_LEN=4096 &&
export FRELATAGE_MAX_THREADS=8 &&
export FRELATAGE_MAX_CYCLES_WITHOUT_NEW_PATHS=5000 &&
export FRELATAGE_INPUT_DIR="./in" &&
export FRELATAGE_DICTIONARY_DIR="./dict" &&

Then we launch it

python3 pillow_fuzzer.py

The fuzzing process can take from a few hours to several days, depending on the new paths found by fuzzing during the process.

💥 Triaging the crashes reports

now enters the less pleasant part, namely triaging the crashes.

Crash triage involves examining each crash discovered by Frelatage to determine whether the crash might be worth investigating further (for security researchers, this typically means determining whether the crash is likely due to a vulnerability) and, if so, what the root cause of the crash is. Reviewing each crash in detail can be very time consuming, especially if the fuzzer has identified dozens or hundreds of crashes. This will probably be the case if you leave Frelatage running for several hours/days.

Each crash is saved in the output folder (./out by default), in a folder named : id:<crash ID>,err:<error type>,err_file:<error file>,err_pos:<err_pos> Which you can read as follows:

  • id: The crash number, e.g. 000001
  • err: The type of error triggered, e.g. OSError, AttributeError
  • err_file: File in which an error has occurred, e.g: image
  • err_pos: Line where an error occurred, e.g. 34

-> The report directory is in the following form and contains files passed as argument:

    ├── out
    │   ├── id:<crash ID>,err:<error type>,err_file:<error file>,err_pos:<err_pos>
    │       ├── input
    │       ├── 0
    │            ├── <inputfile1>
    │       ├── ...
    │   ├── ...
Examples of reports generated by Frelatage

📄 Read a report

Inputs passed to a function are serialized using the pickle module before being saved in the <report_folder>/input file. It is therefore necessary to deserialize it to be able to read the contents of the file. This action can be performed with this script.

./read_report.py input

{'input': [{'value': '/tmp/frelatage/0/0/0e8ef3773a13824c42f021c1af856c351effa6a2-2', 'file': True}]}

👉 Upcoming features

Frelatage is still in active development, so what has been written above may (will?) change, as I plan to work on new features to make my fuzzer even more effective.

Here are the features I plan to implement in the next few weeks:

  • Possibility to use different dictionaries for each input
  • Implementation of a crash triage system
  • A more colorful interface (who wants to work with dull tools?)

To keep up to date with the changes made to Frelatage, the very best way is to look at the Github repo

😃 Thanks for reading!

for more informations or suggestions, you can contact me at: r0g3r5@protonmail.com, or on twitter at @Rog3rSm1th