Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
blackbox-fuzzing — Fuzzing von IoT-Geräten am Beispiel des Routers TL-WR902AC | Kitploit
Tools/GitHubGitHub/otsmr/blackbox-fuzzing
IoT-SicherheitSchwachstellenanalyseExploitationReverse EngineeringFuzzingBinäranalysePapers & ForschungLernen & BildungFirmware-Analyse
GitHubotsmr/blackbox-fuzzing

blackbox-fuzzing

Fuzzing von IoT-Geräten am Beispiel des Routers TL-WR902AC

132174vor 9 MonatenVon Kitploit geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen
Repository anzeigen
Webseite

Blackbox-Fuzzing von IoT-Geräten am Beispiel des Routers TL-WR902AC

Dies ist die HTML-Version meiner Studienarbeit, die als PDF hier heruntergeladen werden kann.

Einleitung

Fuzzing hat sich zu „einer der effektivsten Methoden“ entwickelt, um Fehler in Software zu finden. Mit dieser oder ähnlichen Behauptungen beginnen viele aktuelle Arbeiten zum Thema Fuzzing [google-scholar]. Das Hauptziel unserer letzten Studienarbeit zum Thema „Internet of Vulnerable Things“ war es, einen speicherbezogenen Fehler zu finden und anschließend einen Exploit für diese Schwachstelle zu schreiben. Wir konnten eine Schwachstelle durch das Reverse Engineering der Firmware finden, aber es wurden keine speicherbezogenen Fehler gefunden. Einen Pufferüberlauf durch manuelles Reverse Engineering einer Binärdatei zu finden, ist nicht nur zeitaufwendig, sondern erfordert auch viel Erfahrung. Fuzzing zielt gleichzeitig darauf ab, der „effektivste Weg“ zu sein, um solche speicherbezogenen Schwachstellen zu finden. Google zum Beispiel hat OSS-Fuzz eingeführt, das kontinuierlich Open-Source-Software fuzzt und bereits über 10.000 Schwachstellen in 1.000 Projekten gefunden hat [oss-fuzz].

Das Ziel dieser Studienarbeit ist es erneut, eine speicherbezogene Schwachstelle zu finden, diesmal jedoch mithilfe von Fuzzing. Die angestrebte Schwachstelle soll über das Netzwerk ausnutzbar sein, ohne Kenntnis der Administrator-Anmeldedaten. Dieses Papier beschreibt den Weg zur Erreichung dieses Ziels. Dazu ist das Papier in zwei Teile gegliedert. Der erste Teil konzentriert sich darauf, wie man ein vielversprechendes Ziel findet, welche Werkzeuge verwendet werden können und was ein gutes Fuzzing-Ziel ausmachen sollte. Der zweite Teil beschreibt dann, wie man einen Harness entwickelt und debuggt, der in der Lage ist, eine bestimmte Funktion in einer Binärdatei zu fuzzen. Anschließend wird der entwickelte Harness von AFL++ verwendet, um die Zielfunktion zu fuzzen. Im Folgenden werden ein kurzer Hintergrund und der aktuelle Stand der Technik im Bereich IoT-Geräte-Fuzzing dargestellt.

Alle im Rahmen dieser Studienarbeit erstellten Dateien werden ebenfalls vollständig auf GitHub veröffentlicht und können über die folgende URL abgerufen werden: otsmr/blackbox-fuzzing.

Stand der Technik

Fuzzing von IoT-Geräten ist nicht so einfach wie das Fuzzing eines Open-Source-Projekts. Oft ist der Quellcode proprietär, was Gray-Box-Fuzzing, bei dem der Quellcode für die beste Fuzzing-Leistung instrumentiert wird, unmöglich macht [afl-persistent]. Außerdem wird die CPU-Architektur von Fuzzern oft nicht nativ unterstützt, was einen Emulator wie QEMU [qemu] erfordert, was ebenfalls die Fuzzing-Geschwindigkeit verlangsamt [afl-persistent]. Ein weiteres Problem sind die Hardware-Peripheriegeräte, was die Entwicklung eines allgemeinen Ansatzes erschwert. Die Arbeit „Embedded Fuzzing: A Review of Challenges, Tools, and Solutions“ [embedded-fuzzing] gibt einen Überblick über verschiedene Fuzzing-Strategien, wie hardwarebasiertes Embedded-Fuzzing. Die meisten dieser Strategien benötigen den Quellcode des Zielprogramms, z. B. wenn der Quellcode von Fuzzern wie AFL auf ARM-basierte IoT-Geräte portiert wird, um den Fuzzer auf der IoT-Hardware auszuführen. Auch das Ausführen des Fuzzers auf der Hardware des Geräts führt zu Leistungsproblemen, da diese häufig über CPUs mit geringer Leistung verfügen, die langsamer sind als normale Desktop-CPUs. Ein weiterer in dieser Arbeit vorgestellter Ansatz ist emulationsbasiertes Embedded-Fuzzing. Dabei wird entweder ein einzelnes Zielprogramm in einem Emulator ausgeführt, um coverage-guided Fuzzing durchzuführen, oder das gesamte System.

Die oben genannten Ansätze zielen alle direkt auf eine Binärdatei ab, indem sie einen Emulator verwenden oder den Quellcode instrumentieren. Diese Ansätze erfordern ein Fuzzing-Setup, das oft speziell für ein einzelnes IoT-Gerät maßgeschneidert werden muss und schwer zu verallgemeinern ist. Aus diesem Grund haben Forscher ein Programm IoTFuzzer entwickelt, das ein automatisiertes Fuzzing-Framework sein soll, das darauf abzielt, „Speicherkorruptionsschwachstellen ohne Zugriff auf ihre Firmware-Images zu finden [iotfuzzer].“ IoTFuzzer basiert auf der Beobachtung, dass die meisten IoT-Geräte eine mobile App zur Steuerung haben und solche Apps Informationen über das Protokoll enthalten, das zur Kommunikation mit dem Gerät verwendet wird. Das Programm identifiziert und verwendet dann programmspezifische Logik wieder, um die Testfälle zu mutieren und IoT-Ziele effektiv zu testen [iotfuzzer].

Hintergrund

Harness

Ein Harness beschreibt eine Sequenz von API-Aufrufen, die die vom Fuzzer bereitgestellten Eingaben verarbeiten. Im Gegensatz zu einer normalen Anwendung, die oft keinen Harness benötigt, muss eine Bibliothek, die wiederverwendbare Funktionen implementiert, mit den richtigen Parametern und auch in der richtigen Reihenfolge aufgerufen werden, damit der Zustand zwischen mehreren gemeinsamen Funktionsaufrufen aufgerufen werden kann. Wenn die Bibliothek zufällig gefuzzt wird, ohne die Zustandsmaschine aufzubauen, wird das wahrscheinlich nicht erfolgreich sein und stattdessen eine Menge falsch-positiver Abstürze erzeugen, wenn die Abhängigkeiten der Bibliothek nicht erzwungen werden. Dies kann zum Beispiel passieren, wenn eine Puffergrößenprüfung vom Fuzzer übersprungen wird, was zu einem scheinbaren Pufferüberlauf führt.

In diesem Papier werden normale Anwendungen gefuzzt, aber wegen der Hardware-Abhängigkeiten bei der Verwendung von Sockets und Multithreading müssen wir auch für sie einen Harness erstellen. Der Harness wird im Kontext der Binärdatei geladen und kann interne Funktionen des Zielprogramms aufrufen, wie in Code 10 gezeigt.

Corpus

Der Begriff „Corpus“ beschreibt gültige Eingabebeispiele oder Testfälle und dient als grundlegende Referenz für die Erzeugung neuer Eingabedaten während des Fuzzing-Prozesses. In Code 10 wäre dies beispielsweise eine HTTP-Anfrage. Fuzzer nutzen diesen Corpus, um mutierte oder diversifizierte Testfälle zu erzeugen, was die Erkennung von Softwareschwachstellen durch die Erkundung verschiedener Eingabeszenarien unterstützt.

Ein vielversprechendes Ziel finden

Der zeitaufwendigste Teil des Blackbox-Fuzzing ist das Finden einer potenziell verwundbaren Funktion in der Firmware. Der erste Schritt besteht darin, interessante Binärdateien zu finden, die zum Beispiel über das Netzwerk erreichbar sind, unsichere Funktionen verwenden oder keine Sicherheitsfunktionen wie Stack-Canary aktiviert haben, was ein Schutz vor Pufferüberläufen ist. Unsere letzte Arbeit ([iovt]) hat bereits beschrieben, wie man die Firmware aus dem Zielrouter extrahiert und wie man eine potenziell gefährliche Binärdatei findet. Dazu wurde das Tool EMBA [emba] verwendet. EMBA bewertet alle in der Firmware gefundenen Binärdateien nach der Anzahl unsicherer Funktionen wie strcpy, Netzwerkzugriff und Sicherheitsmechanismen wie Stack-Canary oder dem NX-Bit, die bei der Ausnutzung eines Pufferüberlaufs interessant werden, was in Code 1 zu finden ist.

```txt [+] STRCPY - top 10 results: 235 : libcmm.so : common linux file: no | No RELRO | No Canary | NX disabled | No Symbols | No Networking | 77 : wscd : common linux file: no | No RELRO | No Canary | NX disabled | No Symbols | Networking | [snip] 28 : httpd : common linux file: yes | RELRO | No Canary | NX enabled | No Symbols | Networking | 27 : cli : common linux file: no | No RELRO | No Canary | NX disabled | No Symbols | No Networking | ```

Code 1: EMBAs Ergebnis von unsicheren Verwendungen der Funktion strcpy.

Da das Ziel dieses Papers darin besteht, eine Speicherschwachstelle zu finden, die über das Netzwerk ohne Kenntnis der Admin-Zugangsdaten ausgenutzt werden kann, muss die verwundbare Funktion über das Netzwerk aufrufbar sein und direkt mit der bereitgestellten Benutzereingabe interagieren. Aber eine Netzwerkinteraktion bedeutet nicht, dass die Binärdatei auch direkt über das Netzwerk zugänglich ist. Um herauszufinden, welche Binärdateien lauschen, können wir die UART-Root-Shell verwenden, die bereits in [iovt] eingerichtet wurde.

```txt ~ # netstat -tulpn Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 127.0.0.1:20002 0.0.0.0:* LISTEN 1045/tmpd tcp 0 0 0.0.0.0:1900 0.0.0.0:* LISTEN 1034/upnpd tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1027/httpd tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1224/dropbear udp 0 0 0.0.0.0:20002 0.0.0.0:* 1048/tdpd [...] ```

Code 2: Verwendung der UART-Root-Shell zur Ausführung von netstat

Reverse Engineering des Binärprogramms

Das erste vielversprechende Binärprogramm ist wscd. Das Binärprogramm enthält die meisten unsicheren strcpy-Aufrufe (abgesehen von der Bibliothek libcmm.so) und Netzwerkinteraktion, was bei wscd bedeutet, dass es eine Verbindung zu einem UPnP-Gerät herstellt und nicht auf einem bestimmten Port lauscht. Es hat, wie später gezeigt wird, eine Funktion, die sich einfach fuzzen lässt, weshalb dieses Binärprogramm in diesem Dokument als Beispiel zur Erläuterung des allgemeinen Vorgehens ausgewählt wurde. Bevor wir mit dem Reverse Engineering beginnen, können wir die UART-Root-Shell nutzen, um herauszufinden, ob das Binärprogramm ausgeführt wird und wie es gestartet wurde.

```txt $ ps PID USER VSZ STAT COMMAND 962 admin 1096 S wscd -i ra0 -m 1 -w /var/tmp/wsc_upnp/ 1018 admin 1080 S wscd_5G -i rai0 -m 1 -w /var/tmp/wsc_upnp_5G/ ```

Code 3: Verwenden des Befehls ps, um alle laufenden Programme anzuzeigen.

Mit ps sehen wir nicht nur, dass die Binärdatei ausgeführt wird, sondern auch, welche Argumente übergeben werden, die wichtig sind, um zu überprüfen, ob eine potenzielle Funktion überhaupt aufgerufen wird. Die Bedeutung dieser Argumente kann der CLI-Hilfe entnommen werden, die angezeigt wird, wenn die Binärdatei ohne Argumente aufgerufen wird.

```txt $ chroot root /qemu-mipsel-static /usr/bin/wscd Usage: wscd [-i infName] [-a ipaddress] [-p port] [-f descDoc] [-w webRootDir] -m UPnPOpMode -D [-d debugLevel] -h -i: Interface name this daemon will run wsc protocol(if not set, will use the default interface name - ra0) e.g.: ra0 -w: Filesystem path where descDoc and web files related to the device are stored e.g.: /etc/xml/ -m: UPnP system operation mode 1: Enable UPnP Device service(Support Enrolle or Proxy functions) 2: Enable UPnP Control Point service(Support Registratr function) 3: Enable both UPnP device service and Control Point services. [...] ```

Code 4: Optionen der Binärdatei wscd.

Wie in Code 4 gezeigt, wird wscd mit "Enabled UPnP Device service" gestartet, was vielversprechend aussieht. Nachdem verifiziert wurde, dass die Binärdatei tatsächlich auf dem Router läuft, kann sie dann mit Ghidra analysiert werden, um nach verdächtigen Funktionen zu suchen. Für das Fuzzing sind Parsing-Funktionen besonders interessant, weil sie normalerweise komplex sind und die Eingabe, die geparst wird, oft Längenfelder für die enthaltenen Daten besitzt, so wie das TCP-Paket die Länge der Payload enthält.

Abbildung 1: Verwenden von Ghidra zur Suche nach Parsing-Funktionen.

Ein weiterer Vorteil von Parsing-Funktionen ist, dass sie oft nicht mit anderen Teilen des Codes interagieren und keine Benutzerinteraktion über das Netzwerk erfordern. Die Parsing-Funktion kann also direkt mit der Eingabe aufgerufen werden, ohne die Binärdatei zu verändern oder andere Funktionen zu überschreiben, sodass die Funktion gefuzzt werden kann.

Bevor mit dem Fuzzing der Funktion begonnen wird, sollte überprüft werden, ob die Funktion überhaupt ausgelöst wird, denn die Funktion ist nur interessant, wenn sie mit einer vom Benutzer kontrollierten Eingabe aufgerufen wird. Dazu kann Ghidra verwendet werden, um nach Referenzen auf die Zielfunktion zu suchen. Im Fall der Funktion parser_parse gibt es mehrere Möglichkeiten. Da wir wissen, wie das Programm gestartet wird, können die Aufrufe auf einen einzigen Funktionsaufrufbaum reduziert werden, wie in Code 5 gezeigt.

```c main() if ((WscUPnPOpMode & 1) != 0) // Argument -m 1 WscUPnPDevStart() UpnpDownloadXmlDoc() -> my_http_Download() -> http_Download() if (http_MakeMessage()) http_RequestAndResponse() http_RecvMessage() ```

Code 5: Aufrufbaum der Funktion parser_parse

Nachdem eine Zielfunktion gefunden wurde, können wir nun ein Fuzzing-Setup erstellen, um die Funktion zu fuzzen, was im nächsten Teil beschrieben wird. Aber zuerst werden andere vielversprechende Funktionen vorgestellt.

Andere potenziell verwundbare Funktionen

Für diese Arbeit wurden mehrere potenzielle Binärdateien manuell auf verdächtige Funktionen analysiert. Im Folgenden finden Sie eine kurze Zusammenfassung anderer möglicher Ziele, die gefunden wurden.

Die Binärdatei httpd ist das Backend für die Admin-Weboberfläche. Die Binärdatei ist über das Netzwerk auf Port 80 erreichbar. Eine interessante Funktion in httpd ist die Funktion httpd_parser_main. Beim Überfliegen der Parser-Implementierung mit Ghidra konnten mehrere verdächtige Codeteile identifiziert werden. Einer der verdächtigen Teile ist das Parsen des Content-Type. Im Folgenden finden Sie eine einfache HTTP-Anfrage.```txt POST / HTTP/1.1\r\n Content-Type: multipart/form-data; boundary=X;\r\n Host: example.com\r\n \r\n \r\n DATA\r\n

root@kitploit:~
Unten ist ein Ausschnitt aus der Funktion `httpd_parser_main`, die den `Content-Type` aus der
vom Benutzer bereitgestellten HTTP-Anfrage parst.


<div id="c6"></div>```c
// user_input_ptr points to
//  "Content-Type: multipart/form-data; boundary=X;\r\nHost: example.com\r\n..."
cursor = strstr(user_input_ptr,"multipart/form-data");

if (user_input_ptr == cursor) {
 cursor = strstr(user_input_ptr,"boundary=");
 user_input_ptr = cursor + 9;

 // user_input_ptr points now to "X;\r\nHost: example.com\r\n..."

 if (cursor != (char *)0x0) {

  do {
    while (cursor = user_input_ptr, *cursor == " ") {
      user_input_ptr = cursor + 1;
    }
    user_input_ptr = cursor + 1;
  } while (*cursor == "\t");

  // cursor points now to "X;\r\nHost: example.com\r\n..."

  // strchr returns a pointer to the first occurrence of ";" in the user request.
  // If ";" is not found, the function returns a null pointer.
  user_input_ptr = strchr(cursor, ";");
  if (user_input_ptr != (char *)0x0) {
    // The character ";" is replaced by an null byte to terminate the string
    *user_input_ptr = "\0";
    // cursor points now to "X\0\r\nHost: example.com\r\n..."
  }

  // DAT_00444050 global array from 0x00444050 to 0x0044414f (255 Bytes)
  strcpy(&DAT_00444050, cursor);
  // DAT_00444050 contains now "X"
 }
}

Code 6: Aufrufbaum der Funktion parser_parse

