Ein Haufen Risiko

Seite 5: Header fälschen

Inhaltsverzeichnis

Es stellt sich immer noch die Frage, wie der Angreifer nun seinen Code konkret einschleust und ausführen lässt. An dieser Stelle hängt aber schon dieses einfache Beispiel von vielen weiteren Faktoren ab. Jedes Betriebssystem legt die Speicherbereiche, die das Programm als Heap verwenden möchte, an anderen Stellen im Speicher ab. Bei Betriebssystemen mit verschiedenen Möglichkeiten, einen Speicherblock beim Kernel anzufordern, kommt es auch auf die tatsächlich verwendete Methode an.

Das konkrete Beispielprogramm läuft unter Windows XP und reserviert den Speicherblock für SimpleHeap über die Funktion LocalAlloc(). Sie erstellt den SimpleHeap an der nächsten durch 0x10000 teilbaren Adresse nach dem Code und den statischen Daten des Hauptprogramms im Speicher. Die genaue Adresse hängt unter anderem von den Einstellungen des Compilers beim Übersetzen des Programms ab, da diese wiederum die Größe des Hauptprogramms und damit die nächste verfügbare Adresse beeinflussen. An dieser Stelle wird auch nochmals deutlich, warum Angreifer einen Stack-basierten Buffer-Overflow so sehr bevorzugen: Es gibt dort viel weniger Unbekannte.

Auf unserem Testsystem liegt der Speicherbereich für SimpleHeap bei einem Debug-Build des Programms an Adresse 0x00430020. Die 32 Byte Offset zum durch 0x10000 teilbaren Wert entstehen durch Verwaltungsinformationen, dieses Mal von LocalAlloc().

Die Zeiger myRoot und myHeap zeigen also auf die Adresse 0x00430020. Da der Angriff den SimpleHeap-Block Header für line überschreibt, sollte man sich diesen Speicherbereich direkt vor dem fatalen Kopiervorgang einmal genauer ansehen. An der Adresse 0x00430020 steht der Header des Speicherblocks image, den das Programm als Erstes alloziert hat. Das Größenfeld gibt korrekt die reservierte Größe von 0 Bytes an. Die ersten 4 Byte (next) zeigen auf den Header des nächsten Blocks (line) an 0x00430030.

Der Speicher vor dem fatalen Kopiervorgang: Hinter dem leeren Block image folgt direkt line.

Wenn das Programm die erste Zeile der Bilddatei aus dem Puffer line nach image kopiert, überschreibt diese die Daten ab 0x00430030. Damit beim anschließenden Aufruf von SimpleHeap_free(line) der anvisierte Befehl zum Zusammenfassen der freien Blöcke ausgeführt wird, muss dort hdr->next->used den Wert 0 haben. Des Weiteren soll ja hdr->next->prev für den Angriff einen passenden Wert enthalten. Sprich, der Angreifer muss an der Adresse hdr->next einen fingierten Header platzieren; in Fachkreisen spricht man von einem "Fake-Header". Dazu lässt er zunächst hdr->next auf einen Bereich zeigen, den er kontrolliert: die Bilddaten an der Adresse 0x00430040.

Der Exploit baut einen fingierten Header ein.

Der Inhalt der übrigen Felder hdr->prev, hdr->size und hdr->used ist beliebig; sie werden nicht verwendet. Spannend wird es dann erst wieder ab Offset 16 der Bilddaten: Sie landen an der Adresse 0x00430040, auf die hdr->next zeigt. Der dort platzierte Fake-Header muss einen unbenutzten Block ausweisen (hdr->next->used == 0) was bedeutet, dass an Offset 16+12 der Bilddatei der Wert 0 stehen muss.

Damit führt das Programm den kritischen Befehl aus:

hdr->next->next->prev=hdr->next->prev;

Er schreibt das prev-Feld des Fake-Headers, das an Offset 16+4 liegt und mit 0x00430050 auf den eigenen Code in den Bilddaten zeigt. Die Zieladresse hdr->next->next->prev für diese Schreiboperation errechnet das Programm aus dem Wert an der Stelle hdr->next-> next plus einem Offset von 4 für das prev-Element. Der einfache Beispiel-Exploit soll die Rücksprungadresse des Aufrufs von SimpleHeap_free() auf dem Stack überschreiben. Der Debugger lokalisiert sie an der Adresse 0x0012FD9C. Folglich muss hdr->next->next die Adresse 0x0012FD98 enthalten.

Somit steht der Inhalt des Fake-Headers und daher auch der Bilddaten fest:

0x00430040 Adresse des fingierten Headers (hdr->next)
12 Bytes beliebig
0x0012FD98 Zieladresse minus 4 (hdr->next->next)
0x00430050 Zielwert (hdr->next->prev)
4 Bytes beliebig (hdr->next->size)
0x00000000 hdr->next->used

Das ergibt zum Zeitpunkt, zu dem die CPU SimpleHeap_free(line) abarbeitet, die folgende SimpleHeap-Speicherbelegung:

Der Pufferüberlauf hat den Speicher für den fatalen Aufruf von <tt>SimpleHeap_free()</tt> vorbereitet.

Die Zusammenlegung der freien Blöcke platziert die gewünschte Adresse auf dem Stack und der Return-Befehl am Ende von SimpleHeap_free() benutzt sie als Rücksprungadresse. Die CPU führt dann den eingeschleusten Code aus; das Ziel ist erreicht.

Alternativ zur Rücksprungadresse auf dem Stack hätte der Angreifer auch andere Speicheradressen überschreiben können. Besonders beliebt sind Adressen von Exception Handlern auf dem Stack oder im Datensegment. Der resultierende Exploit wird dann oft nach dem Structured Exception Handler als SEH-Exploit bezeichnet. Aber auch andere Funktionszeiger im Datensegment oder Adressen von Funktionen, welche das Betriebssystem für den Prozesskontext hinterlegt, werden in der Praxis genutzt.

Das direkte Verändern des Programmcodes selbst ist im Übrigen nicht empfehlenswert, da die entsprechenden Speicherbereiche gewöhnlich als Read Only markiert sind und ein Schreibzugriff die Beendigung des Prozesses nach sich ziehen würde.