AFL Dyninst
AFL Dyninst uses Dyninst to insert AFL instrumentations into binaries for binary fuzzing.
The tool has two parts. The instrumentation tool and the instrumentation library. Instrumentation library has an initialization callback and basic block callback functions which are designed to emulate what AFL is doing with afl-gcc/afl-g++/afl-as.
Instrumentation tool (afl-dyninst) instruments the supplied binary by
inserting callbacks for each basic block and an initialization
callback either at _init
or at specified entry point.
Installation
As its name suggest, AFL Dyninst requires AFL++ 4.22 or above and Dyninst 10 or above at running time. To build afl-dyninst, you also need make(1p), m4(1p) and a C++11 compiler.
Since afl-dyninst
does not relink the output binary,
path to Dyninst library needs to be passed to the build tools
to be injected into wrapper scripts:
make DYNINST_LIB=$dyninst_prefix/lib PREFIX=$afl_dyninst_prefix install
Usage
$ afl-dyninst --help
Usage: afl-dyninst [OPTIONS]... INFILE OUTFILE
Instrument binary to be fuzzed by AFL.
Options:
-h, --help show this help message and exit
--version show program's version number and exit
-e ADDR, --entry=ADDR entry point address to patch (required for stripped binaries)
-E ADDR, --exit=ADDR force exit(0) at this address (multiple use)
-D instrument only a simple fork server and also forced exit functions
-r PATH, --library=PATH runtime library to instrument (multiple use)
-I NAME, --include=NAME instrument only this function and nothing else (multiple use)
-S NAME, --exclude=NAME do not instrument this function (multiple use)
-m N, --min-size=N minimum size of a basic bock to instrument (default to 10)
-s N, --skip=N number of initial basic blocks to skip in binary
-v, --verbose enable verbose output (up to 3 levels)
-x experimental performance mode (multiple use, ~25-50% speed improvement)
Switch -e is used to manually specify the entry point where initialization
callback is to be inserted. For unstripped binaries, afl-dyninst defaults
to using _init
of the binary as an entry point. In case of stripped binaries
this option is required and is best set to the address of main which
can easily be determined by disassembling the binary and looking for an
argument to __libc_start_main
.
Switch -E is used to specify addresses that should force a clean exit when reached. This can speed up the fuzzing tremendously.
Switch -s instructs afl-dyninst to skip the first NUMBER of basic blocks. Currently, it is used to work around a bug in Dyninst but doubles as an optimization option, as skipping the basic blocks of the initialization routines makes things run faster. If the instrumented binary is crashing by itself, try skiping a number of blocks.
Switch -r allows you to specify a path to a library that is loaded
via dlopen() at runtime. Instrumented runtime libraries will be
written to the same location with a .ins
suffix as not to overwrite
the original ones. Make sure to backup the originals and then rename the
instrumented ones to original name.
Switch -m allows you to only instrument basic blocks of a minimum size - the default minimum size is 10.
Switch -S allows you to not instrument specific functions. This options is mainly to hunt down bugs in dyninst. Can be specified multiple times.
Switch -I specified to only instrument specific functions. This option is amazing with large and threaded targets. Can be specified multiple times.
Switch -D installs the afl fork server and forced exit functions but no basic block instrumentation. That would serve no purpose - unless there are other tools that need that:
Switch -x enables an experimental performance mode (+25-50% speed). Just try it and if the target crashes too often, instrument again without this. Should not crash though.
After instrumenting the target binary always check if it works. Dyninst makes big changes to the code, and hence more often than not things are not working anymore.
The instrumentation libraries libafldyninst.so
and libdyninstAPI_RT.so
must be available in LD_LIBRARY_PATH
as the instrumented binary
will be looking for them. The helper script afl-dyninst-env sets up
the environment accordingly.
Examples
Instrumentation
afl-dyninst --entry=0x4034c0 -x unrar unrar-ins
Here we are instrumenting the unrar
binary with entry point at 0x4034c0
(manually found address of main
), skipping the first 10 basic blocks
and outputting to unrar-ins
. This instrumented binary can be tested via
afl-dyninst-env ./unrar-ins ...
Fuzzing instrumented binary
Since AFL checks if the binary has been instrumented by afl-gcc,
the AFL_SKIP_BIN_CHECK
environment variable needs to be set.
No modifications to AFL itself is needed.
export AFL_SKIP_BIN_CHECK=1
You can use the afl-dyninst-env helper script to sets the required environment variables:
afl-dyninst-env afl-fuzz -i testcases/archives/common/gzip/ -o test_gzip\
-- ./gzip_ins -d -c
Problems
Instrumented binary not working
If the instrumented binary crashes or hangs, try to increase
the --min-size
parameter. 8 is the minimum recommended,
on some targets e.g. 16 is required.
You can also try to remove the performance enhancer flag -x
.
AFL reporting executions as crash
If the target is using throw/catch, Dyninst fails to handle
the caught exception, hence abort
is triggered.
AFL will report most fuzzing test case is reported as a crash
although it does not when running it from the command line.
No solution to this issue is known yet.
Binary editing the target binary to perform _exit(0)
would help though.
Copying
AFL Dyninst is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
AFL Dyninst was originally developed as part of a project with the Cisco Talos VULNDEV Team and released under the Apache License, version 2.0.