Die Schwachstelle in diesem Code ist der Funktionsaufruf strcpy und die Annahme, dass der Content-Type mit einem Semikolon endet. Da strcpy den Puffer bis zum nächsten Nullbyte kopiert und, wie in Code 6 gezeigt, das Nullbyte nur dann hinzugefügt wird, wenn ein Semikolon gefunden wird. Durch das Entfernen des Semikolons befindet sich das nächste Nullbyte am Ende des Eingabepuffers, z. B. am Ende der HTTP-Anfrage. Dadurch kann die globale Variable DAT_00444050 überlaufen, wodurch Daten jenseits der Adresse 0x0044414f überschrieben werden. Die Herausforderung besteht nicht nur darin, eine interessante globale Variable jenseits dieser Adresse zu finden, die überschrieben werden könnte, sondern auch darin, dass wegen strcpy keine Nullbytes verwendet werden können. Wenn es aber einen solchen Fehler gibt, sind wahrscheinlich noch mehr zu finden.

Die Binärdatei tdpd wird von der mobilen App verwendet und ist über UDP im lokalen Netzwerk erreichbar. tdpd hat fast dieselben Funktionen wie tmpd, die jedoch größtenteils nie aufgerufen werden. Die Hauptfunktion lauscht lediglich auf dem UDP-Port auf Nachrichten und antwortet immer mit grundlegenden Informationen über den Router, etwa Name oder Modell. Es gibt kaum eine Interaktion mit der vom Benutzer bereitgestellten Eingabe, weshalb sie für das Fuzzing nicht interessant ist.

