Sie sind auf Seite 1von 35

BACHELORARBEIT

Ausfallsicherheit bei der Verwendung


von dynamischen Maÿnahmen zur
Verhinderung von Buer-Overow
Angrien
ausgeführt von Peter Falkensteiner

E-Mail: f a l k e n s t e i n e r @ g m x . a t

Begutachter: FH-Prof. DI Alexander Mense

Wien, 29. Juni 2009

Ausgeführt an der FH Technikum Wien

Studiengang Informations- und Kommunikationssysteme


Eidesstattliche Erklärung

Ich erkläre hiermit an Eides Statt, dass ich die vorliegende Arbeit selbständig angefertigt
habe. Die aus fremden Quellen direkt oder indirekt übernommenen Gedanken sind als
solche kenntlich gemacht. Die Arbeit wurde bisher weder in gleicher noch in ähnlicher
Form einer anderen Prüfungsbehörde vorgelegt und auch noch nicht veröentlicht.

Ort, Datum Unterschrift


Kurzfassung

Zu einem der am häugsten registrierten Angrien auf IT-Systeme zählen die Buer-
Overow-Attacken, die gleichzeitig auch ein groÿes Schadens-Potential in sich bergen.
Deshalb wurden in den letzten Jahren verschiedene Technologien realisiert, um Angris-
möglichkeiten aufzudecken oder die erfolgreiche Durchführung der Attacken abzuwehren.
Bei den verfügbaren Techniken zur Verhinderung von Buer-Overow-Angrien wurde
besonderes Augenmerk auf die Sicherheit der Systeme vor unbefugtem Zugri gelegt
- allerdings zielen nicht alle Attacken auf den Zugri auf das System ab. Es kann
bei Angrien der Umstand genutzt werden, dass Ausfallsicherheit, im Gegensatz zu
Zugrissicherheit, oft nicht gewährleistet wird. So ist es möglich, dass selbst geschütz-
te Dienste durch Puerüberläufe angegrien werden und nicht mehr zu Verfügung stehen.

Schlagwörter: Puerüberlauf, Sicherheit, Zuverlässigkeit, Angrie


Abstract

One of the most widely used class of attacks against IT-systems are buer-overow-
attacks. As the eects of such an attack may be fatal, there has been research into
methods that allow the detection of buer-overow vulnerabilities or prevent successful
exploitation.
The main techniques used today are designed to prevent unauthorized access to the
system through a buer-overow. But not all attacks are designed to gain access to a
system, for example a denial-of-service attack. These attacks may still be successful
with most of these protections enabled. Therefore, seeing each of these technologies as
a full protection against buer-overows does not cover the availability of the protected
services and a successful attack may still be possible.

Keywords: buer-overow, security, reliability, availability, attacks


Inhaltsverzeichnis
1. Einleitung 1
2. Angris-Grundlagen 2
2.1. Buer-Overow-Schwachstellen . . . . . . . . . . . . . . . . . . . . . . . . 2
2.2. Speichermanagement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.2.1. Speicherorganisation von Prozessen . . . . . . . . . . . . . . . . . . 3
2.2.2. Speicherorganisation bei Unterprogramm-Aufrufen . . . . . . . . . 4
2.3. Buer-Overow-Angrie . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.3.1. Einteilung von Buer-Overow-Angrien . . . . . . . . . . . . . . . 6
2.3.2. Buer-Overow-Angrie auf den RIP . . . . . . . . . . . . . . . . . 7
2.3.3. Buer-Overow-Angrie auf den SFP . . . . . . . . . . . . . . . . 10
2.3.4. Buer-Overow-Angrie auf Variablen . . . . . . . . . . . . . . . . 11
2.3.5. Speicherverwaltungsbasierte Buer-Overow-Angrie im Heap-
Segment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

3. Dynamische Methoden 16
3.1. Nicht-ausführbare Speicherbereiche . . . . . . . . . . . . . . . . . . . . . . 16
3.2. Canary-Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.3. Explizite Erkennung von Speichergrenzen . . . . . . . . . . . . . . . . . . 19
3.4. Verbesserte Heap-Implementation . . . . . . . . . . . . . . . . . . . . . . . 20
3.5. Address Space Layout Randomization . . . . . . . . . . . . . . . . . . . . 21

4. Diskussion und Ergebnisse 23


Literaturverzeichnis 24
Abbildungsverzeichnis 28
Abkürzungsverzeichnis 28
A. Zielsystem-Spezikationen 29
R 2008 . . . . . . . . . . . . . . . . . . . . . . . . . . .
A.1. Microsoft Windows 29
A.2. Debian Linux Etch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
A.3. OpenBSD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

B. Programm mit Buer-Overow-Schwachstelle 30


1. Einleitung
Mittlerweile sind Computer ein xer Bestandteil unseres Lebens. Damit steigt der Be-
darf an Programmen, die uns bei unserer täglichen Arbeit immer besser unterstützen
und diese erleichtern. Doch mit der Komplexität der Programme steigt auch die Gefahr,
dass sicherheitskritische Fehler unentdeckt bleiben. ([Viking2006], S.1f )
Eine Angrismöglichkeit, die bereits von Cowan et al. ([Cowan1999], S.1) als vulnerabi-
lity of the decade (Schwachstelle des Jahrzehnts) bezeichnet wurde, ist auch heute noch
von groÿer Bedeutung - der Puerüberlauf (engl. Buer-Overow).
Buer-Overow-Attacken zählen noch immer zu gebräuchlichen Angrien auf IT-Systeme
([Cristey2007], Table 1), obwohl die Idee dahinter schon seit langem bekannt ist. In dem
Internet-Magazin Phrack wurde bereits 1996 einer der ersten Artikel über die Möglich-
keiten veröentlicht, Buer-Overows für gezielte Angrie zu nutzen ([AlephOne1996]).
Seitdem sind die Techniken der Angrie natürlich verfeinert und optimiert worden, das
Prinzip ist aber gleich geblieben.
Aufgrund der Gefährlichkeit und Häugkeit dieser Attacken wurden Gegenmaÿnahmen
entwickelt, um

1. Buer-Overow-Schwachstellen in einem Programm vor der Ausführung aufdecken


zu können (statische Methoden)
oder

2. die Auswirkungen von Buer-Overow-Angrien zur Laufzeit einschränken zu


können (dynamische Methoden). ([Vallentin2007], S.18)

Ziel dieser Arbeit ist es, die Auswirkung von dynamischen Methoden zur Verhinderung
von erfolgreichen Buer-Overow-Attacken auf die Ausfallsicherheit von Diensten zu un-
tersuchen und derzeit verbreitete Technologien zu vergleichen.

1
2. Angris-Grundlagen
2.1. Buer-Overow-Schwachstellen

Wie Vallentin feststellte, werden sicherheitskritische Fehler in Software nicht einfach ver-
schwinden: Exploitable software aws produced by software developers will not cease to
exist. ([Vallentin2007], S.18)
Buer-Overow-Schwachstellen entstehen durch simple - oft marginale - Fehler in
Software-Algorithmen. Es ist für Programmierer durchaus gebräuchlich, einen Puer mit
einer frei gewählten Gröÿe anzulegen - im guten Glauben, dass niemals mehr Speicher
in diesem Puer benötigt wird. Weiters wird meist die Überschreitung der maximalen
Gröÿe nicht explizit behandelt ([RussellCunningham2000], S.204). Diese Vorgangsweise
kann unter Anderem dazu führen, dass Angrie durch Puerüberläufe möglich werden.
Ein Angreifer kann etwa unberechtigt die Kontrolle über Systeme übernehmen oder ein
verwundbares Service manipulieren.
Die verfügbaren statischen Werkzeuge zum Aufspüren von Schwachstellen neigen aller-
dings einerseits zu false-positives, also falschen Treern, andererseits ist es diesen Tools
oft nicht möglich, ein Risiko bei Überlagerung von Speicherstrukturen und komplexer
Pointer-Arithmetik zu erkennen ([Johns2005], S.10).
Schaden von Buer-Overow-Attacken dynamisch begrenzen zu können, ist das Ziel von
anderen - heute weit verbreiteten - Techniken. Es wird versucht, allen heute bekannten
Angris-Techniken auf Buer-Overow-Schwachstellen mit unterschiedlichen Vorgehens-
weisen entgegenzuwirken und damit erfolgreiche Angrie zu verhindern ([Johns2005],
S.43f ). Wie unter anderem Klein ([Klein2003a], S.9, S.187f ) beschreibt, können diese
Technologien Attacken nicht immer unterbinden, jedoch sind sie in der Lage, deren Aus-
wirkung einzudämmen.
Dynamische Ansätze wurden in der Vergangenheit stark weiterentwickelt, um Systeme
vor einer Veränderung der Programm-Algorithmen und vor der Ausführung von frem-
dem Code zu schützen, und daher auf Angrie zur Laufzeit reagieren zu können. Dabei
kann sich jedoch die Reaktion auf einen Puerüberlauf auf einen Programm-Abbruch
beschränken, was einem Ausfall des Programms gleichkommt.
Eine Charakteristik von Buer-Overows ist, dass Daten eines Prozesses überschrieben
und Information zerstört wird, die für den gewünschten Programm-Ablauf notwendig ist.
Durch diesen Umstand bleiben wenige Möglichkeiten, das betroene Programm regulär
fortsetzen zu können, selbst wenn der Puerüberlauf bereits während der Ausführung
erkannt wird. Daher untersucht diese Arbeit gebräuchliche dynamische Technologien auf
Ausfallsicherheit bzw. das Verhalten bei verschiedenen Generationen von Puerüberläu-
fen.

2
2.2. SPEICHERMANAGEMENT

2.2. Speichermanagement

2.2.1. Speicherorganisation von Prozessen


Das Verständnis der Organisation des Speicherbereichs von Prozessen ist essentiell für
die vorgestellten Buer-Overow-Schwachstellen. Ein Prozess ist unter UNIX und vie-
len anderen Betriebssystemen in sogenannte Segmente (engl. segments ) unterteilt, die
zur Speicherung unterschiedlicher Daten innerhalb eines Prozesses verwendet werden
([Vallentin2007], S.3). Das grundlegende Speichermodell von UNIX-Prozessen in einer
x86-Umgebung ist in Abbildung 2.1 ersichtlich. Andere Betriebssysteme und Architek-
turen verwalten den Prozess-Speicher sehr ähnlich, weshalb in dieser Arbeit beispielhaft
nur auf UNIX-Prozesse in der genannten Architektur eingegangen wird.

hohe Speicheradressen

Stack

Heap
BSS Segment

Data Segment

Text Segment

niedere Speicheradressen

Abbildung 2.1.: Speichermodell eines UNIX-Prozesses ([Viking2006], S.5)

Hierzu muss allerdings angemerkt werden, dass verschiedene Compiler das Speicher-
modell in erweiterter oder abgeänderter Form implementieren und daher dieses Modell
nur zur prinzipiellen Veranschaulichung dient.
Die gezeigten Segmente haben folgende Aufgaben:

• Text Segment
Das Text-Segment beinhaltet den ausführbaren Programmcode und kann standard-
mäÿig zur Laufzeit nicht verändert werden (read-only).

• Data Segment
Im Data-Segment benden sich initialisierte globale Daten.

3
2.2. SPEICHERMANAGEMENT

• BSS (Block Started by Symbol) Segment


Globale Daten, die nicht initialisiert werden, benden sich im BSS-Segment eines
Prozesses.

• Heap-Segment
Das Heap-Segment (kurz Heap) wird dazu verwendet, um dynamisch allozierte
Variablen abzulegen. Dieses Segment wächst in seiner Gröÿe hin zu den höheren
Speicheradressen. (siehe Abbildung 2.1)

• Stack-Segment
Werden lokale Variablen deniert, einer Funktion Parameter übergeben oder Re-
gister während eines Funktionsaufrufs gesichert, so werden diese Daten auf dem
Stack-Segment (kurz Stack) gespeichert. Der Stack ist mit den typischen Befeh-
len push und pop ein FILO-Puer (First-In Last-Out) und wächst hin zu den
niederen Speicheradressen.
([Viking2006], S.5)

Die Korrelation zwischen Variablen in einem Programm und den einzelnen Segmenten
ist in Abbildung 2.2 illustriert.

0 #include <stdlib.h>
1
2 int g_counter = 0; // g_counter liegt im Data Segment
3 int g_current_char; // g_current_char liegt im BSS Segment
4
5 void main(void)
6 {
7 static char idx; // idx liegt im BSS Segment
7 int tmp = 1; // tmp liegt im Stack Segment
8 void* buffer = malloc(24); // buffer zeigt auf das Heap Segment
9
10 g_counter ++; // Befehle liegen im Text Segment
11 }

Abbildung 2.2.: Prozess-Segmente in einem C-Programm ([Viking2006], S.6)

2.2.2. Speicherorganisation bei Unterprogramm-Aufrufen


Bei der bisherigen Betrachtung der Segmente fehlt noch eine detailliertere Betrachtung
von Unterprogramm-Aufrufen, da diese für einige Buer-Overow-Angrie von Bedeu-
tung sind.
Als Beispiel dient der Unterprogramm-Aufruf (Zeile 12) im C-Programm in Abbildung
2.3.

4
2.2. SPEICHERMANAGEMENT

0 #include <stdio.h>
1
2 int mult(int a, int b)
3 {
4 int x;
5 char buffer[2];
6 x = a * b;
7 return x;
8 }
9
10 void main(int argc, char ** argv)
11 {
12 int res;
13 res = mult(3, 7);
14 }

Abbildung 2.3.: Unterprogramm-Aufruf in einem C-Programm

Bevor die Ausführung des Unterprogramms mult() erfolgt, werden folgende Daten auf
den Stack geschrieben:

• Argumente
(im Beispiel die Werte 3 und 7)
Die Argumente werden dem Unterprogramm übergeben, indem sie auf den Stack
geschrieben werden.

• Rücksprungadresse - engl. Return-Instruction-Pointer RIP


(im Beispiel die Adresse des Aufrufs in Zeile 12)
Es wird die Speicher-Adresse auf den Stack geschrieben, nach der die Ausführung
nach Abschluss des Unterprogramms fortgesetzt werden soll. Der RIP stellt bei
der ersten Generation von Buer-Overows den Haupt-Angrispunkt dar (siehe
Kapitel 2.3.2).

• Vorhergehender Stackframe-Zeiger - engl. Stack-Frame-Pointer SFP


Mit dem Begri Stackframe wird jener Bereich innerhalb des Stack-Segments be-
zeichnet, der von einer Funktion bzw. Prozedur genutzt wird. Ein SFP deniert
den Startpunkt eines Stack-Frames. (siehe Abbildung 2.4)
([BryantO'Hallaron2003] 2003, S.170 )

In Abbildung 2.4 wird der Stack vor und während der Ausführung des Unterprogramms
mult() illustriert. Dabei ist die Reihenfolge ersichtlich, in der Daten auf dem Stack liegen.
Der Stack wird dabei hin zu den niedrigen Speicheradressen gefüllt, die Variablen wachsen
im Gegensatz dazu hin zu den hohen Speicheradressen.

5
2.3. BUFFER-OVERFLOW-ANGRIFFE

a) b) hohe Speicheradressen
Argument: *argv Argument: *argv
Argument: argc Argument: argc
"RIP" "RIP"

Stackframe
von main()
vorhergehender "SFP" vorhergehender "SFP"

Stackframe
lokale Variable: res lokale Variable: res

von main()
Argument: "7"
Argument: "3"
"RIP"
"SFP" von main()

Stackframe
von mult()
lokale Variable: b[1]
lokale Variable: b[0]
lokale Variable: x

niedere Speicheradressen

Abbildung 2.4.: Stack-Segment eines UNIX-Prozesses bei einem Funktionsaufruf (a) vor
dem Funktionsaufruf von mult() (b) bei der Ausführung von mult()
([BryantO'Hallaron2003], S.170f )

2.3. Buer-Overow-Angrie

2.3.1. Einteilung von Buer-Overow-Angrien


Ein Angri auf eine Puerüberlauf-Schwachstelle kann nach der Art des Schadenspoten-
tials klassiziert werden:

• Denial-of-Service (DoS)
Das Ziel eines DoS-Angris ist, dass das Zielservice nicht mehr zur Verfügung
steht. Die Durchführung von DoS-Attacken bei Buer-Overows ist meist trivial.
([Klein2003a], S.49)

• Ausführung von vorhandenem Programmcode mit spezischen Argu-


menten oder zu einem spezischen Zeitpunkt
Hierbei wird der Programmuss (Algorithmus) eines Programms oder die Argumen-
te für die Ausführung von vorhandenen Routinen geändert [Klein2003a], S.52-61).
Dabei ist meist das Ziel eines Angreifers, den Zugri auf eine Kommandozeile mit
den Berechtigungen des attackierten Programms zu erhalten (Vgl. etwa return-to-
libc, [Nergal2001] 2001).

• Einschleusen von fremdem Programmcode


Kann ein beliebiger Binärcode in den Algorithmus integriert werden, so können de-
nierte Kommandos mit den Berechtigungen des angegrienen Dienstes ausgeführt
werden ([Klein2003a], S.61 ).

6
2.3. BUFFER-OVERFLOW-ANGRIFFE

2.3.2. Buer-Overow-Angrie auf den RIP


Angrie auf stackbasierte Puerüberläufe werden oft als erste Generation von Buer-
Overow-Angrien bezeichnet, da es sich um eine der ältesten Attacken handelt
([Klein2003a], 18f ).
In Abbildung 2.5 ist ein C-Programm illustriert, das die Funktion strcpy() benutzt, um
das erste Kommandozeilen-Argument in eine lokale Variable zu kopieren und danach
diese Variable ausgibt.

0 #include <string.h>
1 #include <stdio.h>
2
3 void argcpy(char * ar)
4 {
5 char buffer[11];
6 strcpy(buffer, ar);
7 printf("%s\n", buffer);
8 }
9
10 int main(int argc, char ** argv)
11 {
12 argcpy(argv[1]);
13 printf ("finish\n");
14 return 0;
15 }

Abbildung 2.5.: C-Programm mit einer Puerüberlauf-Schwachstelle

Wie in Zeile 5 ersichtlich ist, hat die lokale Variable buer eine Gröÿe von 11 Bytes
- daher ist bei einem String Platz für 10 Zeichen plus das terminierende NULL-Byte.
Die Funktion strcpy() (Zeile 6) kopiert nun den Inhalt des Arguments ar in die lokale
Variable buer, ohne dessen maximale Gröÿe zu berücksichtigen.

a) [work@localhost ~]$ ./argcpy 1234567890


