int zahl;
char buf[99];
scanf("%d %20s", buf, &zahl);
Diesen fehlerhaften Code quittiert GCC (aufgerufen per »gcc -Wall -o foo foo.c«) mit folgenden Warnungen:
foo.c: In function `main': foo.c:8: warning: int format, differentU type arg (arg 2) foo.c:8: warning: char format, differentU type arg (arg 3)
Falle 4: Buffer-Overflows
Buffer Overflows sind eng verwandt mit Formatstring-Fehlern und länger bekannt als sie. Hier wie dort ist der Stack ein beliebtes, wenn auch nicht das einzige Ziel böswilliger Angreifer. Ein Buffer-Overflow tritt dann auf, wenn ein Programm Daten in einem Speicherbereich (Buffer) ablegt, der zu klein ist, um alles aufzunehmen. Dann überschreibt es Speicherzellen, denen eine andere Rolle zugewiesen ist.
Beispielsweise führt ein Überlauf der »eingabe«-Variablen in Listing 2 (Zeile 20) dazu, dass nacheinander »len«, der Framepointer und die Rücksprungadresse der überlangen Eingabe zum Opfer fallen (siehe Abbildung 3). Beim zufälligen Überlauf stürzt das Programm in der Regel beim Verlassen der Funktion ab, weil die Rücksprungadresse ins digitale Nirwana zeigt.
Viel unangenehmer wird es, wenn Fred im Buffer ausführbaren Code einschmuggelt. Meist startet der Code einfach eine Shell: »execve(“/bin/sh”, 0, 0);« und Fred übernimmt das angegriffene Benutzerkonto. Den so genannten Shellcode schreiben ist zwar nicht ganz einfach, aber neu erfinden muss ihn kein Möchtegern-Cracker. Dem ungeübten Angreifer kommen Bibliotheken wie Libshellcode [11] zur Hilfe. Mit dabei ist ein kleines Ncurses-GUI namens Scbuilder (Abbildungen 5a und 5b).
Knick in der Logik
Subtiler und kaum zu verhindern sind Versuche, die Programmlogik zu überlisten, indem Fred nur andere lokale Variablen überschreibt, die bei höheren Speicheradressen liegen. Im Beispiel wären das die Variable »len« oder Variablen der aufrufenden Funktion. In der harten Wirklichkeit haben sich findige Hacker auf diese Weise schon fremde Rechte angeeignet, indem sie etwa eine Variable überschrieben haben, die die UID enthielt, auf die das Programm später wechseln sollte. Mehr Details zu Buffer-Overflows geben [12] und [13].
|
Tabelle 3: Ein- und |
|
|---|---|
|
Falsch |
Richtig |
|
sprintf(buf, “%s”, str) |
sprintf(buf, “%99s”, str) odersnprintf(buf, 100, “%s”, |
|
scanf(“%s”, str) |
scanf(“%99s”, str) |
|
gets(buf) |
fgets(buf, 100, stdin) |
|
strcat(buf, str) |
strncat(buf, str, 99) |
|
strcpy(buf, str) |
strncpy(buf, str, 99); buf[99] = 0; |
Vorsicht mit fremden Daten
Mit im Spiel bei Speicherüberläufen ist immer Programmcode, der Daten liest, schreibt oder kopiert. In C stehen im Quelltext dann oft eine String-Funktion (»strcat()«, »strcpy()«…), eine Funktion zur formatierten Ein- und Ausgabe (»sprintf()«, »scanf()«…), eine Dateizugriffsfunktion (»fread()«, »gets()«…) oder wilde Zeigerarithmetik.
Ein Aufruf der »gets()«-Routine (Zeile einlesen; Listing 2, Zeile 20) ist fast immer falsch: Der Entwickler kann der Funktion nicht mitteilen, wie viel Speicher ihr zur Verfügung steht. Eine automatische Prüfung von Puffergrenzen sieht C nicht vor, folglich hat Gets den Buffer-Overflow schon eingebaut. Auch »scanf(input, “%s”, buffer)« ist nicht besser: Scanf speichert den gelesenen String im Buffer, egal wie groß der ist. Tabelle 3 sagt, wie es besser geht, und Zeile 20 in Listing 3 wendet dies an.
|
Listing 4: |
|---|
01 #include <limits.h>
02 #include <string.h>
03 #include <unistd.h>
04
05 int main(void)
06 {
07 char abs_path[PATH_MAX];
08
09 getcwd(abs_path, PATH_MAX);
10 strcat(abs_path, "/dateiname");
11 /* ... */
12
13 return 0;
14 }
|
Listing 4 zeigt einen weiteren recht häufigen Fehler, hier am Beispiel des Verarbeitens von Dateinamen. Der Aufruf von »getcwd()« in Zeile 9 speichert den Pfad des Arbeitsverzeichnisses in der Variablen »abs_path«. Bis hierher gibt es noch kein Problem, weil der Puffer groß genug ist. Allerdings prüft »strcat()« in Zeile 10 nicht mehr, ob der Puffer bereits voll ist, und schreibt möglicherweise über sein Ende hinaus.
Weitere Gefahr droht in diesem Beispiel eventuell, wenn der Pfad bereits länger ist als »PATH_MAX«. Diese Situation kann bei manchen Dateisystemen eintreten, wenn sie unbegrenzt lange Pfadnamen unterstützen. »getcwd()« kopiert dann zwar nur die angegebene Menge Zeichen, das ist aber auch ein Fehler, den das Programm erkennen (Rückgabewert »ERANGE«) müsste und angemessen darauf reagieren.





