Fuzzware is a project for automated, self-configuring fuzzing of firmware images.
The idea of this project is to configure the memory ranges of an ARM Cortex-M3 / M4 firmware image, and start emulating / fuzzing the target without full device emulation. Fuzzware will figure out how MMIO values are used, configure models, and involve the fuzzer to provide hardware behavior which is not fully covered by MMIO models.
Our paper from USENIX Security '22 explains the system in more detail. For a demo, check out our screen cast.
The fuzzware-experiments repository contains the data sets, scripts, and documentation required to replicate our experiments.
First install:
./build_docker.sh
Then run:
./run_docker.sh examples fuzzware pipeline --skip-afl-cpufreq pw-recovery/ARCH_PRO
Directories within this repository. For the experiments in our paper, as well as submodules. | Directory | Description |
---|---|---|
docs | Documentation files (config optimizations, cov analysis, crash analysis). | |
examples | Target firmware samples to test Fuzzware on. | |
fuzzware-emulator | The emulator which performs runs of a firmware for a given input file. | |
modeling | MMIO modeling (based on angr). | |
fuzzware-pipeline | Orchestration between MMIO modeling and emulator. | |
scripts | Some helper scripts (e.g., gather basic blocks in IDB). | |
fuzzware-experiments | Pre-built images/config, crashing POCs/analyses, build scripts to re-run our experiments. |
To not let this document explode, we provide specific documentation in different places:
$ fuzzware -h
, and $ fuzzware <util_name> -h
At its core, Fuzzware works by plugging an instruction set emulator (currently: Unicorn Engine) to a fuzzer (currently: afl / AFL++) and having the fuzzer supply inputs for all hardware accesses. Whenever hardware in Memory-Mapped (MMIO) registers is accessed, the value is served from fuzzing input.
"The fuzzer has no idea about how the hardware it is emulating is supposed to behave, so how can anything useful come out of this?" You might ask. There are different components to this:
Fuzzware is comprised of different components:
For more information on the different components, please refer to the corresponding component subdirectories and READMEs.
There are two out-of-the-box ways to go about using Fuzzware: Native and Docker-based setups. For local development of Fuzzware itself, you may prefer a local setup while for using it, Docker may be the way to go.
After one of the installation options has been successful, Fuzzware should be available (within docker or the fuzzware
virtualenv):
fuzzware -h
To build as Docker container:
./build_docker.sh
A docker image "fuzzware"
is built which contains all the necessary binaries and python modules. To start fuzzing and emulation, a directory can be mapped into the container which contains firmware images and configurations. To run:
./run_docker.sh <my/path/to/targets/repository> [/bin/bash]
For a local setup, your system will have to have a list of local tooling installed to handle building unicorn, setting up virtual environments and finally running different pipeline components. You can see how to set those dependencies up in the Docker file. Without installing all the dependencies first, different steps of the installation process will complain and you will be able to install them one by one.
To install locally:
./install_local.sh
The script will set up two Python virtualenvs:
fuzzware
: The virtualenv containing the local pipeline and emulator modules. This also includes the fuzzware
executable which exposes different parts of the system.fuzzware-modeling
: The virtualenv used for performing symbolic execution-based MMIO access modeling. You should not try installing this without a virtualenv as angr
is one of its dependencies.To use Fuzzware from here, simply use the fuzzware
virtualenv.
workon fuzzware
Find a detailed overview of configuration options in fuzzware-emulator/README_config.yml and more in-depth documentation in docs/ target_configuration.md.
At minimum, you will need a bare-metal firmware blob and know where it is located in memory. With this, you can setup a memory map. For a firmware blob fw.bin
located at address 0x08000000
in ROM, a config located in a newly created examples/my-fw
directory would look like this:
include:
- ../configs/hw/cortexm_memory.yml
# For optional interrupts
- ./../configs/fuzzing/round_robin_interrupts.yml
memory_map:
rom:
base_addr: 0x08000000
size: 0x800000
permissions: r-x
file: ./fw.bin
Alternatively, you may also try out the experimental fuzzware genconfig
utility which creates a basic configuration based on an elf file (extracts the binary from the ELF file, parses sections, and creates an initial memory config).
This will get your firmware image up and running initially. There are additional configuration options to set which are specific to a firmware image and can support hardware features such as DMA, increase performance / decrease MMIO overhead, guide the firmware boot process, focus the fuzzer on specific interrupts, add introspection and debug symbols.
An inline-documentation of firmware image configuration features can be found in the config README of the emulator. These configuration options allow you to configure different aspects concerning:
Workflow for fellow academics:
fuzzware genconfig
)fuzzware pipeline --run-for 24:00:00
fuzzware genstats coverage
fuzzware-project/stats
If you want to get the best out of Fuzzware (as a human in the loop), you should prefer the following steps:
fuzzware genconfig
(works best with elf files, still take the output with a grain of salt and verify manually!)fuzzware pipeline
fuzzware cov
, fuzzware cov -o cov.txt
and fuzzware cov <target_function>
, fuzzware replay --covering <target_function>
fuzzware pipeline -n 16
.fuzzware genstats crashcontexts
fuzzware replay -M -t mainXXX/fuzzers/fuzzerY/crashes/idZZZ
There is a range of fuzzware utilities which we created that you may find useful along the way. The utils and their command-line arguments are documented in fuzzware
itself:
fuzzware -h
For additional descriptions of different steps of the workflow, also check out
In case issues with worker processes are indicated by the pipeline, refer to the project's fuzzware-project/logs
directory for information on what made the worker processes unhappy.
If you are running the pipeline with a large number of fuzzing processes, inotify instance limits may be reached. In this case, run (as root, on the host):
scripts/set_inotify_limits.sh
In case things are missing in your local setup, refer to the dockerfile to figure out which package you might have missed or use a docker setup.
We based our MMIO modeling component on angr version 8.19.10.30. We learned just before the publication of the prototype that this version of angr only supports Python versions lower than 3.10. We added a detection for this into the install scripts and let you install based on a previous version of Python using the environment variable MODELING_VENV_PYTHON3
.
As Fuzzware relies on Unicorn and unicorn does not support floating-point instructions, Cortex-M4f targets need to be compiled using a softfpu.
In case you would like to cite Fuzzware, you may use the following BibTex entry:
@inproceedings {277252,
title = {Fuzzware: Using Precise {MMIO} Modeling for Effective Firmware Fuzzing},
booktitle = {31st USENIX Security Symposium (USENIX Security 22)},
year = {2022},
address = {Boston, MA},
url = {https://www.usenix.org/conference/usenixsecurity22/presentation/scharnowski},
publisher = {USENIX Association},
author={Scharnowski, Tobias and Bars, Nils and Schloegel, Moritz and Gustafson, Eric and Muench, Marius and Vigna, Giovanni and Kruegel, Christopher and Holz, Thorsten and Abbasi, Ali},
month = aug,
}
In case you found bugs using Fuzzware, feel free to let us know! :-)
As a researcher, time for coding is finite. This is why there are still TODOs which could make the Fuzzware implementation better (even if we had infinite time, there would always be more things to improve, of course). If you are interested, here are some sample projects to work on for hacking on Fuzzware: