Ein Haufen Risiko

Seite 3: Zum Angriff

Inhaltsverzeichnis

Zwei Fehler können einen Heap-Overflow verursachen: Die Größe des reservierten Speichers ist statisch und ein Angreifer kann dafür sorgen, dass mehr als die vorgesehene Menge Daten hineingeschrieben wird, oder die Berechnung des benötigten Speichers bei der Reservierung ist fehlerhaft beziehungsweise beruht auf Angaben des Angreifers.

Aus welchem Grund der Heap-Overflow auch zustande kommt, der Angreifer kann fast immer nur im Speicher weiter schreiben, nicht aber rückwärts. Das heißt, der Overflow kann den eigenen Heap-Header nicht überschreiben, da er ja vor dem Datenblock liegt, wohl aber einen oder mehrere der nachfolgenden Header.

Hier wird ein wichtiger Unterschied zu einem Buffer-Overflow auf dem Stack deutlich: Der Angreifer muss wissen, welche Heap-Implementierung das Programm zur Laufzeit verwendet, um die Verwaltungsinformationen gezielt mit manipulierten Werten zu präparieren. Das Stack-Layout ist hingegen bei jedem Programmlauf mehr oder weniger gleich und die Bedeutung der außer der Rücksprungadresse überschriebenen Werte weitgehend egal.

Noch ein weiterer Unterschied macht dem Angreifer das Leben schwer: Anders als auf dem Stack kann er auf dem Heap keine Speicherbereiche überschreiben, deren Inhalt das Programm direkt als Sprungadresse nutzt. Er kontrolliert lediglich Daten für die Heap-Verwaltung, also die Zeiger next und prev und die Felder size und used.

Damit wird bereits klar, dass SimpleHeap noch weitere Operationen auf dem Heap durchführen muss, damit die überschriebenen Werte überhaupt verwendet werden. Beendet sich das Programm direkt nach dem Überlauf, werden die überschriebenen Werte nicht verwendet und der Angriff ist erfolglos. Ein nahe liegender Kandidat für einen gezielten Angriff ist die Funktion SimpleHeap_free(), die für jeden reservierten Block irgendwann einmal aufgerufen werden muss.

Um eigenen Code zur Ausführung zu bringen, muss der Angreifer dafür sorgen, dass das System des Opfers einen Speicherbereich überschreibt, von dem es zu einem späteren Zeitpunkt eine Sprungadresse lädt. Da es dabei darum geht, einen vom Angreifer vorgegebenen Wert an eine ebenfalls von ihm ausgewählte Adresse zu schreiben, sind Zeigeroperationen mit next und prev in SimpleHeap die aussichtsreichsten Kandidaten.

Betrachtet man SimpleHeap_free() noch einmal unter diesem Gesichtspunkt, fällt das Augenmerk sofort auf die Defragmentierung, bei der die manipulierbaren Verwaltungsinformationen für Schreiboperationen herangezogen werden. Insbesondere bei

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

kann der Angreifer sowohl den zu schreibenden Wert als auch die Zieladresse bestimmen, wenn es ihm durch Überlauf gelingt, hdr->next auf einen von ihm fingierten Header zeigen zu lassen. Er muss dort dann nur das used-Feld auf 0 setzen, und schon führt SimpleHeap beim Freigeben des Puffers den obigen Befehl aus. Die richtigen Werte in hdr->next->prev und hdr->next->next->prev sorgen dafür, dass die vom Angreifer gewünschte Speicherstelle mit einem bestimmten Wert überschrieben wird. Grundsätzlich gilt, dass der Angreifer über hdr->next auch alle weiteren Dereferenzierungen kontrollieren kann. Das illustriert auch der vom Compiler erzeugte Assembler-Code, den man jedoch zum weiteren Verständnis des Artikels nicht benötigt.

C-Code:

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

Assembler:

mov eax, [ebp+hdr]  ; EAX = hdr
mov ecx, [eax] ; ECX = Daten an Adresse in EAX, also
; ECX = hdr->next
mov edx, [ebp+hdr] ; EDX = hdr
mov eax, [edx] ; EAX = Daten an Adresse in EDX, also
; EAX = hdr->next
mov edx, [eax] ; EDX = Daten an Adresse in EAX, also
; EDX = hdr->next->next
mov eax, [ecx+4] ; EAX = Daten an Adresse in ECX + 4, also
; EAX = hdr->next->prev
mov [edx+4], eax ; Daten an Adresse in EDX + 4 = EAX, also
; hdr->next->next->prev = hdr->next->prev