The goal of this project is to explore what can QEMU do with plugins and its viability for fuzzing. The project is made in the context of a Master semester project under the supervision of Florian Hofhammer, in the HexHive laboratory at École Polytechnique Fédérale de Lausanne, Switzerland.
This project bundles simple targets for testing the plugins that are under
the targets/
directory.
⚠ This a PoC, not a production-ready tool. ⚠
QEMU is a powerful emulator that can emulate a wide range of architectures.
To leverage QEMU for fuzzing, AFL++ has a QEMU mode (-Q
), based on a
≥4 years old fork of QEMU. While this hasn't been a problem for most users, it
can be interesting to see what are the downsides and upsides of using latest
QEMU versions, leveraging plugins, with AFL++.
Quickly summarized, we have (at least) the following advantages and disadvantages:
- Slower than the
-Q
mode to the lack of two main things:- Improving the FS performance by caching TBs translations (even though, this can be achieved.)
Forking at a really early stage of QEMU initialization.Now fixed!
- Easier implementation and maintainability of new features, while being compatible with a wide range of QEMU versions
- Does not require obscure patches to QEMU
It is also a very good occasion to highlight the excellent work of the QEMU and AFL++ developers, and to see how they can be combined.
AFLplusplus/
: AFLplusplus submoduleqemu/
: QEMU submoduleMakefile
: self-explanatorytargets/
: target programsafl.c/afl.h/afl.o
: used for communication with AFLfs.c/fs.h/fs.o
: used for spinning up the forkserverplugin.c/plugin.so
: the main plugin
About cmp-log:
afl-cmplog.c/afl-cmplog.h/afl-cmplog.o
: AFL protocol for cmplogdisas.c/disas.h/disas.o
: disassembler for cmplog
The fork server is initialized at the initialization of a CPU. The fork server
then waits for a message from AFL to start the target program. Why the fork
server is started at the initialization of a CPU you might ask: it is because
it is the only way to fork
a process in QEMU from a plugin.
The plugin is implemented in plugin.c
. For every basic block executed (a.k.a.
translation blocks in QEMU's terminology), the plugin will mark the block as
executed and send the coverage information to AFL.
This is a bit more complicated. The plugin will disassemble the basic block and check what registers, memory and constants are used as operands for each instruction. On memory access or execution, the plugin will log the operands, the result of the operation and forward the information to AFL.
For speed, the plugin will also cache disassemblies of instructions between the fork server and the forked childs.
cmplog.so
is built with the plugin.c
file with the CMPLOG
preprocessor
variable defined.
For convenience, AFLplusplus and QEMU are submodules. If not used as submodules, the initialization of submodules can be ignored. All the commands below are to be run with the correct path of your QEMU and AFL++ executables.
Requirements:
clang
orgcc
- GNU
make
nasm
ld
glib-2.0
libcapstone
(forcmplog
)
Most of those are requirements from QEMU itself.
git clone [email protected]:Maeeen/qemu-semester-project.git
cd qemu-semester-project
git submodule init
git submodule update
cd ./AFLplusplus
make distrib
cd qemu
mkdir build
../configure --target-list=x86_64-linux-user
make -j0
mkdir test && cd $_
mkdir afl-in afl-out
echo prout > ./afl-in/seed
make -C ../ clean
make -C ../ plugin.so
make -C ../ targets/coverage # example target that cheks for "fuzz"
AFL_SKIP_BIN_CHECK=1 ../AFLplusplus/afl-fuzz -i ./afl-in -o ./afl-out -- ../qemu/build/qemu-x86_64 -plugin ../plugin.so ../targets/coverage
or in an easy and straightforward way:
make TARGET_BIN=coverage fuzz
mkdir test && cd $_
mkdir afl-in afl-out
echo prout > ./afl-in/seed
make -C ../ clean
make -C ../ plugin.so
make -C ../ TARGET_BIN=coverage-long cmplog.so cmplog-bootstrapper plugin.so targets/coverage-long
AFL_SKIP_BIN_CHECK=1 ../AFLplusplus/afl-fuzz -i ./afl-in -o ./afl-out -c ./cmplog-bootstrapper -- ../qemu/build/qemu-x86_64 -plugin ../plugin.so ../targets/coverage-long
The cmplog
feature is very powerful, with observed little slowdowns compared to
the coverage-only plugin. The above example should find the magic input in mere seconds.
or:
make TARGET_BIN=coverage-long fuzz
Once built, the plugin can be loaded with the -plugin
option of QEMU for coverage. With AFL++:
make plugin.so
AFL_SKIP_BIN_CHECK=1 afl-fuzz -i afl-in -o afl-out -- qemu-x86_64 -plugin ./plugin.so <target>
As well, you can use the cmplog
feature:
make plugin.so cmplog.so
make cmplog-bootstrapper QEMUPATH=<qemu-path> TARGET_FULLPATH=<target>
AFL_SKIP_BIN_CHECK=1 afl-fuzz -i afl-in -o afl-out -c ./cmplog-bootstrapper -- qemu-x86_64 -plugin ./plugin.so <target>
Note the need for a bootstrapper for cmplog
. This is because -c
flag for
AFL++ takes only a single argument, and the bootstrapper is used to pass the
plugin path and target path to QEMU.
If you want to use your own QEMU, you can specify the path to the QEMU directory
via Make variable QEMUPATH
:
make QEMUPATH=$(pwd)/qemu
If you would like to build the plugin with a specific QEMU plugin API version, note the following:
- coverage has been tested from QEMU v8.1.1 to v9.1.1 (plugin API version 1, to 4)
- cmplog has been tested only on v9.1.1 (plugin API version 4)
The code will automatically detect the QEMU version and use the correct API if it is supported.
Preprocessor variables:
DEBUG=0
: disable debug messagesTARGET_BIN=<target>
: specify the target binary intargets/
, used for building the bootstrapper.
Targets:
cmplog.so
: build thecmplog
plugincmplog-bootstrapper
: build the bootstrapper forcmplog
plugin.so
: build the main pluginclean
: clean the build directory
More for debugging purposes:
run
: run the target binary with the pluginrun-cmplog
: run the target binary with thecmplog
plugindbg
: run the target binary with the plugin ingdb
dbg-cmplog
: run the target binary with thecmplog
plugin ingdb