Hierbei handelt es sich um eine Studienarbeit, deren Thema Stack- und Heap-Overflows unter Windows XP und Windows Vista ist. Zur Einführung ist ein umfangreiches Grundlagenkapitel enthalten, dass die generelle Funktionsweise von Overflows und Code-Injection unter Windows beschreibt. Eine Besonderheit sind hierbei die ausführlich auf Kompatibilität getesteten Demo-Programme mit Quellcode.
Um Experimentierfreudigen einen möglichst leichten Einstieg zu geben, wird in den Quellcode-Beschreibungen auf Sicherheitsoptimierungen unterschiedlicher Compilerversionen eingegangen.
Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
1. Studienarbeit
Stack- und Heap-Overflow-Schutz
bei
Windows XP und Windows Vista
Fachbereich Informationstechnik
Studiengang Softwaretechnik und Medieninformatik
Semester: SWT6
Name: Johannes Hohenbichler
Datum: 20.08.2008
Version: 1.03
Betreuer: Dominik Schoop
2. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
I Inhaltsverzeichnis
I Inhaltsverzeichnis ......................................................................................................................... 1-2
II Abbildungsverzeichnis .................................................................................................................. 1-4
III Abkürzungsverzeichnis ................................................................................................................. 1-5
IV Toolsverzeichnis ........................................................................................................................... 1-6
V Besondere Darstellungsformen.................................................................................................... 1-7
1 Überblick ...................................................................................................................................... 1-8
2 Grundlagen – Systemarchitektur................................................................................................ 2-10
2.1 Speicherorganisation .......................................................................................................... 2-10
2.1.1 Virtueller Speicher ...................................................................................................... 2-10
2.1.2 Systemfunktionen....................................................................................................... 2-11
2.1.3 Speichersegmente ...................................................................................................... 2-13
2.1.4 Big- und Little-Endian ................................................................................................. 2-15
3 Grundlagen – Buffer Overflows .................................................................................................. 3-16
3.1 Buffer Overflows ................................................................................................................ 3-16
3.1.1 Off-By-One Buffer Overflows ..................................................................................... 3-17
3.1.2 Mit Buffer Overflows den Kontrollfluss ändern ......................................................... 3-18
3.1.3 Ausführen von beliebigem Code ................................................................................ 3-21
3.1.4 Klassische Stack-basierende Angriffe ......................................................................... 3-22
3.1.5 Exception Handler-basierende Angriffe ..................................................................... 3-25
3.1.6 Heap-basierende Angriffe .......................................................................................... 3-27
3.2 Shellcode und Payload ....................................................................................................... 3-29
3.2.1 Anforderungen an Shellcode ...................................................................................... 3-29
3.2.2 Besonderheiten von Windows-Shellcode .................................................................. 3-30
3.2.3 Was möglich ist........................................................................................................... 3-30
3.2.4 Abstraktes Beispiel: Portbind Shellcode für Windows ............................................... 3-31
4 Das Sicherheits-Ensemble von Windows und seine Schwachstellen ........................................ 4-32
4.1 Das Windows-Zwei-Schichten-Modell................................................................................ 4-32
4.2 Zugriffsrechte, Authentifizierung und Access Token.......................................................... 4-33
4.2.1 Rechte von Prozessen unter Windows ....................................................................... 4-34
4.2.2 Windows Service Hardening....................................................................................... 4-35
4.2.3 Rechte von Prozessen unter Windows Vista: UAC ..................................................... 4-35
Seite - 2 -
3. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
4.3 PatchGuard ......................................................................................................................... 4-37
4.4 Process Exection Block (PEB) Randomization..................................................................... 4-37
4.5 Heap-Schutz........................................................................................................................ 4-38
4.6 Visual Studio 2005 - Der "Windows-Compiler" .................................................................. 4-40
4.6.1 Standard Annotation Language (SAL) ......................................................................... 4-40
4.6.2 Optimierte Anordnung von Stack-Elementen (O2) .................................................... 4-42
4.6.3 Puffer-Sicherheitsüberprüfung (GS) ........................................................................... 4-43
4.6.4 Sichere Ausnahmebehandlung (SafeSEH) .................................................................. 4-47
4.6.5 Data Execution Prevention (DEP) ............................................................................... 4-48
4.6.6 Address Space Layout Randomization (ASLR) ............................................................ 4-51
4.7 Abschließender Vergleich: XP gegenüber Vista ................................................................. 4-52
5 Fazit ............................................................................................................................................ 5-54
A Literaturverzeichnis .................................................................................................................... 5-56
B Quellcode ................................................................................................................................... 5-60
Seite - 3 -
4. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
II Abbildungsverzeichnis
Abbildung 2-1: 32-Bit-Windows Standard-Adresslayout nach [ 2 ] .................................................. 2-11
Abbildung 2-2: Prinzipielles Windows Prozess-Speicher-Layout nach [ 3 S. 182 ] und [ 4 S. 397 ] .. 2-13
Abbildung 2-3: Big-Endian im Speicher .............................................................................................. 2-15
Abbildung 2-4: Little-Endian im Speicher ........................................................................................... 2-15
Abbildung 3-1: Die Buffer buf0 und buf1 im Speicher ....................................................................... 3-18
Abbildung 3-2: Buffer Overflow von password_buffer in auth_flag .................................................. 3-21
Abbildung 3-3: Beispielhaftes Stackframe unter Windows allgemein ............................................... 3-22
Abbildung 3-4: Angriffsvektor ............................................................................................................ 3-23
Abbildung 3-5: Beispielhaftes Stackframe unter Verwendung von SEH [ 13 ] .................................. 3-27
Abbildung 4-1: Windows-Zwei-Schichten-Modell [ 4 ] ...................................................................... 4-33
Abbildung 4-2: Optimierte Anordnung von Stack-Elementen bei VS 2003 ....................................... 4-43
Abbildung 4-3: Beispielhaftes Stacklayout unter Verwendung von /GS bei VS 2005 [ 32 ] .............. 4-45
Seite - 4 -
5. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
III Abkürzungsverzeichnis
ASLR Address Space Layout Randomization
BS Betriebssystem
DACL Discretionary Access Control List
DEP Data Execution Protection, auch bekannt als NX
DoS Denial of Service
Dll Dynamic Link Library
EH Exception Handler
GByte Gigabyte
IE Internet Explorer
LSA Local Security Authority Subsystem Service
NX No eXecute, auch bekannt als DEP
RET Returnaddress
SAL Standard Annotation Language
SEH Structured Exception Handling
SFP Saved Frame Pointer
SID Security Identifier
VS Visual Studio
Seite - 5 -
6. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
IV Toolsverzeichnis
Programm Bezugsmöglichkeit
Windows power shell http://www.microsoft.com/windowsserver2003/technologies/management/power
shell/default.mspx
Strawberry Perl http://strawberryperl.com/
Process Explorer http://technet.microsoft.com/de-de/sysinternals/default(en-us).aspx
Display Heap Windows Server 2003 Resource Kit Tools
http://support.microsoft.com/kb/168609/de
Global Flags Debugging Tools for Windows
http://www.microsoft.com/whdc/devtools/debugging/default.mspx
Seite - 6 -
7. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
V Besondere Darstellungsformen
In dieser Arbeit kommen im Wesentlichen vier spezielle Gestaltungselemente zur Anwendung
1. Begriffserklärungen und Hinweise sind in einer Box der folgenden Art untergebracht
Begriffserklärungen und Hinweise
Begriff: Beschreibung oder Definition des Begriffs bzw. Hinweistext
2. Kommandos
Kommandos die auf der Kommandozeile eingegeben werden (cmd.exe)
sind grau hinterlegt
3. Programmausgaben
Programmausgaben
sind orange hinterlegt
4. Quellcode / Ausschnitte aus Dateien
Auszüge aus Quellcode / Konfigurationsdatein
sind hellblau hinterlegt
Seite - 7 -
8. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
1 Überblick
Ausgangssituation
In der Vergangenheit waren Buffer-Overflows eines der Hauptsicherheitsrisiken für Software. In den
letzten Jahren haben Softwarehersteller an diesem Problem intensiv gearbeitet und den Schutz stark
erhöhen können. Das beweisen nicht zuletzt CVE1-Statistiken, die Windows Vista für das Jahr 2008
bis zum Erscheinen dies Dokuments, in dem Monat Juni, nur eine einzige Schwachstelle
bescheinigen, die direkt auf Buffer-Overflows zurückzuführen ist. [ 1 ]
Ziel dieser Arbeit
Das Ziel dieser Arbeit ist es, einen Überblick zu schaffen, welche Mechanismen die Betriebssysteme
Windows XP und Windows Vista zur Vermeidung und zur Schadensbegrenzung von Buffer Overflows
bieten. Dabei wird auf die Schwachstellen der vorhandenen Mechanismen eingegangen und es
werden Wege aufgezeigt, wie sich diese Schwachstellen praktisch auszunutzen lassen. Außerdem hat
es diese Arbeit zum Ziel, die Neuerungen von Windows Vista, dem noch weiter verbreiteten
Windows XP gegenüber zu stellen und so den sicherheitstechnischen Mehrwert von Windows Vista
darzulegen.
An wen richtet sich diese Arbeit
Diese Arbeit richtet sich an alle Leser, die an der definierten Zielsetzung interessiert sind. Zum
vollständigen Verständnis werden jedoch Grundkenntnisse der Programmiersprachen c und x86
Assembler vorausgesetzt. Für Buffer Overflows besonders relevante Punkte sind jedoch grundliegend
beschrieben, so dass die Arbeit auch für Leser ohne Vorkenntnisse aus dem Themengebiet "Buffer-
Overflows" verständlich sein sollte.
Gliederung dieser Arbeit
Die einzelnen Kapitel dieser Arbeit können für sich selektiv gelesen werden. Die beiden
Grundlagenkapitel(Kapitel 2, 3) bauen jedoch aufeinander auf und sollten von Beginn bis Ende
gelesen werden. Zudem wird im Hauptkapitel(Kapitel 4) auf die Angriffsmöglichkeiten bezug
genommen, die im Grundlagenkapitel "Buffer Overflows" erklärt werden.
In dem Kapitel 2 werden einige, für Buffer Overflows wichtige, Grundlagen zur Systemarchitektur
vorgestellt. Der Fokus liegt hierbei speziell auf Eigenheiten des NT-Kerns der Betriebssystemfamilie
Microsoft Windows.
1
National Vulnerability Database: http://nvd.nist.gov/
Seite - 8 -
9. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
In dem Kapitel 3 wird eine Einführung in das Thema Buffer Overflows gegeben und es werden
einzelne Codestücke präsentiert. Um die Übersichtlichkeit an dieser Stelle nicht unnötig zu
beeinträchtigen sind die vollständigen Programme in einem Zip-Archiv(Kapitel B) zu finden. Ebenfalls
im Kapitel B, finden sich Hinweise zum Übersetzen der jeweiligen Programme mit dem Compiler der
Entwicklungsumgebung Visual Studio 2003 / 2005. Ziel dieses Kapitels ist es, die grundliegenden
Konzepte von Buffer Overflows zu umreißen und einige windows-spezifische Angriffe zu erläutern.
Das Hauptkapitel dieser Arbeit beschäftigt sich im Detail mit den verschiedenen Schutzmaßnahmen
von Windows XP und Vista. Hierbei wird vor allem auf den effektiven Nutzen der einzelnen
Maßnahmen eingegangen. In diesem Zusammenhang werden Implementierungs- und
Designschwachstellen aufgezeigt und objektiv beurteilt. Dabei wird auf die im Kapitel 3 vorgestellten
Angriffe eingegangen und es wird geklärt, warum die Angriffe noch, oder nicht mehr, funktionieren.
Abschließend folg eine Zusammenfassung der wesentlichen Erkenntnisse der wesentlichen
Erkenntnisse und kritischen Blick auf die derzeitigen Entwicklungen.
Seite - 9 -
10. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
2 Grundlagen – Systemarchitektur
In diesem Kapitel werden Grundlagen aus den Bereichen Betriebssystem- und Rechnerarchitektur
vorgestellt, die für ein fundiertes Verständnis von Buffer Overflows benötigt werden. Dabei wird
speziell auf Eigenheiten des NT-Kernels, der Betriebssystemfamilie Microsoft Windows, eingegangen.
2.1 Speicherorganisation
In diesem Abschnitt wird die Speicherorganisation von Windows XP und Windows Vista grundliegend
erläutert um ein eine Basis für das Verständnis, der in Kapitel 3 beschriebenen Angriffstechniken, zu
schaffen.
2.1.1 Virtueller Speicher
Bei 32-Bit-Windows-Systemen steht einer Anwendung theoretisch der maximal adressierbare
Speicher von 0x00000000 bis 0xFFFFFFFF zur Verfügung, was einer Größe von 2³² Byte = 4 GByte
entspricht. Dabei ist die kleinste adressierbare Einheit ein Byte groß.
Diese 4 GByte teilt Windows in zwei Hälften. Den höherwertigen Bereich verwendet es für
Betriebssystemfunktionen und den niederwertigen Bereich für das Programm selbst. Die Trennung
ist normalerweise, wie in der Abbildung 2-1 zu sehen ist, in der Mitte des Gesamtspeichers
vorgenommen. Dank dem Konzept des virtuellen Adressraums, ist es dabei nicht wichtig, welche
Menge an Arbeitsspeicher das System tatsächlich benötigt. Mit Hilfe der Hardware, zur
Adressvirtualisierung, kann das Betriebssystem die Umsetzung von physikalischen in virtuelle
Adressen erledigen, ohne dass sich ein laufendes Programm darum bemühen muss. Dabei kümmert
sich das Betriebssystem automatisch um das Ein- und Auslagern von Speicher.
Seite - 10 -
11. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Hinweis
0000000h
(Kleine Adressen)
Unter 32-Bit-Windows lässt sich
die Aufteilung auch mittels einer
00000000h
Benutzermodus- Bootoption in das Verhältnis 3GB
Prozesse
(2GB) zu 1GB (Programmspeicher zu
7FFFFFFFh
Systemspeicher) ändern. Unter
80000000h
Systemspeicher 64-Bit-Windows-Varianten ist die
(2GB)
Speicheraufteilung nahezu 50%
FFFFFFFFh
zu 50% [ 2 S. 14ff ] .
FFFFFFFFh
(Große Adressen) Außerdem gibt es die Address
Windowing Extensions (AWE) mit
deren Hilfe sich der virtuelle
Abbildung 2-1: 32-Bit-Windows Standard-Adresslayout
nach [ 2 ] Speicher vergrößern lässt [ 2 S.
383ff ]
2.1.2 Systemfunktionen
Im Zusammenhang mit Buffer Overflows ist weniger die im letzen Kapitel vorgestellte
Speicheraufteilung interessant, sondern viel mehr die Art wie auf Systemfunktionen zugegriffen wird.
Im normalen Programmbetrieb werden alle benötigten Systemfunktionen, wie beispielsweise
sleep(), in den virtuellen Adressbereich der Anwendung eingeblendet. Beim Übersetzen des
Quellcodes erzeugt der Compiler automatisch die Befehle zum Laden der nötigen Systemfunktionen.
Die Adressen an denen die verschiedenen Systemfunktionen zu finden sind, sind innerhalb eines
Systems normalerweise immer gleich. Unterschiede bezüglich der Funktionsadressen gibt es lediglich
zwischen den einzelnen Windows-Versionen sowie zwischen den verschiedenen Service-Packs. Eine
wirkliche Ausnahme entsteht allerdings durch Address Space Layout Randomization (ASLR), auf das in
dem Kapitel 4.6.4 eingegangen wird.
Ein Beispiel:
Mit dem Programm dumpbin.exe, das Teil von Visual Studio 2005 ist, lässt sich die Basis-Adresse des
Windows-Kernels ermitteln:
PS C:WINDOWSsystem32> dumpbin /headers kernel32.dll | select-string -
pattern "image base"
7C800000 image base (7C800000 to 7C906FFF)
Um nun beispielsweise die Absolute Adresse der Sleep-Funktion zu ermitteln muss noch die relative
Adresse, ausgehend von der Basis-Adresse, ermittelt werden:
Seite - 11 -
12. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
PS C:WINDOWSsystem32> dumpbin /all kernel32.dll | select-string -pattern
"sleep"
7C8087E0: 73 6F 75 72 63 65 00 53 6C 65 65 70 00 53 6C 65 source.Sleep.Sle
831 33E 00002442 Sleep
832 33F 0000239C SleepEx
Aus der Basis-Adresse und der relativen Adresse ergibt sich die absolute Adresse der Funktion
sleep()
7c800000h + 00002442h = 7c802442h
Wie bereits erwähnt ist diese Adresse bei jedem Programmstart, unter Berücksichtigung der oben
genannten Ausnahmen, gleich.
Tools-Hinweis
Die obigen Kommandos wurden nicht in der normalen Eingabeaufforderung, sondern in der
kostenlos über Microsoft erhältlichen Powershell, ausgeführt. Der Grund hierfür ist, dass in der
Powershell die Funktion select-string() zur Verfügung steht. Bezugsmöglichkeiten: siehe
Toolsverzeichnis
Seite - 12 -
13. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
2.1.3 Speichersegmente
Der einem Prozess zur Verfügung stehende virtuelle Speicher, teilt sich wie in Abbildung 2-2 zu sehen
ist, in verschiedene Segmente auf. Wichtig sind hierbei vor allem die relative Position der Segmente
zueinander, sowie die Richtung des Schreibzugriffs. Der Schreibzugriff erfolgt immer von einer
Basisadresse in Richtung der höherwertigen Adressen.
Prozess-Speicher Beispielhaftes
Stackframe
0000000h
(Kleine Adressen)
Gespeicherte Register der
Heap
aufrufenden Funktion
Lokale Variablen
Stack Frame Pointer (SFP)
Schreibrichtung
Stack Return Adresse (RET)
Parameter
.TXT
.DATA
Heap entspricht einer Dynamischen
.BSS Erweiterung des BSS-Segments
(langlebige Daten)
Stack Registerinhalte
FFFFFFFFh Systemspeicher lokale Variablen
(Große Adressen) Funktionsparameter
(kurzlebige Daten)
.TXT Programmkode
.DATA Initialisierte
globale und statische Variablen
.BSS Nicht initialisierte
globale und statische Variablen
Wachstumsrichtung
Abbildung 2-2: Prinzipielles Windows Prozess-Speicher-Layout nach [ 3 S. 182 ] und [ 4 S. 397 ]
Seite - 13 -
14. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Stack
Der Stack ist das Speichersegment, das zum Ablegen kurzlebiger Daten dient. So werden auf dem
Stack beispielsweise lokale Variablen und Funktionsparameter abgelegt.
In Abbildung 2-2 ist, auf der rechten Seite, ein typisches Stackframe abgebildet. Anhand der Pfeile,
die die Wachstumsrichtung angeben, lässt sich erkennen, dass der Stack von großen Adressen in
Richtung kleiner Adressen wächst.
Das Prinzip eines Stacks, oder auch Stapelspeicher genannt, ist folgendes:
1. Sollen neue, kurzlebige Daten zwischengespeichert werden, so werden diese auf die bereits
auf dem Stack liegenden Daten von oben aufgelegt. Die Operation zum Auflegen neuer
Daten nennt sich "push".
2. Werden die auf dem Stack gelegten Daten zur Verarbeitung benötigt oder muss der Stack
aufgeräumt werden, so wird von oben, Stück für Stück, abgebaut. Die Operation zum
Herunternehmen von vorhandenen Daten nennt sich "pop".
Wenn innerhalb eines Prozesses eine Funktion aufgerufen wird, so werden die benötigten Daten, wie
in der Abbildung 2-2 auf der rechten Seite zu sehen ist, auf dem Stack abgelegt (push). Ist die
Funktion beendet, so werden die Daten in umgekehrter Reihenfolge wieder vom Stack genommen
(pop).
Der Teil des Maschinencodes, der ein Stackframe aufbaut, nennt sich Prolog und der abbauende Teil
nennt sich Epilog.
Genauere Informationen zur Funktionsweise des Stacks können in [ 5 ] gefunden werden.
Heap
Der Heap ist der dynamische Speicherbereich eines Prozesses. Er kann dynamisch in seiner Größe
wachsen, somit kann der Prozess dynamisch Speicher anfordern und freigeben.
Der Heap ist in Abbildung 2-2 im Bereich der niedrigen Speicheradressen zu sehen, was für Windows
typisch ist. Außerdem ist in der Abbildung 2-2 dargestellt, dass der Heap in Richtung großer Adressen
wächst.
Inhaltlich gesehen werden auf dem Heap, verglichen mit dem Stack, langlebigere Daten abgelegt.
Ein Prozess hat typischer Weise mehrere Heaps. Ein neuer Heap kann z. B. mit der Windows-API-
Funktion HeapCreate() angefordert und mit HeapDestroy() wieder freigegeben werden. Das
Anfordern eines Speicherbereiches erfolgt anschließend mit HeapAlloc() und wird analog mit
HeapFree() wieder freigegeben. Bei Verwendung des c++-Operators new, wird automatisch
Speicher von dem Standard-Heap des Prozesses angefordert.
Seite - 14 -
15. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Wie bereits angedeutet, hat der Programmierer die Wahl. Entweder er verwendet die von der
Windows-API bereitgestellten Funktionen für das Heap-Management, oder er verwendet seine
eigene Heap-Verwaltung. [ 6 ]
Die von Windows standardmäßig verwendete Heap-Verwaltung organisiert den Speicher in einer
Reihe unterschiedlicher Listen, die zusammen den gesamten Heap beschreiben. Dabei gibt es unter
anderem, eine Liste für noch freie, kleine Speicherblöcke mit Größe 4KByte und eine separat
geführte Liste, die alle belegten Blöcke enthält. Zudem werden temporären Listen erstellt, die der
Geschwindigkeitserhöhung dienen. [ 7 ] [ 8 ] [ 9 ]
2.1.4 Big- und Little-Endian
Als Big- und Little-Endian, werden zwei verschiedene Darstellungsformen von Werten, im Speicher
bezeichnet.
Beispiel: Darstellung der Speicher-Adresse AABBCCDD Hex = 2864434397 Dez im Speicher
Big-Endian: Das höchstwertige Paar steht ganz links, wie in Abbildung 2-3 zu sehen.
Big-Endian
AA BB CC DD
00000000h FFFFFFFFh
Abbildung 2-3: Big-Endian im Speicher
Little-Endian: Das höchstwertige Paar steht ganz rechts, wie in Abbildung 2-4 zu sehen.
Little-Endian
DD CC BB AA
00000000h FFFFFFFFh
Abbildung 2-4: Little-Endian im Speicher
Seite - 15 -
16. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
3 Grundlagen – Buffer Overflows
Ziel dieses Kapitel ist es, dem Leser die Grundkonzepte von Buffer Overflows zu vermitteln um so die
von Windows XP und Windows Vista verwendeten Schutzmechanismen besser beurteilen zu können.
Im Folgenden sind immer nur die unmittelbar relevanten Codeabschnitte gelistet. Die vollständigen
Programme mit Hinweisen zum Übersetzen und zugehöriger Programmausgaben sind im Anhang zu
finden.
Wichtiger Hinweis für Selbstversuche
Mit Visual Studio 2005 lassen sich die meisten Beispielprogramme nicht ohne weiteres mit den
erwarteten Ergebnissen übersetzen. Der Grund hierfür sind Optimierungen und
Schutzmechanismen, die der Visual Studio 2005 Compiler verwendet. Hinweise zum
„richtigen“ Übersetzen, sowie die vollständigen Programme finden sich jeweils im Anhang
3.1 Buffer Overflows
Im Kontext der Programmiersprache c, ist ein Buffer ein zusammenhängender Speicherbereich, der
Daten desselben Typs beinhaltet. In c werden Buffer häufig auch als Arrays bezeichnet.
Ein primitiver Buffer kann in c beispielsweise folgende Form haben:
char buf0[4] = "ABC0";
In diesem Fall ist der Buffer 4 Byte groß und ist ein String-Buffer. Dabei dient das letzte Byte zur
Terminierung des Strings.
Ein Buffer Overflow, oder auch Puffer-Überlauf, passiert demnach, wenn die Grenzen des
vorgesehenen Speicherbereichs überschritten werden.
Die Folgen für das Gesamtsystem können dabei sehr unterschiedlich ausfallen.
So zeigt sich die gefährliche Seite von Buffer Overflows, wenn es dem Angreifer möglich ist
beliebigen Code einzuschleusen und seine Ausführung zu erreichen. In diesem Fall stehen dem
Angreifer nahezu unbegrenzte Möglichkeiten zur Verfügung. Von einem primitiven Beenden der
verwundbaren Software bis hin zum Erstellen von Benutzerkonten oder dem installieren eines Root-
Kits (Details zu Angriffen dieser Art in dem Kapitel 3.1.4).
Seite - 16 -
17. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Bei weniger kritischen Programmierfehlern kann es zu inkonsistenten Daten kommen die nicht
zwangsläufige einen Programmabsturz nach sich ziehen müssen und somit lange unentdeckt bleiben
können. Damit beeinträchtigen diese Fehler zwar den Nutzen der Software, die
Angriffsmöglichkeiten können aber, wie das Beispiel im nächsten Abschnitt zeigt, recht beschränkt
sein.
3.1.1 Off-By-One Buffer Overflows
Ein klassischer Buffer Overflow im Zusammenhang mit c-Strings ist der "Off-By-One"-Fehler. Dabei
vergisst der Programmierer entweder den bereits erwähnten String-Terminator mitzuzählen, oder er
ist bei der indexzählweise der Arrays unaufmerksam.
Mit folgenden Programmausschnitten sollen die Folgen eines Off-By-One Overflows verdeutlicht
werden. Das vollständige Programm mit Compiler-Optionen und vollständiger Ausgabe sind im
Quellcode-Archiv zu finden (Kapitel B).
Quellcode: Kapitel-3-1-1_off-by-oneoff-by-one.c
//gekürzt...
char buf0[] = "1234";
char buf1[4] = "ABC0";
strcpy(buf1,buf0);
Führt man das vollständige Programm aus, so erhält man folgende Ausgabe
~~~ vorher ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Adresse von buf0: 0012FF68 sizeof(buf0): 5 Byte
Adresse von buf1: 0012FF64 sizeof(buf1): 4 Byte
buf0 1234
buf1 ABC
~~~ nachher ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
buf0
buf1 1234
Betrachtet man diese Programmausgabe genauer, so fällt auf, dass der Buffer buf1 zwar den
gewünschten Inhalt erhalten hat, buf0 seinen Inhalt aber offenbar verloren hat.
Was hier passiert ist schnell erklärt: Es kommt zu einen Buffer Overflow, weil der Programmierer
vergessen hat, dass der Compiler an der Stelle char buf0[] = "1234"; automatisch ein 5-Byte
großes, null-terminiertes String-Array erstellt.
Deshalb überschreibt strcpy() mit dem '0' den ersten Teil des Buffers buf0, der nach buf1 im
Speicher liegt. Die Abbildung 3-1 soll diesen Vorgang verdeutlichen.
Hinweis zum Speicherlayout: Die Anordnung von Variablen im Speicher wird immer zum Zeitpunkt
des Übersetzens festgelegt und wird von dem Compiler entschieden. Der Compiler folgt beim
Erstellen des Speicherlayouts zwar festen Regeln, eine feste Beziehung zwischen der Reihenfolge der
Seite - 17 -
18. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Definition/Deklaration im Quellcode und dem tatsächlichen Layout existieren jedoch nicht. Zur
Erinnerung: Der Heap unterliegt anderen Regeln, da dieser Speicher erst zur Laufzeit erstellt wird.
buf1 buf0
0x0012FF64 0x0012FF68
1. Vorher A B C '0' 1 2 3 4 '0'
2. strcyp()
3. Nachher 1 2 3 4 '0' 2 3 4 '0'
Abbildung 3-1: Die Buffer buf0 und buf1 im Speicher
Gibt man mittels printf("buf0 %sn", buf0); den String buf0 aus, so wird nichts ausgegeben,
weil der String-Terminator bereits an erster Stelle steht und printf() mit dem Formatstring %s, nur
Zeichen bis zu dem ersten vorkommenden '0' ausgibt.
Off-By-One Buffer-Overflows sind in den meisten Fällen nichts weiter als ärgerliche
Programmierfehler die relativ schwer zu finden sind. Der Nutzen ist für Angreifer bei
Programmierfehlern dieser Art meist sehr beschränkt. Hauptsächlich deshalb, weil der
Speicherbereich der durch den Benutzer beeinflusst werden kann, mit einer Speichereinheit (im
obigen Beispiel 1 Byte) nur sehr wenig Spielraum bietet. Die Folgen hängen aber davon ab, welche
Art von Informationen in den überschreibbaren Bereichen gespeichert sind.
Wie interessant selbst ein Off-By-One Buffer-Overflow werden kann zeigt der folgende Abschnitt.
3.1.2 Mit Buffer Overflows den Kontrollfluss ändern
Im dem folgenden Programm kann ein Buffer Overflow dazu verwendet werden eine Variable zu
überschreiben, die wesentlich für den Ablauf des Programms ist.
Quellcode: Kapitel-3-1-2_auth_overflowauth_overflow.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "hacking.h"
int check_authentication(char *password) {
int auth_flag = 0;
char password_buffer[8] = {'F', 'F', 'F', 'F', 'F', 'F', 'F', 'F'};
printf("~~~ vorher ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~n");
printf("dump: password_buffern");
Seite - 18 -
19. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
dump(password_buffer, 8);
printf("dump: auth_flagn");
dump(&auth_flag, 4);
strcpy(password_buffer, password);
if(strcmp(password_buffer, "password") == 0)
auth_flag = 1;
if(strcmp(password_buffer, "adminpw") == 0)
auth_flag = 1;
printf("~~~ nachher ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~n");
printf("dump: password_buffern");
dump(password_buffer, 8);
printf("dump: auth_flagn");
dump(&auth_flag, 4);
return auth_flag;
}
int main(int argc, char *argv[]) {
//... gekürtzt
if(check_authentication(argv[1])) {
printf("n-=-=-=-=-=-=-=-=-=-=-=-=-=-n");
printf(" Zugang freigegeben.n");
printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-n");
} else {
printf("nZugang verweigert.n");
}
getchar();
}
Hinweis: die Funktion dump() ist in der Include-Datei hacking.h definiert. Sie gibt von einer, per
ersten Parameter übergebenen Speicheradresse ausgehend, eine mit dem zweiten Parameter
definierte, Menge an Speicherinhalten, aus.
Das Programm übernimmt den ersten Parameter, den das Hauptprogramm übergeben bekommt und
kopiert ihn in der Funktion check_authentication() in einen Buffer, der als Zwischenspeicher
dient.
Stimmt das Passwort mit einem der gespeicherten Passwörter überein, so wird die Variable
auth_flag auf 1 gesetzt, was "wahr" entspricht.
Das Hauptprogramm nimmt den Rückgabewert der Funktion check_authentication() um
Zugang zu einem geschützten Bereich zu gewähren oder zu verweigern.
Seite - 19 -
20. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Dabei gibt es in dem Programmfluss keinen Else-Zweig, der im Falle, keiner Übereinstimmung, die die
Variable auth_flag auf 0 setzen würde. Der Programmierer hat diesen Fall durch das Initaialisieren
mit 0 abgedeckt.
Gegen dieses Vorgehen ist prinzipiell nichts einzuwenden. Das Problem mit dem obigen Code ist
jedoch, dass die Funktion strcpy() unabhängig von der Größe des Zielbuffers password_buffer
alle Zeichen bis zum ersten '0' kopiert.
Diese Frage lässt sich mit Hilfe der Programmausgabe beantworten:
~~~ vorher ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dump: password_buffer
0012FF54: 46 46 46 46 46 46 46 46 | FFFFFFFF
dump: auth_flag
0012FF5C: 00 00 00 00 | ....
... gekürzt
Wie hier zu sehen ist liegt die Variable auth_flag unmittelbar nach dem Buffer password_buffer
im Speicher.
Das bedeutet, sollte es dem Benutzer möglich sein, dem Programm als ersten Parameter mehr als
acht Zeichen zu übergeben, so findet durch strcpy()ein Buffer Overflow in den Speicherbereich
der Variable auth_flag statt.
Startet man das Programm beispielsweise folgendermaßen:
auth_overflow.exe 12345678A
So erhält man folgende Ausgabe:
~~~ vorher ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dump: password_buffer
0012FF54: 46 46 46 46 46 46 46 46 | FFFFFFFF
dump: auth_flag);
0012FF5C: 00 00 00 00 | ....
~~~ nachher ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dump: password_buffer
0012FF54: 31 32 33 34 35 36 37 38 | 12345678
dump: auth_flag);
0012FF5C: 41 00 00 00 | A...
-=-=-=-=-=-=-=-=-=-=-=-=-=-
Access Granted.
-=-=-=-=-=-=-=-=-=-=-=-=-=-
Wie zu sehen ist, hat der Benutzer mittels "12345678" acht Zeichen übergeben und noch ein
zusätzliches 'A' angehängt.
Wie in der Abbildung 3-2 zu sehen ist, wird das 'A' an die Speicheradresse 0x0012FF5C geschrieben.
Der Rückgabewert der Funktion check_authentication() ist als int deklariert und wird durch
das Programm als wahr oder falsch interpretiert. Da in der Programmiersprache c alle Werte ungleich
0 dem Wert "Wahr" entsprechen, wird im Falle eines Overflows der Zugang zum geschützten Bereich
gewährt.
Seite - 20 -
21. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Overflowrichtung
password_buffer auth_flag
0x0012FF54 0x0012FF5C
1. Vorher F F F F F F F F 0 0 0 0
2. strcyp()
3. Nachher 1 2 3 4 5 6 7 8 A 0 0 0
Abbildung 3-2: Buffer Overflow von password_buffer in auth_flag
Dieses Beispiel zeigt eindrucksvoll, wie bereits ein einziges Zeichen an der richtigen Stelle genügen
kann, um den Ablauf eines Programms wesentlich zu beeinflussen.
Hinweis: Werden in diesem Programm mehr wie zwölf Zeichen übergeben, so stürzt das Programm
ab, da Informationen auf dem Stack überschrieben werden, die für den weiteren Programmablauf
benötigt werden. Abgesehen von Programmabstürzen, bietet diese Möglichkeit der Manipulation
von Werten, einen interessanten Ansatzpunkt für weitere Angriffsmöglichkeiten, auf die in den
folgenden Abschnitten eingegangen wird.
Bei dem zuletzt vorgestellten Overflow, ist außerdem zu bedenken, dass nicht nur Zahlenwerte und
Strings auf diese Weise manipuliert werden können. Es ist ebenso möglich Funktionspointer zu
überschreiben. Auf diese Weise kann wahlfrei, vorhandener, oder auch eingeschleuster
Programmcode ausgeführt werden.
Damit kommt dieses Grundagekapitel zu den "nützlicheren" Angriffsmöglichkeiten mittels Buffer
Overflows: Dem Ausführen von beliebigem Code.
3.1.3 Ausführen von beliebigem Code
Um auf einem Zielsystem beliebigen Code auszuführen sind prinzipiell immer zwei Schritte nötig
1. Den auszuführenden Code in den Arbeitsspeicher des Zielsystems einschleusen
2. Eine Möglichkeit finden den eingeschleusten Code ausführen zu lassen
Der erste Punkt lässt sich, ähnlich wie im Beispiel des letzten Abschnitts, mittels Programmparameter
erreichen.
Der zweite Punkt wird meistens durch das Manipulieren eins Funktionspointers erreicht. Dazu
können vom Programmierer selbst angelegte Funktionspointer verwendet werden, oder aber auch
auf dem Stack liegende Rücksprungadressen. [ 5 ]
Seite - 21 -
22. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Da die wirkliche Herausforderung mehr beim Ausführen, als beim Einschleusen liegt, werden in den
restlichen Abschnitten dieses Kapitels einige Möglichkeiten vorgestellt, um den Code zur Ausführung
zu bringen.
3.1.4 Klassische Stack-basierende Angriffe
Stack-basierende Angriffe verfahren normaler weise streng nach dem bereits vorgestellten Schema:
1. Den auszuführenden Code in Arbeitsspeicher des Zielsystems einschleusen
2. Eine Möglichkeit finden den gespeicherten Code ausführen zu lassen
Eine Möglichkeit den Code einzuschleusen haben wir bereits kennen gelernt: Die strcpy()-Funktion.
Kommen wir also zu Punkt zwei: Dem Starten des eingeschleusten Codes.
Bei klassischen Stack-basierenden Angriffen erfolgt das Starten des Codes durch das Manipulieren,
der auf dem Stack gespeicherten Rücksprung-Adresse(RET). Betrachtet man die in Abbildung 3-3
dargestellte Speicherbelegung, so stellt man fest, dass RET an einer höheren Adresse im Speicher
liegt als die lokalen Variablen.
Beispielhaftes
Stackframe
0000000h
(Kleine Adressen) Gespeicherte Register der
aufgerufenen Funktion
Overflowrichtung
Wachstumsrichtung
Lokale Variablen
Stack Frame Pointer (SFP)
Return Adresse (RET)
FFFFFFFFh Parameter
(Große Adressen)
Abbildung 3-3: Beispielhaftes Stackframe unter Windows allgemein
Da lokal angelegte String-Arrays (Buffer) nichts weiter sind als die in Abbildung 3-3 dargestellten
lokalen Variablen, ist es möglich, mit einer ausreichend großen Eingabe die Return-Adresse zu
überschreiben.
Damit der nächste Schritt, das Bestimmen der gewünschten Rücksprungadresse, einfacher ist, wird
als Füllmenge (um RET zu erreichen) ein spezieller Assembler-Befehl verwendet: ein NOP. NOP steht
für No OPeration und hat als einzige Aufgabe einen Maschinenzyklus zu verbrauchen. Danach führt
Seite - 22 -
23. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
die CPU die Befehlsverarbeitung mit dem darauf folgenden Befehl fort. Typischer Weise werden an
dieser Stelle einige NOPs aufeinander folgen und bilden so einen NOP-Sled2.
Um nun den eingeschleusten Code ausführen zu lassen, muss RET auf den ersten eingeschleusten
Befehl zeigen. Wenn ein NOP-Sled verwendet wird, reicht es an dieser Stelle aus, einen beliebigen
Punkt auf dem NOP-Sled zu treffen. Das ist besonders hilfreich, weil der Compiler mit dem das
verwundbare Programm übersetzt wird möglicher Weise einige Optimierungen durchführt, die die
exakte Positionsbestimmung des ersten Befehls erschweren können. Aus demselben Grund ist es
üblich die gewünschte Return-Adresse mehrfach zu wiederholen.
Besondere Aufmerksamkeit muss man der Ausrichtung der eingeschleusten Return-Adresse widmen.
Das bedeutet man muss darauf achten, dass das erste Byte der selbst geschriebenen Return-Adresse
auch wirklich über das erste Byte der alten Return-Adresse geschrieben wird. Ohne Verwendung
eines Debuggers, lässt sich die korrekte Ausrichtung am einfachsten durch Probieren finden. Dabei
sollten maximal vier Versuche nötig sein, da eine Adresse bei 32-Bit-Systemen nur 4 Byte groß ist und
es somit nur vier Möglichkeiten gibt.
Die absolute Adresse des Puffers wird meist mittels eines Debuggers ermittelt und auf x86-Systemen
in Little-Endian-Anordnung eingebgeben. Interessant hierbei ist auch, dass der Stack eines Prozesses
immer an derselben Adresse beginnt, was bedeutet, dass auch hier Probieren zum Ziel führen kann.
Alternativ können zur Positionsbestimmung von Buffern auch Formatstring-Verwundbarkeit
verwendet werden [ 5 ] . Auf Formatstrings wird in dieser Arbeit jedoch nicht eingegangen, weil sie
nicht Teil des Themas "Buffer-Overflows" sind.
Insgesamt wird dem auszunutzenden Programm die in Abbildung 3-4 dargestellte Kombination
übergeben. Diese Kombination aus NOP-Sled, Payload3 und widerholter Return-Adresse, wird häufig
als Angriffsvektor bezeichnet.
RET
NOP-sled Payload
(mehrfach wiederholt)
Abbildung 3-4: Angriffsvektor
Bleibt noch zu erwähnen, dass die Speichermenge die für den Payload zur Verfügung steht, durch die
Position der Return-Adresse begrenzt ist. Dieses Problem lässt sich aber durch einen relativen
Sprung, nach den Speicherbereich mit der Return-Adresse umgehen. [ 10 ]
2
NOP-Sled/NOP-Sledge: Übersetzt: NOP-Schlitten. Das bedeutet, eine Folge von NOPs führen direkt, ohne jede weitere
Funktion, zu dem nächsten „richtigen“ Maschinenbefehl. Beispielsweise dem ersten Befehl des Sehellcodes.
3
Der Payload/Shellcode besteht aus Maschinencode. Er ist der Teil des einzuschleusenden Codes, der die eigentliche
Funktionalität beinhaltet. Häufig werden die Begriffe Payload und Shellcode als Synonym verwendet. Details zu Shellcode
werden in dem Kapitel 3.2 behandelt
Seite - 23 -
24. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Tools-Hinweis
Die felxibelste Möglichkeit einen Angriffsvektor zu erstellen ist mit der Programmiersprach Perl.
Perl gibt es auch für Windows: Toolsverzeichnis IV
Beispiel: stack-overflow.c im Quellcode-Ordner "Kapitel-3-1-4_stack-overflow"
Eine Variante des Programms auth_overflow.c ist unter dem Namen stack-overflow.c Quellcode-
Archiv zu finden. Dieses Programm bekommt seine Eingabe dabei nicht als Parameter, sondern liest
von einer Datei. Außerdem wird nicht die Funktion strcpy() verwendet, sondern die Funktion
memcpy(). Für diese Modifikationen gibt es zwei Gründe:
1. Mit der Windows-Eingabeaufforderung ist es nicht ohne weiteres möglich einem Programm,
per Parameter Zeichen zu übergeben, die nicht "druckbar" sind. Da das auf die meisten
Maschinenbefehle zutrifft, ist ein einfaches Einschleusen von Code über die Kommandozeile
nicht möglich. Abhilfe würde an dieser Stelle das Programm cat4 schaffen mit dem man die
Eingabe an das Programm umleiten kann. Cat ist aber nicht Bestandteil von Windows. Zudem
besteht das Problem der Parameterübergabe mit nichtdruckbaren Zeichen auch in den
meisten Debuggern. Aus diesem Grund liest das Demo-Programm aus einer Datei.
2. Wie in dem Abschnitt 2.1 beschrieben, liegt der für das Programm selbst zur Verfügung
stehende Speicher im niederen Adressbereich. Deshalb ist die Wahrscheinlichkeit sehr hoch,
dass der Buffer in dem der eingeschleuste Code liegt in einem Adressbereich liegt, der mit
zwei führenden Nullen (0x00AABBCC) beginnt. Das bedeutet, dass strcpy() die
Speicheradresse nicht mehr kopiert. Es gibt eine Reihe von Möglichkeiten den
eingeschleusten Code trotz dieses strcpy()-Problems zur Ausführung zu bringen
(beispielsweise "return to libc"). Um das Beispiel einfach zu halten wird dieses Problem an
dieser Stelle durch die Verwendung von memcpy() umgangen.
Trotz dieser zwei Vereinfachungen, bleibt das demonstrierte Prinzip in allen Fällen gleich: Den
Angriffsvektor einschleusen und den Maschinencode zur Ausführung bringen.
Ein funktionierender Angriffsvektor zu dem Programm stack-overflow.c ist ebenfalls im Quellcode-
Archiv zu finden. Dabei wird der Angriffsvektor mittels eines Perl-Skripts generiert. Der hierbei
verwendete Payload startet hierbei nichts weiter als den Windows-Taschenrechner clac.exe. Details
zur Erstellung von Payload folgt in dem Abschnitt 3.2.
4
cat ist ein Kommandozeilen-Tool aus der UNIX-Welt, mit dem sich unter anderem sehr einfach Eingaben aus einer Datei
lesen lassen. Das Tool gibt es auch für Windows
Seite - 24 -
25. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Gekürzte Ausgabe des Programms stack-overflow.exe
~~~ nachher ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dump(password_buffer...);
0012FA48: 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
0012FA58: 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................ NOP-sled
0012FA68: 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
0012FA78: 29 c9 83 e9 dd d9 ee d9 74 24 f4 5b 81 73 13 b3 | ).......t$.[.s..
0012FA88: 87 5d 85 83 eb fc e2 f4 4f 6f 19 85 b3 87 d6 c0 | .]......Oo......
0012FA98: 8f 0c 21 80 cb 86 b2 0e fc 9f d6 da 93 86 b6 cc | ..!.............
0012FAA8: 38 b3 d6 84 5d b6 9d 1c 1f 03 9d f1 b4 46 97 88 | 8...]........F..
0012FAB8: b2 45 b6 71 88 d3 79 81 c6 62 d6 da 97 86 b6 e3 | .E.q..y..b......
0012FAC8: 38 8b 16 0e ec 9b 5c 6e 38 9b d6 84 58 0e 01 a1 | 8.....n8...X... Payload
0012FAD8: b7 44 6c 45 d7 0c 1d b5 36 47 25 89 38 c7 51 0e | .DlE....6G%.8.Q.
0012FAE8: c3 9b f0 0e db 8f b6 8c 38 07 ed 85 b3 87 d6 ed | ........8.......
0012FAF8: 8f d8 6c 73 d3 d1 d4 7d 30 47 26 d5 db f9 85 67 | ..ls...}0G&....g
0012FB08: c0 ef c5 7b 39 89 0a 7a 54 e4 3c e9 d0 a9 38 fd | ...{9..zT.<...8.
0012FB18: d6 87 5d 85 50 fa 12 00 50 fa 12 00 50 fa 12 00 | ..].P...P...P...
0012FB28: 50 fa 12 00 50 fa 12 00 50 fa 12 00 50 fa 12 00 | P...P...P...P... RET
0012FB38: 50 fa 12 00 50 fa 12 00 50 fa 12 00 50 fa 12 00 | P...P...P...P... (mehrfach wiederholt)
0012FB48: 50 fa 12 00 50 fa 12 00 50 fa 12 00 50 fa 12 00 | P...P...P...P...
0012FB58: ff | .
dump(&auth_flag, 4);
0012FA44: 00 00 00 00 | ....
Erklärung:
1. Der Wert 90 Hex entspricht einem NOP
2. Der rot markierte Bereich ist der Maschinencode zum Starten von calc.exe
3. Am Schluss folgt die mehrfach wiederholte Return-Adresse
3.1.5 Exception Handler-basierende Angriffe
Die Programmiersprache c bietet von sich aus keinerlei Möglichkeiten, Ausnahmen im
Programmablauf abzufangen. Eine solche Ausnahme ist beispielsweise eine Division durch Null. Mit
C++ wurde dieser Missstand behoben und es ist somit, durch Elemente der Programmiersprache,
möglich eine Ausnahmebehandlung durchzuführen. Das bedeutet, dass ein Programm, das eine
Division durch null durchführt, nicht mehr zwangsläufig abstürzt, sondern durch eine geeignete
Ausnahmebehandlung weiterlaufen kann. [ 11 ]
Microsoft hat für die Programmiersprache c eigene Elemente eingebracht, mit denen eine
Ausnahmebehandlung möglich wird: __try und __except
Diese Art der Ausnahmebehandlung nennt sich Structured Exception Handling (SEH). Im Unterschied
zu c++ wird die Art der Ausnahme mittels eines unsigned int ermittelt, während in c++ anhand des
Klassen-Typs unterschieden wird. [ 11 ] [ 12 ]
Seite - 25 -
26. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Beispiel: Strukturierte Ausnahmebehandlung (SEH) - Division durch Null
Quellcode: Kapitel-3-1-5_exceptionexception.c
#include <stdio.h>
int MyExceptionHandler(void);
int main(int argc,char *argv[]){
int a = 1000, b = 0, c = 0;
if(argc != 2){
printf("Divisor als Parameter uebergebenn");
exit(0);
}
b = atoi(argv[1]);
/* Falls eine 0 als erster Programmparameter übergeben wird, wird
* eine EXCEPTION_INT_DIVIDE_BY_ZERO ausgelößt */
__try {
c = a / b;
printf("Ergebnis der Division %i / %i = %in", a, b, c);
} __except ( MyExceptionHandler() ){}
printf("Programm beendet sichn");
return 0;
}
/* return 1, damit die Exception nicht and das
* Betriebsystem weitergereicht wird */
int MyExceptionHandler(void){
printf("Exception.. Ausnahme abgefangen und als behandelt markiert");
return 1;
}
SEH lässt sich im Zusammenhang mit Buffer Overflows nutzen, um eingeschleusten Code zur
Ausführung zu bringen. Dabei macht sich der Angreifer den Umstand zu nutze, dass die
Funktionsadresse der Funktion zur Ausnahmebehandlung, wie in Abbildung 3-5 zu sehen ist, auf dem
Stack gespeichert wird.
Seite - 26 -
27. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Beispielhaftes
Stackframe
0000000h
(Kleine Adressen)
Gespeicherte Register der
aufgerufenen Funktion
Overflowrichtung
Wachstumsrichtung
Lokale Variablen
Stack Frame Pointer (SFP)
Return Adresse (RET)
FFFFFFFFh
(Große Adressen) Parameter
...
Pointer zur nächsten
SEH-Funktion
Pointer zur SEH-Funktion
Abbildung 3-5: Beispielhaftes Stackframe unter Verwendung von SEH [ 13 ]
Wie bei den klassischen Stack-basierenden Buffer Overflows, wird von einem verwundbaren Buffer
ausgehend, diese Funktionsadresse im Speicher überschrieben. Der Unterschied besteht lediglich
darin, dass die Behandlung der Ausnahme veranlasst werden muss, z. B. mit einer Division durch
Null. [ 14 ]
Interessant ist hierbei, dass jedes Programm mindestens einen, automatisch erstellten SEH besitzt,
den der main()-Routine.
Im Quellcode-Archiv findet sich im Ordner "Kapitel-3-1-5_exception_exploit" ein vollständiges
Beispiel, wie sich SEH unter Windows praktisch ausnutzen lässt.
3.1.6 Heap-basierende Angriffe
Heap-basierende Angriffe funktionieren prinzipiell genauso, wie alle anderen Buffer-Overflows auch.
Es werden Daten überschrieben, die nach einem verwundbaren Puffer im Speicher liegen. Der
einzige Unterschied besteht darin, dass, verglichen mit den anderen Datensegmenten, im Heap
andere Daten gespeichert sind.
Folgende Daten finden sich vermehrt bzw. nur auf dem Heap:
1. Nutzdaten. Beispielsweise die Variable auth_flag aus dem Abschnitt 3.1.2 (gibt es auch auf
beim Stack)
Seite - 27 -
28. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
2. Daten die den Programmablauf steuern und vom Programmierer bewusst angelegt wurden
(auch auf dem Stack vorhanden, aber selten)
3. Daten die den Programmablauf steuern und vom Compiler automatisch angelegt werden
4. Daten die der Speicherverwaltung dienen. Beispielsweise das Windows-Heap-Management
Den Punkt "Nutzdaten", wurde bereits in dem Abschnitt 3.1.2 ausführlich behandelt und da an dieser
Stelle keine wesentlichen Unterschiede existieren, wird an dieser Stelle nicht näher darauf
eingegangen.
Der zweite Punkt ist vom prinzipiellen Vorgehen bereits bekannt.
Der Punkt Nummer Drei bietet interessante Möglichkeiten, ist in der Praxis jedoch kaum anzutreffen.
Punkt Nummer Vier hat in den letzten Jahren deutlich an Bedeutung gewonnen und ist vor allem im
Zusammenhang mit dem Windows-Heap-Management interessant. Deshalb wird im Folgenden das
prinzipielle Vorgehen bei Angriffen auf das Windows-Heap-Management erläutert.
Angriffe auf das Windows-Heap-Management
Wie bereits in dem Abschnitt 2.1.3 beschrieben, ist ein Heap, der mit HeapCreate()erzeugt wird,
durch eine Reihe von Listen organisiert. Wird mit der Funktion HeapAlloc() ein Speicherbereich
angefordert, so wird von der Heapverwaltung ein passendes Stück Speicher gesucht. Dieses Stück
Speicher wird, unmittelbar vor dem nutzbaren Bereich, mit einem Header versehen, der
Informationen für die Heapverwaltung enthält. [ 15 ] [ 8 ]
Bei Angriffen auf die Heapverwaltung ist es das Ziel, die Headerinformationen derart zu
manipulieren, dass eine begrenzte Menge an Daten an eine beliebige Stelle im Speicher geschrieben
werden kann. Hierbei macht sich der Angreifer zunutze, dass die Headerinformationen Pointer
enthalten, die zur Verkettung der Listen und zur Referenzierung des Nutzspeichers dienen. Beim
Anfordern und Freigeben von Speicher werden die Header teilweise kopiert und unter bestimmten
Bedingungen in einer doppelt verketteten Liste gespeichert. Zur Bestimmung der jeweiligen
Nachbarelemente werden die Headerinformationen verwendet. Somit ist es möglich, durch gezieltes
Überschreiben der entsprechenden Pointer, einen wahlfreien Schreibzugriff mit einer Datenmenge
von 4 Bytes zu erreichen. Dieser Vorgang lässt sich prinzipiell beliebig oft wiederholen, meist ist
jedoch eine Menge von 4 Bytes vollkommen ausreichend, da dies die Größe eines Pointers ist. Somit
lässt sich beispielsweise im Process Execution Block (PEB) ein Pointer überschreiben. Hier bietet sich
der Pointer auf die Funktion ExitProcess() an, die vor dem endgültigen Beenden des Prozesses
ausgeführt wird. Durch das Überschreiben des ExitProcess()-Pointers kann eingeschleuster Code
durch das Beenden des Programms zur Ausführung gebracht werden. [ 15 ] [ 8 ] [ 16 ] [ 17 ] [ 18 ] [
16 ] [ 19 ]
In der Praxis gibt es eine Vielzahl von Variationen der eben beschriebenen Methode. Hierbei haben
jedoch alle Varianten die Gemeinsamkeit, dass sie durch gezieltes Manipulieren der Header, Code zur
Ausführung bringen.
Seite - 28 -
29. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Einen besonderen Reiz haben vor allem die Varianten, die es dem angegriffenen Programm
ermöglichen weiter zu laufen, indem sie die beschädigten Heapstrukturen wieder mit gültigen
Werten reparieren. Ein weiterer Grund für die gestiegene Popularität von Angriffen auf das
Heapmanagement sind vor allem Mechanismen, die zum Schutz des Stacks eingeführt wurden und
den Heap so zum schwächsten Glied der Kette werden ließen. Details zu aktuellen
Schutzmechanismen des Stacks werden im Kapitel 4.6 behandelt.
3.2 Shellcode und Payload
Die beiden Begriffe Shellcode und Payload werden heutzutage häufig als Synonym verwendet. Streng
genommen ist der Shellcode jedoch eine spezielle Form des Payloads:
1. Der Payload an sich ist der Teil des Angriffsvektors, der die eigentliche Funktionalität
beinhaltet, also beispielsweise das Starten des Windows-Taschenrechners.
2. Der Shellcode ist streng genommen ein spezieller Payload, der eine Shell
(Eingabeaufforderung) startet, mit der der Angreifer interagieren kann.
In dieser Arbeit wird häufig ebenfalls der Begriff "Shellcode", anstelle des historisch korrekten
Begriffes verwendet. Die Begründung ist die Tatsache, dass der Begriff Shellcode einen höheren
Wiedererkennungswert bietet und selbst in Printmedien gebräuchlicher ist.
Da das Thema Shellcode, bei sorgfältiger Behandlung, den Umfang dieser Arbeit bei weitem
sprengen würde, wird im Folgenden nur stichpunktartig auf Anforderungen an Shellcode
eingegangen.
3.2.1 Anforderungen an Shellcode
Anforderungen an die Darstellung des Shellcodes :
1. Zeichensatz: Shellcode unterliegt einigen Einschränkungen bezüglich der verwendbaren
Zeichen. Beispielsweise darf in Shellcode der durch strcpy() eingeschleust wird, nicht das
Zeichen 0x00 vorkommen, weil strcpy() nur bis zu diesem Zeichen kopiert. Außerdem werden
die eingegebenen Zeichen häufig von dem angegriffenen Programm geprüft und z. B. nur
druckbare Zeichen verarbeitet.
2. Kompatibilität: Die Kompatibilität von Shellcode ist unumgänglich auf eine Art der
Speicheranordnung beschränkt: entweder Big- oder Little-Endian.
3. Kodierung: Soll eine Funktion angegriffen werden die eine UTF-8 kodierte Zeichenfolge
erwartet, so muss außer der Big- und Little-Endian-Anordnung außerdem eine bestimmte
Reihenfolge von Zwei-Byte-Paaren eingehalten werden. [ 20 ]
4. Kompaktheit: Häufig ist die Menge an einschleusbaren Zeichen beschränkt.
5. Unentdeckt bleiben: Unter Umständen passiert der Shellcode auf dem Weg zum Ziel
Intrusion Detection Systeme (IDS), die die übertragenen Daten auf bekannte Schad-
Signaturen hin überprüfen.
Seite - 29 -
30. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Anforderungen an die Logik des Shellcodes:
1. Betriebssystemabhängig: Da die meisten Betriebssysteme einen grundliegend
unterschiedlichen Zugriff auf Systemfunktionen haben, muss der Shellcode meistens
Betriebssystemspezifisch sein.
2. Behandlung der Anwendung: Unter bestimmten Umständen hat man die Möglichkeit, das
angegriffene Programm weiter laufen zu lassen und so die Wahrscheinlichkeit, das der
Angriff entdeckt wird stark zu reduzieren.
3.2.2 Besonderheiten von Windows-Shellcode
Verglichen mit Shellcode der unter Linux lauffähig ist, gibt es für Windows-Shellcode eine Reihe von
Besonderheiten
1. Windows-Shellcode ist größer als Linux-Shellcode [ 21 ]
2. Keine 1-zu-1-Abbildung der Bibliotheksfunktionen des Betriebssystems zu den Systemcalls auf
Kernelebene [ 21 ]
3. Die verwundbaren Buffer liegen meist in einem Speicherbereich, der mit 0x00… beginnt und
somit ein nicht verwendbares Zeichen darstellt.
3.2.3 Was möglich ist
Je nachdem welchen Anforderungen (siehe 3.2.1) der eingeschleuste Code unterliegt, ergeben sich
unterschiedliche Möglichkeiten:
1. Erweitern der Rechte durch angreifen von lokalen Prozessen die mit umfangreicheren Privilegien
ausgestattet sind.
2. Nachladen von Systemfunktionen, falls der Prozess eine für den Angriff wichtige Dll-Funktion5
nicht geladen hat, kann man diese Dll selbst nachladen.
3. Nachladen von Code, falls der eingeschleuste Code in seiner Größe beschränkt war, hat man die
Möglichkeit z. B. aus dem Internet Code nachzuladen. [ 21 ]
Wenn man davon ausgeht, dass man die Möglichkeit hat, eine unbegrenzte Menge an
eingeschleusten Code, mit Administrator-Rechten auszuführen, dann hat man praktisch keine
Einschränkungen mehr. Man kann beispielsweise:
1. Ein lokales Benutzer-Konto erstellen (Local Access)
2. Einen Prozess starten, der beliebige Befehle über das Netzwerk ausführt (Remote Access)
3. Einen Keylogger installieren, der sämtliche Aktivitäten des Benutzers aufzeichnet, inklusive
Tastatur- und Mausinteraktion (Spionage)
5
Dynamic Link Library (Dll)
Seite - 30 -
31. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
3.2.4 Abstraktes Beispiel: Portbind Shellcode für Windows
An dieser Stelle folgt ein eine allgemeine Beschreibung, der Vorgehensweise, zum Starten einer
Remote-Shell: [ 21 ]
1. Suchen der Datei kernel32.dll
2. Auflösen der Symbole GetProcAddressA(), LoadLibraryA() sowie ExitProcess()
3. Laden der Winsock Bibliothek ws2_32.dll
4. eine Shell an einen TCP-Port binden
4.1. Socket erzeugen
4.2. Socket an Port binden
4.3. Auf dem Port auf eingehende Verbindung warten
4.4. Verbindung akzeptieren
5. Kommandointerpreter starten
6. Den Elternprozess sauber beenden oder die beschädigten Strukturen reparieren
Um nicht zu sehr vom eigentlichen Thema, den Buffer Overflows, abzukommen, wird an dieser Stelle
nicht weiter auf das Themengebiet Shellcode eingegangen. Interessierten sei [ 21 ] für weitere
Informationen empfohlen.
Es folgt nun eine Vorstellung der Verschiedenen Mechanismen in Windows, die dazu dienen den
Schaden durch Buffer-Overflows so gering wie möglich zu halten.
Seite - 31 -
32. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
4 Das Sicherheits-Ensemble von Windows und seine Schwachstellen
Dieses Kapitels hat es zum Ziel, die Frage nach dem betitelten Stack- und Heap-Overflow-Schutz bei
Windows XP und Windows Vista zu klären. Nicht eingegangen wird auf weitere nützliche Funktionen
zur Erhöhung der Sicherheit, die nichts direkt mit Buffer-Overflows zu tun haben.
Die vorhergegangenen beiden Grundlagenkapitel, haben sich im Wesentlichen damit beschäftigt,
welche Bedingungen, unter früheren Windowsversionen, gegeben sein mussten, um einem Buffer-
Overflow erfolgreich auszunutzen.
Dieses Kapitel beschäftigt sich zu Beginn damit, wie sich der Schaden, eines unter fremder Kontrolle
stehenden Programms, einschränken lässt und welche neuen Ansätze mit Windows Vista verfolgt
werden (bis einschließlich Kapitel 4.2).
Anschließend werden Methoden von Windows XP und Vista analysiert und bewertet, die es zum Ziel
haben, Angriffe mittels Buffer-Overflows so früh wie möglich zu entschärfen.
4.1 Das Windows-Zwei-Schichten-Modell
Den grundlegendsten Schutzmechanismus der Windows-Architektur bildet das Zwei-
Schichtenmodell.
Unter Windows ist die gesamte Funktionalität in zwei unterschiedlich privilegierte Bereiche
eingeteilt: [ 4 ]
Kernel-Mode (Ring 0): Der Modus mit den höchstmöglichen Privilegien – Keine
Einschränkungen beim Zugriff auf die Ressourcen des Kernels
User-Mode (Ring 1): Eingeschränkter Modus – Beim Zugriff auf Systemressourcen muss eine
ganze Abfolge von Funktionen abgearbeitet werden, die vom Ring0 als Services zur
Verfügung gestellt werden.
Häufig werden User- und Kernel-Mode wie in Abbildung 4-1 grafisch dargestellt. Dabei soll
symbolisiert werden, dass auf die innen liegenden Funktionen und Ressourcen nur über die
verbindende Schicht zugegriffen werden kann.
Seite - 32 -
33. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Ring1
(User-Mode)
Ring0
(Kernel-Mode)
Kernel
Abbildung 4-1: Windows-Zwei-Schichten-Modell [ 4 ]
Im Ring0 finden sich vorrangig Gerätetreiber und Systemfunktionen. Im Ring1 hingegen findet sich
alles Übrige, z. B. ein Browser oder ein FTP-Server.
Schafft es ein Angreifer direkt im Ring0 einen Programmierfehler für sich zu nutzen, so stehen im
praktisch keine Hindernisse mehr im Weg. Allerdings erfordern Angriffe auf dieser Ebene sehr
spezifisches Detailwissen, das äußerst selten offen zugänglich ist. Dies ist auch der Grund dafür, dass
Angriffe auf den Ring0 verhältnismäßig selten vorkommen. Ein Beispiel für einen erfolgreichen
Angriff auf den Gerätetreiber diverser W-LAN-Chips ist der Angriff von Jon Ellch (Pseudonym: Jonny
Cache)[ 22 ] . [ 4 ] [ 23 ]
Meist ist es aus dem oben genannten Grund einfacher, eine verwundbare Anwendung im Ring1
anzugreifen. Jedoch haben Prozesse im Ring1 einen Prozess-Kontext, der einen
rechtebeschreibenden Token beinhaltet. Näheres dazu im nächsten Abschnitt.
4.2 Zugriffsrechte, Authentifizierung und Access Token
Meldet sich ein Benutzer an einem Windows-System an, so bekommt er vom Local Security Authority
Subsystem Service (LSA) ein Access Token zugeteilt. Dieser Token besteht aus einem Security
Identifier (SID), der zu seinem Benutzerkonto gehört, und einigen weiteren SIDs, die zu seinen
Gruppen gehören in denen er Mitglied ist. Eine solche SID ist eine 48 Bit lange Nummer, die
domainweit einzigartig ist. Außerdem enthält der Token, falls definiert, eine Liste von nicht erlaubten
SIDs und eine Liste mit Privilegien, die zu den jeweiligen SIDs gehören. Privilegien sind z. B. das Recht
einen Treiber zu laden (SeLoadDriverPrivilege) oder das Recht den Computer herunterzufahren
(SeShutdownPrivilege).[ 4 ] [ 24 ]
Seite - 33 -
34. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Alle Ressourcen, auf die ein Benutzer theoretisch zugreifen kann, haben eine so genannte
Discretionary Access Control List (DACL)6. Praktisch wird der Zugang auf die Ressource durch LSA
reglementiert. Das bedeutet, der Zugriff ist nur erlaubt, wenn eine der SIDs aus dem Token des
Benutzers in der DACL-Liste unter dem entsprechenden Privileg (Lesen, Schreiben usw.) zu finden ist.
[4]
Nach [ 4 ] lässt sich zur besseren Übersicht eine Einteilung in Subjekte und Objekte vornehmen
1. Subjekte: Einheiten, die Aktionen durchführt – unter Windows sind das Prozesse
2. Objekte: Sind die Empfänger einer Aktion – unter Windows zugriffsgeschützte Objekte mit
zugehörigen DACL wie z. B. Dateien und Registry-Einträge
4.2.1 Rechte von Prozessen unter Windows
Startet ein am System angemeldeter Benutzer einen Prozess, so wird in dem Prozess-Kontext der
Token des Benutzers gespeichert. Fortan darf der Prozess (Subjekt) auf alle Ressourcen (Objekte)
zugreifen, auf die auch der Benutzer durch seinen Token Zugriff hat.
Ist nun unter Windows XP ein Administrator am System angemeldet und startet den Internet
Explorer, so hat der Browser denselben Token wie der Administrator. Surft der Administrator nun
beispielsweise über eine Seite, die den populären Animated-Cursor Exploit7 eingebaut hat, so stehen
dem Angreifer keinerlei Hindernisse im Weg. Er hat dank des hoch-privilegierten Token, mit der SID
des angemeldeten Administrators, vollständigen Zugriff auf das gesamte System.
Mit Windows 2000 wurde die Möglichkeit eingeführt, einen Kindprozess/Thread mit einem
eingeschränkten Token zu starten. Der eingeschränkte Token kann, ausgehend von dem Original-
Token, wie folgt definiert werden: Privilegien entfernen, SIDs entfernen, nicht erlaubte SIDs
hinzufügen. Wurde ein Thread mit einem eingeschränkten Token gestartet, so ist er an die neuen
Einschränkungen gebunden. Allerdings ist es möglich dem Thread mit dem eingeschränkten Token,
wieder den uneingeschränkten Token zuzuweisen. Weil diese Möglichkeit besteht, empfiehlt es sich
die API-Funktion CreateProcessAsUser( ) zu verwenden, so dass diese Möglichkeit nicht mehr
besteht. [ 24 ]
Mit Windows Server 2003 wurde es möglich die Privilegien eines Prozess-Token dauerhaft
einzuschränken. Diese Möglichkeit ist aber ungleich der des eingeschränkten Token, da hier der
Prozess dauerhaft seine eigenen Privilegien beschneidet. Alle von ihm erzeugten Kindprozesse erben,
nach demselben Prinzip wie bei Windows 2000, den Token des Elternprozesses, z. B. einen, in den
Privilegien eingeschränkten. Die Möglichkeit die SIDs des Token zu modifizieren besteht hier nicht.[
24 ]
6
DACL wird in der Literatur häufig auch ohne das führende D, nur als Access Control List (ACL), verwendet.
Außerdem wird häufig der Ausdruck Security Descriptor als Synonym verwendet.
7
Animated-Cursor Exploit: http://www.securityfocus.com/advisories/7814
Seite - 34 -
35. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
4.2.2 Windows Service Hardening
Da auch Windows-Services nichts weiter wie Prozesse sind, haben auch sie einen Token über den sie
im Betrieb ihre Berechtigungen erhalten. Für einen Angreifer sind damit die Standard-Services einer
Windows-Installation besonders interessant, weil sie aufgrund ihrer Funktionalität ständig laufen und
typischer Weise Systemfunktionen ausführen, die hohe Privilegien benötigen.
Mit Windows Server 2003 wurden zwei neue Service-Konten eingerichtet die den bisher
übermächtigen LocalSystem entlasten sollten: LocalService und NetworkService. Dabei sind die
beiden neuen Konten in den Rechten derart beschnitten, dass viele Services nicht mit ihnen arbeiten
können und deshalb den bisherigen LocalSystem weiterhin verwenden.[ 4 ]
Zur verbesserten Schadensbegrenzung wurde bei Windows Vista eine Reihe von Maßnahmen
eingeführt, die ein erheblich granulareres Rechtemanagement wie bisher ermöglichen:
1. Service-spezifische SIDs: Jeder Service hat eine eigene SID. Sinnvoller Weise bekommen nur
diejenigen Objekte in ihrer DACL die SIDs des speziellen Services eingetragen, die sie auch
wirklich benötigen.[ 4 ]
2. Eingeschränkte SIDs: Die RESTRICTED-SID kennzeichnet einen Prozess, der ein
eingeschränktes Token enthält. [ 25 ] (nicht auf Services beschränkt)
3. Reduzierte Privilegien für Services: Überflüssige Privilegien, wie das Debugging Privileg
wurden den Diensten generell entzogen [ 4 ]
4. Session 0 Isolation: Unter Windows Server 2003 und früheren Versionen, war es unter
Umständen möglich seine Privilegien durch Angriffe auf Services mit höheren Privilegien
innerhalb derselben Session zu erweitern. Diese Problem wird dadurch entschärft, dass nur
noch Services in der Session 0 laufen und alle vom User gestarteten Anwendungen in
eigenen Session (1 und höher) gestartet werden. [ 26 ] [ 27 ]
5. UAC: siehe nächster Abschnitt (nicht auf Services beschränkt)
4.2.3 Rechte von Prozessen unter Windows Vista: UAC
Es ist zwar bekannt, dass es sich nicht empfiehlt mit Administratorrechten im Internet zu surfen, die
Konsequenz als normaler Benutzer an einem System zu arbeiten, haben jedoch nur wenige
Anwender gezogen. Der Hauptgrund hierfür ist die mangelnde Unterstützung seitens Windows. So ist
es bis zu der Version "Vista" nötig gewesen, eine Druckerinstallation oder das Stellen der Uhrzeit mit
Administratorrechten zu erledigen.
UAC steht für User Account Control und soll das Arbeiten als eingeschränkter Benutzer erleichtern.
Dazu fragt Windows Vista den Benutzer, falls nötig, automatisch nach dem Passwort, für das
Benutzerkonto, dass zum ausführen der Aktion nötig ist. Dabei ist die Interaktion mit den BS solange
gesperrt, bis der Benutzer auf die Anfrage reagiert hat.
Seite - 35 -
36. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Im Detail verwendet UAC eine neue Technik namens Mandatory Integrity Control (MIC). MIC ist eine
Erweiterung für die DACLs um sogenannte Integrity Level (IL). Gespeichert werden diese neuen
Rechte in den Mandatory ACLs (MACL).
Als IL wurden folgende vier Möglichkeiten eingeführt:
Low
Medium (als Standard)
High
System
Jeder Prozess hat in seinem Token zusätzlich ein IL gespeichert. Will ein Prozess mit einem niedrigen
IL auf eine Ressource mit einem höheren IL zugreifen, so ist die oben beschriebene Interaktion mit
dem Benutzer vonnöten.
Damit UAC funktioniert wurde der LSA modifiziert, so dass es jetzt zwei Arten von Token gibt:
einen gefilterten: ohne erweiterte Rechte
einen gelinkten: mit allen Originalrechten
Außerdem muss der Programmierer im Manifest seiner Anwendung das benötigte IL angeben.
Standardmäßig werden alle Prozesse mit dem gefilterten Token ausgeführt. Der gelinkte Token wird
nur verwendet, wenn eine Applikation, aufgrund des Eintrags im Manifest, nach erweiterten Rechten
fragt. Dieses Verhalten führt auch dazu, dass selbst ein angemeldeter Administrator zuerst vom
System um Erlaubnis gefragt wird, wenn die Aufgabe ein höheres IL erfordert.
Als Beispiel für eine sinnvolle Anwendung des UAC kann der Internet Explorer betrachtet werden:
Der Internet Explorer läuft per Voreinstellung mit Low-IL, was bedeutet, er kann nur auf
Objekte mit LOW-IL SIDs zugreifen. Standardmäßig sind das nur
%USERPROFILE%AppDataLocalLow und der Registry-Key HKCUSoftwareAppDataLow. Auf
alle anderen Systemressourcen hat der Prozess des IE keinen Zugriff, was die Sicherheit stark
erhöht.
Wird der IE mit aktivierten UAC betrieben, so wird diese Konstellation häufig als Protected Mode IE
(PMIE) oder auch Low Rights Internet Explorer (LoRIE) bezeichnet.
UAC ist die für den Benutzer offensichtlichste und auch eine der nützlichsten Sicherheitsmaßnahmen
zur Schadensbegrenzung. Allerdings empfinden viele Benutzer die ständige Nachfrage nach
erweiterten Rechten als störend. Außerdem funktionieren nicht alle Programme einwandfrei mit
UAC. Diese zwei Kritikpunkte führen dazu, dass viele Benutzer UAC Systemweit deaktivieren, was den
gesamten Nutzen hinfällig macht.
Seite - 36 -
37. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
4.3 PatchGuard
PatchGuard wurde von Microsoft entwickelt und hat es zum Ziel Software von Drittherstellern daran
zu hindern Änderungen am Kernel vorzunehmen. Dadurch soll potentiell gefährlicher Software
erschwert werden sich einzunisten.
Eingeführt wurde PatchGuard mit den Windows-Versionen Server 2003 mit SP1 (64-Bit) und XP
Professional x64. Eine Portierung auf die x86-Versionen ist derzeit nicht geplant. [ 28 ]
Da PatchGuard eine Reihe von Unzulänglichkeiten besitzt, die das Umgehen des Schutzes relativ
einfach machen, ist der tatsächliche Nutzen in Bezug auf Schadsoftware fraglich. Häufig wird sogar
behauptet, PatchGuard sei lediglich ein Instrument von Microsoft, um die Dritthersteller dazu zu
zwingen, saubereren und stabileren Code zu schreiben. [ 29 ]
Viel Kritik an dem System gab es auch von den Herstellern von Sicherheits-Software, wie Anti-Viren-
Software und Rootkit-Detection-Tools, da sich diese Produkte häufig an den, durch dieses System
nicht mehr erreichbaren Stellen, eingenistet haben, um so von einem möglichst uneingeschränkten
Punkt aus operieren zu können. [ 29 ]
Eine umfassende Analyse der neusten Version (v3) von PatchGuard ist im Internet unter [ 28 ] zu
finden.
4.4 Process Exection Block (PEB) Randomization
Überblick
Mit Windows XP SP28 wurde eine Funktionalität namens PEB-Randomization eingeführt. Ihr Ziel ist
es, Angriffe zu erschweren, die zur Codeausführung PEB-Einträge manipulieren. Im Process Exection
Block (PEB) sind vor allem für Angriffe interessante Funktions-Pointer gespeichert, wie ein Pointer auf
die Funktion ExitProcess().
Funktionsweise
Beim Programmstart wird die Basisadresse des PEB dynamisch gewählt. Dadurch ist ein Remote-
Exploit9, dass den PEB als Einstiegspunkt verwendet, nicht mehr zuverlässig, da die zu
überschreibende Adresse des Funktions-Pointers in dem, jetzt an einem zufälligen Ort im Speicher
liegenden, PEB gespeichert ist.
8
Funktionalität ist auch in Server 2003 SP1 / Vista vorhanden
9
Ein Remote-Exploit ist dadurch charakterisiert, dass der Angreifer entfernt über ein Netzwerk zugreift und
nicht an der Maschine selbst sitzt.
Seite - 37 -
38. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Schwachstellen
Wie sich ein einer Untersuchung von Symantec herausstellte [ 30 ] , beschränkt sich die Anzahl der
Möglichkeiten, für die Basisadresse des PEB, auf 16. Hinzu kommt noch, dass die Adresse in 20% aller
Fälle gleich ist.
Effektiver Schutz
Die Möglichkeit verlässliche Remote-Exploits zu schreiben wurde reduziert. Die Option der Brute-
Force-Angriffe bleibt jedoch, mit einem recht hohen Erfolgsgrad, bestehen.
4.5 Heap-Schutz
Überblick
Wie in Abschnitt 3.1.6 beschrieben, kann es auch auf dem Heap zu Buffer-Overflows kommen. Um
den Schaden begrenzen zu können, wurden mit dem SP2 von Windows XP im Wesentlichen zwei
neue Features, für die von Windows bereitgestellte Heap-Verwaltung, eingeführt: [ 8 ]
Cookies auf dem Heap (vergleichbar mit denen der GS-Funktionalität des Stacks)
Plausibilitätsprüfung (sanity checking) der Headerinformationen der einzelnen Heap-
Segmente
Funktionsweise Cookies
Bei jedem Aufruf von HeapAlloc() wird vor und nach jedem Puffer auf dem Heap ein 1-Byte
großes, mit Pseudo-Zufallszahlen initialisiertes Cookie, abgelegt. [ 8 ]
Im Betrieb wird vor jedem Entnehmen eines Blocks aus der "Frei-Liste" geprüft, ob der Wert des
Cookies noch der Selbe ist, wie zum Zeitpunkt der Initialisierung. Dazu wird der Wert mit dem des
separat gespeicherten Original-Cookies verglichen. Hat sich der Wert geändert, so wird zuerst ein
HeapDestory() ausgeführt und anschließend wird eine Exception geworfen. Falls diese nicht
behandelt wird, wird das Programm beendet. [ 8 ]
Außerdem wurde ein sogenannter Low Fragmentation Heap (LFH) entwickelt, der Primär der
Performancesteigerung dient. Dieser LFH ist ebenfalls durch ein Cookie geschützt. Allerdings durch
eins der Größe 4-Byte. Durch das größere Cookie, wird das erraten des Werts und die
Wahrscheinlichkeit für erfolgreiche Brute-Force-Angriffe, stark reduziert. [ 17 ]
Funktionsweise Plausibilitätsprüfung
Die "Frei-Listen" sind als doppelt verkette Listen aufgebaut. Die Pointer, die zur Verkettung der Liste
in beide Richtungen dienen, nennen sich Blink, für das vorhergegangene Element und Flink, für das
nächste Element. [ 8 ]
Seite - 38 -
39. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Vor jedem Entnehmen eines Blocks aus der "Free-List" werden Blink und Flink auf ihre Plausibilität
hin geprüft. Das bedeutet, es wird geprüft, ob der der Blink aus dem Header des Folgeelements auch
tatsächlich auf das aktuelle Element zeigt. Dieselbe Prüfung wird in die andere Richtung vollzogen.
[8]
Schwachstellen
Innerhalb der Heap-Verwaltung gibt es nicht nur die oben genannten "Frei-Listen", sondern auch
Lookaside-Liste, die primär der Geschwindigkeitserhöhung dienen. Diese Listen sind nur einfach
verkette Listen und aus diesem Grund könnten die Pointer nicht auf Plausibilität hin geprüft werden..
Da aber unter XP mit SP2 der PEB und der Heap dynamische Startadressen haben, benötigt ein Brute-
Force-Angriff nach mehr wie 2000 Versuche, vorausgesetzt die Applikation läuft nach einem
fehlgeschlagenen Versuch weiter. [ 17 ]
Eine weitere Schwachstelle ist das nur 1-Byte große Cookie und die in Abschnitt 4.4 beschriebene,
schwache Zufallswahl des PEB-Speicherortes. Hinzu kommt noch die Tatsache, dass der LFH unter
Windows XP SP2 standardmäßig von keiner Applikation verwendet wird. [ 17 ]
Einen weiteren Angriff auf das Heap-Management beschreibt Alexander Anisimov. Dabei nutzt er die
Tatsache, dass die Cookie-Prüfung und die Plausibilitätsprüfung der Pointer nur unter bestimmten
Umständen stattfinden. So ist es Alexander Anisimov, unter einer Reihe von Voraussetzungen
möglich, die Blink- und Flink-Pointer derart zu manipulieren, dass ein wahlfreier Schreibzugriff von
1016 Bytes möglich ist. Die Voraussetzungen für diese Art von Angriff sind jedoch derart exotisch,
dass Microsoft an dieser Stelle keinen unmittelbaren Handlungsbedarf sieht. [ 8 ]
Eine etwas robustere Methode, als die von Alexander Anisimov, beschreibt Nicolas Falliere in [ 15 ] .
Dabei ist das Prinzip das gleiche: Es werden Blink- und Flink-Pointer überschrieben, dadurch kommt
es zu einem wahlfreien Schreibzugriff der Größe von 4-Byte. Dabei unterliegt dieser Angriff aber den
gleichen Problemen wie der Angriff, auf die einfach verketteten Lookaside-Listen. Es ist also auch hier
nur ein Brute-Force-Angriff möglich. [ 15 ]
Nach [ 31 ] werden die Lookaside-Listen in den aktuellen Windows-Versionen nicht mehr verwendet,
wodurch auf ihnen basierende Angriffe nicht mehr möglich sind.
Effektiver Schutz
Unter Windows XP mit SP1 war es aufgrund der fixen Speicherorte des PEB und des Heaps, sowie des
Fehlens, der oben beschrieben Neuerungen, möglich zuverlässige Remote-Exploits zu schreiben [ 17 ]
Mit Windows XP SP2 wurden Maßnahmen eingeführt, die das Entwickeln zuverlässiger Exploits
deutlich erschwert haben. Dennoch sind Brute-Force-Angriffe weiterhin möglich.
Hinweis: Die in diesem Abschnitt beschriebenen Schwachstellen und Verbesserungen beziehen sich
ausschließlich auf das Heap-Management von Windows. Programme die ihr eigenes Heap-
Seite - 39 -
40. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Management implementieren, profitieren von keiner der hier vorgestellten Neuerungen. Der Heap-
Schutz wurde von Microsoft dennoch durch andere, in diesem Dokument vorgestellte
Sicherheitsfunktionen, stark verbessert. Zu nennen sind in diesem Zusammenhang vor allem DEP,
SafeSEH und ASLR (Vista).
Insgesamt hat sich die Gefahr durch Heap-basierende Angriffe stark verringert. So dass sich Angriffe
zukünftig verstärkt auf Anwendungsdaten konzentrieren werden, die auf dem Heap liegen (frei
übersetzt nach [ 9 ] ).
4.6 Visual Studio 2005 - Der "Windows-Compiler"
Wie jedes andere Programm auch, müssen die Quelldateien eins Betriebssystems mit einem
Compiler in ausführbare Dateien übersetzt werden. Im Falle von Windows kommt bei Microsoft
Visual Studio (VS) zum Einsatz [ 32 ] .
Im Folgenden werden die, für Buffer-Overflows relevanten, Funktionen von VS 2005 vorgestellt und
mit den Verbesserungen gegenüber früher Schutzfunktionen verglichen. Der Focus wird hierbei auf
VS 200510 liegen, da dies der Compiler ist, mit dem das Betriebssystem Windows Vista übersetzt
wird. Außerdem wird der effektive Nutzen der Maßnahmen diskutiert.
4.6.1 Standard Annotation Language (SAL)
Option Beschreibung [ 33 ] Standard Windows Binaries
/analyze Codeanalyse in der Enterprise-Version - Windows Vista
(Compiler)
Überblick
SAL dient der Compiler gestützten Qualitätssicherung des Codes und ist in den Versionen "Team
System" und "Team Edition" von VS 2005 mittels der Option /analyze verfügbar. Ihr Ziel ist es dem
Entwickler beim Übersetzen seiner Programme Hinweise zu geben, an welcher Stelle der
Programmcode möglicher weise nicht so arbeitet wie erwartet. Die Hauptaufgabe von SAL ist es
dabei, auf potentielle Buffer-Overflows aufmerksam zu machen. [ 31 ] [ 34 ]
10
In den verwendeten Quellen ist häufig die Rede von dem „Whidbey-Compiler“. Whidbey ist der Codename
für Visual Studio 2005
Seite - 40 -
41. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Funktionsweise
Beim Erstellen des Quellcodes muss der Programmierer sogenannte Annotations in seinen Code
einbringen. Zum Beispiel die Annotation __out_ecount, wie in folgendem Beispiel zu sehen ist:
void FillString(
__out_ecount(cchBuf) TCHAR* buf,
size_t cchBuf,
char ch) {
for (size_t i = 0; i < cchBuf; i++) {
buf[i] = ch;
}
}
Quelle: [ 34 ]
Beim Kompilieren mit der /analyse Option wird VS in diesem Beispiel folgende Ausgabe erstellen:
c:codesaltestsaltest.cpp(54) : warning C6203: Buffer overrun for non-stack buffer 'b' in
call to 'FillString': length '420' exceeds buffer size '400'
c:codesaltestsaltest.cpp(54) : warning C6386: Buffer overrun: accessing 'argument 1', the
writable size is '200*2' bytes, but '420' bytes might be written: Lines: 53, 54
c:codesaltestsaltest.cpp(54) : warning C6387: 'argument 1' might be '0': this does not
adhere to the specification for the function 'FillString': Lines: 53, 54
Quelle: (43)
Dabei gibt es eine Reihe weiterer Annotations, die der Programmierer in seinen Code verwenden
kann.
Da die ursprüngliche Hauptaufgabe von SAL darin bestand Buffer zu beschreiben, auf die lesend und
schreibend zugegriffen wird, versehen die Entwickler von Windows Vista alle entsprechenden
Funktionen mit Annotations, bevor das Produkt an den Kunden ausgeliefert wird. [ 34 ]
Schwachstellen
Da SAL eine Meta-Sprache ist, die zur Unterstützung von statischen Analysetools entwickelt wurde,
können dem entsprechend nur Fehler gefunden werden, die beim Kompilieren erkennbar sind.
Buffer-Overflows, die zur Laufzeit auftreten, weil die Puffergröße beispielsweise von einer
Benutzereingabe abhängt, können nicht erkannt werden. [ 34 ]
Seite - 41 -
42. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Effektiver Nutzen
Durch die Verwendung von SAL ist der Programmierer gezwungen sich mehr Gedanken über den
Ablauf seiner Funktionen zu machen. Zusammen mit einer konsequenten Anwendung und der
Berücksichtigung der Compilerwarnungen, hätten mit SAL bereits in der Vergangenheit einige Buffer-
Overflows vermieden werden können.
Da SAL eine automatisierte Neuerung zur Verbesserung der Code-Qualität ist, ist SAL eine effektive
Maßnahme gegen die Entstehung von Sicherheitslücken.
4.6.2 Optimierte Anordnung von Stack-Elementen (O2)
Option Beschreibung [ 33 ] Standard Windows
Binaries
/O2 Erstellt schnellen Code. bei Release-Build Theoretisch alle
(Compiler)
Überblick
Seit VS 2003 wird, beim Erstellen eins Release-Builds11, die Anordnung von Variablen (Puffern) auf
dem Stack optimiert. Dabei ist es das Ziel potentiell verwundbare Puffer an einer ungefährlicheren
Position anzulegen [ 35 ] .
Funktionsweise
Wie auf der Abbildung 4-2 zu erkennen ist, wird die lokale Variable auth_flag, vor dem potentiell
verwundbaren Puffer im Speicher angelegt. Da der Schreibzugriff immer nur in Richtung größer
werdender Adressen stattfindet, ist die Variable auth_flag in Sicherheit. Dasselbe Verfahren wird
auch bei dem Anlegen von Pointer angewendet.
11
Standardmäßig ist bei dem Profil „Release“, in Visual Studio, die Compiler-Option O2 aktiviert
Seite - 42 -
43. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Stackframe Stackframe
Quellcode Ohne Mit
Optimierung Optimierung 0000000h
int check_authentication(char *password) { (Kleine Adressen)
int auth_flag = 0;
char password_buffer[8];
password_buffer: char[32] auth_flag: int
Wachstumsrichtung
strcpy(password_buffer, password);
Overflowrichtung
auth_flag: int password_buffer: char[32]
if(strcmp(password_buffer, "password") == 0)
auth_flag = 1;
if(strcmp(password_buffer, "adminpw") == 0) Stack Frame Pointer (SFP) Stack Frame Pointer (SFP)
auth_flag = 1;
Return Adresse (RET) Return Adresse (RET)
return auth_flag;
}
Parameter Parameter
FFFFFFFFh
(Große Adressen)
Abbildung 4-2: Optimierte Anordnung von Stack-Elementen bei VS 2003
Effektiver Schutz
Durch dieses Verfahren wird zwar der Schutz, von lokalen Variablen und Pointern, vor Buffer-
Overflows gewährt. Nicht geschützt sind diese Ziele jedoch vor Buffer-Underruns12.
Die größte Gefahr bleibt nach wie vor bestehen. Wie in der Abbildung 4-2 zu sehen ist, ist es immer
noch möglich, ausgehend von dem verwundbaren Puffer, die Returnadresse (RET) zu überschreiben
und so beliebigen Code zur Ausführung zu bringen. Kombiniert mit den im Folgenden vorgestellten
Mechanismen ergibt sich dennoch ein echter Mehrwert durch diese Optimierungen.
4.6.3 Puffer-Sicherheitsüberprüfung (GS)
Option Beschreibung [ 33 ] Standard [ 35 ] Windows
Binaries [ 32 ]
/GS Puffer-Sicherheitsüberprüfung aktiviert teilweise bei
(Compiler) seit VS 2005 XP SP2, Vista
/GS- Puffer-Sicherheitsüberprüfung deaktivieren deaktiviert teilweise bei
(Compiler) seit Visual C++ 2002 XP SP2, Vista
12
Zu einem Buffer-Underrun kann es beispielsweise durch fehlerhafte Pointerarithmetik kommen
Seite - 43 -
44. Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Überblick
Hinter dem Compilerswitch /GS verbirgt sich eine Funktion namens Buffer Security Check. Das
Haupt-Ziel dieser Funktion ist es, ein Überschreiben der Return-Adresse zu erkennen und das
Programm gegebenenfalls abzubrechen. Unter Linux gibt es mit StackGuard und ProPolice Lösungen,
die diesem Prinzip ebenfalls folgen.
Verfügbar ist diese Option seit Visual C++ 2002 und standardmäßig aktiviert ist die Option seit VS
2005[ 35 ] . Dabei gilt es allerdings zu beachten, dass sich die Implementierungsdetails und damit der
effektive Schutz, seit der ersten Version stark verändert haben. Im Folgenden wird nur auf die
Implementierung von VS 2005 eingegangen.
Seit Windows XP SP2 bzw. Windows Server 2003 ist die Option auch in den meisten Windows-
Binaries Standard. [ 32 ]
Funktionsweise
Programme, die mit der /GS Option kompiliert wurden, initialisieren nach dem Programmstart 4Byte
großes Master-Cookie, mit nicht vorhersagbarem Inhalt. Wird anschließend im Programmverlauf eine
geschützte Funktion aufgerufen, so wird innerhalb des Funktionsprologs eine Kopie des Master-
Cookies, wie auf Abbildung 4-3 zu sehen, auf dem Stack platziert. Wichtig ist hierbei vor allem die
Position des Cookies auf dem Stack: es liegt nach den potentiell verbundbaren lokalen Variablen und
vor der Return-Adresse. Hat die Funktion ihre Arbeit erledigt, so wird innerhalb des Funktionsepilogs
der momentane Wert, des auf dem Stack liegenden Cookies, mit dem Master-Cookie verglichen.
Sollte innerhalb der Funktion ein Buffer-Overflow vorgekommen sein, der bis zu dem Cookie reicht,
so wird das an dieser Stelle erkannt. Gegebenenfalls wird anschließend eine Ausnahme geworfen
und falls diese nicht entsprechend behandelt wird, wird das Programm wird mit einer Fehlermeldung
abgebrochen. [ 32 ]
Seite - 44 -