NEWS | BLOG | THINGS

Fuzzing von eingebetteten Systemen

Motivation

Sicherheitsrelevante Fehler in Software resultieren aus unerwarteten Programmzuständen und werden durch üblicherweise eingesetzte Unit-Tests nicht entdeckt. Statische Code-Analysen untersuchen den Programmcode auf häufig gemachte Programmierfehler, wie beispielsweise Aufrufe unsicherer Funktionen wie strcpy(), ohne dabei die untersuchte Software auszuführen.

  

Um die Software auf unerwartete Programmzuständen und somit Fehler zu testen, welche in Abhängigkeit dynamischer Laufzeitparameter auftreten, werden dynamische Testverfahren eingesetzt. Das in den frühen 1990er Jahren eingeführte Fuzz Testing wird in die Gruppe der dynamischen Testverfahren eingeordnet, da es auf der Ausführung der zu testenden Software basiert, und ist seitdem eine effektive Methode, Sicherheitsschwachstellen in Software zu entdecken.

   

Der Einsatz von Fuzzing hat sich insbesondere zur Identifikation von Speicherkorruptionsfehlern wie Buffer Overflows bewährt, welche in Programmiersprachen wie C/C++ auftreten. Diese Fehler werden typischerweise in spezifischen Programmzuständen ausgelöst und sind somit schwer zu finden. Von einem Angreifer ausgenutzt, können diese Fehler zum Ausfall des Systems oder zur Kontrollübernahme durch den Angreifer führen, was schwere finanzielle Schäden anrichtet und in Sektoren wie Gesundheit und Transport die Gefährdung von Menschenleben zur Folge hat. Aus diesem Grund wird Fuzzing als automatisiertes Testverfahren eingesetzt, um möglichst viele Programmzustände auf Anomalien zu prüfen.

    

Als allgemeine Definition lässt sich festhalten: Fuzz Testing, oder Fuzzing, ist ein Prozess, bei dem Eingaben automatisiert generiert und an die zu testende Software gesendet werden, während das Verhalten der Software auf Anomalien überwacht wird.

Fuzzing-Varianten

Das Testverfahren wird in die drei Varianten Blackbox, Greybox und Whitebox Fuzzing unterteilt. Diese Kategorien unterscheiden sich in der Menge an Informationen, welche sie von der zu testenden Software während des Tests benötigen. Diese Informationen umfassen beispielsweise die erreichte Codeabdeckung oder die Dauer der Ausführung und werden dazu verwendet, die Generierung von neuen Eingaben zu steuern.

 

Beim Blackbox Fuzzing wird lediglich das Ein-/ und Ausgabeverhalten der zu testenden Software berücksichtigt. Neue Eingaben werden durch zufällige Mutationen vorheriger Eingaben erzeugt oder auf Basis einer Grammatik generiert. Diese Variante hat den Vorteil, dass sie einfach und leicht anzuwenden ist, allerdings ist die Generierung von Eingaben nicht intelligent.

 

Beim Whitebox Fuzzing wird ein Analyseverfahren namens Dynamic Symbolic Execution (DSE) verwendet, um Eingaben basierend auf der internen Struktur der zu testenden Software und Laufzeitinformationen zu generieren. Dadurch deckt jede neue Eingabe einen neuen Ausführungspfad ab, was zu einer höheren Anzahl der erreichten Programmzustände führt. Komplexe Berechnungen sowie die Menge an Informationen, die von der zu testenden Software für die Erzeugung neuer Eingaben benötigt werden, erhöhen den Aufwand von Whitebox Fuzzing enorm.

 

Zwischen Blackbox und Whitebox Fuzzing befindet sich das Greybox Fuzzing. Im Gegensatz zum Whitebox Fuzzing können beim Greybox Fuzzing keine Rückschlüsse auf die vollständige Semantik der zu testenden Software gezogen werden. Stattdessen werden leichtgewichtige statische Analysen durchgeführt oder dynamische Laufzeitinformationen wie die erreichte Codeabdeckung durch eine Instrumentierung der zu testenden Software gesammelt, um Geschwindigkeit zu gewinnen und mehr Eingaben testen zu können. Dadurch profitiert das Greybox Fuzzing von einer höheren Effizienz als das Whitebox Fuzzing, während im Vergleich zum Blackbox Fuzzing Eingaben auf intelligentere Weise erzeugt werden und somit mehr Programmzustände erreicht werden können.

Fuzzing-Setups

Mikrocontroller-basierte Geräte in Verbindung mit ihrer dedizierten Software werden als eingebettete Systeme oder Embedded Systems bezeichnet, welche typischerweise für eine spezifische Anwendung entwickelt werden. Ein wesentliches Merkmal eingebetteter Systeme ist ihre als Firmware bezeichnete Software, welche eng mit der verbauten Hardware, einschließlich der angeschlossenen Peripheriegeräte, gekoppelt ist. Ein weiteres wesentliches Merkmal ist die Vielfalt dieser Systeme, die aus den eingesetzten Betriebssystemen, CPU-Architekturen, Kommunikationsmechanismen und Peripheriegeräten resultiert. Diese zwei Hauptmerkmale machen das Fuzz Testing von Firmware zu einer Herausforderung, für die bis jetzt keine allgemeingültige Lösung existiert.

 