1234567890
finish

b) [work@localhost ~]$ ./argcpy 12345678901234567890


12345678901234567890
Segmentation fault

Abbildung 2.6.: Aufruf des Programms aus Abb. 2.5 (a) Normale Ausführung (b)
Puerüberlauf

7
2.3. BUFFER-OVERFLOW-ANGRIFFE

In Abbildung 2.6b ist das Ergebnis eines Puerüberlaufs zu sehen, der zur Kategorie
der DoS-Angrie gehört, denn das Programm wird durch den Buer-Overow auÿerplan-
mäÿig beendet ([Klein2003a], S.49). Die entsprechenden Ausschnitte des Stacks werden
in Abbildung 2.7b gezeigt. Hierbei ist ebenso ersichtlich, weshalb man bei derartigen
Attacken von Stack-Zertrümmerung (engl. stack-smashing) spricht: Es werden Bereiche
des Stack-Segments mit beliebigen Werten überschrieben - dadurch benden sich anstelle
von sorgfältig gewählten Zeigern (RIP und vorhergehender SFP), ASCII-Zeichen auf dem
Stack ([AlephOne1996]).

a) b) ’6’ c)
’5’
Argument: *ar ’4’ NULL
"RIP" ’3’ "RIP"
manipulierter "RIP"
vorhergehender "SFP" ’2’ manipulierter "SFP"
NULL ’1’ ’1’
’0’ ’0’ ’0’
’9’ ’9’ ’9’
Stackframe

Stackframe

Stackframe
’8’ ’8’ ’8’
’7’ ’7’ ’7’
’6’ ’6’ ’6’
’5’ ’5’ ’5’
’4’ ’4’ ’4’
’3’ ’3’ ’3’
’2’ ’2’ ’2’
’1’ ’1’ ’1’

Abbildung 2.7.: Stack-Segment bei den Aufrufen aus Abb. 2.6 - (a) ohne Überlauf (b)
mit willkürlichem Überlauf (c) mit gezieltem Überlauf

Durch den Umstand, dass man mit einem stackbasierten Buer-Overow den RIP
und den gesicherten SFP überschreiben kann, ist es möglich, diese zwei Pointer mit
präparierten Werten zu versehen
1 (siehe Abbildung 2.7c). Beispielsweise kann man den

RIP mit der Adresse einer bestimmten Funktion überschreiben, was zu der Ausführung
dieser Funktion nach dem Abschluss der aktuellen Prozedur führt. Die Manipulation
des SFP wird hier nicht behandelt, da dieser das Kapitel 2.3.3 gewidmet ist.
Hierbei gibt es grundsätzlich drei Möglichkeiten, das Programm zu manipulieren, wie
Abbildung 2.8 zeigt. Der RIP kann so manipuliert werden, dass eine bereits vorhandene
Prozedur nach Beendigung der Aktuellen ausgeführt wird (Abbildung 2.8a). Unter
diversen UNIX-Betriebssystemen kann man hiermit beispielsweise Prozeduren der
libc-Programmbibliothek ausführen. Diese Angristechnik wird in der Literatur oft als
return-to-libc oder kurz ret2libc bezeichnet ([Klein2003a], S.405).

1
Eine gleichzeitige und direkte Manipulation von SFP und RIP bei der strcpy() -Funktion ist auf man-
chen Systemen schwierig, da keine (führenden) NULL-Bytes bei Adressen verwendet werden können.
NULL-Bytes terminieren Strings - daher kopiert die strcpy() -Funktion den übergebenen String nur
bis zum ersten NULL-Byte.

8
2.3. BUFFER-OVERFLOW-ANGRIFFE

Da bei einem Angri auf eine Buer-Overow-Schwachstelle typischerweise beliebige


Daten auf den Stack geschrieben werden können, ist es auch möglich, beliebigen
Programmcode dort einzuschleusen. Wird nun der RIP so gesetzt, dass dieser auf den
zuvor eingefügten Code zeigt, so werden die vom Angreifer eingefügten Befehle nach Be-
endigung des aktuellen Unterprogramms ausgeführt (Abbildung 2.8b). Jedoch ist hierbei
zu beachten, dass nicht alle Betriebssysteme einen sogenannten ausführbaren Stack
(engl. executeable stack ) besitzen, und daher ein Programmcode im Stack-Segment nicht
ausgeführt werden kann ([Leidecker2006], S.76).

a) b) c)
STACK-SEGMENT

STACK-SEGMENT

STACK-SEGMENT
Programm-Code
Programm-Code
Programm-Code
manipulierter "RIP"
"RIP" Programm-Code NOP
’2’ Programm-Code NOP
’1’ Programm-Code NOP
Programm-Code
Programm-Code
TEXT-SEGMENT

Programm-Code NOP
Programm-Code NOP
Programm-Code NOP
"RIP"
manipulierter "RIP" manipulierter "RIP"
"RIP"
’2’ ’2’
Prozedur
’1’ ’1’

Abbildung 2.8.: Angris-Typen bei gezielter Manipulation des RIP (a) Ausführung von
bereits vorhandenem Programmcode (b) Einschleusen und Ausführung
von beliebigem Programmcode (c) Angri mit NOP-Sliding

Es ist oft schwierig, den RIP bei letzteren Attacken auf die exakte Start-Adresse des
eingeschleusten Codes zu setzen. Daher gibt es eine hilfreiche Technik für derartige
Attacken - das NOP Sliding. Eine NOP2 -Anweisung selbst hat keine Auswirkungen auf
ein Programm, auÿer dass im nächsten CPU-Takt sofort die nachfolgende Anweisung
ausgeführt wird ([Leidecker2006], S.41). Diese Eigenschaft wird nun dazu verwendet,
um einen Bereich auf dem Stack, ein NOP Slide, zu erstellen, der unmittelbar an den
eingeschleusten Code anschlieÿt und in den der RIP höchstwahrscheinlich zeigt. Wird
nun ein beliebige NOP-Anweisung innerhalb dieses Bereichs angesprungen, so werden die
NOPs ausgeführt, bis der Programmcode erreicht ist.

2
Die Abkürzung NOP steht hier für den Assembler-Befehl No OPeration

9
2.3. BUFFER-OVERFLOW-ANGRIFFE

2.3.3. Buer-Overow-Angrie auf den SFP


Bei dem - oftmals als zweite Generation von Buer-Overow-Attacken bezeichnetem -
gezielten Überschreiben des SFP geht es meist um die indirekte Manipulation eines RIP
([Klein2003a], S.95f ). In der Literatur wird oft der englische Begri SFP-Overwrite für
diese Angristechnik verwendet.
In dem Programm in Abbildung 2.9 sehen wir einen weit verbreiteten Programmierfehler
in Zeile 8: Das Array buer hat eine Gröÿe von 10 Elementen (Index 0 bis Index 9).
Allerdings kopieren wir 11 Elemente in diesen Puer (Index 0 bis Index 10), was zu einem
Puerüberlauf führen kann. Diese Art von Fehler wird oft als o-by-one-Schwachstelle
bezeichnet ([Klein2003a], S.96f ). Der Stack während der Ausführung von argcpy() ist in
Abbildung 2.10a illustriert.

0 #include <string.h>
1 #include <stdio.h>
2
3 void argcpy(char * arg)
4 {
5 char buffer[10];
6 int i = 0;
7
8 for(i=0;i<=10;i++)
9 buffer[i] = arg[i];
10 }
11
12 void do_copy(char ** argv)
13 {
14 argcpy(argv[1]);
15 }
16
17 int main(int argc, char ** argv)
18 {
19 do_copy(argv);
20 printf("finish\n");
21 return 0;
22 }

Abbildung 2.9.: C-Programm mit einer SFP-Overwrite -Schwachstelle

Auf einer 32 Bit Plattform ist sowohl der RIP als auch der SFP 4 Byte groÿ - bei 64
Bit sind es entsprechend 8 Byte ([Leidecker2006], S.13f; [Klein2003a], S.21). Mit einer
typischen o-by-one-Schwachstelle (wie in Abb. 2.9) lässt sich nun das letzte Byte des
gesicherten SFP überschreiben, wodurch sich dieser Zeiger um maximal 255 Bytes ver-
schieben lässt.
Dies kann ausreichen, um den SFP so zu verschieben, dass dieser auf ein gefälschtes
Stack-Frame mit zugehörigem RIP zeigt (Abbildung 2.10b). Beim Verlassen der aktuel-
len Prozedur argcpy() wird das manipulierte Stack-Frame als ursprüngliches Stack-Frame

10
2.3. BUFFER-OVERFLOW-ANGRIFFE

betrachtet und äquivalent der manipulierte RIP als ursprünglicher RIP. Beim Verlassen
der vorhergehenden Prozedur do_copy() wird die Ausführung unmittelbar bei der Posi-
tion des manipulierten RIP fortgesetzt.
Es ist jedoch unbedingt zu beachten, dass für das Einschleusen von beliebigem Pro-
grammcode auch hier ein ausführbarer Stack benötigt wird.

a) b)

Argument: *argv Argument: *argv


Argument: argc Argument: argc
"RIP" "RIP"
vorhergehender "SFP" vorhergehender "SFP"
Argument: *argv Stackframe Argument: *argv Stackframe
von main() von main()
"RIP" von main() "RIP" von main()
"SFP" von main() "SFP" von main()
Argument: argv[1] Stackframe Argument: argv[1] Stackframe
von do_copy() von do_copy()
"RIP" von do_copy() "RIP" von do_copy()
"SFP" von do_copy() manipulierter "SFP"
Stackframe
lokale Variable: buffer Stackframe frei definierter "RIP" von argcpy()
von argcpy()
lokale Variable: i lokale Variable: i
manipuliertes
Stackframe

Abbildung 2.10.: Stack während der Ausführung von dem Programm aus Abb. 2.9 (a)
während der normalen Ausführung von argcpy() (b) SFP-Overwrite
während argcpy()

Trivialerweise kann eine gezielte Manipulation des SFP auch dazu benutzt werden, den
Programmuss maÿgeblich zu beeinussen, ohne einen Programmcode einzuschleusen.
Dies wird etwa durch die gezielte Konstruktion eines Stackframes erreicht, das lokale
Variablen auf gezielte Werte setzt ([Klein2003a], S.126).

2.3.4. Buer-Overow-Angrie auf Variablen


Es ist nicht immer eine direkte Manipulation des RIP möglich oder gewünscht. Puf-
ferüberläufe können ebenso dazu benutzt werden, verschiedene Variablen (in jeglichen
Segmenten) zu manipulieren und so den Algorithmus zu beeinussen. Besonders
interessante Angrisvektoren stellen hier function-pointer (Zeiger auf Prozeduren)
und le-pointer (Zeiger auf Dateien) dar. Erstere werden gerne dazu benutzt, einen
gewünschten Programmcode gezielt auszuführen, letztere, um mit den Berechtigungen
des angegrienen Prozesses Dateien zu lesen oder zu schreiben. Aber auch mit be-
liebigen anderen Variablen ist es möglich, methodisch den Algorithmus zu verändern
([Klein2003a], S.132).
Die in Kapitel 2.3.2 behandelten stackbasierten Overows können natürlich nicht nur
dazu verwendet werden, den RIP bzw. SFP zu manipulieren, sondern auch lokale
Variablen zu überschreiben.

11
2.3. BUFFER-OVERFLOW-ANGRIFFE

0 #include <string.h>
1 #include <stdio.h>
2
3 int (*fct)(int);
4 char buffer[11];
5
6 int add10(int j)
7 {
8 return (j + 10);
9 }
10 void argcpy(char * ar)
11 {
12 strcpy(buffer, ar);
13 printf("%s\n", buffer);
14 }
15 int main(int argc, char ** argv)
16 {
17 int k = 0;
18 fct = &add10; // setze function-pointer
19 argcpy(argv[1]);
20 k = fct(k); // führe function-pointer aus
21 }

Abbildung 2.11.: C-Programm mit einer Puerüberlauf-Schwachstelle im BSS-Segment

Im Programm in Abbildung 2.11 ist in Zeile 3 ein function-pointer deniert, der


über einen Puerüberlauf in der Variablen buer manipuliert werden kann. Der
Buer-Overow wird durch die bereits aus Kapitel 2.3.2 bekannten Funktion argcpy()
ausgelöst. Ein Ausschnitt des Stack- und des BSS-Segments während der Ausführung
dieser Funktion ist in Abbildung 2.12 dargestellt.
Äquivalent zu den stackbasierten Puerüberläufen schreibt die strcpy() -Funktion über
die Grenzen der Variablen buer hinaus. Folglich ist es hier durch die Übergabe
eines längeren Arguments möglich, den function-pointer mit einem beliebigen Wert zu
überschreiben.
Mit dieser Technik ist es einem Angreifer natürlich möglich, jegliche Variablen, die an
höheren Speicheradressen liegen, als die Variable mit der Buer-Overow-Schwachstelle
zu überschreiben. Zur Kompromittierung des Systems sind neben function-pointer -
Variablen auch File- oder Socket-Deskriptoren besonders geeignet.

2.3.5. Speicherverwaltungsbasierte Buer-Overow-Angrie im


Heap-Segment
Die hier beschriebene, etwa im Text once upon a free() ([Anon2001]) reichlich illustrierte
Heap-basierte Angristechnik, stellt im Prinzip nur eine indirekte Manipulation von Spei-

12
2.3. BUFFER-OVERFLOW-ANGRIFFE

*fct
buffer[10]

BSS-SEGMENT
buffer[9]

buffer[2]
"RIP"
buffer[1]
buffer[0]

TEXT-SEGMENT

Prozedur

Abbildung 2.12.: Stack während der Ausführung von dem Programm aus Abb. 2.11

cherstellen dar. Diese indirekte Veränderung wird aufgrund des Speicher-Layouts in ei-
nigen Heap-Implementierungen ermöglicht. Bei der Implementierung des Heap-Speichers
von Lea, die auch in einer alten GNU C-Bibliothek verwendet wird, werden beispiels-
weise mit free() freigegebene Blöcke als doppelt verkettete Liste im Speicher markiert.
([Lea2005], Zeile 1482  )
Gelingt es nun diese Management-Information durch einen gezielten Puerüberlauf zu
überschreiben, so ist es unter bestimmten Umständen möglich, beim Aufruf der Funktion
free() Schreiboperationen auf eine beliebige Speicherstelle (etwa einen RIP) auszuführen.
In Abbildung 2.13 ist zu sehen, wie der Heap unter Verwendung der malloc-
Implementation von Lea ([Lea2005]) strukturiert ist. Der Header besteht dabei aus drei
wichtigen Komponenten:

• Gröÿe des vorhergehenden Chunks


Wichtig, um den Header des vorhergehenden Chunks zu nden.

• Gröÿe des eigenen Chunks


Wird benötigt, um den Header des nächsten Chunks zu nden.

• previous chunk in-use-Bit


Dieses Bit gibt an, ob der vorhergehende Block frei oder belegt ist. Dies ist wichtig
zur Defragmentierung während einem free() -Befehl.

Diese Angristechnik ist seit einigen Jahren bei der GNU C Library nur mehr teilweise
möglich, da heutzutage nur mehr sehr wenig Information in den Header-Bereichen liegt
und diese auch auf Konsistenz überwacht wird. Für die in dieser Arbeit verwendeten
DoS-Attacken ist ein Programm mit einer Heap-Overow-Schwachstelle allerdings noch
immer anfällig, weshalb diese Technik hier ausführlicher beschrieben wird.
Mit dem Aufruf der Funktion malloc() wird im nächsten freien Block, der eine entspre-
chende Gröÿe hat, ein neuer Chunk angelegt und der Pointer auf den Daten-Bereich des

13
2.3. BUFFER-OVERFLOW-ANGRIFFE

vorherige Chunk−Größe PREV_IN_USE Bit


Chunk−Größe 1

Daten−Bereich

vorherige Chunk−Größe
Header
Zeiger auf den Chunk−Größe 1
allokierten Speicher
Chunk
Daten−Bereich

vorherige Chunk−Größe
Chunk−Größe 1

Daten−Bereich

Abbildung 2.13.: Struktur des Heap bei Verwendung der Implementation der GNU C-
Library von Lea ([Lea2005], Zeile 1484 - 1530)

(a) (b) (c)

vorherige Chunk−Größe vorherige Chunk−Größe vorherige Chunk−Größe


Chunk−Größe 1 Chunk−Größe 1 Chunk−Größe 1
vorheriger freier Chunk
Daten−Bereich Daten−Bereich
feigegebener Chunk

nächster freier Chunk

vorherige Chunk−Größe vorherige Chunk−Größe


feigegebener Chunk

Chunk−Größe 1 Chunk−Größe 1
vorheriger freier Chunk vorheriger freier Chunk
feigegebener Chunk

nächster freier Chunk nächster freier Chunk

vorherige Chunk−Größe vorherige Chunk−Größe


Chunk−Größe 0 Chunk−Größe 0

Daten−Bereich Daten−Bereich

Abbildung 2.14.: (a) Freigabe eines einzelnen Chunks (b) Konsolidierung mit nachfolgen-
dem freien Chunk (c) Konsolidierung mit vorhergehendem freien Chunk
([Lea2005], Zeile 1530 )

14
2.3. BUFFER-OVERFLOW-ANGRIFFE

Chunks zurückgeliefert (Abbildung 2.13). Wird nun ein Chunk mit der Funktion free()
freigegeben, so werden im Daten-Bereich zwei Zeiger (in der Literatur in Anlehnung an
[Lea2005] oft fd und bk genannt) abgelegt, die für die doppelt verkettete Liste von freien
Chunks verwendet werden (Abbildung 2.14a).
Bendet sich vor oder nach dem Chunk ein freier Chunk, so wird dieser mit dem
aktuellen in einen gröÿeren freien Chunk konsolidiert (Abbildung 2.14b,c), der sich
wiederum richtig in die doppelt verkettete Liste integrieren muss (Abbildung 2.15a).

(a) (b)

vorherige Chunk−Größe vorherige Chunk−Größe


Chunk−Größe 1 Chunk−Größe 1
vorheriger freier Chunk
nächster freier Chunk
frei definierter
vorherige Chunk−Größe Programmcode
Chunk−Größe 0
Buffer gewählte Zahl
Overflow gewählte Zahl 1
Daten−Bereich
manipulierter Zeiger
manipulierter Zeiger

überschreibt RIP
vorherige Chunk−Größe vorherige Chunk−Größe
Chunk−Größe 1 Chunk−Größe 0
vorheriger freier Chunk
Daten−Bereich
nächster freier Chunk

Abbildung 2.15.: (a) doppelte Verkettung von zwei freien Speicherblöcken (b) Beispiel
für einen gezielten Buer-Overow im Heap-Segment mit Einschleusen
von deniertem Programmcode

Die Eigenschaft, dass bei dieser Heap-Implementation sich der Header im gleichen
Speicherbereich mit den Daten bendet, kann dazu verwendet werden, über einen Puer-
überlauf gezielt manipulierte Header-Informationen einzuschleusen. Ein Angrisvektor
ist hier die Anpassung der doppelt-verketteten Liste bei der Konsolidierung mit einem
anderen freien Chunk. Hierbei müssen zwei Schreiboperationen ausgeführt werden,
um den neu entstandenen, gröÿeren freien Speicherblock in die doppelt verkettete
Liste einzufügen. Kann man nun den fd - und bk -Zeiger durch den Buer-Overow
manipulieren, so überschreibt die Konsolidierung die angegebenen Speicherstellen.
Abbildung 2.15b zeigt ein Beispiel eines Heap-Buer-Overows: Der Puerüberlauf
des ersten Chunks wird einerseits dazu verwendet, den Header des zweiten Chunks zu
manipulieren, andererseits wird auf diese Weise ein Programmcode eingeschleust, der
später zur Ausführung gebracht werden kann.

15
3. Dynamische Methoden zur
Verhinderung von
Buer-Overow-Angrien
Im Gegensatz zu statischen Methoden (etwa Source-Code-Analyse) wirken dynamische
Methoden direkt während der Laufzeit eines Programms. Dabei kann es erforderlich
sein, den Programmcode inklusive der dynamischen Schutz-Algorithmen neu zu kompi-
lieren oder ohne Neuübersetzung dem Betriebssystem die Kontroll- bzw. Schutzaufgabe
zu überlassen.
In den folgenden Punkten sollen dynamische Methoden vorgestellt werden, die häug in
der Praxis Verwendung nden. Beispielsweise sind unter anderem folgende Werkzeuge
derzeit verfügbar:

• PaX
http://pax.grsecurity.net/
• grsecurity
http://www.grsecurity.net/
• ProPolice / Stack-Smashing-Protector SSP
http://www.trl.ibm.com/projects/security/ssp/
• OpenWall Owl
http://www.openwall.com/Owl/
• R)
Betriebssystems-Funktionen (Linux, OpenBSD, Microsoft Windows

3.1. Nicht-ausführbare Speicherbereiche

Eine einfache Möglichkeit, die Ausführung von fremdem Code zu verhindern ist,
bestimmte Speicherbereiche, die im normalen Programmuss nur zur Speicherung von
Daten dienen, als nicht-ausführbar zu kennzeichnen. Dies zielt im Besonderen auf das
Einschleusen von fremdem Programmcode ab (vgl. 2.3.1), wo Befehle beispielsweise in
den Stack- oder Heap-Bereich geschrieben und dort ausgeführt werden.
Sowohl der Stack- als auch der Heap-Bereich werden für die Speicherung von Daten
verwendet. Entzieht man diesen Bereichen die Berechtigung, Code auszuführen, so sollte
es - der Theorie nach - zu keiner Einschränkung kommen. In der Praxis sieht man
allerdings, dass bei einigen Programmen eine dynamische Code-Generierung in einem
der beiden Segmente vorausgesetzt wird. So verwendet etwa der Compiler GCC eine
Technik Trampolines, die einen ausführbaren Stack benötigt ([Leidecker2006]).
In der Literatur werden nicht-ausführbare Speicherbereiche oft mit dem Namen der
OpenBSD-Technik W xor X (Write eXclusive-OR eXecute) bezeichnet, was die Trennung

16
3.1. NICHT-AUSFÜHRBARE SPEICHERBEREICHE

von beschreibbaren und ausführbaren Speicherbereichen symbolisiert ([Buehler2006],


S.9). Kann man ausführbare Segmente nicht beschreiben und beschreibbare Segmente
nicht ausführen, ist es in erster Linie nicht möglich, benutzerdenierte Befehle auszu-
führen.
Eine Hardware-Unterstützung von nicht-ausführbaren Speicherbereichen wird durch das
NX -Bit garantiert. Hierbei muss die Granularität der Einschränkung der Ausführbarkeit
betrachtet werden, denn das NX -Bit legt die Nicht-Ausführbarkeit auf der Ebene von
Speicherpages fest. Dadurch können z.B. nur bestimmte Speicherbereiche eines Segments
explizit zur Ausführung gesperrt werden ([Raiu2006], S.10f ).
Die Ausführung von Speicherbereichen zu limitieren wird derzeit bereits als Standard
in vielen Betriebssystemen eingesetzt - in OpenBSD ab Version 3.3 (W xor X ) oder
R (Windows) ab Version XP Service Pack 2 (Data Execution
in Microsoft Windows
Prevention, kurz DEP ). Diese Funktionalität kann auch bei Abwesenheit durch spezielle
Programme nachgerüstet werden. Alle betrachteten Sicherheits-Tools setzen ebenfalls
auf verschiedene Weise eine derartige Technik ein, um das Risiko der Ausführung von
fremdem Code zu minimieren.
Jedoch wird zur Verhinderung eines erfolgreichen Puerüberlaufs bei allen Betriebssys-
temen und Tools das Programm beim Ausführen von Programmcode innerhalb eines
geschützten Speicherbereichs abgebrochen. Abbildung 3.1 illustriert dieses Verhalten bei
der DEP unter einem System nach der Spezikation im Anhang A.1.

Abbildung 3.1.: Ein Programm, das die Ausführungssperre überschreitet wird von der
Data-Execution-Prevention beendet (Windows)

In Abbbildung 3.2 ist das beschriebene Verhalten des Programms aus Anhang B beim
Versuch, eingeschleusten Programmcode auszuführen, zu sehen. Das System entspricht
der Spezikation im Anhang A.3.

[work@localhost ~]$ ./bo_stack_exploit


*** buffer overflow detected ***: /home/work/bo_stack_exploit terminated

Abbildung 3.2.: Nach dem Puerüberlauf wird zum Schutz vor dem eingeschleusten Pro-
grammcode das Programm abgebrochen

17
3.2. CANARY-VALUES

3.2. Canary-Values1

Wie bereits diskutiert, zielen viele Buer-Overow-Angrie auf das Überschreiben des
Return-Instruction-Pointers (RIP) ab, um den Algorithmus maÿgeblich zu verändern.
Daher wurde eine Technik entwickelt, um eine Manipulation dieses Werts erkennen zu
können, indem zwischen dem Stackframe einer Prozedur und dem RIP ein überwachter
Wert gespeichert wird. Dieser Wert wird meist als Canary-Value bezeichnet.
Will man durch einen klassischen, stack-basierten Puerüberlauf den RIP überschreiben,
so muss auch der Canary-Value überschrieben werden. ([Cowan1999], S.2f )
Wie in Abbildung 3.3 zu sehen ist, bildet der Canary-Value eine Grenze zwischen den
lokal verfügbaren Daten (lokale Variablen) und dem RIP sowie den Argumenten. Über-
schreibt nun der Puerüberlauf den Canary-Value mit einem beliebigen Wert, so kann
diese Wertänderung vor dem Rücksprung detektiert und entsprechend behandelt werden.
([Klein2003a], S.306)

Argument
"RIP"
Canary-Value
vorhergehender "SFP"
lokale Variable
Stackframe

lokale Variable
lokale Variable
lokale Variable
lokale Variable

Abbildung 3.3.: Canary-Value auf dem Stack ([Klein2003a], S.307)

Sollte es möglich sein, den Canary-Value mit dem korrekten Wert während des Buer-
Overow Angris zu überschreiben, so würde ein erfolgreicher Angri nicht erkannt wer-
den ([Klein2003a], S.311). Deshalb wird typischerweise für eine Canary-Value einer der
folgenden Werte gewählt:

• ein zum Programmstart zufällig gewählter Wert


Die Wahrscheinlichkeit, dass dieser erraten wird, ist bei einem entsprechend groÿen
Wert vernachlässigbar. Bei indirektem Schreiben auf den RIP bzw. den SFP bleibt
der Canary-Value in diesem Fall allerdings intakt (vgl. [?]).

• ein zum Programmstart zufällig gewählter Wert, XOR-verknüpft mit


dem RIP
Die Bindung des Werts an die Rücksprungadresse erkennt auch eine indirekte Ma-
nipulation des RIP.

1
Die Bezeichnung Canary-Value (Kanarienvogel-Wert) geht laut [Cowan1999] auf die Kanarienvögel
von Bergarbeitern zurück. Die Vögel starben bei gefährlichen Methankonzentrationen in den Stollen,
was wiederum den Arbeitern unmittelbare Gefahr signalisierte.

18
3.3. EXPLIZITE ERKENNUNG VON SPEICHERGRENZEN

• eine Sammlung von Terminatoren


Hierbei werden alle üblichen End-Zeichen - z.B. NULL, Newline, Tabulator - als
Canary-Value verwendet, da diese typischerweise Eingaben terminieren, daher nicht
in einen Puer übernommen werden und folglich auch nicht geschrieben werden
können. Jedoch bleibt diese Canary-Value bei indirektem Schreiben ebenfalls un-
verändert und intakt.
([Klein2003a], S.311f )

Die Granularität einer Canary-Value


2 reicht nicht aus, um auf eine spezische

Beschädigung des Prozess-Speichers schlieÿen zu können. Wird beispielsweise der


RIP zusätzlich an einer anderen Speicherstelle gesichert und bei einer veränderten
Canary-Value zurückgeschrieben, so kann zwar eine Veränderung des RIP kompensiert
werden, jedoch bleiben andere Beschädigungen (siehe z.B. Kapitel 2.3.4) unbehandelt.
Eine Variation der Canaray-Values wird auch in Compilern des Herstellers Microsoft
verwendet, wobei dort der Wert zwischen den lokalen Variablen und dem gesicherten
SFP liegt. Der Canary-Value wird dort als Security Cookie bezeichnet und entspricht
einem zufällig gewählten Wert XOR-verknüpft mit der Rücksprungadresse ([Klein2003a],
S.347f ).

Auch bei der Verwendung von Canary-Values werden die Ziel-Programme abgebro-
chen, sobald eine Manipulation detektiert wird. Dies geschieht, wie bereits erwähnt, um
das System vor Kompromittierung zu schützen. Die Verfügbarkeit eines angegrienen
Dienstes wird durch diese Maÿnahme nicht erhöht.

3.3. Explizite Erkennung von Speichergrenzen3

Wird bei jedem Schreibvorgang in den Speicher überprüft, ob die Speichergrenzen der
entsprechenden Variablen eingehalten werden, so kann es zu keinem Puerüberlauf kom-
men ([Klein2003a], S.305).

buffer[3] pointer
buffer[4]
limit
buffer[3]
pointer
buffer[2] base
buffer[1]
buffer[0]

Abbildung 3.4.: Pointer als Triplett ([JonesKelly1997], S.2)

2
Entspricht prinzipiell einem boolschen Wert {Canary-Value intakt, Canary-Value nicht-intakt}.
3
In der Literatur oft auch mit dem englischen Begri strict bounds checking bezeichnet.

19
3.4. VERBESSERTE HEAP-IMPLEMENTATION

Eine allgemeine Variante, die Erkennung der Speichergrenzen zu ermöglichen, ist das
Verwenden des in Abbildung 3.4 illustrierten Tripletts für Zeiger auf Speicherelemente.
Dabei wird der Zeiger auf den Anfang (base ), der Zeiger auf das aktuelle Speicher-
Element (pointer ) und der Zeiger auf das Ende (limit ) der Variable mitgeführt. Bei
Schreibvorgängen muss nun überwacht werden, dass der pointer nicht gröÿer als das
limit und nicht kleiner als die base wird ([JonesKelly1997], S.2f ).

Ein weiterer Vorteil, der sich implizit aus der Überprüfung bei jedem Schreibzugri
ergibt, liegt darin, dass ein Programm nicht abgebrochen werden muss, wenn ein nicht
erlaubter Zugri detektiert wird. Der Schreibvorgang kann verhindert werden und das
Programm fortgesetzt werden. Der Zugris-Fehler kann dem Benutzer etwa durch das
Konzept der Exceptions signalisiert werden ([Klein2003a], S.305).

Der Performance-Verlust durch Bounds Checking ist allerdings groÿ und wird, etwa
in [Frykholm2000], mit einer Verlangsamung der Programmausführung von 200% und
mehr angegeben. Weiters kann das gleichzeitige Verwenden von nicht-objektorientierte
und objektorientierter Logik (z.B. in C++) manchmal nicht korrekt erkannt werden,
was wiederum zu false positives und eine Beeinträchtigung des Programm-Algorithmus
führt. Dadurch wird diese Technologie in nicht-objektorientierten Sprachen praktisch
nicht eingesetzt und wird auch in dieser Arbeit nicht weiter behandelt.
In objektorientierten Hochsprachen wird eine ähnliche Technik verwendet, die beispiels-
4
weise eine Zeichenkette bereits als Objekt implementiert . Das Schreiben in die Zei-
chenkette kann somit mit Prüfungen versehen werden und Puerüberläufe erfolgreich
verhindern. Die Angrismöglichkeit liegt nun nicht mehr bei den Programmen selbst,
sondern in den Compilern bzw. den Virtual-Machines der Hochsprachen (siehe etwa die
Beschreibung der Java-Virtual-Machine-Schwachstelle in [SunSolve2007]).

3.4. Verbesserte Heap-Implementation

Der in Abbildung 2.15b (Kapitel 2.3.5) gezeigte Puerüberlauf basiert auf der Möglich-
keit, die internen Zeiger einer doppelt verketteten Liste zu manipulieren, um etwa einen
RIP zu überschreiben. Diese Technik kann allerdings durch eine triviale Überprüfung der
Zeiger verhindert werden. In [Lea2005] ist beispielsweise eine Adressbereichsüberprüfung
vorhanden, die Zeiger auf Speicherstellen auÿerhalb des Heap-Segments nicht zulässt.
Weiters können durch diverse Laufzeitüberprüfungen beim Behandeln von Chunks
und Header-Informationen mögliche Attacken (z.B. Umlenken von Zeigern), aber auch
Programmierfehler, erkannt werden ([Lea2005], Zeile 236f ).

Das Standardverhalten dieser Heap-Implementation ist ein Abbruch bei Vorlie-


gen eines erkannten Fehlers. Diese Eigenschaft kann über die Compiler-Variable
PROCEED_ON_ERROR ([Lea2005], Zeile 255) geändert werden, sodass bei einer erkannten
Manipulation die fehlerhaften Chunks und Header ignoriert werden. Dies erhöht
einerseits die Ausfallsicherheit des Programms, andererseits wird die Fehlersuche bei
der Entwicklung erschwert, da ein Ignorieren von Fehlern in einer Applikation zu schwer

4
Diese Implementierung geht natürlich auf Kosten der Laufzeit-Performance der Applikation, da kein
direkter Speicherzugri erfolgen kann.

20
3.5. ADDRESS SPACE LAYOUT RANDOMIZATION

nachvollziehbaren Verhaltensweisen führen kann. Ein Absturz hingegen signalisiert


während der Entwicklung zumindest einen oensichtlichen Fehler im Algorithmus
([Lea2005], Zeile 91-105).

Somit kann eine verbesserte Implementierung der Heap-Verwaltung durchaus zur Ver-
fügbarkeit von Diensten während Heap-basierten Puerüberläufen beitragen, allerdings
ergeben sich zwei Einschränkungen: Die Fehlersuche wird durch ignorierte Fehler er-
schwert und die Laufzeit-Performance drastisch gesenkt ([Lea2005], Zeile 263-264).

3.5. Address Space Layout Randomization (ASLR)

Manche Techniken, wie etwa die nicht-ausführbaren Speicherbereiche (siehe 3.1), zielen
primär darauf ab, das Einschleusen von Programmcode zu verhindern. Es kann jedoch
genügen, eine bestimmte vorhandene Funktion auszuführen, um Zugri auf ein geschütz-
tes System zu erlangen (Vgl. return-to-libc, [Klein2003a], S.405).
Deshalb wurde eine Technik entwickelt, die das beabsichtigte Aufrufen von vorhandenen
Prozeduren erschweren soll, indem die Speicheradressen, an denen sich die Prozeduren
benden, variiert werden. Dies wird erreicht, indem sowohl die Reihenfolge als auch die
Position der Segmente auf zufällige Weise geändert wird (siehe Abbildung 3.5b).

a) b)