Ein weiteres interessantes Binärpaar sind upnpd und ushare. Beide Binärdateien verarbeiten UPnP-Nachrichten, die daher XML parsen müssen. Da sich in der Binärdatei eine Copyright-Zeichenkette finden lässt, kann davon ausgegangen werden, dass diese Programme nicht von TP-Link entwickelt wurden.```sh $ strings usr/bin/ushare | grep "(C)" Benjamin Zores (C) 2005-2007, for GeeXboX Team.

root@kitploit:~
Beide Binärdateien laden die gemeinsam genutzten Bibliotheken `libupnp.so` und `libixml.so`, die dieselben
Funktionen wie das Open-Source-Projekt `pupnp` [\[pupnp\]](https://github.com/pupnp/pupnp/) besitzen. Da
der Fokus dieses Papers auf Black-Box-Fuzzing liegt, werden diese Binärdateien ignoriert. Aber Gray-Box-Fuzzing dieser
Bibliothek könnte Potenzial haben, denn 2021 wurde ein Speicherleck in `libixml.so` gefunden
[\[pupnp-mem-leak\]](https://github.com/pupnp/pupnp/issues/249).

Die Binärdatei **tmpd** ist das Backend der mobilen App. Das Interessante daran ist, dass der Router und
die mobile App über ein benutzerdefiniertes binäres Protokoll kommunizieren. Im Folgenden wird eine Nachricht vom
Client an den Server gezeigt.

<div id="c7"></div>```txt
00000000  01 00 05 00 00 08 00 00  00 00 00 17 50 7b 6e fe  |............P{n.|
00000010  01 01 02 00 00 00 00 00                           |........        |

Code 7: Nachricht von der Mobile-App an den Router.

Um das binäre Protokoll zu verstehen, wurde die Binärdatei tmpd mithilfe von Ghidra zurückentwickelt. Mit diesen Informationen kann die Nachricht in Code 7 wie folgt aufgeschlüsselt werden:```txt 01 00 05 00 : Version 00 08 00 00 : Size (8 Bytes) 00 00 00 17 : Datatype 50 7b 6e fe : Checksum (CRC32) 01 01 : Options 02 00 : Function id 00 00 00 00 : Function parameters

root@kitploit:~
<p class="text-align: center">Code 8: Benutzerdefiniertes binäres Protokoll aufgeschlüsselt.</p>

Das sieht vielversprechend aus, da solche binären Protokolle geparst werden müssen. Aber der
verdächtigste Teil des binären Protokolls ist nicht das Längenfeld, sondern die Verwendung der
Funktions-ID und der Funktionsparameter.


<figure id="f2">
  <p><img src="https://assets.kitploit.com/production/public/readmes/48851/43cb089d7f85d30ba036234ff0fdb29b6a54dd4a3382b2bd68e065f9f6c75369.png" style="width:90.0%" /></p>
  <figcaption>
    <p style="text-align: center">Abbildung 2: Rekonstruierte Funktion aus tmpd, die die Funktions-ID und deren Parameter parst.</p>
  </figcaption>
</figure>

[Abbildung 2](#f2) zeigt einen Teil der dekompilierten Parserfunktion des benutzerdefinierten
Protokolls. In Zeile 16 wird die Funktions-ID extrahiert, und die entsprechende Funktion wird dann
in Zeile 29 aufgerufen. Das verdächtige Verhalten besteht darin, dass die Funktion mit Parametern
aufgerufen wird, die ohne jede Prüfung aus dem benutzergesteuerten Eingabepuffer extrahiert werden.
Wir könnten nun versuchen, eine Funktion in der in [Abbildung 3](#f3) gezeigten Sprungtabelle zu
finden, bei der dies gefährlich sein könnte, etwa wenn der Parameter verwendet wird, um einen Puffer
zu indizieren oder als Zeichenkette interpretiert wird. Anstatt manuell zu rekonstruieren und die
über 100 Funktionen zu durchsuchen, was zeitaufwändig wäre, können wir einen Fuzzer verwenden, der
dies automatisch erledigt.

<figure id="f3">
<p><img src="https://assets.kitploit.com/production/public/readmes/48851/708cf59aee459b840ca6dbcac32948d2b80fb426c3d4cc745157eac4f9b7c28e.png"
style="width:90.0%" /></p>
<figcaption><p style="text-align: center">Abbildung 3: Rekonstruierte Funktion aus tmpd, die die Funktions-ID
und ihre Parameter parst.</p></figcaption>
</figure>

Leider ist das Binary `tmpd` über das Netzwerk nur lokal erreichbar, wie in [Code 2](#c2) gezeigt.
Um sich mit diesem Binary zu verbinden, verbindet sich die App zunächst per SSH im Modus
`direct-tcpip` mit dem Router, der die Pakete lediglich an den lokalen Prozess weiterleitet. Und die
SSH-Verbindung ist durch die Admin-Zugangsdaten geschützt. Aber wie in
[\[iovt\]](https://raw.githubusercontent.com/otsmr/internet-of-vulnerable-things/main/Internet_of_Vulnerable_Things.pdf)
beschrieben, kann die SSH-Verbindung leicht kompromittiert werden, da der Server-Host-Key von der App
nie überprüft wird. Indem man jedes an das Internet weitergeleitete Paket verwirft, kann der Admin
dazu verleitet werden, sich am Router anzumelden, während ein Man-in-the-Middle-Angriff durchgeführt
wird, um die Zugangsdaten zu stehlen.

## Fuzzing mit AFL++ und QEMU

In diesem Abschnitt wird ein Harness entwickelt, das auf eine der zuvor gefundenen Funktionen
abzielt. Nachdem das Harness entwickelt wurde, wird der State-of-the-Art-Fuzzer AFL++
[\[aflpp\]](https://github.com/AFLplusplus/AFLplusplus) verwendet, um die Zielfunktion zu fuzzen.
Da die Binaries für die `mipsel`-Architektur kompiliert sind, wird der Emulator QEMU verwendet, um
das Binary auszuführen. Das grundlegende Fuzzing-Setup, das in dieser Arbeit verwendet wird, ist
weitgehend von dem Blogeintrag "Firmware Fuzzing 101" von Adam Van Prooyen
[\[b101\]](https://www.mayhem.security/blog/firmware-fuzzing-101) inspiriert.

### Fuzzing-Umgebung 

Um eine einfach reproduzierbare Fuzzing-Umgebung zu erstellen, ist Docker die beste Wahl. Wir haben
ein Dockerfile erstellt, das alle notwendigen Werkzeuge installiert, wie einen Cross-Compiler für
die `mipsel`-CPU-Architektur oder `gdb-multiarch`, mit dem das Harness debuggt werden kann.

Darüber hinaus wird AFLplusplus heruntergeladen und zusammen mit QEMU kompiliert, das in einer
Version mit kleinen Anpassungen gebaut wird, um nicht instrumentierte Binaries unter afl-fuzz
ausführen zu können.```docker
FROM debian:latest

RUN apt update && apt install -y \
      curl \
      vim \
      gcc-mipsel-linux-gnu \
      openssh-server \
      qemu-user-static \
      gdb-multiarch
# Qemu statics are installed at /usr/bin/qemu-mipsel-static

# Compiling AFL++
RUN apt install -y git make build-essential clang ninja-build pkg-config libglib2.0-dev libpixman-1-dev
RUN git clone https://github.com/AFLplusplus/AFLplusplus /AFLplusplus
WORKDIR /AFLplusplus
RUN make all
WORKDIR /AFLplusplus/qemu_mode
RUN CPU_TARGET=mipsel ./build_qemu_support.sh

RUN echo "#!/bin/bash\n\nsleep infinity" >> /entry.sh
RUN chmod +x /entry.sh

WORKDIR /share
ENTRYPOINT [ "/entry.sh" ]

Dockerfile, das die notwendigen Werkzeuge installiert.

Das Image kann dann mit docker build erstellt werden.```sh docker build -t fuzz .

root@kitploit:~
Wenn das Image erstellt wurde, kann es einfach mit `docker run` verwendet werden, das dann den Container startet.```sh
docker run -d --rm -v $PWD/:/share --name fuzz fuzz

Mit der Option -d wird der Container im Hintergrund gestartet. Mit docker exec können mehrere Shells im Container gestartet werden, was hilfreich ist, um das ausführbare Programm in einer Sitzung mit QEMU und in der anderen Sitzung gdb-multiarch zu starten.```sh docker exec -it fuzz /bin/bash

root@kitploit:~
### Die main-Funktion überschreiben

Im vorherigen Abschnitt wurde ein leistungsfähiges Fuzz-Ziel identifiziert. Das Problem ist, dass beim Ausführen der
Binärdatei der Funktionsaufruf nie erreicht wird, da die Funktion `parser_parse` nur aufgerufen wird, wenn
ein TCP-Paket über einen Socket empfangen wird. Das wäre nicht nur schlecht für die Leistung, sondern auch schwer zu
Einrichten. Deshalb sollte der Einstiegspunkt des Fuzzers an einer anderen Stelle liegen als die normale
main-Funktion.  Dazu kann die Umgebungsvariable `LD_PRELOAD` verwendet werden, die das Injizieren eines Harness,
das Zugriff auf interne Funktionen hat, ermöglicht. Wie die Manpage von `ld.so`, die für das
Verlinken der gemeinsam genutzten Bibliotheken, die eine ausführbare Datei zur Laufzeit benötigt, verantwortlich ist, beschreibt, kann `LD_PRELOAD` verwendet werden
„Funktionen in anderen gemeinsam genutzten Objekten selektiv zu überschreiben
[\[man-pages\]](https://www.man7.org/linux/man-pages/man8/ld.so.8.html)."

Die Funktion `__uClibc_main` eignet sich am besten für diesen Zweck. Um diese Funktion zu überschreiben, muss eine C-Datei
erstellt werden, die eine Funktion mit demselben Namen enthält.```c
void __uClibc_main(void *main, int argc, char** argv) {
    // Harness code, e.g. call the function parser_append
    printf("My custom __uClibc_main was called!");
}

Die C-Datei kann dann mit mipsel-linux-gnu-gcc zu einem Shared Object für die mipsel-Architektur cross-kompiliert werden. Die Option -fPIC ermöglicht „Position Independent Code", was bedeutet, dass der Maschinencode nicht davon abhängt, an einer bestimmten Adresse platziert zu sein, da relative Adressierung anstelle von absoluter verwendet wird.```txt $ mipsel-linux-gnu-gcc parser_parse_hook.c -o parser_parse_hook.o -shared -fPIC

root@kitploit:~
Die neu erstellte Shared Library kann dann geladen werden, indem die Umgebungsvariable `LD_PRELOAD`
zum QEMU-Befehl hinzugefügt wird.```txt
$ chroot root /qemu-mipsel-static -E LD_PRELOAD=/parser_parse_hook.o /usr/bin/wscd
My custom __uClibc_main was called!

Mit dem Befehl chroot können das aktuelle Verzeichnis und das Root-Verzeichnis für den angegebenen Befehl geändert werden. Dies ist hilfreich, da die ausführbare Datei wscd andere Dateien öffnet, wie gemeinsam genutzte Bibliotheken aus der Firmware. Wir können dieses Verhalten sehen, indem wir das Argument -strace zu QEMU hinzufügen.```txt chroot root /qemu-mipsel-static -E LD_PRELOAD=/parser_parse_hook.o -strace /usr/bin/wscd /corpus/notify.txt 38180 mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS|0x4000000,-1,0) = 0x7f7e7000 38180 stat("/etc/ld.so.cache",0x7ffffa48) = -1 errno=2 (No such file or directory) 38180 open("/parser_parse_hook.o",O_RDONLY) = 3 38180 fstat(3,0x7ffff920) = 0 38180 close(3) = 0 38180 munmap(0x7f7e6000,4096) = 0 38180 open("/lib/libpthread.so.0",O_RDONLY) = 3 38180 open("/lib/libc.so.0",O_RDONLY) = 3 [...]