Das Zusammenspiel von Hardware und Firmware während der Ausführung muss beim Fuzzing beachtet werden. Zudem ist die Maximierung der Ausführungsgeschwindigkeit, sowie die Messung der erreichten Codeabdeckung elementar für den effektiven Einsatz des Testverfahrens. Basierend auf diesen Rahmenbedingungen werden in vorhandenen Ansätzen Kompromisse zwischen der Genauigkeit von Tests, dem dafür entstehenden Aufwand und  der Performanz des Fuzzing-Prozesses eingegangen.

 

Beim Fuzzing definiert ein Testgerüst ein spezielles Stück Code, welches die Interaktion mit der zu testenden  Software ermöglicht und eine kontrollierte Umgebung für den Fuzzing-Prozess bereitstellt. Testgerüste übermitteln Eingaben an die zu testende Software und stellen sicher, dass diese während eines Tests immer aus einem definierten Zustand gestartet wird. Testgerüste für das Fuzzing von Firmware basieren auf der Abstraktion und Emulation der Hardware oder dem Einsatz von Hardware-in-the-loop-Verfahren.

 

Sollen Firmwareteile getestet werden, die keine Abhängigkeit zu spezieller Hardware aufweisen oder liegt der Fokus des Tests auf der Funktionalität von bestimmten Firmwareteilen, anstatt auf ihrer Interaktion mit der Hardware, wird ein architekturunabhängiges Testgerüst erstellt. Dazu wird entweder hardwareunabhängiger Code verwendet oder auf Funktionen zum Abstrahieren der Hardware zurückgegriffen. Sollen Teile der Firmware, welche auf spezielle Hardwarekomponenten, wie Peripheriegeräte, angewiesen sind, ohne Hardwarebezug getestet werden, so werden die Interaktionen mit diesen Komponenten abstrahiert. Der Vorteil dieser Methode ist die Architekturunabhängigkeit und eine hohe Ausführungsgeschwindigkeit, die Abstraktion der Hardware kann allerdings zu einem hohen initialen Aufwand führen.

  

Alternativ können Emulatoren verwendet werden, um Abhängigkeiten der Firmware zur Hardware abzubilden. Emulatoren ermöglichen es, Betriebssysteme und Programme, die für eine bestimmte Prozessorarchitektur kompiliert wurden, mit einer anderen Prozessorarchitektur auszuführen. Dabei werden die Instruktionen der Zielarchitektur durch Instruktionen der Hostarchitektur ersetzt. So kann beispielsweise eine Firmware, die für die ARM-Architektur gebaut wurde, ebenso auf Systemen mit einer x86-Architektur ausgeführt werden. Für die Emulation der Zielhardware oder einzelner Hardwarekomponenten existieren zahlreiche frei verfügbare Werkzeuge, wie etwa QEMU.

   

Beim Emulator-basierten Fuzzing wird durch die Simulation der Zielarchitektur das Verhalten der Firmware realitätsgetreuer abgebildet, als dies bei Testgerüsten mit Hardwareabstraktionen der Fall ist, wodurch Interaktionen mit der Hardware getestet werden können. Außerdem entbindet das Testen mit einem Emulator von der Notwendigkeit, eine dedizierte Zielhardware zu verwenden und ermöglicht die Nutzung der Ressourcen des Host-PCs, was zu einer erhöhten Effizienz von Tests führt. Für die Emulation wird eine Emulator-spezifische Modellierung der Hardware benötigt, welche auf Grund der Vielfalt eingebetteter Systeme oft nicht vordefiniert ist und manuell erstellt werden muss. Abstraktionen und Ungenauigkeiten dieser Modellierung, genauso wie Fehler in der Emulation können das Ergebnis des Fuzz Testings von hardwarenaher Software negativ beeinflussen.

  

Die realitätsgetreuste Methode für das Fuzzing von Firmware stellt die Ausführung des Testgerüstes direkt auf der Zielhardware dar. Das als Hardware-in-the-Loop (HIL) bezeichnete Verfahren ermöglicht das Testen von hardwarenaher Software wie Treibern, ist bezüglich der Ausführungsgeschwindigkeit und des Sammelns von Laufzeitinformationen allerdings durch die verwendete Zielhardware beschränkt. Um die Effizienz zu erhöhen, wird die Zielhardware lediglich zum Ausführen des Testgerüstes verwendet. Die Analyse der Laufzeitinformationen und die Generierung neuer Eingaben findet auf dem Host-PC statt, welcher mit der Zielhardware über Debug-Schnittstellen wie JTAG oder SWD kommuniziert und den Ablauf des Tests steuert.

Fazit

Grundsätzlich hängt die Wahl eines Fuzzing-Setups an verschiedenen Fragestellungen. Ressourcen, Zeitaufwand, Kosten, aber auch die Einfachheit der Lösung und deren Nutzbarkeit spielen hier eine Rolle. Eine perfekte Lösung, welche universell zu empfehlen ist, existiert nicht. Stattdessen müssen die konkreten Anforderungen berücksichtig und entsprechend umgesetzt werden.

   

Falls Sie Interesse am Thema Fuzzing haben, sprechen Sie uns gerne an!

Sie möchten mehr Details erfahren?

Rufen Sie uns an

+49 6332 811 0

Schreiben Sie uns

Es freut uns von Ihnen zu hören! Wir melden uns umgehend bei Ihnen zurück.