BSS Segment
Stack
Text Segment

Heap Data Segment


BSS Segment Stack

Data Segment

Heap
Text Segment
0x0 0x0

Abbildung 3.5.: Festlegung der Position der Segmente (a) ohne ASLR (b) mit ASLR
(nach [Shacham2004], S.1)

Weiÿ ein Angreifer nicht, welche Speicherstelle welcher Prozedur zugeordnet ist,
so kann dieser keinen gezielten Prozeduraufruf durchführen. Hierbei wird weder der
Puerüberlauf selbst, noch die Veränderung von Rücksprungadressen verhindert oder
behandelt. Vielmehr baut diese Technologie auf der These auf, dass ein willkürlicher
Sprung an eine Speicherstelle meist zu einer Zugrisverletzung oder einem anderen
Programmabbruch führt ([Shacham2004], S.1f ).

21
3.5. ADDRESS SPACE LAYOUT RANDOMIZATION

Um festzustellen ob ein System ASLR einsetzt, kann beispielsweise das Programm aus
Abbildung 3.6 verwendet werden. Bei der mehrmaligen Ausführung zeigt sich, dass sich
bei aktiviertem ASLR die ausgegebene Adresse bei jeder Ausführung ändert (Abbildung
3.7b), bei einem statischen Memory-Layout jedoch unverändert bleibt.

0 #include <stdio.h>
1
2 int main(void)
3 {
4 static int x = 0;
5 printf ("address of x: 0x%0X\n", (unsigned int)&x);
6 return x;
7 }

Abbildung 3.6.: Programm zur Ausgabe der Speicher-Adresse der Variable x

(a) (b)

[work@localhost ~]$ ./aslr_check [work@localhost ~]$ ./aslr_check


address of x: 0x8048384 address of x: 0x501C08AC
[work@localhost ~]$ ./aslr_check [work@localhost ~]$ ./aslr_check
address of x: 0x8048384 address of x: 0xED780FDC
[work@localhost ~]$ ./aslr_check [work@localhost ~]$ ./aslr_check
address of x: 0x8048384 address of x: 0x505F8BC
[work@localhost ~]$ ./aslr_check [work@localhost ~]$ ./aslr_check
address of x: 0x8048384 address of x: 0xB4A1926C

Abbildung 3.7.: Ausführung des Programms aus Abbildung 3.6 (a) ohne ASLR (b) mit
ASLR

ASLR ist eine eektive Möglichkeit um Attacken zu verhindern, die versuchen, mit
Sprüngen an bestimmte Speicherstellen die Kontrolle über Programme zu übernehmen.
Trotzdem gibt es auch hier Schwachstellen, die etwa auf der vorherigen Suche der
richtigen Speicheradresse oder auf nicht zufällig verteilten Speicherbereichen beruhen
([Shacham2004], S. 2 ).

Die ASLR-Technik kann aufgrund der Unvorhersagbarkeit des Sprungziels einen Pro-
grammabbruch (z.B. bei einer Speicherzugrisverletzung) natürlich nicht verhindern.

22
4. Diskussion und Ergebnisse
Es hat sich gezeigt, dass eine einzige dynamischen Methode - die explizite Erkennung
von Speichergrenzen - im Stande ist, Puerüberläufe vollständig zu unterbinden und da-
her auch zu keinem Ausfall des entsprechenden Dienstes bei einem Angri führt. Die in
dieser Methode verwendete Kapselung entspricht, wie bereits erwähnt, praktisch der Be-
trachtung eines Puers als Objekt nach Grundlage der Objektorientierung. Diese Technik
erfordert natürlich einen groÿen Overhead aufgrund der Verwaltungsmechanismen für die
Objekte, was wiederum zu einer verminderten Performance führt. Wenn eine gute Lauf-
zeitgeschwindigkeit des Algorithmus nicht das primäre Ziel ist und ausreichend Resourcen
vorhanden sind, kann daher durch Kapselung der Puer oder durch Verwendung einer
objektorientierten Hochsprache das Risiko eines Buer-Overows im Programm ausge-
schlossen werden.
Alle verbleibenden Methoden führen in der Standard-Konguration bei einem geziel-
ten Puerüberlauf zu einer Beendigung des entsprechenden Programms, und daher zu
einem Ausfall des Dienstes. Aus diesem Grund sollten diese Techniken nur der Schadens-
Begrenzung dienen, d.h. kommt es zu einem Buer-Overow, so darf es dem Angreifer
nicht möglich sein, das Programm unter seine Kontrolle zu bringen. Diese Methoden kön-
nen allerdings Angrie über Puerüberläufe nicht verhindern oder ausschlieÿen, da durch
einen Buer-Overow-Angri das Programm meist beendet wird und eine DoS-Attacke
trotz Einsatz der entsprechenden Techniken durchaus noch erfolgreich sein kann.

23
Literaturverzeichnis
[AlephOne1996] Aleph One. Smashing the stack for fun and prot. Phrack Magazine, 7,
11 1996.
http://www.phrack.org/issues.html?issue=49&id=14&mode=txt
Zugri 2008-10-28.

[Anon2001] Anon. Once upon a free(). Phrack Magazine, 11, 11 2001.


http://www.phrack.com/issues.html?issue=57&id=9
Zugri 2008-10-29.

