Ein Haufen Risiko

Seite 6: Ein Haufen Risiko

Inhaltsverzeichnis

Den eingeschleusten Code bezeichnet man häufig als Shellcode, weil die ersten einfachen Vorlagen dafür dem Angreifer eine Eingabeaufforderung – die Shell – auf dem attackierten System lieferten. Seine Erstellung ist eine eigene Wissenschaft und geht über den Rahmen dieses Artikels weit hinaus. Lediglich ein paar Randbedingungen seien noch erwähnt.

Nach einem Heap-Overflow ist es selten ratsam, die angegriffene Applikation weiter laufen zu lassen. Schließlich hat der Angreifer gerade die interne Struktur des Primärspeichers für Daten, den Heap, manipuliert und in der Regel damit beschädigt. Der Prozess würde wahrscheinlich sofort eine Zugriffsverletzung erzeugen und beendet werden. Des Weiteren sollte auch der Angreifer in seinem Code den Heap nicht mehr verwenden. Zur Verfügung stehen ihm allerdings durchaus API-Funktionen, die der Prozess ohnehin verwendet und deren Adressen er mit dem Debugger ermitteln kann.

Der folgende Shellcode gibt via printf() eine Zeichenkette aus und beendet den Prozess dann sauber:

       JMP      SHORT End
Back: CALL SimpleHeap.printf
PUSH 0
CALL SimpleHeap.exit
End: CALL Back
DB "***Hacked***"

Der Sprung zum Label End und der nachfolgende Call zu Back platziert die Adresse der Zeichenkette ***Hacked*** als Rücksprungadresse auf dem Stack, wo sie printf() als Argument erwartet. Analog schiebt der Push-Befehl den Rückgabewert 0 für exit() auf den Stack.

Damit ist der Exploit fertig; die präparierte Bilddatei hat folgenden Inhalt:

00: 80 00 00 00 00 00 00 10 40 00 43 00 FF FF FF FF
10: FF FF FF FF FF FF FF FF 98 FD 12 00 50 00 43 00
20: FF FF FF FF 00 00 00 00 EB 0C E8 B9 24 FE FF 6A
30: 00 E8 E2 80 FE FF E8 EF FF FF FF (Zeichenkette
40: fuer Ausgabe hier einfuegen und mit 00 beenden)

Beim Aufruf von SimpleHeap.exe mit der angeblichen Bilddatei wird der eingeschleuste Code ausgeführt.

Die angebliche Bilddatei nutzt einen Heap-Overflow in SimpleHeap.exe.

Das Programm kann man im Übrigen durchaus mit der Option /GS des Microsoft-Compilers übersetzen, die den Buffer-Overflow-Schutz aktiviert. Das gezielte Überschreiben einer einzelnen Adresse auf dem Stack fängt dieser Schutzmechanismus nicht ab und ist daher wirkungslos. Nur der Einsatz von DEP (Data Execution Prevention, etwas unglücklich mit Datenausführungsverhinderung übersetzt) könnte diesen Angriff stoppen.

Die hier vorgestellte Methodik kommt prinzipiell auch bei echten Exploits zum Einsatz. Allerdings haben die hier der Einfachheit halber verwendeten festen Adressen den Nachteil, dass sie im Vorfeld bekannt sein müssen und der Exploit nicht funktioniert, wenn eine davon nicht stimmt. So kann der Beispiel-Exploit fehlschlagen, weil LocalAlloc() aus irgendwelchen Gründen einen anderen Speicherbereich liefert oder spezielle DLLs für andere Stack-Adressen sorgen. Will man den hier dargestellten Prozess trotzdem nachvollziehen, ist in solchen Fällen folglich Handarbeit angesagt. Die Kunst, verlässliche Exploits zu schreiben, ist vergleichbar mit dem Schreiben von fehlerfreier Software. Nur ein Angriff, der keine Adresse auf dem Zielsystem kennen muss, ist wirklich verlässlich und funktioniert auch auf jedem anderen System.

Vor allem bei den immer mehr an Bedeutung gewinnenden Schwachstellen in Browsern laufen oft Puffer auf dem Heap über. In Fällen wie dem kürzlich gefundenen Windows Embedded Open Type Font Heap Overflow wird das Erstellen eines stabilen Exploits zur Kunstform. Auch Schwachstellen, die unter Begriffen wie "Memory Corruption" firmieren, wie das aktuelle QueryInterface-Problem im Firefox-Browser, lassen sich mit ähnlichen Techniken angreifen. Meist unterscheiden sie sich vor allem in der Art, wie die Daten vorher im Speicher kopiert werden.

Und schließlich sind Heap-Implementierungen in den letzten Jahren auch als Folge der vielen Heap-Overflow-Exploits stark gereift. So verhindern viele Systeme solche einfachen Exploits durch zusätzliche Tests. Sie überprüfen beispielsweise die Heap-internen Zeiger auf Konsistenz, bevor sie sie verwenden:

if ( this_block->prev->next == this_block )

stellt sicher, dass der vorherige Block tatsächlich auf den aktuellen verweist. Andere Tests prüfen, ob ein Zeiger überhaupt auf einen Heap-Speicherblock zeigt, indem sie die Liste aller Blöcke durchlaufen. Nur wenn sie das Ziel des Zeigers dabei finden, darf er verwendet werden. Solche Sicherungsmaßnahmen sind bei heutigen CPUs keine Performance-Killer mehr.

Doch keine dieser Maßnahmen kann sicher verhindern, dass ein Pufferüberlauf ausgenutzt wird. Wenn ein Angreifer Verwaltungsdaten eines Programms überschreiben kann, sind die Folgen immer unangenehm. Die Heap-Checks tragen allerdings nebenbei auch dazu bei, dass inkonsistente Datenstrukturen durch Programmierfehler schneller auffallen, was letztlich den Kunden bessere, weil stabilere Software beschert.

Felix "FX" Lindner betreibt SABRE Labs und ist auf professionelle Sicherheits-Dienstleistungen und -analysen spezialisiert.

Listing: SimpleHeap.cpp

Download der Beispieldateien zu diesem Artikel.