Ich habe über einen älteren Exploit gegen GDI + unter Windows XP und Windows Server 2003 gelesen, der als JPEG des Todes für ein Projekt bezeichnet wird, an dem ich arbeite.
Der Exploit wird unter folgendem Link ausführlich erläutert: http://www.infosecwriters.com/text_resources/pdf/JPEG.pdf
Grundsätzlich enthält eine JPEG-Datei einen Abschnitt namens COM mit einem (möglicherweise leeren) Kommentarfeld und einen Zwei-Byte-Wert mit der Größe von COM. Wenn keine Kommentare vorhanden sind, beträgt die Größe 2. Der Leser (GDI +) liest die Größe, subtrahiert zwei und weist einen Puffer der entsprechenden Größe zu, um die Kommentare in den Heap zu kopieren. Der Angriff beinhaltet das Platzieren eines Wertes von 0
im Feld. GDI + subtrahiert 2
, was dazu führt, dass ein Wert von -2 (0xFFFe)
in die vorzeichenlose Ganzzahl 0XFFFFFFFE
von konvertiert wird memcpy
.
Beispielcode:
unsigned int size;
size = len - 2;
char *comment = (char *)malloc(size + 1);
memcpy(comment, src, size);
Beachten Sie, dass malloc(0)
in der dritten Zeile ein Zeiger auf den nicht zugewiesenen Speicher auf dem Heap zurückgegeben werden sollte. Wie kann das Schreiben von 0XFFFFFFFE
Bytes ( 4GB
!!!!) das Programm möglicherweise nicht zum Absturz bringen? Schreibt dies über den Heap-Bereich hinaus und in den Bereich anderer Programme und des Betriebssystems? Was passiert dann?
Soweit ich memcpy
weiß, werden einfach n
Zeichen vom Ziel in die Quelle kopiert . In diesem Fall sollte sich die Quelle auf dem Stapel befinden, das Ziel auf dem Heap und n
ist 4GB
.
malloc
ED-Größe beträgt nur 2 Bytes anstatt0xFFFFFFFE
. Diese enorme Größe wird nur für die Kopiengröße verwendet, nicht für die Zuordnungsgröße.Antworten:
Diese Sicherheitsanfälligkeit war definitiv ein Haufenüberlauf .
Dies wird wahrscheinlich der Fall sein, aber in einigen Fällen haben Sie Zeit zum Ausnutzen, bevor der Absturz auftritt (manchmal können Sie das Programm auf seine normale Ausführung zurücksetzen und den Absturz vermeiden).
Wenn memcpy () gestartet wird, überschreibt die Kopie entweder einige andere Heap-Blöcke oder einige Teile der Heap-Verwaltungsstruktur (z. B. freie Liste, Besetztliste usw.).
Irgendwann stößt die Kopie auf eine nicht zugewiesene Seite und löst beim Schreiben eine AV (Zugriffsverletzung) aus. GDI + wird dann versuchen, einen neuen Block im Heap zuzuweisen (siehe ntdll! RtlAllocateHeap ) ... aber die Heap-Strukturen sind jetzt alle durcheinander.
An diesem Punkt können Sie durch sorgfältiges Erstellen Ihres JPEG-Bilds die Heap-Verwaltungsstrukturen mit kontrollierten Daten überschreiben. Wenn das System versucht, den neuen Block zuzuweisen, wird wahrscheinlich die Verknüpfung eines (freien) Blocks mit der freien Liste aufgehoben.
Blöcke werden mit (insbesondere) einem Flink- (Vorwärtslink; der nächste Block in der Liste) und einem Blink- (Rückwärtslink; der vorherige Block in der Liste) Zeiger verwaltet. Wenn Sie sowohl das Flinken als auch das Blinken steuern, haben Sie möglicherweise eine mögliche WRITE4 (Write What / Where-Bedingung), in der Sie steuern, was Sie schreiben und wo Sie schreiben können.
Zu diesem Zeitpunkt können Sie einen Funktionszeiger überschreiben ( SEH- Zeiger ( Structured Exception Handlers) waren zu diesem Zeitpunkt im Jahr 2004 ein Ziel der Wahl) und die Codeausführung erhalten.
Siehe Blog-Beitrag Heap Corruption: Eine Fallstudie .
Hinweis: Obwohl ich über die Ausnutzung mithilfe der Freelist geschrieben habe, kann ein Angreifer einen anderen Pfad unter Verwendung anderer Heap-Metadaten auswählen ("Heap-Metadaten" sind Strukturen, die vom System zur Verwaltung des Heaps verwendet werden; Flink und Blink sind Teil der Heap-Metadaten) Die Unlink-Ausnutzung ist wahrscheinlich die "einfachste". Eine Google-Suche nach "Heap Exploitation" wird zahlreiche Studien dazu liefern.
Noch nie. Moderne Betriebssysteme basieren auf dem Konzept des virtuellen Adressraums, sodass jeder Prozess seinen eigenen virtuellen Adressraum hat, der die Adressierung von bis zu 4 Gigabyte Speicher auf einem 32-Bit-System ermöglicht (in der Praxis haben Sie nur die Hälfte davon im Benutzerland). der Rest ist für den Kernel).
Kurz gesagt, ein Prozess kann nicht auf den Speicher eines anderen Prozesses zugreifen (außer wenn er den Kernel über einen Dienst / eine API danach fragt, der Kernel jedoch prüft, ob der Aufrufer das Recht dazu hat).
Ich habe beschlossen, diese Sicherheitsanfälligkeit am Wochenende zu testen, damit wir eine gute Vorstellung davon bekommen, was vor sich geht, und nicht nur über Spekulationen. Die Sicherheitsanfälligkeit ist jetzt 10 Jahre alt, daher dachte ich, es sei in Ordnung, darüber zu schreiben, obwohl ich den Ausnutzungsteil in dieser Antwort nicht erklärt habe.
Planung
Die schwierigste Aufgabe war es, ein Windows XP mit nur SP1 zu finden, wie es 2004 war :)
Dann habe ich ein JPEG-Bild heruntergeladen, das nur aus einem einzelnen Pixel besteht, wie unten gezeigt (der Kürze halber geschnitten):
Ein JPEG-Bild besteht aus binären Markern (die Segmente einführen). Im obigen Bild
FF D8
befindet sich der SOI-Marker (Start Of Image), währendFF E0
er beispielsweise ein Anwendungsmarker ist.Der erste Parameter in einem Markersegment (mit Ausnahme einiger Marker wie SOI) ist ein Zwei-Byte-Längenparameter, der die Anzahl der Bytes im Markersegment einschließlich des Längenparameters und ohne den Zwei-Byte-Marker codiert.
Ich habe einfach einen COM-Marker (0x
FFFE
) direkt nach dem SOI hinzugefügt , da die Marker keine strenge Reihenfolge haben.Die Länge des COM-Segments wird so eingestellt, dass
00 00
die Sicherheitsanfälligkeit ausgelöst wird. Ich habe auch 0xFFFC-Bytes direkt nach dem COM-Marker mit einem wiederkehrenden Muster, einer 4-Byte-Zahl in hexadezimaler Form, eingefügt, was nützlich sein wird, wenn die Sicherheitsanfälligkeit "ausgenutzt" wird.Debuggen
Ein Doppelklick auf das Bild löst sofort den Fehler in der Windows-Shell (auch bekannt als "explorer.exe") irgendwo
gdiplus.dll
in einer Funktion mit dem Namen ausGpJpegDecoder::read_jpeg_marker()
.Diese Funktion wird für jeden Marker im Bild aufgerufen. Sie liest einfach die Markersegmentgröße, weist einen Puffer zu, dessen Länge der Segmentgröße entspricht, und kopiert den Inhalt des Segments in diesen neu zugewiesenen Puffer.
Hier der Start der Funktion:
eax
Das Register zeigt auf die Segmentgröße undedi
gibt die Anzahl der im Bild verbleibenden Bytes an.Der Code liest dann die Segmentgröße, beginnend mit dem höchstwertigen Byte (Länge ist ein 16-Bit-Wert):
Und das niedrigstwertige Byte:
Sobald dies erledigt ist, wird die Segmentgröße verwendet, um einen Puffer nach dieser Berechnung zuzuweisen:
alloc_size = segment_size + 2
Dies geschieht durch den folgenden Code:
In unserem Fall beträgt die zugewiesene Größe für den Puffer 2 Byte , da die Segmentgröße 0 ist .
Die Sicherheitsanfälligkeit liegt direkt nach der Zuweisung:
Der Code subtrahiert einfach die Größe segment_size (Segmentlänge ist ein Wert von 2 Byte) von der gesamten Segmentgröße (in unserem Fall 0) und führt zu einem ganzzahligen Unterlauf: 0 - 2 = 0xFFFFFFFE
Der Code prüft dann, ob noch Bytes im Bild zu analysieren sind (was wahr ist), und springt dann zur Kopie:
Das obige Snippet zeigt, dass die Kopiergröße 0xFFFFFFFE 32-Bit-Chunks beträgt. Der Quellpuffer wird gesteuert (Bildinhalt) und das Ziel ist ein Puffer auf dem Heap.
Schreibbedingung
Die Kopie löst eine Ausnahme von Zugriffsverletzungen (AV) aus, wenn sie das Ende der Speicherseite erreicht (dies kann entweder vom Quellzeiger oder vom Zielzeiger sein). Wenn der AV ausgelöst wird, befindet sich der Heap bereits in einem anfälligen Zustand, da die Kopie bereits alle folgenden Heap-Blöcke überschrieben hat, bis eine nicht zugeordnete Seite gefunden wurde.
Was diesen Fehler ausnutzbar macht, ist, dass 3 SEH (Structured Exception Handler; dies ist try / außer auf niedriger Ebene) Ausnahmen für diesen Teil des Codes abfangen. Genauer gesagt, der 1. SEH wickelt den Stapel ab, sodass er wieder einen anderen JPEG-Marker analysiert und den Marker, der die Ausnahme ausgelöst hat, vollständig überspringt.
Ohne SEH hätte der Code gerade das gesamte Programm zum Absturz gebracht. Der Code überspringt also das COM-Segment und analysiert ein anderes Segment. Wir kehren also
GpJpegDecoder::read_jpeg_marker()
mit einem neuen Segment zurück und wenn der Code einen neuen Puffer zuweist:Das System hebt die Verknüpfung eines Blocks mit der freien Liste auf. Es kommt vor, dass Metadatenstrukturen durch den Inhalt des Bildes überschrieben wurden; Daher steuern wir die Verknüpfung mit kontrollierten Metadaten. Der folgende Code befindet sich irgendwo im System (ntdll) im Heap-Manager:
Jetzt können wir schreiben, was wir wollen, wo wir wollen ...
quelle
Da ich den Code von GDI nicht kenne, ist das Folgende nur Spekulation.
Nun, eine Sache, die mir in den Sinn kommt, ist ein Verhalten, das ich bei einigen Betriebssystemen bemerkt habe (ich weiß nicht, ob Windows XP dies hatte), wenn Sie new /
malloc
zuweisen. Sie können tatsächlich mehr als Ihren RAM zuweisen, solange Sie schreiben nicht in diese Erinnerung.Dies ist eigentlich ein Verhalten des Linux-Kernels.
Von www.kernel.org:
Um in den residenten Speicher zu gelangen, muss ein Seitenfehler ausgelöst werden.
Grundsätzlich müssen Sie den Speicher verschmutzen, bevor er tatsächlich auf dem System zugewiesen wird:
Manchmal wird keine echte RAM-Zuweisung vorgenommen (Ihr Programm verwendet immer noch keine 4 GB). Ich weiß, dass ich dieses Verhalten unter Linux gesehen habe, kann es jedoch jetzt in meiner Windows 7-Installation nicht replizieren.
Ausgehend von diesem Verhalten ist das folgende Szenario möglich.
Um diesen Speicher im RAM vorhanden zu machen, müssen Sie ihn verschmutzen (im Grunde Memset oder ein anderes Schreiben darauf):
Die Sicherheitsanfälligkeit nutzt jedoch einen Pufferüberlauf und keinen Zuordnungsfehler.
Mit anderen Worten, wenn ich das hätte:
Dies führt zu einem Schreib-nach-Puffer, da es kein 4-GB-Segment kontinuierlichen Speichers gibt.
Sie haben nichts in p eingefügt, um die gesamten 4 GB Speicher schmutzig zu machen, und ich weiß nicht, ob
memcpy
der Speicher auf einmal oder nur Seite für Seite schmutzig wird (ich denke, es ist Seite für Seite).Schließlich wird der Stapelrahmen überschrieben (Stapelpufferüberlauf).
Eine weitere mögliche Sicherheitsanfälligkeit bestand darin, dass das Bild als Byte-Array im Speicher gehalten wurde (ganze Datei in Puffer lesen) und die Größe der Kommentare nur verwendet wurde, um nicht wichtige Informationen zu überspringen.
Beispielsweise
Wie Sie bereits erwähnt haben, stürzt das Programm niemals ab, wenn der GDI diese Größe nicht zugewiesen hat.
quelle
malloc(-1U)
wird ein sicherlich scheitern, zurückkehrenNULL
undmemcpy()
abstürzen.