Es stimmt natürlich beides. Der Begriff „Fuzzing“ an sich bedeutet lediglich, dass einer Schnittstelle automatisiert Daten gesendet werden, mit welchen die Schnittstelle nicht unbedingt rechnet. Im Optimalfall werden dabei Daten gefunden, die von der Schnittstelle zwar akzeptiert werden, jedoch in der Programmlogik nicht oder falsch bedacht wurden. Das Ziel eines solchen Fuzzing-Tests ist dabei, den Dienst oder das Gerät reproduzierbar zum Absturz zu bringen. Sobald eine Eingabe gefunden wurde, die den Dienst erfolgreich zum Absturz bringt, kann untersucht werden, ob die entdeckte Schwachstelle womöglich eine Kompromittierung des Systems erlaubt, oder auch, an welcher Stelle im Code der Fehler entsteht und wie dieser behoben werden kann. Ziel hierbei ist, das System gegen Angriffe zu härten. Im Entwicklungsprozess eingesetzt kann das Ziel aber auch sein, generelle Robustheit zu erreichen. Angreifer verwenden Fuzzing auch gerne als ersten Schritt, um Speichermanagement-Schwachstellen zu erkennen, um diese im nächsten Schritt auszunutzen. Die NSIDE entdeckte beispielsweise im Jahr 2019 eine Buffer-Overflow-Schwachstelle durch Fuzzing, die es ermöglichte, die O2 HomeBox 6441 aus der Ferne zu übernehmen.
Beim „Fuzzing“ können die gesendeten Daten beliebig intelligent oder stupide sein.
So kann bereits von Fuzzing gesprochen werden, wenn beispielsweise allein zufällige Zeichenketten an einen Webserver gesendet werden. Eine Stufe intelligenter wäre, in diesem Fall das HTTP-Protokoll als Grundgerüst zu verwenden und nach und nach für jeden einzelnen Parameter und Header Zufallszeichenketten zu senden. Dies ist bis hin zum sogenannten „Coverage-guided“-Fuzzing steigerbar, bei welchem das Ziel des Tests permanent überwacht wird (auf Debugger-Niveau durch Instrumentierung) und gleichzeitig die Zufallsketten dynamisch angepasst werden, um eine möglichst große Abdeckung im Code des Ziels zu erlangen. Durch diese Abstufungen wird schnell klar, dass Fuzzing (wenn man es ernsthaft betreibt) kein triviales Testen bedeutet und die Quantität entdeckter Schwachstellen sehr stark vom Niveau der Fuzzing-Technik abhängt.
Ein weiteres Problem, welches beliebig granular gelöst werden kann, ist ein Rück- oder Debug-Kanal, sodass als Ergebnis nicht nur ein Absturz detektiert werden kann. Nur den Absturz zu detektieren, ist prinzipiell in Ordnung, jedoch sind Softwareentwickler (und Schadsoftwareentwickler) dankbar über mehr Informationen, wo/wie/warum dieser Absturz auftritt, um das Problem schneller zu identifizieren und zu lösen (oder auszunutzen). Diesem Problem kann wieder auf sehr unterschiedlichem Niveau begegnet werden:
- Die Ausgaben des untersuchten Programms werden zusätzlich aufgezeichnet. Viele Programme/Dienste schreiben Informationen in Logdateien o.ä. Im Optimalfall werden hier sogar Stack-Traces abgelegt, sodass klar wird, in welcher Funktion ein Fehler auftrat.
- Probleme im Kernel eines untersuchten Embedded-Gerätes (oder auch Debug-Ausgaben von Programmen) sind oftmals über einen speziellen Debug-Zugang (meist UART) mitlesbar.
- Das untersuchte Programm wird von vornherein in einem Debugger gestartet, sodass im Falle eines Absturzes der Zustand nachvollzogen werden kann.
- Prinzipiell sind sogar Side-Channel-Vektoren denkbar. So könnten während des Fuzzings der Stromverbrauch oder Schwingungen in verschiedenen Frequenzbereichen durch externe Geräte überwacht werden.
Für diverse Standard- und Spezialfälle gibt es deshalb sowohl kommerzielle Produkte als auch eine sehr aktive Community, die feingranular programmierbare Frameworks entwickelt, aber auch maßgeschneiderte Lösungen für konkrete Einsatzzwecke. Sehr maßgeschneiderte Lösungen sind zum Beispiel Jazzer für das Fuzzen von Java-Code oder FormatFuzzer, um gezielt Dateiformate nachzubauen um diese im nächsten Schritt als Eingaben zu verwenden.
Bei Security-Assessments der NSIDE kommen auch vermehrt Fuzzing-Techniken zum Einsatz, wobei wir in der Regel auf maßgeschneiderte Individuallösungen setzen, um für Kunden das optimale Kosten-Nutzen-Verhältnis zu ermöglichen. Gerade im Embedded-Bereich treten oftmals Einschränkungen auf, die durch kommerzielle Produkte nicht ausreichend gelöst sind:
- Es ist nicht trivial, im Falle eines Absturzes Debug-Informationen wie Stack-Traces zu erhalten, da das Gerät prinzipiell bereits gehärtet ist und einen Betriebssystemzugriff (also beispielsweise Shell-Zugang über SSH, Telnet oder UART) eigentlich nicht zulässt.
- Die Geräte haben eine andere Prozessor-Architektur als die Testsysteme der Analysten. Eine Ausführung von Gerätecode auf anderen Systemen ist deshalb nur mit Emulatoren, manchmal aber auch gar nicht möglich (zum Beispiel, wenn Funktionen von Programmen direkten Hardwarezugriff benötigen).
- Der Kostenfaktor: Sobald der Fuzzer einmal läuft, entstehen kaum Kosten mehr (den Stromverbrauch und etwaige Hardwaredefekte einmal außen vor gelassen). Es ist deshalb essenziell, eine möglichst hohe Abdeckung des Zielsystems in kurzer Zeit zu erreichen.
Je nach Gerät und Kunde ergeben sich deshalb verschiedene Möglichkeiten und Zielsetzungen.
Um die ganze Theorie mit etwas Praxis zu untermauern, zeigt der zweite Teil dieses Blogeintrags, wie der Fuzzer Boofuzz verwendet werden kann, um gezielt Protokolle zu „fuzzen“. Im dritten Teil wird darauf eingegangen, wie es durch Fuzzing bei der NSIDE möglich war, eine Schwachstelle in einem Router zu entdecken, die die Übernahme einer großen Zahl an Routern ermöglicht hätte.