[BryantO'Hallaron2003] R. E. Bryant and D. O'Hallaron. Computer Systems. A Pro-


grammers Perspective: A Programmer's Perspective. Prentice Hall, inter-
national ed edition, 8 2003.

[Buehler2006] W. Bühler. Openbsd - free, functional and secure. Technical report,


Stadtwiki - Gesellschaft zur Förderung regionalen Freien Wissens e.V.,
2006.
http://infotage.pf-lug.de/2006/downloads/vortraege/OpenBSD.
pdf
Zugri 2008-11-12.

[Cowan1999] C. Cowan, P. Wagle, S. Beattie C. Pu, and J. Walpole. Buer overows:


Attacks and defenses for the vulnerability of the decade. Technical report,
Oregon Graduate Institute of Science and Technology, 1999.
http://www.ece.cmu.edu/~adrian/630-f04/readings/
cowan-vulnerability.pdf
Zugri 2008-10-30.

[Cristey2007] M. Cristey and R.A. Martin. Vulnerability type distributions in cve. Tech-
nical report, The MITRE Corporation, 2007.
http://cwe.mitre.org/documents/vuln-trends/index.html
Zugri 2008-10-28.

[Frykholm2000] N. Frykholm. Countermeasures against buer overow attacks. Techni-


cal report, RSA, The Security Division of EMC, 11 2000.
http://www.rsa.com/rsalabs/node.asp?id=2011
Zugri 2008-11-22.

[Johns2005] M. Johns. Finding and preventing buer overows. Technical report,


Hamburg, Universität Hamburg, Fachbereich Informatik, 12 2005.
http://events.ccc.de/congress/2005/fahrplan/attachments/
653-slides_buffer_overflow.pdf
Zugri 2008-10-30.

24
Literaturverzeichnis

[JonesKelly1997] R. W. M. Jones and P. H. J. Kelly. Backwards compatible bounds


checking for arrays and pointers in c programs. Technical report, London,
Imperial College of Science, Technology and Medicine, 9 1997.
http://www.ep.liu.se/ea/cis/1997/009/02/cis9700902.pdf
Zugri 2008-11-21.

[Klein2003a] Tobias Klein. Buer Overows und Format-String-Schwachstellen: Funk-


tionsweisen, Exploits und Gegenmaÿnahmen. Dpunkt Verlag, 1 edition, 9
2003. ISBN 3-89864-192-9.

[Lea2005] D. Lea. The gnu c-library malloc/free/realloc implementation. Technical


report, State University of New York at Oswego, 9 2005.
ftp://gee.cs.oswego.edu/pub/misc/malloc.c
Zugri 2008-10-13.

[Leidecker2006] N. Leidecker. Ausführungssperren von programmcode. Technical report,


Heidelberg, Ruprecht-Karls Univerität Heidelberg, Fachbereich Informatik,
2006.
http://pvs.informatik.uni-heidelberg.de/Teaching/CSFP-0506/
leidecker.pdf
Zugri 2008-10-05.

[Nergal2001] Nergal. The advanced return-into-lib(c) exploits: Pax case study. Phrack
Magazine, 58, 12 2001.
http://www.phrack.com/issues.html?issue=58&id=4&mode=txt
Zugri 2008-10-18.

[Raiu2006] C. G. Raiu. Enhanced virus protection. Technical report, Kaspersky Lab,


Rumänien, 2006.
http://www.virusbtn.com/pdf/conference_slides/2005/Costin_
Raiu.pdf
Zugri 2008-11-13.

[RussellCunningham2000] R. Russell and S. Cunningham. Hack Proong Your Network:


Internet Tradecraft: The Only Way to Stop a Hacker Is to Think Like One.
Syngress Media,U.S., 8 2000.

[Shacham2004] H. Shacham, M. Page, B. Pfa, E. Goh, N. Modadugu, and D. Boneh.


On the eectiveness of address space randomization. Technical report,
Stanford University, 10 2004.
http://www.stanford.edu/~blp/papers/asrandom.pdf
Zugri 2008-12-07.

[SunSolve2007] Inc. Sun Microsystems. Security vulnerability in processing gif images


in the java runtime environment may allow an untrusted applet to elevate
privileges. SunSolve, 07 2007.
http://sunsolve.sun.com/search/document.do?assetkey=
1-26-102760-1
Zugri 2008-12-05.

25
Literaturverzeichnis

[Vallentin2007] M. Vallentin. On the evolution of buer overows. Technical report,


University of Berkley, 2007.
http://matthias.vallentin.cc/cw/buffer_overflows.pdf
Zugri 2008-10-30.

[Viking2006] P. Viking. Comparison of dynamic buer overow protection tools. Final


thesis., Linköping University, 2006.
http://www.ep.liu.se/exjobb/ida/2006/dd-d/009/
Zugri 2008-10-29.

26
Abbildungsverzeichnis
2.1. Speichermodell eines UNIX-Prozesses ([Viking2006], S.5) . . . . . . . . . . 3
2.2. Prozess-Segmente in einem C-Programm ([Viking2006], S.6) . . . . . . . . 4
2.3. Unterprogramm-Aufruf in einem C-Programm . . . . . . . . . . . . . . . . 5
2.4. Stack-Segment eines UNIX-Prozesses bei einem Funktionsaufruf (a) vor
dem Funktionsaufruf von mult() (b) bei der Ausführung von mult()
([BryantO'Hallaron2003], S.170f ) . . . . . . . . . . . . . . . . . . . . . . . 6
2.5. C-Programm mit einer Puerüberlauf-Schwachstelle . . . . . . . . . . . . 7
2.6. Aufruf des Programms aus Abb. 2.5 (a) Normale Ausführung (b) Puer-
überlauf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.7. Stack-Segment bei den Aufrufen aus Abb. 2.6 - (a) ohne Überlauf (b) mit
willkürlichem Überlauf (c) mit gezieltem Überlauf . . . . . . . . . . . . . . 8
2.8. Angris-Typen bei gezielter Manipulation des RIP (a) Ausführung von be-
reits vorhandenem Programmcode (b) Einschleusen und Ausführung von
beliebigem Programmcode (c) Angri mit NOP-Sliding . . . . . . . . . . . 9
2.9. C-Programm mit einer SFP-Overwrite -Schwachstelle . . . . . . . . . . . . 10
2.10. Stack während der Ausführung von dem Programm aus Abb. 2.9 (a) wäh-
rend der normalen Ausführung von argcpy() (b) SFP-Overwrite während
argcpy() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.11. C-Programm mit einer Puerüberlauf-Schwachstelle im BSS-Segment . . . 12
2.12. Stack während der Ausführung von dem Programm aus Abb. 2.11 . . . . . 12
2.13. Struktur des Heap bei Verwendung der Implementation der GNU C-
Library von Lea ([Lea2005], Zeile 1484 - 1530) . . . . . . . . . . . . . . . . 13
2.14. (a) Freigabe eines einzelnen Chunks (b) Konsolidierung mit nachfolgen-
dem freien Chunk (c) Konsolidierung mit vorhergehendem freien Chunk
([Lea2005], Zeile 1530 ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.15. (a) doppelte Verkettung von zwei freien Speicherblöcken (b) Beispiel für
einen gezielten Buer-Overow im Heap-Segment mit Einschleusen von
deniertem Programmcode . . . . . . . . . . . . . . . . . . . . . . . . . . 15

3.1. Ein Programm, das die Ausführungssperre überschreitet wird von der
Data-Execution-Prevention beendet (Windows) . . . . . . . . . . . . . . . 17
3.2. Nach dem Puerüberlauf wird zum Schutz vor dem eingeschleusten Pro-
grammcode das Programm abgebrochen . . . . . . . . . . . . . . . . . . . 17
3.3. Canary-Value auf dem Stack ([Klein2003a], S.307) . . . . . . . . . . . . . 18
3.4. Pointer als Triplett ([JonesKelly1997], S.2) . . . . . . . . . . . . . . . . . . 19
3.5. Festlegung der Position der Segmente (a) ohne ASLR (b) mit ASLR (nach
[Shacham2004], S.1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.6. Programm zur Ausgabe der Speicher-Adresse der Variable x . . . . . . . . 22
3.7. Ausführung des Programms aus Abbildung 3.6 (a) ohne ASLR (b) mit
ASLR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

27
Abkürzungsverzeichnis
BSS Block Started by Symbol
CPU Central Processing Unit
DEP Data Execution Prevention
DoS Denial of Service
NOP No-Operation (Assembler Befehl)
NX No-eXecution (Bit einer Prozessor-Erweiterung)
RAM Random Access Memory
RIP Return Instruction Pointer
SFP Stack Frame Pointer
XOR eXclusive-OR (exklusiv-oder Verknüpfung)

28
A. Zielsystem-Spezikationen
R 2008
A.1. Microsoft Windows

• R WebServer 2008
Microsoft Windows

• 32 Bit Intel-Prozessor, 32 Bit (x86) Betriebssystem-Version

• Kein NX-Bit verfügbar

• Service Pack 1

• Alle Windows-Updates bis inklusive 2008-11-20

• 2 GB RAM

• Software-DEP ist standardmäÿig auf einem solchen System aktiviert

A.2. Debian Linux Etch

• Debian Linux Etch 4.0r5

• 32 Bit Intel-Prozessor, 32 Bit (x86) Betriebssystem-Version

• Kein NX-Bit verfügbar

• Full-Installation

• Alle Updates bis inklusive 2008-11-21

• 1 GB RAM

A.3. OpenBSD

• OpenBSD 4.4

• 64 Bit Intel Core2-Prozessor, 64 Bit (x86_64) Betriebssystem

• Full-Installation

• NX-Bit verfügbar

• Alle Updates bis inklusive 2008-11-29

• 3 GB RAM

29
B. Programm mit
Buer-Overow-Schwachstelle
/* buffer_overflow_stack.c
*/
#include <stdio.h>

void copy (char * arg)


{
char buffer[10];
memset(buffer, 0, sizeof(buffer));
strcpy(buffer, arg);
}

int main (int argc, char ** argv)


{
copy (argv[1]);
return 0;
}

30