root@kitploit:~
Wie wir sehen können, öffnet die ausführbare Datei mehrere Bibliotheken im `/lib/`-Ordner auf der Firmware und nicht auf dem Host.

### Entwicklung und Debugging des Harness

Nachdem das Setup erstellt wurde, können wir nun mit der Entwicklung eines Harness beginnen. Wie im Hintergrundabschnitt beschrieben, ist der Harness der Treiber zwischen dem Fuzzer und der Zielfunktion. Der Harness lädt die Fuzz-Eingabe, die von AFL++ in einer Datei gespeichert wird. Mit dem Dateipfad als Parameter ruft der Harness dann das Fuzzing-Ziel auf; in diesem Fall wäre das `parser_append`. Die Funktionen können über die Adresse aufgerufen werden.

<div id="c10"></div>```c
void __uClibc_main(void *main, int argc, char** argv)
{
  // Verify that a filename is provided
  if (argc != 2) exit(1);

  // Create function pointer to the fuzz target
  int (*parser_request_init)(void *, int) = (void *) 0x00412564;
  int (*parser_append)(void *, void *, int) = (void *) 0x00412e98;

  // Open the fuzz input file
  int fd = open(argv[1], O_RDONLY);
  char fuzz_buf[2048 + 1];
  int fuzz_buf_len = read(fd, fuzz_buf, sizeof(fuzz_buf) - 1);
  if (fuzz_buf_len < 0) exit(1);
  fuzz_buf[fuzz_buf_len] = 0;

  // Call the target functions
  uint8_t parsed_data[220]; 
  parser_request_init(parsed_data, 8);
  int status = parser_append(parsed_data, fuzz_buf, fuzz_buf_len);
  printf("Response is %d\n", status);
  exit(0);
}

Code 10: Harness-Code mit dem Fuzz-Target `parser_append` in der Binärdatei wscd.

Wie in Code 10 gezeigt, wird die Funktion parser_parse nicht direkt aufgerufen, sondern über die Funktion parser_append. Bevor diese Funktion aufgerufen wird, muss die Initialisierungsfunktion parser_request_init aufgerufen werden, die die Ausgabestruktur der Funktion parser_parse initialisiert.

Während der Harness im Fall von parser_parse recht einfach einzurichten ist, erfordern andere Ziele anspruchsvollere Harnesses wie die Funktion httpd_parser_main. Beispielsweise muss vor dem Aufruf des Ziels die Funktion http_init_main aufgerufen werden, die in einem SIGSEGV endet. Um herauszufinden, wo dieser Segmentation Fault verursacht wird, ist es nützlich, den Code mit einem Debugger wie gdb zu debuggen. Dazu kann QEMU mit der Option -g gestartet werden, die einen gdb-server auf dem angegebenen Port startet.```sh chroot root /qemu-mipsel-static -strace -g 1234 -E LD_PRELOAD="/httpd_parser_main.o" /usr/bin/httpd corpus/httpd/simple.txt

root@kitploit:~
Da die Binärdatei in der `mipsel`-Architektur vorliegt, muss `gdb-multiarch` verwendet werden. Nachdem gdb
gestartet wurde, kann das folgende Init-Skript mit gdb geladen werden, indem `sources <path to script>` verwendet wird.```sh
set solib-absolute-prefix /share/root/
file /share/root/usr/bin/httpd
target remote :1234
# break bevor fuzz target is called
# break __uClibc_main
break http_parser_main
display/4i $pc

Aufgrund des Chroots änderte das Skript zuerst den absoluten Präfixpfad, sodass gdb die Datei findet, wenn das Binary ein Shared Object lädt. Danach wird die Zieldatei festgelegt, da der gdb-Server von QEMU keinen Dateitransfer unterstützt, sodass gdb versucht, die Dateien stattdessen von der Festplatte zu laden. Nachdem gdb konfiguriert ist, verbindet sich das Skript mit target remote mit dem gdb-Server und erstellt einen Breakpoint am Anfang der Zielfunktion. Mit display wird die Ausgabe nur verbessert, sodass beim schrittweisen Durchgehen die nächsten vier Assemblerzeilen angezeigt werden. Mit si können wir eine einzelne Instruktion ausführen, was nützlich ist, wenn das Harness mit dem Standard-Korpus einen Segmentierungsfehler aufweist, was immer funktionieren sollte. Wie in Code 11 gezeigt, hat das Binary einen Segmentierungsfehler in der Funktion fprintf.

```sh (gdb) si 0x004059b0 in http_parser_makeHeader () 1: x/4i \$pc => 0x4059b0 : jalr t9 0x4059b4 : addiu a1,a1,16248 0x4059b8 : li v0,200 0x4059bc : lw gp,16(sp) (gdb) ni 0x7f56a8ac in fprintf () from /share/root/lib/libc.so.0 1: x/4i \$pc => 0x7f56a8ac \: bal 0x7f56db80 0x7f56a8b0 \: nop 0x7f56a8b4 \: lw ra,36(sp) 0x7f56a8b8 \: jr ra 0x7f56a8bc \: addiu sp,sp,40 (gdb) n Single stepping until exit from function fprintf, which has no line number information.

