mirror of
https://github.com/AFLplusplus/AFLplusplus.git
synced 2025-06-14 11:08:06 +00:00
Update the documents of the custom mutator
- Merge python_mutators.md into custom_mutator.md - Remove python_mutators.md
This commit is contained in:
@ -1,45 +1,201 @@
|
||||
# Adding custom mutators to AFL
|
||||
# Custom Mutators in AFL++
|
||||
|
||||
This file describes how you can implement custom mutations to be used in AFL.
|
||||
For now, we support C/C++ library and Python module, collectivelly named as the
|
||||
custom mutator.
|
||||
|
||||
Implemented by Khaled Yakdan from Code Intelligence <yakdan@code-intelligence.de>
|
||||
Implemented by
|
||||
- C/C++ library (`*.so`): Khaled Yakdan from Code Intelligence (<yakdan@code-intelligence.de>)
|
||||
- Python module: Christian Holler from Mozilla (<choller@mozilla.com>)
|
||||
|
||||
## 1) Description
|
||||
## 1) Introduction
|
||||
|
||||
Custom mutator libraries can be passed to afl-fuzz to perform custom mutations
|
||||
on test cases beyond those available in AFL - for example, to enable
|
||||
structure-aware fuzzing by using libraries that perform mutations according to
|
||||
a given grammar.
|
||||
Custom mutators can be passed to `afl-fuzz` to perform custom mutations on test
|
||||
cases beyond those available in AFL. For example, to enable structure-aware
|
||||
fuzzing by using libraries that perform mutations according to a given grammar.
|
||||
|
||||
The custom mutator library is passed to afl-fuzz via the
|
||||
AFL_CUSTOM_MUTATOR_LIBRARY environment variable. The library must export
|
||||
the afl_custom_fuzz() function and must be compiled as a shared object.
|
||||
For example:
|
||||
The custom mutator is passed to `afl-fuzz` via the `AFL_CUSTOM_MUTATOR_LIBRARY`
|
||||
or `AFL_PYTHON_MODULE` environment variable., and must export a fuzz function.
|
||||
Please see [APIs](#2-apis) and [Usage](#3-usage) for detail.
|
||||
|
||||
The custom mutation stage is set to be the first non-deterministic stage (right before the havoc stage).
|
||||
|
||||
Note: If `AFL_CUSTOM_MUTATOR_ONLY` is set, all mutations will solely be
|
||||
performed with the custom mutator.
|
||||
|
||||
## 2) APIs
|
||||
|
||||
C/C++:
|
||||
```c
|
||||
void afl_custom_init(unsigned int seed);
|
||||
size_t afl_custom_fuzz(u8* buf, size_t buf_size,
|
||||
u8* add_buf, size_t add_buf_size,
|
||||
u8* mutated_out, size_t max_size);
|
||||
size_t afl_custom_pre_save(u8* buf, size_t buf_size, u8** out_buf);
|
||||
u32 afl_custom_init_trim(u8* buf, size_t buf_size);
|
||||
void afl_custom_trim(u8** out_buf, size_t* out_buf_size);
|
||||
u32 afl_custom_post_trim(u8 success);
|
||||
```
|
||||
$CC -shared -Wall -O3 <lib-name>.c -o <lib-name>.so
|
||||
```
|
||||
Note: unless AFL_CUSTOM_MUTATOR_ONLY is set, it is a state mutator like any
|
||||
other, so it will be used for some test cases, and other mutators for others.
|
||||
|
||||
Only if AFL_CUSTOM_MUTATOR_ONLY is set the afl_custom_mutator() function will
|
||||
be called every time it needs to mutate a test case.
|
||||
Python:
|
||||
```python
|
||||
def init(seed):
|
||||
pass
|
||||
|
||||
def fuzz(buf, add_buf, max_size):
|
||||
return mutated_out
|
||||
|
||||
def pre_save(buf):
|
||||
return out_buf
|
||||
|
||||
def init_trim(buf):
|
||||
return cnt
|
||||
|
||||
def trim():
|
||||
return out_buf
|
||||
|
||||
def post_trim(success):
|
||||
return next_index
|
||||
```
|
||||
|
||||
### Custom Mutation
|
||||
|
||||
- `init` (optional):
|
||||
|
||||
This method is called when AFL++ starts up and is used to seed RNG.
|
||||
|
||||
- `fuzz` (required):
|
||||
|
||||
This method performs custom mutations on a given input. It also accepts an
|
||||
additional test case.
|
||||
|
||||
- `pre_save` (optional):
|
||||
|
||||
For some cases, the format of the mutated data returned from the custom
|
||||
mutator is not suitable to directly execute the target with this input.
|
||||
For example, when using libprotobuf-mutator, the data returned is in a
|
||||
protobuf format which corresponds to a given grammar.
|
||||
In order to execute the target, the protobuf data must be converted to the
|
||||
plain-text format expected by the target.
|
||||
In such scenarios, the user can define the afl_pre_save_handler() function.
|
||||
This function is then transforms the data into the format expected by the
|
||||
API before executing the target.
|
||||
afl_pre_save_handler is optional and does not have to be implemented if its
|
||||
functionality is not needed.
|
||||
protobuf format which corresponds to a given grammar. In order to execute
|
||||
the target, the protobuf data must be converted to the plain-text format expected by the target. In such scenarios, the user can define the
|
||||
`pre_save` function. This function is then transforms the data into the
|
||||
format expected by the API before executing the target.
|
||||
|
||||
## 2) Example
|
||||
|
||||
A simple example is provided in ../examples/custom_mutators/
|
||||
### Trimming Support
|
||||
|
||||
There is also a libprotobuf example available at [https://github.com/bruce30262/libprotobuf-mutator_fuzzing_learning/tree/master/4_libprotobuf_aflpp_custom_mutator](https://github.com/bruce30262/libprotobuf-mutator_fuzzing_learning/tree/master/4_libprotobuf_aflpp_custom_mutator)
|
||||
Another implementation can be found at [https://github.com/thebabush/afl-libprotobuf-mutator](https://github.com/thebabush/afl-libprotobuf-mutator)
|
||||
The generic trimming routines implemented in AFL++ can easily destroy the
|
||||
structure of complex formats, possibly leading to a point where you have a lot
|
||||
of test cases in the queue that your Python module cannot process anymore but
|
||||
your target application still accepts. This is especially the case when your
|
||||
target can process a part of the input (causing coverage) and then errors out
|
||||
on the remaining input.
|
||||
|
||||
In such cases, it makes sense to implement a custom trimming routine. The API
|
||||
consists of multiple methods because after each trimming step, we have to go
|
||||
back into the C code to check if the coverage bitmap is still the same for the
|
||||
trimmed input. Here's a quick API description:
|
||||
|
||||
- `init_trim` (optional):
|
||||
|
||||
This method is called at the start of each trimming operation and receives
|
||||
the initial buffer. It should return the amount of iteration steps possible
|
||||
on this input (e.g. if your input has n elements and you want to remove them
|
||||
one by one, return n, if you do a binary search, return log(n), and so on).
|
||||
|
||||
If your trimming algorithm doesn't allow you to determine the amount of
|
||||
(remaining) steps easily (esp. while running), then you can alternatively
|
||||
return 1 here and always return 0 in `post_trim` until you are finished and
|
||||
no steps remain. In that case, returning 1 in `post_trim` will end the
|
||||
trimming routine. The whole current index/max iterations stuff is only used
|
||||
to show progress.
|
||||
|
||||
- `trim` (optional)
|
||||
|
||||
This method is called for each trimming operation. It doesn't have any
|
||||
arguments because we already have the initial buffer from `init_trim` and we
|
||||
can memorize the current state in global variables. This can also save
|
||||
reparsing steps for each iteration. It should return the trimmed input
|
||||
buffer, where the returned data must not exceed the initial input data in
|
||||
length. Returning anything that is larger than the original data (passed to
|
||||
`init_trim`) will result in a fatal abort of AFL++.
|
||||
|
||||
- `post_trim` (optional)
|
||||
|
||||
This method is called after each trim operation to inform you if your
|
||||
trimming step was successful or not (in terms of coverage). If you receive
|
||||
a failure here, you should reset your input to the last known good state.
|
||||
In any case, this method must return the next trim iteration index (from 0
|
||||
to the maximum amount of steps you returned in `init_trim`).
|
||||
|
||||
Omitting any of three methods will cause the trimming to be disabled and trigger
|
||||
a fallback to the builtin default trimming routine.
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Optionally, the following environment variables are supported:
|
||||
|
||||
- `AFL_PYTHON_ONLY`
|
||||
|
||||
Disable all other mutation stages. This can prevent broken testcases
|
||||
(those that your Python module can't work with anymore) to fill up your
|
||||
queue. Best combined with a custom trimming routine (see below) because
|
||||
trimming can cause the same test breakage like havoc and splice.
|
||||
|
||||
- `AFL_DEBUG`
|
||||
|
||||
When combined with `AFL_NO_UI`, this causes the C trimming code to emit additional messages about the performance and actions of your custom trimmer. Use this to see if it works :)
|
||||
|
||||
## 3) Usage
|
||||
|
||||
### Prerequisite
|
||||
|
||||
For Python mutator, the python 3 or 2 development package is required. On
|
||||
Debian/Ubuntu/Kali this can be done:
|
||||
|
||||
```bash
|
||||
sudo apt install python3-dev
|
||||
# or
|
||||
sudo apt install python-dev
|
||||
```
|
||||
|
||||
Then, AFL++ can be compiled with Python support. The AFL++ Makefile detects
|
||||
Python 2 and 3 through `python-config` if it is in the PATH and compiles
|
||||
`afl-fuzz` with the feature if available.
|
||||
|
||||
Note: for some distributions, you might also need the package `python[23]-apt`.
|
||||
In case your setup is different, set the necessary variables like this:
|
||||
`PYTHON_INCLUDE=/path/to/python/include LDFLAGS=-L/path/to/python/lib make`.
|
||||
|
||||
### Custom Mutator Preparation
|
||||
|
||||
For C/C++ mutator, the source code must be compiled as a shared object:
|
||||
```bash
|
||||
gcc -shared -Wall -O3 example.c -o example.so
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
C/C++
|
||||
```bash
|
||||
export AFL_CUSTOM_MUTATOR_LIBRARY=/full/path/to/example.so
|
||||
afl-fuzz /path/to/program
|
||||
```
|
||||
|
||||
Python
|
||||
```bash
|
||||
export PYTHONPATH=`dirname /full/path/to/example.py`
|
||||
export AFL_PYTHON_MODULE=example
|
||||
afl-fuzz /path/to/program
|
||||
```
|
||||
|
||||
## 4) Example
|
||||
|
||||
Please see [example.c](../examples/custom_mutators/example.c) and
|
||||
[example.py](../examples/custom_mutators/example.py)
|
||||
|
||||
## 6) Other Resources
|
||||
|
||||
- AFL libprotobuf mutator
|
||||
- [bruce30262/libprotobuf-mutator_fuzzing_learning](https://github.com/bruce30262/libprotobuf-mutator_fuzzing_learning/tree/master/4_libprotobuf_aflpp_custom_mutator)
|
||||
- [thebabush/afl-libprotobuf-mutator](https://github.com/thebabush/afl-libprotobuf-mutator)
|
||||
- [XML Fuzzing@NullCon 2017](https://www.agarri.fr/docs/XML_Fuzzing-NullCon2017-PUBLIC.pdf)
|
||||
- [A bug detected by AFL + XML-aware mutators](https://bugs.chromium.org/p/chromium/issues/detail?id=930663)
|
||||
|
@ -228,10 +228,10 @@ checks or alter some of the more exotic semantics of the tool:
|
||||
afl-fuzz), setting AFL_PYTHON_MODULE to a Python module can also provide
|
||||
additional mutations.
|
||||
If AFL_CUSTOM_MUTATOR_ONLY is also set, all mutations will solely be
|
||||
performed with/from the library/Python module.
|
||||
This feature allows to configure custom mutators which can be very helpful
|
||||
in e.g. fuzzing XML or other highly flexible structured input.
|
||||
Please see [custom_mutator.md](custom_mutator.md) or [python_mutators.md](python_mutators.md).
|
||||
performed with the custom mutator.
|
||||
This feature allows to configure custom mutators which can be very helpful,
|
||||
e.g. fuzzing XML or other highly flexible structured input.
|
||||
Please see [custom_mutator.md](custom_mutator.md).
|
||||
|
||||
- AFL_FAST_CAL keeps the calibration stage about 2.5x faster (albeit less
|
||||
precise), which can help when starting a session against a slow target.
|
||||
|
@ -1,148 +0,0 @@
|
||||
# Adding custom mutators to AFL using Python modules
|
||||
|
||||
This file describes how you can utilize the external Python API to write
|
||||
your own custom mutation routines.
|
||||
|
||||
Note: This feature is highly experimental. Use at your own risk.
|
||||
|
||||
Implemented by Christian Holler (:decoder) <choller@mozilla.com>.
|
||||
|
||||
NOTE: Only cPython 2.7, 3.7 and above are supported, although others may work.
|
||||
Depending on with which version afl-fuzz was compiled against, you must use
|
||||
python2 or python3 syntax in your scripts!
|
||||
After a major version upgrade (e.g. 3.7 -> 3.8), a recompilation of afl-fuzz may be needed.
|
||||
|
||||
For an example and a template see ../examples/python_mutators/
|
||||
|
||||
|
||||
## 1) Description and purpose
|
||||
|
||||
While AFLFuzz comes with a good selection of generic deterministic and
|
||||
non-deterministic mutation operations, it sometimes might make sense to extend
|
||||
these to implement strategies more specific to the target you are fuzzing.
|
||||
|
||||
For simplicity and in order to allow people without C knowledge to extend
|
||||
AFLFuzz, I implemented a "Python" stage that can make use of an external
|
||||
module (written in Python) that implements a custom mutation stage.
|
||||
|
||||
The main motivation behind this is to lower the barrier for people
|
||||
experimenting with this tool. Hopefully, someone will be able to do useful
|
||||
things with this extension.
|
||||
|
||||
If you find it useful, have questions or need additional features added to the
|
||||
interface, feel free to send a mail to <choller@mozilla.com>.
|
||||
|
||||
See the following information to get a better pictures:
|
||||
https://www.agarri.fr/docs/XML_Fuzzing-NullCon2017-PUBLIC.pdf
|
||||
https://bugs.chromium.org/p/chromium/issues/detail?id=930663
|
||||
|
||||
|
||||
## 2) How the Python module looks like
|
||||
|
||||
You can find a simple example in pymodules/example.py including documentation
|
||||
explaining each function. In the same directory, you can find another simple
|
||||
module that performs simple mutations.
|
||||
|
||||
Right now, "init" is called at program startup and can be used to perform any
|
||||
kinds of one-time initializations while "fuzz" is called each time a mutation
|
||||
is requested.
|
||||
|
||||
There is also optional support for a trimming API, see the section below for
|
||||
further information about this feature.
|
||||
|
||||
|
||||
## 3) How to compile AFLFuzz with Python support
|
||||
|
||||
You must install the python 3 or 2 development package of your Linux
|
||||
distribution before this will work. On Debian/Ubuntu/Kali this can be done
|
||||
with either:
|
||||
apt install python3-dev
|
||||
or
|
||||
apt install python-dev
|
||||
Note that for some distributions you might also need the package python[23]-apt
|
||||
|
||||
A prerequisite for using this mode is to compile AFLFuzz with Python support.
|
||||
|
||||
The AFL++ Makefile detects Python 3 and 2 through `python-config` if is is in the PATH
|
||||
and compiles afl-fuzz with the feature if available.
|
||||
|
||||
In case your setup is different set the necessary variables like this:
|
||||
PYTHON_INCLUDE=/path/to/python/include LDFLAGS=-L/path/to/python/lib make
|
||||
|
||||
|
||||
## 4) How to run AFLFuzz with your custom module
|
||||
|
||||
You must pass the module name inside the env variable AFL_PYTHON_MODULE.
|
||||
|
||||
In addition, if you are trying to load the module from the local directory,
|
||||
you must adjust your PYTHONPATH to reflect this circumstance. The following
|
||||
command should work if you are inside the aflfuzz directory:
|
||||
|
||||
$ AFL_PYTHON_MODULE="pymodules.test" PYTHONPATH=. ./afl-fuzz
|
||||
|
||||
Optionally, the following environment variables are supported:
|
||||
|
||||
AFL_PYTHON_ONLY - Disable all other mutation stages. This can prevent broken
|
||||
testcases (those that your Python module can't work with
|
||||
anymore) to fill up your queue. Best combined with a custom
|
||||
trimming routine (see below) because trimming can cause the
|
||||
same test breakage like havoc and splice.
|
||||
|
||||
AFL_DEBUG - When combined with AFL_NO_UI, this causes the C trimming code
|
||||
to emit additional messages about the performance and actions
|
||||
of your custom Python trimmer. Use this to see if it works :)
|
||||
|
||||
|
||||
## 5) Order and statistics
|
||||
|
||||
The Python stage is set to be the first non-deterministic stage (right before
|
||||
the havoc stage). In the statistics however, it shows up as the third number
|
||||
under "havoc". That's because I'm lazy and I didn't want to mess with the UI
|
||||
too much ;)
|
||||
|
||||
|
||||
## 6) Trimming support
|
||||
|
||||
The generic trimming routines implemented in AFLFuzz can easily destroy the
|
||||
structure of complex formats, possibly leading to a point where you have a lot
|
||||
of testcases in the queue that your Python module cannot process anymore but
|
||||
your target application still accepts. This is especially the case when your
|
||||
target can process a part of the input (causing coverage) and then errors out
|
||||
on the remaining input.
|
||||
|
||||
In such cases, it makes sense to implement a custom trimming routine in Python.
|
||||
The API consists of multiple methods because after each trimming step, we have
|
||||
to go back into the C code to check if the coverage bitmap is still the same
|
||||
for the trimmed input. Here's a quick API description:
|
||||
|
||||
init_trim: This method is called at the start of each trimming operation
|
||||
and receives the initial buffer. It should return the amount
|
||||
of iteration steps possible on this input (e.g. if your input
|
||||
has n elements and you want to remove them one by one, return n,
|
||||
if you do a binary search, return log(n), and so on...).
|
||||
|
||||
If your trimming algorithm doesn't allow you to determine the
|
||||
amount of (remaining) steps easily (esp. while running), then you
|
||||
can alternatively return 1 here and always return 0 in post_trim
|
||||
until you are finished and no steps remain. In that case,
|
||||
returning 1 in post_trim will end the trimming routine. The whole
|
||||
current index/max iterations stuff is only used to show progress.
|
||||
|
||||
trim: This method is called for each trimming operation. It doesn't
|
||||
have any arguments because we already have the initial buffer
|
||||
from init_trim and we can memorize the current state in global
|
||||
variables. This can also save reparsing steps for each iteration.
|
||||
It should return the trimmed input buffer, where the returned data
|
||||
must not exceed the initial input data in length. Returning anything
|
||||
that is larger than the original data (passed to init_trim) will
|
||||
result in a fatal abort of AFLFuzz.
|
||||
|
||||
post_trim: This method is called after each trim operation to inform you
|
||||
if your trimming step was successful or not (in terms of coverage).
|
||||
If you receive a failure here, you should reset your input to the
|
||||
last known good state.
|
||||
In any case, this method must return the next trim iteration index
|
||||
(from 0 to the maximum amount of steps you returned in init_trim).
|
||||
|
||||
Omitting any of the methods will cause Python trimming to be disabled and
|
||||
trigger a fallback to the builtin default trimming routine.
|
@ -194,7 +194,7 @@ static void usage(u8* argv0, int more_help) {
|
||||
"use \"-hh\".\n\n");
|
||||
|
||||
#ifdef USE_PYTHON
|
||||
SAYF("Compiled with %s module support, see docs/python_mutators.md\n",
|
||||
SAYF("Compiled with %s module support, see docs/custom_mutator.md\n",
|
||||
(char*)PYTHON_VERSION);
|
||||
#endif
|
||||
|
||||
|
Reference in New Issue
Block a user