Frelatage is a coverage-based Python fuzzing library which can be used to fuzz python code. 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.

Requirements

Python 3

Installation

Install with pip (recommended)

pip3 install frelatage

Or build from source

Recommended for developers. It automatically clones the main branch from the frelatage repo, and installs from source.

Automatically clone the Frelatage repository and install Frelatage from source
bash <(wget -q https://raw.githubusercontent.com/Rog3rSm1th/Frelatage/main/scripts/autoinstall.sh -O -)

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. The functioning of a fuzzing cycle can be roughly summarized with this diagram :

Features

Fuzzing different argument types

  • String
  • Int
  • Float
  • List
  • Tuple
  • Dictionary

File fuzzing

Frelatage allows to fuzz a function by passing a file as input.

Fuzzer efficiency

  • Corpus
  • Dictionnary

Use Frelatage

Fuzz a classical parameter

import frelatage
import my_vulnerable_library
def MyFunctionFuzz(data):
my_vulnerable_library.parse(data)
input = frelatage.Input(value=”initial_value”)
f = frelatage.Fuzzer(MyFunctionFuzz, [[input]])
f.fuzz()

Fuzz a file parameter

Frelatage gives you the possibility to fuzz file type input parameters. To initialize the value of these files, you must create files in the input folder (./in by default).

If we want to initialize the value of a file used to fuzz, we can do it like this:

echo “initial value” > ./in/input.txt

And then run the fuzzer:

import frelatage
import my_vulnerable_library
def MyFunctionFuzz(data):
my_vulnerable_library.load_file(data)
input = frelatage.Input(file=True, value=”input.txt”)
f = frelatage.Fuzzer(MyFunctionFuzz, [[input]])
f.fuzz()

Load several files to a corpus at once

If you need to load several files into a corpus at once (useful if you use a large corpus) You can use the built-in function of Frelatage load_corpus. This function returns a list of inputs.

load_corpus(directory: str, file_extensions: list) -> list[Input]

  • directory: Subdirectory of the input directory (relative path), e.g ././images
  • file_extensions: List of file extensions to include in the corpus entries, e.g. ["jpeg", "gif"]["pdf"]

import frelatage
import my_vulnerable_library
def MyFunctionFuzz(data):
my_vulnerable_library.load_file(data)
my_vulnerable_library.load_file(data2)
Load every every file in the ./in directory
corpus_1 = frelatage.load_corpus(directory=”./”)
Load every .gif/.jpeg file in the ./in/images subdirectory
corpus_2 = frelatage.load_corpus(directory=”./images”, file_extension=[“gif”, “jpeg”])
f = frelatage.Fuzzer(MyFunctionFuzz, [corpus_1, corpus_2])
f.fuzz()

Fuzz with a dictionary

You can copy one or more dictionaries located here in the directory dedicated to dictionaries (./dict by default).

Differential fuzzing

Differental fuzzing is a popular software testing technique that attempts to detect bugs by providing the same input to multiple libraries/programs and observing differences in their behaviors. You will find an example here of a use of differential fuzzing with Frelatage with the json and ujson libraries.

Examples

You can find more examples of fuzzers and corpus in the examples directory.

  • Fuzzing Pillow with Frelatage to find bugs and vulnerabilities

Reports

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

The report directory is in the following form:

├── out
│ ├── id:,err:,err_file:,err_pos:
│ ├── input
│ ├── 0
│ ├──
│ ├── …
│ ├── …

Read a crash 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

Configuration

There are two ways to set up Frelatage:

Using the environment variables

ENV VariableDescriptionPossible ValuesDefault Value
FRELATAGE_DICTIONARY_ENABLEEnable the use of mutations based on dictionary elements1 to enable, 0 otherwise1
FRELATAGE_TIMEOUT_DELAYDelay in seconds after which a function will return a TimeoutError1 – infinity2
FRELATAGE_INPUT_FILE_TMP_DIRTemporary folder where input files are storedabsolute path to a folder, e.g. /tmp/custom_dir/tmp/frelatage
FRELATAGE_INPUT_MAX_LENMaximum size of an input variable in bytes4 – infinity4094
FRELATAGE_MAX_THREADSMaximum number of simultaneous threads8 – infinity8
FRELATAGE_MAX_CYCLES_WITHOUT_NEW_PATHSNumber of cycles without new paths found after which we go to the next stage10 – infinity5000
FRELATAGE_INPUT_DIRDirectory 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_DIRDefault 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
FRELATAGE_DEBUG_MODEEnable the debug mode (show the error when Frelatage crash)1 to enable, 0 otherwise1

A configuration example :

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” &&
python3 fuzzer.py

Passing arguments to the fuzzer

def myfunction(input1_string, input2_int):
pass
input1 = frelatage.Input(value=”initial_value”)
input2 = frelatage.Input(value=2)
f = frelatage.Fuzzer(
# The method you want to fuzz
method=myfunction,
# Corpus
corpus=[[input1], [input2]],
# Number of threads
threads_count=8,
# Exceptions that will be taken into account
exceptions_whitelist=(OSError),
# Exceptions that will not be taken into account
exceptions_blacklist=(),
# Directory where the error reports will be stored
output_directory=”./out”,
# Enable or disable silent mode
silent=False,
# Enable or disable infinite fuzzing
infinite_fuzz=False
)
f.fuzz()