Program received signal SIGSEGV, Segmentation fault.

root@kitploit:~
<p style="text-align: center">Code 11: Segmentation Fault in printf.</p>

Um den Fehler zu untersuchen, kann Ghidra verwendet werden, um herauszufinden, mit welchen Parametern die Funktion aufgerufen wird.```c
fprintf(
 *(FILE **)(iVar1 + 0x101c),
 "HTTP/1.1 %d %s\r\n",
 *(undefined4 *)(&DAT_0042ee68 + (uint)(byte)(&DAT_00414570)[statuscode & 0x3f] * 8),
 (&PTR_DAT_0042ee6c)[(uint)(byte)(&DAT_00414570)[statuscode & 0x3f] * 2]
);

Der SIGSEGV wird wahrscheinlich dadurch verursacht, dass der erste Parameter kein Dateideskriptor, sondern ein Nullzeiger ist. Dabei ist iVar1 nur ein Verweis auf die Eingabe der Funktion httpd_parser_main. Das bedeutet, dass die Fuzzing-Eingabe an Position 0x101c einen Dateideskriptor aufweisen muss. Die Eingabe muss also an die folgende Struktur angepasst werden.```c typedef struct { int _a; // 4 Bytes int _b; // 4 Bytes int socket; // 4 Bytes int ip; // 4 Bytes int mac; // 4 Bytes unsigned char body[0x1008]; // 0x101c - 4*5 = 0x1008 Bytes FILE * fd_out; // expected to be a valid file descriptor } HttpMainT;

root@kitploit:~
Da `fd_out` nur ein gültiger Dateideskriptor-Zeiger sein muss, kann es problemlos auf `stdout` gesetzt werden.
Die erneute Ausführung von `httpd_parser_main` erzeugt nun eine gültige HTTP-Ausgabe.```c
$ chroot root /qemu-mipsel-static -E LD_PRELOAD=/httpd_parser_main.o \
    /usr/bin/httpd /httpd_corpus.txt

bind: No such file or directory
[ dm_shmInit ] 086:  shmget to exitst shared memory failed. Could not create shared memory.
rdp_getObj is called with: 4274932gdpr_getSystemGDPREntry Error
gdpr_getNewSystemGDPREntry OK
#Msg: getsockname error
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 24257
Set-Cookie: JSESSIONID=deleted; Expires=Thu, 01 Jan 1970 00:00:01 GMT; Path=/; HttpOnly
Connection: close

<!DOCTYPE html>
[...]

Der Harness funktioniert jetzt und kann verwendet werden, um die Funktion mit AFL++ zu fuzzen, was im nächsten Abschnitt erläutert wird.

Korpusdaten erzeugen

Wie im Hintergrund erwähnt, beschreibt ein Seed-Korpus gültige Eingabebeispiele, die als grundlegende Referenz für die Erzeugung neuer Eingabedaten während des Fuzzing-Prozesses dienen.

Diese Eingaben werden in der Regel so gewählt, dass sie verschiedene Aspekte der Zielprogramme repräsentieren. Der Seed-Korpus wird von einem Fuzzer verwendet, um mutierte oder weiterentwickelte Testfälle zu erzeugen, die dann gegen die Zielsoftware ausgeführt werden, um Fehler, Abstürze oder andere Probleme aufzudecken. Dieser Korpus spielt eine wichtige Rolle dabei, den Fuzzer auf relevante Bereiche des Programms zu lenken und die Wahrscheinlichkeit zu erhöhen, Schwachstellen oder unerwartete Verhaltensweisen zu erkennen. Durch die Bereitstellung einer vielfältigen und repräsentativen Menge an Anfangseingaben hilft der Seed-Korpus dem Fuzzer, verschiedene Pfade im Ziel schneller zu erkunden, und erhöht so die Abdeckung.

Bei Funktionen, die Netzwerkdaten parsen, können diese Eingaben erstellt werden, indem man Wireshark verwendet, um verschiedene Pakete aufzuzeichnen.

Für die Funktion httpd_parse_main wurden vier verschiedene Korpora erstellt. Jeder zielt auf verschiedene Pfade in der Binärdatei ab. Ein Beispiel ist die Login-Anfrage, die Benutzername und Passwort enthält. Für diesen Korpus musste der Harness modifiziert werden, weil TP-Link (schwache) Kryptografie verwendet, um das Passwort zu „schützen“. Dazu wird das Passwort im Browser mit AES verschlüsselt und dann im Backend entschlüsselt. Wobei das Passwort im Browser generiert und dann mit RSA verschlüsselt wird. Anschließend werden die verschlüsselten Daten signiert. Da ein Fuzzer keine Signatur erstellen oder Daten verschlüsseln kann, wurden einige Funktionen überschrieben, die die Daten nun nur noch aus Base64 dekodieren. Dazu wurden die Daten zunächst im Klartext aus dem Browser extrahiert, unter Verwendung des Debuggers, der in Abbildung 4 gezeigt wird.

Abbildung 4: Extrahieren der Daten vor der Verschlüsselung.

Im Ziel wurde die Funktion rsa_tmp_decrypt_bypart dann überschrieben, um die Logik von der Entschlüsselung der Daten auf ein reines Dekodieren aus Base64 umzustellen.```c // Replacing the logic with b64_decode int rsa_tmp_decrypt_bypart(uint8_t *input, int input_len, uint8_t *output) { // other params just key data int (*b64_decode)(uint8_t *, int, uint8_t *, int) = (void *) 0x0040bf00; b64_decode(output, 0x1000, input, input_len); int * seqnumber = (int *) 0x00444db0; *seqnumber = 0x3ac28e29-input_len+12; return 0; // says it was okay }

root@kitploit:~
<p style="text-align: center">Code 12: Die Funktion rsa_tmp_decrypt_bypart decodiert jetzt nur noch base64
anstatt die Daten zu entschlüsseln.</p>

Während der Ausführung des Korpus gibt die Zielfunktion immer ein HTML-Dokument mit dem Fehler „408
Request Timeout“ zurück. Mit Ghidra und GDB konnte das Problem identifiziert werden. Der Fehler tritt immer
nach dem Aufruf der Funktion `http_stream_fgets` auf. Die problematische Zeile war die Prüfung auf das
Zeilenumbruchzeichen `\n`.```c
if (((cVar1 == '\n') && (param_3 < pcVar4)) && (pcVar4[-1] == '\r')) {

Diese Bedingung erzwingt, dass nach jedem Zeilenumbruch ein Wagenrücklauf folgen muss. Nach dem Hinzufügen des Wagenrücklaufs funktionierten alle erstellten Korpora.

Ziel fuzzen

Im letzten Abschnitt haben wir mehrere Harnesses entwickelt und mit QEMU ausgeführt. In diesem Abschnitt wird QEMU durch AFL++ ersetzt, das die generierten Korpora als Seed-Eingaben erhält, um die Zielfunktion zu fuzzen. Im Abschnitt „Fuzzing-Umgebung“ wurde ein Docker-Image erstellt, das bereits AFL++ von GitHub lädt und dann ein von AFL++ bereitgestelltes Skript verwendet, um eine gepatchte Version von QEMU zu bauen. So kann AFL++ nun mit dem folgenden Befehl gestartet werden, der verschiedene Parameter erhält, wie z. B. -Q, das AFL++ anweist, die gepatchte Version von QEMU zu verwenden.```sh QEMU_LD_PREFIX=/share/root AFL_PRELOAD=/share/root/httpd_parser_main.o
/AFLplusplus/afl-fuzz -Q
-i /share/root/corpus/httpd/ -o /share/afl-out/httpd/
-- /share/root/usr/bin/httpd @@

root@kitploit:~
<p style="text-align: center">Code 13: Fuzzing der Binärdatei <code>httpd</code> mithilfe des Harness
und <code>afl-fuzz</code>.</p>

Anders als zuvor ist der Befehl `chroot` nicht mehr erforderlich und wird durch die Variable
`QEMU_LD_PREFIX` ersetzt. Diese teilt QEMU mit, wo nach Shared Objects gesucht werden soll. Außerdem wird die `LD_PRELOAD`-
Variable durch die AFL-spezifische Version `AFL_PRELOAD` ersetzt. Das letzte Argument im Befehl sind
die beiden `@`-Zeichen. Sie werden von AFL++ durch einen Dateipfad ersetzt, der den Fuzzing-Input
enthält. Beim Start zeigt AFL++ den Fortschritt über die Terminal-UI an, die in [Abbildung 5](#f5) dargestellt ist.

<div id="f5"></div>
<figure>
<p><img src="https://assets.kitploit.com/production/public/readmes/48851/ac4b12fcf84c2041c9edc2a0871c5bb89b576d976b2eba0a2d0f843e7e708795.png" style="width:95.0%" /></p>
<figcaption><p style="text-align: center">Abbildung 5: Der Statusbildschirm von AFL++.</p></figcaption>
</figure>

Der `AFL++`-Statusbildschirm liefert wesentliche Einblicke in den aktuellen Fuzzing-Prozess. Die Dokumentation von
`AFL++` enthält eine schöne Übersicht der im Statusbildschirm verwendeten Begriffe
[\[afl-screen\]](https://aflplus.plus/docs/status_screen/).  Beim Debuggen des Korpus mit den folgenden
Umgebungsvariablen kann die UI deaktiviert und mit `AFL_DEBUG` eine detaillierte Protokollierung
aktiviert werden, die den aktuellen Fuzzer-Input und die `stdout`-Ausgabe des Zielprogramms anzeigt.```sh
export AFL_DEBUG=1 && export AFL_NO_UI=1
unset AFL_DEBUG && unset AFL_NO_UI

Wie in Abbildung 5 gezeigt, kann das Fuzzing einer Binärdatei durchaus einige Zeit in Anspruch nehmen. Laut der Dokumentation „sollte man damit rechnen, dass es Tage oder Wochen läuft" und „einige Jobs dürfen monatelang laufen." Um die benötigte Zeit zu verbessern, sollte die Ausführungsgeschwindigkeit über 100 Execs/Sek. liegen. Als beispielsweise das Ziel httpd_main_parser gefuzzt wurde, lag die Ausführungsgeschwindigkeit anfangs bei etwa 30/Sek. Um die Geschwindigkeit zu verbessern, wurde die Ziel-Binärdatei nach verdächtigen Funktionen durchsucht, die wahrscheinlich die Ursache für die Verlangsamung sind. Eine der verdächtigen Funktionen war rsa_gdpr_generate_key, da das Erzeugen eines RSA-Schlüssels bekanntermaßen langsam ist. Nach dem Überschreiben der Funktion verbesserte sich die Geschwindigkeit auf 600 Ausführungen pro Sekunde.

Ein Indikator, der dabei hilft zu erkennen, wann das Fuzzing beendet werden sollte, ist der Zykluszähler. AFL++ hebt die Zahl grün hervor, wenn „der Fuzzer schon länger keine Aktivität gesehen hat", was dabei hilft, die Entscheidung zu treffen, den Fuzzer zu stoppen.

Aber die interessanteste Zahl ist wahrscheinlich „total crashes". Diese zeigt an, wann das Programm aufgrund der aktuellen Fuzzing-Eingabe abstürzt, und ist wahrscheinlich ein speicherbezogener Fehler. Um zu überprüfen, ob es sich um einen echten Fehler handelt, kann erneut gdb verwendet werden, um die Stelle des Fehlers zu finden.

Fazit

Fuzzing ist möglicherweise der effektivste Weg, um Sicherheitslücken zu finden. In dieser Seminararbeit wurden drei verschiedene Funktionen gefuzzt, aber es wurden keine gefunden. Während das Black-Box-Fuzzing-Setup selbst nicht besonders komplex und zeitaufwendig ist, sind es das Finden eines geeigneten Ziels und die Entwicklung eines funktionierenden Harnesses. Die meiste Zeit muss der Harness debuggt werden, und dann muss die zugrunde liegende Logik in der Binärdatei nachvollzogen (reversed) werden, was wiederum viel Zeit in Anspruch nimmt.

Tool herunterladen