Paketstruktur untersuchen
Nun geht es endlich um das Programm lupfer_heartbeat_server, das hinter dem UDP-Port des LUPFERs läuft.
Durch das Fuzzing ist bekannt, dass es unerwartetes Verhalten zeigt und nicht mehr erreichbar ist, nachdem es bestimmte Pakete erhält.
Nun gilt es herauszufinden, wie es zu diesem Verhalten kommt und ob es vielleicht ausnutzbar ist.
Um die Ursache des unerwarteten Verhaltens zu ermitteln, untersuchst du die ausführbare Datei. Sie liegt im aktuellen Arbeitsverzeichnis, auf das du über ein Terminal Zugriff hast. So kannst du sie zunächst lokal ausprobieren.
Der Befehl file lupfer_heartbeat_server offenbart wieder, dass es sich um eine Binary der Architektur AArch64 (64-Bit-ARM) handelt.
Um die Binary auszuführen, ist es also erneut erforderlich, die AArch64-Architektur zu emulieren.
Der Befehl dafür lautet:
qemu-aarch64 -L /usr/aarch64-linux-gnu/ lupfer_heartbeat_server <port>
Die Option <port> gibt an, auf welchem Port das Programm gestartet wird.
Du kannst den Port frei wählen, wobei er ohne Administratorrechte größer als 1023 sein sollte.
Mit der Tastenkombination Strg+C kannst du das Programm abbrechen.
Über ein weiteres Terminal kannst du eine Nachricht an den Heartbeat-Server senden.
Dies ist beispielsweise mit dem Netzwerktool nc (Netcat) möglich:
echo -e '<payload>' | nc -u <ip> <port>
Die Bestandteile des Befehls sind:
echo -e '<payload>': Der Befehlechogibt den angegebenen Text aus. Die Option-eermöglicht die Interpretation von Escape-Sequenzen wie z. B.\x41, um hexadezimale Werte darzustellen.<payload>ist der Inhalt, der über das Netzwerk gesendet werden soll.|(Pipe): Die Pipe leitet die Ausgabe vonechoals Eingabe an den nächsten Befehl weiter.nc -u <ip> <port>: Ruftncauf, das für die Kommunikation über TCP/UDP verwendet wird. Mit der Option-uwird Netcat im UDP-Modus betrieben.<ip>und<port>geben die Ziel-IP-Adresse und den Port an, an den der Payload gesendet wird. Hier musst du die IP-Adresse des Pentestleitfadens angeben (172.19.0.4).
Der Payload kann aus UTF-8-Zeichen und Escape-Sequenzen für hexadezimale Bytes bestehen:
'ABC\x01\x12\x41BC'
Wenn ein Payload eine Zeichenfolge wiederholen soll, kann er auch so aussehen:
'\x41\x41'$(printf 'B%.0s' {1..5})
Der Ausdruck $(printf 'B%.0s' {1..5}) gibt den Buchstaben B fünfmal aus.
Das Format %.0s dafür, dass der Buchstabe B ohne zusätzliche Formatierung oder Umbrüche ausgegeben wird.
Die erste Aufgabe ist nun, eine Nachricht zu erstellen, die der Heartbeat-Server erwartet. Dabei hilft dir die Ausgabe im ersten Terminal. Dir wird auffallen, dass das Programm Nachrichten mit einer bestimmten Struktur erwartet. Um diese zu verstehen, solltest du folgende Fragen beantworten:
- Wie viele Felder enthält das Paket? Welche Reihenfolge haben diese Felder?
- Wie lang ist jedes Feld? Gibt es eine feste oder eine variable Größe?
- Welche Datenformate werden verwendet? Welche Datentypen kommen vor (z. B. Integer, String, Byte)? Wie sind diese Daten kodiert (z. B. ASCII, UTF-8, Hexadezimal)?
- Wie wird die Nachricht vom Server verarbeitet? Welche Schritte werden durchlaufen, um das Paket zu validieren und die darin enthaltenen Informationen zu extrahieren?
Du wirst mehrere Nachrichten an den Server senden, um die Paketstruktur zu analysieren. Gehe systematisch vor:
- Starte mit kurzen Nachrichten.
- Teste verschiedene Datentypen.
- Beobachte, welche Eingaben in der Ausgabe des Programms
lupfer_heartbeat_serverauftauchen.
Du wirst voraussichtlich einige gültige und viele ungültige Pakete erzeugen. Denke daran, den Server im ersten Terminal neu zu starten, falls er wegen eines Fehlers nicht mehr reagiert. Hier sind noch ein paar Shell-Befehle, die dir bei der Ermittlung der Paketstruktur helfen können:
Umwandeln einer Zeichenkette aus hexadezimalen Werten (z. B.
414141fürAAA) in druckbare UTF-8-Zeichen:echo '<hex values>' | xxd -r -dUmwandeln einer Hexadezimalzahl (z. B.
0x1Afür26) in eine Dezimalzahl:printf "%d\n" <hex value>Umwandeln einer Dezimalzahl (z. B.
1000für3e8) in eine Hexadezimalzahl:printf "%x\n" <dec value>Ermitteln der Länge eines Payloads:
echo -e '<payload> | wc -c
Programm schrittweise untersuchen
Nachdem du verstanden hast, wie ein gültiges Paket an das Programm lupfer_heartbeat_server gebildet wird, geht es im nächsten Schritt darum, zu untersuchen, was das unerwartete Verhalten auslöst.
Dafür wirst du es mit einem Debugger untersuchen.
Starte den Heartbeat-Server in einem Terminal mit folgendem Befehl:
qemu-aarch64 -L /usr/aarch64-linux-gnu/ -g <g_port> lupfer_heartbeat_server <port>
Die Option -g startet einen GDB-Server und setzt den Debugger-Port auf <g_port>.
Mit der Tastenkombination Strg+C kannst du das Programm abbrechen.
Im einem weiteren Terminal muss anschließend der Debugger gdb-multiarch gestartet werden:
gdb-multiarch -q -ex 'set architecture aarch64' -ex 'file <program>' -ex 'target remote localhost:<g_port>'
Hier ist eine Erklärung der Optionen:
-qstartet den Debugger im stillen Modus (quiet mode).-ex 'set architecture aarch64'setzt die Zielarchitektur aufaarch64.-ex 'file <program>'lädt die Datei<program>als zu debuggendes Programm.-ex 'target remote localhost:<g_port>'verbindet den Debugger über den GDB-Server, der auf dem Port<g_port>läuft, mit dem Zielprogramm, das auflocalhostläuft.
Auch den Debugger kannst du mit der Tastenkombination Strg+C beenden.
Das Programm lupfer_heartbeat_server enthält Debugging-Symbole, sodass es Informationen zum Quellcode bereitstellt und das Debugging erleichtert.
Um nun die Schwachstelle im Programm zu identifizieren, die das unerwartete Verhalten verursacht, solltest du als Erstes folgende Fragen beantworten:
- Welche Funktionen gibt es?
- Wie verläuft der Kontrollfluss? Welche Funktion ruft welche andere Funktion auf?
- Wie wird der Stack aufgebaut sein? Wie sind die Stack Frames der aufgerufenen Funktionen aufgebaut? Hier ist ein Beispiel für den Aufbau eines Stacks beim Ausführen eines Programms.
Als Nächstes solltest du das Programm während der Ausführung schrittweise untersuchen. Gehe dabei wie folgt vor:
- Setze Haltepunkte mit
b *<addr>. - Setze die Ausführung des Programms bis zum nächsten Haltepunkt mit
cfort. Falls der Debugger den Haltepunkt nicht erreicht, liegt dies daran, dass der Heartbeat-Server auf eine Nachricht wartet. - Sende über ein drittes Terminal eine Nachricht an das Programm.
- Verfolge, an welcher Stelle die Benutzereingaben im Stack gespeichert werden.
- Verfolge, an welcher Stelle die Rücksprungadressen im Stack gespeichert werden.
Hier ist ein drittes Terminal, um Nachrichten an den Heartbeat-Server zu senden:
Damit kannst du nun verschiedene Nachrichten an den Server senden und im Debugger analysieren, warum der Server bei bestimmten Nachrichten nicht mehr erreichbar ist.
Stack Buffer Overflow ausnutzen
Der Heartbeat-Server ist also anfällig für einen Stack Buffer Overflow und damit für einen Denial-of-Service-Angriff (DoS-Angriff), bei dem die Verfügbarkeit eines Systems, Dienstes oder Netzwerks beeinträchtigt oder vollständig blockiert wird. Nun stellt sich die Frage, ob der Stack Buffer Overflow für weitere Angriffe genutzt werden kann.
Beim Debuggen sollte dir aufgefallen sein, dass es die Funktion debug gibt, die jedoch nicht aufgerufen wird.
Erstelle einen Payload, um über den Stack Buffer Overflow den Kontrollfluss umzuleiten und die Funktion debug aufzurufen.
Nutze den Assemblercode der Funktion, um zu verstehen, was die Funktion macht.
Wenn es dir gelungen ist, die Funktion debug aufzurufen und du weißt, wie du erkennst, dass dies erfolgreich war, kannst du zum Schluss noch versuchen, den Stack Buffer Overflow beim LUPFER auszunutzen.
Sende dafür den Payload zum Aufruf der Funktion debug an den Heartbeat-Server des LUPFERs.
Der lokal entwickelte Exploit funktioniert also nicht auf dem LUPFER, da die Speicheradressen anders als auf dem lokalen System sind. Außerdem können zusätzliche Schutzmechanismen auf dem LUPFER aktiv sein. Der Exploit müsste daher an die Speicheradressen und die Sicherheitsvorkehrungen des LUPFERs angepasst werden.
Weiterführende Informationen
Binary Exploitation ist ein umfangreiches und komplexes Thema. Weitergehende Aspekte haben wir hier nicht behandelt. Dazu gehören:
- Umgehen von Schutzmechanismen
- Einfügen von Shellcode über den Payload
- Ausnutzen einer Kette bereits existierender Codefragmente (Return Oriented Programming oder ROP)
- Einsatz von Python bei der Binary Exploitation
- Heap Exploitation
Wenn du also mehr über Binary Exploitation erfahren möchtest findest du hier drei Quellen:
- RET2 SYSTEMS: Dies ist ein kommerzielle Lernplattform zu Binary Exploitation mit vielen Übungen. Mit der Demo hat man Zugriff auf grundlegende Informationen und Übungen zur C-Programmierung, zum Debuggen und zu Python.
- A Noob's Guide to ARM Exploitation: Hier werden verschiedene Beispiele für Binary Exploitation auf ARM-Architekturen beschrieben.
- Linux Binary Exploitation: Dieses Github-Repository enthält ebenfalls verschiedene Beispiele und Übungen für Binary Exploitation auf der x86_32-Architektur. Das Repository ermöglicht, eine virtuelle Maschine aufzusetzen, um die Beispiele und Übungen selbst ausprobieren und nachvollziehen zu können.
