Ein Haufen Risiko

Seite 4: Ein fast realistisches Szenario

Inhaltsverzeichnis

Wie sich das nun konkret ausnutzen lässt, um eigenen Code auszuführen, soll eine einfache Beispielapplikation verdeutlichen, die SimpleHeap verwendet. Es handelt sich dabei um ein Bildbearbeitungsprogramm für Dateien in einem stark vereinfachten Grafikformat.

Eine solche Bilddatei enthält am Anfang jeweils 4 Byte mit den Angaben für Breite und Höhe des Bildes und dahinter pro Pixel ein Byte Farbinformation. Das Programm, das der Anwender zur Bearbeitung des Bildes auf seinem System startet, liest die Höhen- und Breiteninformation aus der vom Angreifer gelieferten Datei und reserviert entsprechend viel Speicher auf dem SimpleHeap, um die Bilddaten für die weitere Verarbeitung zu speichern.

Weiterhin reserviert das Programm entsprechend der Breiteninformation Speicher auf dem SimpleHeap, um einzelne Zeilen aus dem Bild auszulesen, diese zu verarbeiten und danach in den großen reservierten Speicherblock für die Gesamtbilddaten zu schreiben.

Das Pseudo-Listing illustriert die einzelnen Schritte:

1 Lese "width"
2 Lese "height"
3 Reserviere Speicher "image" mit der Groesse "width * height"
4 Fuer i in allen Bildzeilen (height):
5 Reserviere Speicher "line" mit der Groesse "width"
6 Lese "width" Bytes aus der Datei in "line"
7 Verarbeite "line"
8 Speichere "line" im Speicherblock "image" an Stelle "width * i"
9 Gib Speicher "line" wieder frei
0 Ende Fuer i

Zu Gunsten des Programmierers nehmen wir an, dass er sich durchaus Gedanken darüber gemacht hat, wie er sicherstellt, dass sein Programm auch dann korrekt funktioniert, wenn man ihm kaputte Bilddateien vorsetzt. So liest es maximal so viele Daten ein, wie der Vorspann der Grafik angekündigt hat und dann auch in den dafür reservierten Puffer passen. Trotzdem ist ihm ein schwerwiegender Fehler unterlaufen. Er findet sich bei der Berechnung der benötigten Speichergröße für die Gesamtbilddaten:

image = (unsigned char *) SimpleHeap_alloc(width * height, myRoot);

Was auf den ersten Blick wie eine sinnvolle Umsetzung des Ziels aussieht, entpuppt sich spätestens bei den folgenden Bildinformationen als fataler Fehler:

Breite	= 0x00000080 = 256
Höhe = 0x10000000 = 268435456

Da die Funktion SimpleHeap_alloc() als erstes Argument die Größe als size_t erwartet und dieser Datentyp bei einem herkömmlichen 32-Bit-Programm genau 32 Bit breit ist, werden auch nur die unteren 32 Bit des Ergebnisses der Multiplikation an die Funktion übergeben, also die 8 Nullen aus 0x800000000.

Die signifikante Information der 8 im höchstwertigen Byte geht verloren und das Programm reserviert einen 0 Byte großen Speicherbereich für die Bilddaten. Dies ist per se eine korrekte Operation, welche SimpleHeap auch pflichtgemäß erledigt. Nur reichen 0 Byte eben nicht, um die nun folgenden Bilddaten zu speichern. Ein Heap-Overflow ist geboren, in diesem Fall als Resultat eines Integer-Overflows bei der Größenberechnung.

Öffnet das Programm ein Bild mit diesen Größenangaben, überschreibt schon das Abspeichern der Bilddaten in der ersten Zeile den für line reservierten Speicher, der auf dem Heap direkt hinter image liegt.

Typischerweise löst der Angreifer diesen Überlauf zunächst mit einer Testdatei aus, die das Zeichen A für alle Bilddaten enthält.

00: 80 00 00 00 00 00 00 10 41 41 41 41 41 41 41 41 ........AAAAAAAA
10: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
20: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
30: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA

Die Zahlenwerte für Höhe und Breite sind übrigens verdreht in der Datei abgelegt, weil Intel-CPUs Daten im Little-Endian-Format speichern, also das niederwertige Byte zuerst. Das Beispielprogramm stürzt beim Bearbeiten des Bildes schon beim ersten Durchlauf der Schleife in Zeile 9 ab, wobei die SimpleHeap_free-Funktion bei dem Test:

if ( 0 == hdr->next->used )

eine Speicherschutzverletzung beim Lesen der Adresse 0x4141414d erzeugt.

Was ist passiert und vor allem: Woher kommt das "d"? Die A-Zeichen haben in Zeile 8 den Header des Blocks line überschrieben. Zeile 9 ruft dann SimpleHeapfree() auf, um line wieder freizugeben. Die Variable hdr zeigt zwar noch auf den richtigen Speicherbereich, dessen Inhalt ist allerdings von den Bilddaten überschrieben worden.

Demzufolge hat der Ausdruck hdr->next dann den Wert 0x41414141, was vier großen ASCII-As (0x41) entspricht. Das Element used eines SimpleHeap-Headers liegt vom Anfang des Headers aus gezählt an Offset 12 hinter den jeweils 4 Bytes für next, prev und size, also an der Adresse

0x41414141 + 0x0c = 0x4141414d

Beim Versuch, den Wert von used auszulesen, erfolgt ein Speicherzugriff auf diese Adresse, der kein Speicher zugeordnet ist, was zu einer Speicherschutzverletzung führt. Damit ist offensichtlich, dass der Angreifer den Heap kontrollieren und mit eigenen Daten überschreiben kann. Außerdem weiß er nach diesem Test, dass die Bilddaten den Header des nächsten Blockes überschreiben.