
Fuzzing eingebetteter Systeme mittels Hardware-Breakpoints
Dies ist der begleitende Code für das Paper: 'Fuzzing Embedded Systems using Debugger Interfaces'. Ein Preprint des Papers kann hier gefunden werden https://publications.cispa.saarland/3950/. Der Code ermöglicht es den Nutzern, die im Paper berichteten Ergebnisse zu reproduzieren und zu erweitern. Bitte zitieren Sie das obige Paper, wenn Sie die Ergebnisse berichten, reproduzieren oder erweitern.
.
├── benchmark # Skripte zum Erstellen von Googles Fuzzer-Testsuite und zum Ausführen von Experimenten
├── dependencies # Enthält ein Makefile zur Installation der Abhängigkeiten von GDBFuzz
├── evaluation # Rohdaten der Experimente, die im Paper vorgestellt werden
├── example_firmware # Eingebettete Beispielanwendungen, die für die Evaluierung verwendet werden
├── example_programs # Enthält ein kompiliertes Beispielprogramm und Konfigurationen zum Testen von GDBFuzz
├── src # Enthält die Implementierung von GDBFuzz
├── Dockerfile # Zum Erstellen eines Docker-Images mit allen installierten Abhängigkeiten von GDBFuzz
├── LICENSE # Lizenz
├── Makefile # Makefile zum Erstellen des Docker-Images oder zur lokalen Installation von GDBFuzz
└── README.md # Diese README-Datei
Die Idee von GDBFuzz ist es, Hardware-Breakpoints von Mikrocontrollern als Feedback für coverage-gesteuertes Fuzzing zu nutzen. Dazu wird GDB als generische Schnittstelle verwendet, um eine breite Anwendbarkeit zu ermöglichen. Für die binäre Analyse der Firmware wird Ghidra verwendet. Der Code enthält ein Benchmark-Setup zur Evaluierung der Methode. Zusätzlich sind Beispiel-Firmware-Dateien enthalten.
GDBFuzz ermöglicht coverage-gesteuertes Fuzzing für eingebettete Systeme, kann aber – zu Evaluierungszwecken – auch beliebige Benutzeranwendungen fuzzen. Für das Fuzzing auf Mikrocontrollern empfehlen wir eine lokale Installation von GDBFuzz, um Fuzz-Daten nahtlos an das zu testende Gerät senden zu können.
GDBFuzz wurde unter Ubuntu 20.04 LTS und Raspberry Pi OS 32-bit getestet. Voraussetzungen sind Java und Python3. Erstellen Sie zunächst eine neue virtuelle Umgebung und installieren Sie alle Abhängigkeiten.
virtualenv .venv
source .venv/bin/activate
make
chmod a+x ./src/GDBFuzz/main.py
GDBFuzz liest Einstellungen aus einer Konfigurationsdatei mit den folgenden Schlüsseln.
[SUT]
# Path to the binary file of the SUT.
# This can, for example, be an .elf file or a .bin file.
binary_file_path = <path>
# Address of the root node of the CFG.
# Breakpoints are placed at nodes of this CFG.
# e.g. 'LLVMFuzzerTestOneInput' or 'main'
entrypoint = <entrypoint>
# Number of inputs that must be executed without a breakpoint hit until
# breakpoints are rotated.
until_rotate_breakpoints = <number>
# Maximum number of breakpoints that can be placed at any given time.
max_breakpoints = <number>
# Blacklist functions that shall be ignored.
# ignore_functions is a space separated list of function names e.g. 'malloc free'.
ignore_functions = <space separated list>
# One of {Hardware, QEMU, SUTRunsOnHost}
# Hardware: An external component starts a gdb server and GDBFuzz can connect to this gdb server.
# QEMU: GDBFuzz starts QEMU. QEMU emulates binary_file_path and starts gdbserver.
# SUTRunsOnHost: GDBFuzz start the target program within GDB.
target_mode = <mode>
# Set this to False if you want to start ghidra, analyze the SUT,
# and start the ghidra bridge server manually.
start_ghidra = True
# Space separated list of addresses where software breakpoints (for error
# handling code) are set. Execution of those is considered a crash.
# Example: software_breakpoint_addresses = 0x123 0x432
software_breakpoint_addresses =
# Whether all triggered software breakpoints are considered as crash
consider_sw_breakpoint_as_error = False
[SUTConnection]
# The class 'SUT_connection_class' in file 'SUT_connection_path' implements
# how inputs are sent to the SUT.
# Inputs can, for example, be sent over Wi-Fi, Serial, Bluetooth, ...
# This class must inherit from ./connections/SUTConnection.py.
# See ./connections/SUTConnection.py for more information.
SUT_connection_file = FIFOConnection.py
[GDB]
path_to_gdb = gdb-multiarch
#Written in address:port
gdb_server_address = localhost:4242
[Fuzzer]
# In Bytes
maximum_input_length = 100000
# In seconds
single_run_timeout = 20
# In seconds
total_runtime = 3600
# Optional
# Path to a directory where each file contains one seed. If you don't want to
# use seeds, leave the value empty.
seeds_directory =
[BreakpointStrategy]
# Strategies to choose basic blocks are located in
# 'src/GDBFuzz/breakpoint_strategies/'
# For the paper we use the following strategies
# 'RandomBasicBlockStrategy.py' - Randomly choosing unreached basic blocks
# 'RandomBasicBlockNoDomStrategy.py' - Like previous, but doesn't use dominance relations to derive transitively reached nodes.
# 'RandomBasicBlockNoCorpusStrategy.py' - Like first, but prevents growing the input corpus and therefore behaves like blackbox fuzzing with coverage measurement.
# 'BlackboxStrategy.py', - Doesn't set any breakpoints
breakpoint_strategy_file = RandomBasicBlockStrategy.py
[Dependencies]
path_to_qemu = dependencies/qemu/build/x86_64-linux-user/qemu-x86_64
path_to_ghidra = dependencies/ghidra
[LogsAndVisualizations]
# One of {DEBUG, INFO, WARNING, ERROR, CRITICAL}
loglevel = INFO
# Path to a directory where output files (e.g. graphs, logfiles) are stored.
output_directory = ./output
# If set to True, an MQTT client sends UI elements (e.g. graphs)
enable_UI = False
Eine Beispielkonfigurationsdatei befindet sich in ./example_programs/ zusammen mit einem Beispielprogramm, das mit unserem Fuzzing-Harness in benchmark/benchSUTs/GDBFuzz_wrapper/common/ kompiliert wurde. Starten Sie das Fuzzing für eine Stunde mit dem folgenden Befehl.
chmod a+x ./example_programs/json-2017-02-12
./src/GDBFuzz/main.py --config ./example_programs/fuzz_json.cfg
Wir sehen zuerst die Ausgabe von Ghidra, das die ausführbare Binärdatei analysiert, und anschließend Meldungen, wenn Breakpoints versetzt oder getroffen werden.
Abhängig vom angegebenen output_directory in der Konfigurationsdatei sollte nun ein Ordner trial-0 mit der folgenden Struktur vorhanden sein
.
├── corpus # Ein Ordner, der den Eingabekorpus enthält.
├── crashes # Ein Ordner, der absturzverursachende Eingaben enthält – falls vorhanden.
├── cfg # Der Kontrollflussgraph als Adjazenzliste.
├── fuzzer_stats # Statistiken der Fuzzing-Kampagne.
├── plot_data # Tabelle, die zeigt, zu welcher relativen Zeit in der Fuzzing-Kampagne welcher Basisblock erreicht wurde.
├── reverse_cfg # Der reverse Kontrollflussgraph.
Durch Setzen von start_ghidra = False in der Konfigurationsdatei verbindet sich GDBFuzz mit einer Ghidra-Instanz, die im GUI-Modus läuft. Dazu muss das ghidra_bridge-Plugin manuell aus dem Skript-Manager gestartet werden. Während des Fuzzings werden erreichte Programmblöcke grün hervorgehoben.