Puffer-Überläufe gibt es schon seit den Anfängen der Von-Neuman-Architektur 1. Sie erlangten zum ersten Mal durch den Internetwurm Morris im Jahre 1988 öffentliche Bekanntheit. Unglücklicherweise funktioniert der gleiche grundlegende Angriff noch heute. Von den 17 CERT-Sicherheitsmeldungen wurden 1999 zehn direkt durch Puffer-Überläufe in der Software verursacht. Die bei weitem häufigste Form eines Puffer-Überlauf-Angriffs basiert darauf, den Stack zu korrumpieren.
Die meisten modernen Computer-Systeme verwenden einen Stack, um Argumente an Prozeduren zu übergeben und lokale Variablen zu speichern. Ein Stack ist ein last-in-first-out-Puffer (LIFO) im hohen Speicherbereich eines Prozesses. Wenn ein Programm eine Funktion aufruft wird ein neuer "Stackframe" erzeugt. Dieser besteht aus den Argumenten, die der Funktion übergeben wurden und einem variabel grossem Bereich für lokale Variablen. Der "Stack-Pointer" ist ein Register, dass die aktuelle Adresse der Stack-Spitze enthält. Da sich dieser Wert oft ändert, wenn neue Werte auf dem Stack abgelegt werden, bieten viele Implementierungen einen "Frame-Pointer", der nahe am Anfang des Stack-Frames liegt und es so leichter macht lokale Variablen relativ zum aktuellen Stackframe zu adressieren. 1 Die Rücksprungadresse der Funktionen werden ebenfalls auf dem Stack gespeichert und das ist der Grund für Stack-Überlauf-Exploits. Denn ein böswilliger Nutzer kann die Rücksprungadresse der Funktion überschreiben indem er eine lokale Variable in der Funktion überlaufen lässt, wodurch es ihm möglich ist beliebigen Code auszuführen.
Obwohl Stack-basierte Angriffe bei weitem die Häufigsten sind, ist es auch möglich den Stack mit einem Heap-basierten (malloc/free) Angriff zu überschreiben.
Die C-Programmiersprache führt keine automatischen Bereichsüprüfungen bei Feldern oder Zeigern durch, wie viele andere Sprachen das tun. Außerdem enthält die C-Standardbibliothek eine Handvoll sehr gefährlicher Funktionen.
strcpy (char *dest, const char *src) |
Kann den Puffer dest überlaufen lassen |
strcat (char *dest, const char *src) |
Kann den Puffer dest überlaufen lassen |
getwd (char *buf) |
Kann den Puffer buf überlaufen lassen |
gets (char *s) |
Kann den Puffer s überlaufen lassen |
[vf]scanf (const char *format, ...) |
Kann sein Argument überlaufen lassen |
realpath (char *path, char resolved_path[]) |
Kann den Puffer path überlaufen lassen |
[v]sprintf (char *str, const char *format, ...) |
Kann den Puffer str überlaufen lassen |
Das folgende Quellcode-Beispiel enthält einen Puffer-Überlauf, der darauf ausgelegt ist die Rücksprungadresse zu überschreiben und die Anweisung direkt nach dem Funktionsaufruf zu überspringen. (Inspiriert durch 4)
#include <stdio.h> void manipulate(char *buffer) { char newbuffer[80]; strcpy(newbuffer,buffer); } int main() { char ch,buffer[4096]; int i=0; while ((buffer[i++] = getchar()) != '\n') {}; i=1; manipulate(buffer); i=2; printf("The value of i is : %d\n",i); return 0; }
Betrachten wir nun, wie das Speicherabbild dieses Prozesses aussehen würde, wenn wir 160 Leerzeichen in unser kleines Programm eingeben, bevor wir Enter drücken.
[XXX figure here!]
Offensichtlich kann man durch böswilligere Eingaben bereits kompilierten Programmtext ausführen (wie z.B. exec(/bin/sh)).
Die direkteste Lösung, um Stack-Überläufe zu vermeiden, ist immer
grössenbegrenzten Speicher und String-Copy-Funktionen zu verwenden. strncpy
und strncat
sind Teil der
C-Standardbibliothek. Diese Funktionen akzeptieren einen Längen-Parameter. Dieser
Wert sollte nicht größer sein als die Länge des Zielpuffers. Die
Funktionen kopieren dann bis zu `length' Bytes von der Quelle zum Ziel. Allerdings gibt
es einige Probleme. Keine der Funktionen garantiert, dass die Zeichenkette NUL-terminiert
ist, wenn die Größe des Eingabepuffers so groß ist wie das Ziel.
Außerdem wird der Parameter length zwischen strncpy und strncat inkonsistent
definiert, weshalb Programmierer leicht bezüglich der korrekten Verwendung
durcheinander kommen können. Weiterhin gibt es einen spürbaren Leistungsverlust
im Vergleich zu strcpy
, wenn eine kurze Zeichenkette in
einen großen Puffer kopiert wird. Denn strncpy
fült den Puffer bis zur angegebenen Länge mit NUL auf.
In OpenBSD wurde eine weitere Möglichkeit zum kopieren von Speicherbereichen
implementiert, die dieses Problem umgeht. Die Funktionen strlcpy
und strlcat
garantieren,
dass das Ziel immer NUL-terminiert wird, wenn das Argument length ungleich null ist.
Für weitere Informationen über diese Funktionen lesen Sie bitte 6. Die OpenBSD-Funktionen strlcpy
und strlcat
sind seit
Version 3.3 auch in FreeBSD verfügbar.
Unglücklicherweise gibt es immer noch sehr viel Quelltext, der allgemein verwendet wird und blind Speicher umherkopiert, ohne eine der gerade besprochenen Funktionen, die Begrenzungen unterstützen, zu verwenden. Glücklicherweise gibt es eine weitere Lösung. Verschiedene Compiler-Erweiterungen und Bibliotheken überprüfen die Grenzen in C/C++ zur Laufzeit.
StackGuard ist eine solche Erweiterung, die als kleiner Patch für den GCC-Code-Generator implementiert ist. Von der StackGuard Webseite (übersetzt):
"StackGuard erkennt und verhindert Stack-Smashing-Angriffe, indem es die Rücksprungadresse auf dem Stack davor schützt, geändert zu werden. StackGuard platziert ein "Canary"-Wort (Anmerkung des Übersetzers: Kanarienvogel, nach einer Sicherheitsvorkehrung von Bergleuten, um Gas frühzeitig zu erkennen, ein Wort ist hier ein 32-Bit-Speicherblock) neben der Rücksprungadresse, wenn eine Funktion aufgerufen wird. Wenn das Canary bei der Rückkehr der Funktion geändert wurde, reagiert das Programm auf den Versuch eines Stack-Smashing-Angriffs, schickt eine Benachrichtigung an Syslog und hält dann an."
"StackGuard ist als ein kleiner Patch für den GCC-Code-Generator implementiert, um genau zu sein, für die Routinen function_prolog() und function_epilog(). function_prolog() wurde erweitert, um Canaries beim Start einer Funktion auf den Stack zu legen und function_epilog() überprüft die Integrität des Canaries beim Beenden der Funktion. Jeder Versuch, die Rücksprungadresse zu ändern, wird daher erkannt, bevor die Funktion zurückkehrt."
Ihre Anwendungen mit StackGuard neu zu kompilieren ist eine effektive Maßnahme, um sie vor den meisten Puffer-Überlauf-Angriffen zu schützen, aber die Programme können noch immer kompromittiert werden.
Compiler-basierte Mechanismen sind bei Software, die nur im Binärformat
vertrieben wird, und die somit nicht neu kompiliert werden kann völlig nutzlos.
Für diesen Fall gibt es einige Bibliotheken, welche die unsicheren Funktionen der
C-Bibliothek (strcpy
, fscanf
,
getwd
, etc..) neu implementieren und sicherstellen, dass
nicht hinter den Stack-Pointer geschrieben werden kann.
libsafe
libverify
libparanoia
Leider haben diese Bibliotheks-basierten Verteidigungen mehrere Schwächen. Diese Bibliotheken schützen nur vor einer kleinen Gruppe von Sicherheitslücken und sie können das eigentliche Problem nicht lösen. Diese Maßnahmen können versagen, wenn die Anwendung mit -fomit-frame-pointer kompiliert wurde. Außerdem kann der Nutzer die Umgebungsvariablen LD_PRELOAD und LD_LIBRARY_PATH überschreiben oder löschen.
Zurück | Zum Anfang | Weiter |
Methoden des sicheren Entwurfs | Nach oben | SetUID-Themen |
Wenn Sie Fragen zu FreeBSD haben, schicken Sie eine E-Mail an
<de-bsd-questions@de.FreeBSD.org>.
Wenn Sie Fragen zu dieser Dokumentation haben, schicken Sie eine E-Mail an <de-bsd-translators@de.FreeBSD.org>.