Intention
Vor einer aktuell kritischen Sicherheitslücke von älteren Microsoft Server Versionen, Windows 11 und Windows 10 wird in einem aktuellen Artikel auf heise.de gewarnt.
Diese offen gelegten Schwachstellen / Sicherheitslücken werden in einer öffentlichen Online-Datenbank aufgelistet:
- https://cwe.mitre.org/data/definitions/122.html
- https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-21391
Konkret handelt es sich dabei um die Ausnutzung von Heap-Overflows für das Einschleusen von Schadcode / schädlichem Code. In diesem Artikel möchte ich darauf eingehen, wie solche Heap-Overflows funktionieren, indem ich die Fragen beantworte, die sich mir vorher gestellt hatten.
Was ist der „Heap“, der überlaufen kann?
Zunächst ist eine Abgrenzung darüber nötig, was ein Heap eigentlich ist. In der Informatik gibt es zwei verschiedene Konzepte, die beide als „Heap“ bezeichnet werden:
- Heap im Speicher-Management: Dies bezieht sich auf den Bereich des Speichers, der für dynamische Speicherzuweisungen verwendet wird. Wenn Sie
malloc()
oder ähnliche Funktionen verwenden, wird der Speicher aus diesem Heap zugewiesen. Ein Heap Overflow tritt auf, wenn mehr Daten in einen Speicherbereich geschrieben werden, als dafür vorgesehen ist, was zu einer Beschädigung des Speichers führen kann. - Heap als Datenstruktur: Wir haben in „Algorithmen und Datenstrukturen“ ebenfalls über Heaps gesprochen. Dies ist eine spezielle Baumstruktur, die bestimmte Eigenschaften erfüllt. Zum Beispiel ist ein Max-Heap ein binärer Baum, bei dem jeder Elternknoten größer oder gleich seinen Kindknoten ist. Diese Art von Heap wird häufig in Algorithmen wie Heapsort oder für Prioritätswarteschlangen verwendet.
Im Nachfolgenden beziehen wir uns auf Definition 1, da der Heap im dynamisch anforderbaren Speicherbereich liegt. Der Prozess kann also von diesem Bereich neuen Speicher für sich anfordern. Der Heap des Prozessspeichers beschränkt sich bei modernen Betriebssystemen auf den ausgeführten Prozess, was bedeutet dass ein Prozess nicht auf den Prozessspeichers eines anderen Prozesses zugreifen kann. Bei früheren Betriebssystemen wie beispielsweise MS DOS war dies noch nicht der Fall, da man hier z.B. direkt in den Bildwiederholspeicher Pixel in einer Farbe durch Speichermanipulation setzen konnte.
Ist die in Definition 1 genannte Beschädigung physisch?
Nein, die Beschädigung bei einem Heap Overflow ist nicht physisch. Sie betrifft den Speicherbereich des Computers und führt zu einer logischen Beschädigung der Daten. Das bedeutet, dass die Daten im Speicher durcheinandergebracht oder überschrieben werden, was zu Programmabstürzen, unerwartetem Verhalten oder Sicherheitslücken führen kann. Physische Hardware wird dabei nicht beschädigt.
Gibt es ein Beispiel, wie ein Heap Overflow zu einer Sicherheitslücke führen kann?
Stellen wir uns vor, ein Programm verwendet die Funktion malloc()
, um Speicher für Benutzereingaben zu reservieren, überprüft jedoch nicht, ob die Eingabe die Größe des zugewiesenen Speichers überschreitet. Ein Angreifer könnte dann eine übermäßig große Eingabe senden, die mehr Speicherplatz benötigt, als zugewiesen wurde. Dies führt dazu, dass benachbarte Speicherbereiche überschrieben werden.
Ein konkretes Beispiel ist der Heap Overflow Angriff auf den Microsoft Internet Explorer im Jahr 2014. Angreifer nutzten eine Schwachstelle aus, bei der sie durch gezielte Manipulation des Heaps die Kontrolle über den Programmfluss erlangen konnten. Sie überschrieben kritische Datenstrukturen im Speicher und führten so schädlichen Code aus
Solche Angriffe können schwerwiegende Folgen haben, einschließlich Datenverlust, unbefugtem Zugriff auf Systeme und der Ausführung von Schadsoftware.
Wie liegen diese Kontrollstrukturen im Speicher vor?
Kontrollstrukturen wie Schleifen, Bedingungen und Funktionsaufrufe werden im Speicher durch den Programmcode und die zugehörigen Datenstrukturen repräsentiert. Hier ist eine kurze Übersicht, wie sie im Speicher organisiert sind:
- Programmcode: Der eigentliche Code der Kontrollstrukturen wird im Textsegment des Speichers abgelegt. Dies umfasst die Anweisungen, die der Prozessor ausführt.
- Stack: Der Stack-Speicher wird verwendet, um lokale Variablen, Funktionsaufrufe und Rücksprungadressen zu speichern. Wenn eine Funktion aufgerufen wird, werden die Parameter und die Rücksprungadresse auf den Stack gelegt. Bei Schleifen und Bedingungen werden die aktuellen Zustände und Variablen ebenfalls auf dem Stack verwaltet.
- Heap: Dynamisch zugewiesene Speicherbereiche, die durch Funktionen wie
malloc()
angefordert werden, befinden sich im Heap. Kontrollstrukturen selbst nutzen den Heap nicht direkt, aber die Daten, die sie verarbeiten, können dort gespeichert sein.
Ein Beispiel für eine Sicherheitslücke durch einen Heap Overflow ist, wenn ein Angreifer den Heap so manipuliert, dass er die Kontrollstrukturen im Speicher überschreibt. Dadurch kann der Angreifer den Programmfluss ändern und schädlichen Code ausführen
Gibt es ein Beispiel, was eine Kontrollstruktur im Heap abspeichern könnte, was dazu führt dass sie manipuliert wird?
Ja, ein Beispiel für eine Kontrollstruktur, die im Heap gespeichert wird und manipuliert werden kann, sind Zeiger auf Funktionszeiger innerhalb von Datenstrukturen. Hier ist ein konkretes Szenario:
Stellen wir uns vor, ein Programm verwendet eine Datenstruktur, die einen Funktionszeiger enthält. Dieser Funktionszeiger zeigt auf eine Routine, die aufgerufen wird, wenn bestimmte Bedingungen erfüllt sind. Wenn ein Heap Overflow auftritt, kann ein Angreifer den Funktionszeiger überschreiben und ihn auf eine schädliche Routine umleiten.
Ein Beispiel für einen solchen Angriff ist der Heap Overflow Angriff auf den iOS-Kernel. In diesem Fall wurde der Heap so manipuliert, dass ein überlaufener Speicherbereich mit einem kritischen Datenobjekt kollidierte. Der Angreifer konnte dadurch den Funktionszeiger innerhalb des Datenobjekts überschreiben und die Kontrolle über den Programmfluss übernehmen.
In dem beschriebenen Szenario hat der Angreifer den Funktionszeiger im Heap-Speicherbereich überschrieben. Der Heap wird für dynamische Speicherzuweisungen verwendet, und in diesem Fall wurde ein Funktionszeiger innerhalb einer Datenstruktur im Heap manipuliert. Durch den Heap Overflow konnte der Angreifer den Funktionszeiger so ändern, dass er auf schädlichen Code zeigt, anstatt auf die ursprünglich vorgesehene Funktion.
Wo lag der schädliche Code, auf den der Funktionszeiger umgeschrieben wurde?
Der schädliche Code, auf den der Funktionszeiger umgeschrieben wurde, kann sich an verschiedenen Stellen im Speicher befinden. Häufig wird er jedoch im Heap oder im Textsegment des Speichers platziert.
Hier sind die beiden häufigsten Szenarien:
- Heap: Der Angreifer kann den schädlichen Code direkt im Heap platzieren, indem er eine übermäßige Eingabe sendet, die den Heap überläuft und den Funktionszeiger überschreibt. Der Funktionszeiger wird dann auf den schädlichen Code im Heap umgeleitet.
- Textsegment: In einigen Fällen kann der Angreifer den Funktionszeiger so manipulieren, dass er auf bereits vorhandenen schädlichen Code im Textsegment zeigt. Dies kann durch das Einfügen von Shellcode in den Speicher erreicht werden, der dann durch den manipulierten Funktionszeiger ausgeführt wird
Kann man sich auf seinem Rechner den Heap anzeigen lassen?
Es ist möglich, den Heap-Speicherinhalt auf seinem Rechner anzuzeigen, allerdings erfordert dies spezielle Tools und Kenntnisse. Hier sind einige Methoden, wie der Heap-Speicher untersucht werden kann:
- Debugger: Mit Debuggern wie GDB (GNU Debugger) oder WinDbg (Windows Debugger) kann man den Speicher eines laufenden Programms untersuchen. Diese Tools ermöglichen es dir, den Heap-Speicher zu durchsuchen und zu analysieren.
- Heap-Profiler: Tools wie Valgrind (insbesondere das Modul Massif) oder Visual Studio Profiler können verwendet werden, um den Heap-Speicher zu profilieren und Speicherlecks sowie Speicherüberläufe zu identifizieren.
- Speicher-Dump-Analyse: Mit einem Speicher-Dump (Abbild des Speichers) eines laufenden Programms erstellen und diesen mit Tools wie WinDbg oder GDB analysieren. Dies ermöglicht, den Zustand des Heaps zu einem bestimmten Zeitpunkt zu untersuchen.
Hier ist ein einfaches Beispiel, wie man mit GDB den Heap-Speicher eines Programms untersuchen kannst:
gdb ./das_programm
(gdb) run
(gdb) info proc mappings
(gdb) x/20xw 0xheap_start_address
In diesem Beispiel ersetzt man./das_programm
durch den Namen des Programms und 0xheap_start_address
durch die Startadresse des Heaps, die aus den Speicherzuordnungen kommt.
Wie findet man eine Benutzereingabe, die zu einem Heap Overflow führt?
Das Finden einer Benutzereingabe, die zu einem Heap Overflow führt, ist eine komplexe Aufgabe und erfordert ein tiefes Verständnis des Zielprogramms sowie der Art und Weise, wie es Speicher verwaltet. Hier sind einige Schritte, die oft verwendet werden:
- Quellcode-Analyse: Durchsuche den Quellcode des Programms nach Stellen, an denen dynamischer Speicher zugewiesen wird (z.B. durch
malloc()
,calloc()
,realloc()
). Achte besonders auf Funktionen, die Benutzereingaben verarbeiten. - Fuzzing: Verwende Fuzzing-Tools, um das Programm mit zufälligen oder speziell gestalteten Eingaben zu testen. Fuzzing kann helfen, Schwachstellen zu finden, indem es das Programm mit einer Vielzahl von Eingaben bombardiert und nach Abstürzen oder ungewöhnlichem Verhalten sucht. Beispiele für Fuzzing-Tools sind AFL (American Fuzzy Lop) und LibFuzzer.
- Manuelle Tests: Erstelle manuell Eingaben, die die Grenzen der Puffer überschreiten könnten. Dies kann durch das Senden von sehr langen Zeichenketten oder durch das Einfügen spezieller Zeichenfolgen geschehen, die das Programm möglicherweise nicht korrekt verarbeitet.
- Debugging: Verwende Debugger wie GDB oder WinDbg, um das Programm während der Ausführung zu überwachen. Setze Breakpoints an kritischen Stellen und beobachte, wie das Programm auf verschiedene Eingaben reagiert. Achte auf Speicherüberläufe und überprüfe, ob der Heap beschädigt wird.
- Speicheranalyse-Tools: Nutze Tools wie Valgrind oder AddressSanitizer, um Speicherprobleme zu erkennen. Diese Tools können helfen, Speicherlecks, Überläufe und andere Speicherprobleme zu identifizieren.
Hier ist ein einfaches Beispiel für Fuzzing mit AFL:
# Installiere AFL
sudo apt-get install afl
# Instrumentiere das Zielprogramm
afl-gcc -o zielprogramm zielprogramm.c
# Führe AFL aus
afl-fuzz -i input_dir -o output_dir ./zielprogramm
Gibt es ein Beispielcode in C oder C++, wie man einen Heap Overflow verursachen kann?
Dieser Code sollte nur zu Lernzwecken dienen und nicht in einer produktiven Umgebung verwendet werden, da er Sicherheitsrisiken birgt.
Hier ist ein Beispielcode:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void vulnerable_function(const char *input) {
// Allokiere einen Puffer von 10 Bytes auf dem Heap
char *buffer = (char *)malloc(10 * sizeof(char));
if (buffer == NULL) {
fprintf(stderr, "Speicherzuweisung fehlgeschlagen\n");
exit(1);
}
// Kopiere die Eingabe in den Puffer ohne Größenüberprüfung
strcpy(buffer, input);
// Ausgabe des Puffers
printf("Pufferinhalt: %s\n", buffer);
// Freigabe des Puffers
free(buffer);
}
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Verwendung: %s <Eingabe>\n", argv[0]);
return 1;
}
// Aufruf der anfälligen Funktion mit Benutzereingabe
vulnerable_function(argv[1]);
return 0;
}
In diesem Beispiel wird ein Puffer von 10 Bytes auf dem Heap allokiert. Die Funktion strcpy
kopiert die Benutzereingabe in diesen Puffer, ohne die Größe der Eingabe zu überprüfen. Wenn die Eingabe länger als 10 Bytes ist, wird der Puffer überlaufen, was zu einem Heap Overflow führt.
Mann kann dieses Programm kompilieren und ausführen, um das Verhalten zu beobachten:
gcc -o heap_overflow_example heap_overflow_example.c
./heap_overflow_example "Dies ist eine sehr lange Eingabe, die den Puffer überläuft"
Dies wird zu einem Heap Overflow führen und möglicherweise das Programm zum Absturz bringen oder unerwartetes Verhalten verursachen.
Kann dieses Programm auch Speicherbereiche anderer Programme überschreiben und diese manipulieren?
Nein, dieses Programm kann nicht direkt Speicherbereiche anderer Programme überschreiben und manipulieren. In modernen Betriebssystemen gibt es Schutzmechanismen, die verhindern, dass ein Programm auf den Speicherbereich eines anderen Programms zugreift. Diese Schutzmechanismen umfassen:
- Speicherschutz: Betriebssysteme wie Windows, Linux und macOS verwenden Speicherschutzmechanismen, um sicherzustellen, dass jedes Programm nur auf seinen eigenen Speicherbereich zugreifen kann. Versuche, auf den Speicherbereich eines anderen Programms zuzugreifen, führen zu einer Speicherzugriffsverletzung (Segmentation Fault) und dem Absturz des Programms.
- Adressraum-Layout-Randomisierung (ASLR): ASLR ist eine Sicherheitsfunktion, die die Speicheradressen von Programmcode, Daten und Bibliotheken zufällig anordnet. Dies erschwert es Angreifern, vorherzusagen, wo sich bestimmte Speicherbereiche befinden, und macht Exploits schwieriger.
- Benutzer- und Kernel-Modus: Betriebssysteme trennen den Speicher in Benutzer- und Kernel-Modus. Programme laufen im Benutzermodus und haben keinen direkten Zugriff auf den Kernel-Speicherbereich, was die Sicherheit weiter erhöht.
Ein Heap Overflow innerhalb eines Programms kann jedoch dazu führen, dass das Programm selbst abstürzt oder unerwartetes Verhalten zeigt. In einem Szenario, in dem ein Angreifer den Heap Overflow ausnutzt, könnte der Angreifer schädlichen Code innerhalb des betroffenen Programms ausführen, aber nicht direkt andere Programme beeinflussen.