You are on page 1of 54

Für alle

wichtigen
Betriebssysteme:
Linux, Unix,
Mac OS X und
Windows

Enrico Perla / Massimiliano Oldani

Kernel
Hacking
Exploits verstehen, schreiben und abwehren: Schwachstellen in
Kernel-Architekturen erkennen und Gegenmaßnahmen ergreifen

• Kernel-Exploits im Detail: Aufbau, Funktionsweise und Quellcodes


• User-Land-, Kernel-Land- und Remote-Kernel-Exploits
• Ein großes Praxisbeispiel zeigt die Exploittechniken im Einsatz
Enrico Perla / Massimiliano Oldani
Kernel Hacking
Enrico Perla / Massimiliano Oldani

Kernel
Hacking
Exploits verstehen, schreiben und abwehren: Schwachstellen in
Kernel-Architekturen erkennen und Gegenmaßnahmen ergreifen

• Kernel-Exploits im Detail: Aufbau, Funktionsweise und Quellcodes


• User-Land- Kernel-Land- und Remote-Kernel-Exploits
• Ein großes Praxisbeispiel zeigt die Exploittechniken im Einsatz
60503-8 Titelei.qxp_X 24.10.16 11:42 Seite 4

Bibliografische Information der Deutschen Bibliothek

Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie;


detaillierte Daten sind im Internet über http://dnb.ddb.de abrufbar.

Alle Angaben in diesem Buch wurden vom Autor mit größter Sorgfalt erarbeitet bzw. zusammengestellt und unter
Einschaltung wirksamer Kontrollmaßnahmen reproduziert. Trotzdem sind Fehler nicht ganz auszuschließen. Der
Verlag und der Autor sehen sich deshalb gezwungen, darauf hinzuweisen, dass sie weder eine Garantie noch die ju-
ristische Verantwortung oder irgendeine Haftung für Folgen, die auf fehlerhafte Angaben zurückgehen, überneh-
men können. Für die Mitteilung etwaiger Fehler sind Verlag und Autor jederzeit dankbar. Internetadressen oder
Versionsnummern stellen den bei Redaktionsschluss verfügbaren Informationsstand dar. Verlag und Autor über-
nehmen keinerlei Verantwortung oder Haftung für Veränderungen, die sich aus nicht von ihnen zu vertretenden
Umständen ergeben. Evtl. beigefügte oder zum Download angebotene Dateien und Informationen dienen aus-
schließlich der nicht gewerblichen Nutzung. Eine gewerbliche Nutzung ist nur mit Zustimmung des Lizenzinha-
bers möglich.

This edition of A Guide to Kernel Exploitation: Attacking the Core by Enrico Perla and Massimiliano Oldani is
published by arrangement with ELSEVIER INC., a Delaware corporation having its principal place of business at
360 Park Avenue South, New York, NY 10010, USA

ISBN der englischen Originalausgabe: 978-1597494861

© 2016 Franzis Verlag GmbH, 85540 Haar bei München

Alle Rechte vorbehalten, auch die der fotomechanischen Wiedergabe und der Speicherung in elektronischen Me-
dien. Das Erstellen und Verbreiten von Kopien auf Papier, auf Datenträgern oder im Internet, insbesondere als PDF,
ist nur mit ausdrücklicher Genehmigung des Verlags gestattet und wird widrigenfalls strafrechtlich verfolgt.

Die meisten Produktbezeichnungen von Hard- und Software sowie Firmennamen und Firmenlogos, die in diesem
Werk genannt werden, sind in der Regel gleichzeitig auch eingetragene Warenzeichen und sollten als solche
betrachtet werden. Der Verlag folgt bei den Produktbezeichnungen im Wesentlichen den Schreibweisen der
Hersteller.

Autor: Enrico Perla und Massimiliano Oldani


Programmleitung: Dr. Markus Stäuble
Satz: G&U Language & Publishing Services GmbH, Flensburg
art & design: www.ideehoch2.de
Druck: M.P. Media-Print Informationstechnologie GmbH, 33100 Paderborn
Printed in Germany

ISBN 978-3-645-60503-8
5

Inhaltsverzeichnis
Vorwort............................................................................................... 11

Einleitung...........................................................................................13
Über dieses Buch................................................................................................ 13
Der Aufbau dieses Buchs.................................................................................... 13
Abschließende Bemerkung................................................................................. 15

Danksagung........................................................................................17

Die Autoren.........................................................................................19

Der Fachgutachter...............................................................................19

Teil 1: Eine Reise ins Kernelland 21

1. Von Userland- zu Kernelland-Angriffen........................................ 23


1.1 Einführung..............................................................................................23
1.2 Der Kernel und die Welt des Kernel-Hackings..........................................24
1.2.1 Die Kunst der Ausnutzung.......................................................................25
1.3 Warum funktioniert mein Userland-Exploit nicht mehr?.............................30
1.3.1 Kernelland- und Userland-Exploits im Vergleich......................................33
1.4 Der Kernel aus der Sicht eines Exploit-Autors.........................................35
1.4.1 Userland-Prozesse und der Scheduler....................................................35
1.4.2 Virtueller Arbeitsspeicher.......................................................................36
1.4.3 Benutzerraum oberhalb des Kernelraums im Vergleich mit
getrennten Adressräumen......................................................................38
1.5 Open-Source- und Closed-Source-Betriebssysteme................................40
1.6 Zusammenfassung.................................................................................. 41
1.6.1 Literatur..................................................................................................42
6 Inhaltsverzeichnis

2. Klassifizierung von ­Kernel­schwachstellen................................... 43


2.1 Einführung..............................................................................................43
2.2 Dereferenzierung nicht initialisierter, nicht validierter
und beschädigter Zeiger.........................................................................44
2.3 Schwachstellen durch beschädigten Arbeitsspeicher.............................49
2.3.1 Schwachstellen des Kernelstacks...........................................................49
2.3.2 Schwachstellen des Kernelheaps............................................................ 51
2.4 Integerprobleme.....................................................................................53
2.4.1 (Arithmetische) Integerüberläufe............................................................53
2.4.2 Vorzeichenfehler.....................................................................................55
2.5 Race Conditions......................................................................................57
2.6 Logikbugs...............................................................................................63
2.6.1 Referenzzählerüberlauf...........................................................................63
2.6.2 Validierung der Eingaben von physischen Geräten.................................65
2.6.3 Vom Kernel hervorgerufene Userland-Schwachstellen............................66
2.7 Zusammenfassung..................................................................................69

3. Der Weg zum erfolgreichen Kernel-Hacking..................................71


3.1 Einführung.............................................................................................. 71
3.2 Die Architekturebene..............................................................................73
3.2.1 Allgemeine Prinzipien.............................................................................73
3.2.2 x86 und x86-64.......................................................................................80
3.3 Der Ausführungsschritt...........................................................................84
3.3.1 Den Shellcode platzieren........................................................................84
3.3.2 Den Shellcode gestalten.........................................................................92
3.3.3 Den Kernelzustand wiederherstellen.......................................................94
3.4 Der Auslöseschritt...................................................................................98
3.4.1 Speicherbeschädigung...........................................................................98
3.4.2 Race Conditions.....................................................................................113
Inhaltsverzeichnis 7

3.5 Der Schritt zur Informationsgewinnung.................................................118


3.5.1 Was uns die Umgebung mitteilt.............................................................119
3.5.2 Was uns die Umgebung nicht mitteilen möchte: Infoleaks.................... 124
3.6 Zusammenfassung................................................................................ 126
3.6.1 Literatur................................................................................................ 127

Teil 2: Die UNIX-Familie, Mac OS X und Windows 129

4. Die UNIX-Familie......................................................................... 131


4.1 Einführung.............................................................................................131
4.2 Die Mitglieder der UNIX-Familie............................................................ 133
4.2.1 Linux..................................................................................................... 133
4.2.2 Solaris/OpenSolaris............................................................................. 144
4.2.3 BSD-Derivate........................................................................................ 157
4.3 Der Ausführungsschritt......................................................................... 157
4.3.1 Das Rechtemodell von Linux missbrauchen.......................................... 158
4.4 UNIX-Hacking in der Praxis................................................................... 172
4.4.1 Hacking des Kernelheaps...................................................................... 172
4.4.2 Angriff auf den Slab-Allokator von OpenSolaris.................................... 173
4.4.3 Angriff auf den SLUB-Allokator von Linux 2.6........................................ 197
4.4.4 Stacküberläufe im (Linux-) Kernel......................................................... 216
4.4.5 CVE-2009-3234, zum Zweiten...............................................................223
4.5 Zusammenfassung................................................................................235

5. Mac OS X.................................................................................... 237


5.1 Einführung............................................................................................237
5.2 Überblick über XNU..............................................................................239
5.2.1 Mach.....................................................................................................239
8 Inhaltsverzeichnis

5.2.2 BSD.......................................................................................................240
5.2.3 IOKit......................................................................................................240
5.2.4 Systemaufruftabellen........................................................................... 241
5.3 Kerneldebugging...................................................................................243
5.4 Kernelerweiterungen (Kext)...................................................................253
5.4.1 IOKit......................................................................................................259
5.4.2 Überprüfen von Kernelerweiterungen...................................................260
5.5 Der Ausführungsschritt.........................................................................273
5.6 Hinweise für Exploits............................................................................275
5.6.1 Willkürliches Überschreiben des Arbeitsspeichers................................275
5.6.2 Stacküberläufe......................................................................................287
5.6.3 Exploits für den Speicherallokator........................................................304
5.6.4 Race Conditions.................................................................................... 319
5.6.5 Snow Leopard....................................................................................... 319
5.7 Zusammenfassung................................................................................ 319

6. Windows......................................................................................321
6.1 Einführung............................................................................................ 321
6.2 Überblick über den Windows-Kernel.....................................................323
6.2.1 Informationen über den Kernel gewinnen.............................................324
6.2.2 DVWD (Dawn Vulnerable Windows Driver)............................................328
6.2.3 Interne Mechanismen des Kernels........................................................330
6.2.4 Kerneldebugging...................................................................................335
6.3 Der Ausführungsschritt.........................................................................338
6.3.1 Das Autorisierungsmodell von Windows...............................................338
6.3.2 Den Shellcode erstellen........................................................................348
6.4 Windows-Hacking in der Praxis.............................................................362
6.4.1 Stackpufferüberlauf..............................................................................374
6.5 Zusammenfassung................................................................................395
Inhaltsverzeichnis 9

Teil 3: Remote-Exploits 397

7. Die Herausforderung durch Remote-Kernelexploits................... 399


7.1 Einführung............................................................................................399
7.2 Schwachstellen über das Netz angreifen..............................................400
7.2.1 Mangel an offengelegten Informationen............................................... 401
7.2.2 Mangelnder Einfluss auf das Ziel..........................................................403
7.3 Die erste Anweisung ausführen............................................................405
7.3.1 Direkte Umleitung des Ausführungsflusses..........................................406
7.3.2 Willkürliches Überschreiben des Kernelarbeitsspeichers...................... 419
7.4 Remote-Payloads.................................................................................. 421
7.4.1 Payload-Migration................................................................................422
7.5 Zusammenfassung................................................................................444

8. Anwendung in der Praxis am Beispiel von Linux........................ 445


8.1 Einführung............................................................................................445
8.2 Heapbeschädigung im SCTP-FWD-Abschnitt.........................................446
8.2.1 Überblick über SCTP.............................................................................446
8.2.2 Der anfällige Pfad.................................................................................449
8.3 Der Remote-Exploit: Allgemeiner Überblick..........................................453
8.4 Die Voraussetzungen zum willkürlichen Überschreiben
des Arbeitsspeichers schaffen..............................................................454
8.4.1 Das Heaplayout über das Netzwerk anpassen......................................455
8.4.2 SCTP-Nachrichten erstellen: Vom relativen zum
absoluten Überschreiben des Arbeitsspeichers....................................458
8.5 Den Shellcode installieren....................................................................464
8.5.1 Direkter Sprung vom Interruptkontext ins Userland..............................464
8.6 Den Shellcode ausführen......................................................................472
8.6.1 Den laufenden Prozess prüfen und die Funktion
gettimeofday() emulieren.....................................................................473
10 Inhaltsverzeichnis

8.6.2 Die rückwärtige Verbindung ausführen.................................................474


8.6.3 Vsyscall wiederherstellen.....................................................................476
8.7 Zusammenfassung................................................................................477
8.8 Literatur................................................................................................478

Teil 4: Schlusswort 479

9. Die Entwicklung des Kernels:


Angriff und Verteidigung in der Zukunft..................................... 481
9.1 Einführung............................................................................................ 481
9.2 Kernelangriffe.......................................................................................482
9.2.1 Vertraulichkeit......................................................................................482
9.2.2 Integrität...............................................................................................484
9.2.3 Verfügbarkeit........................................................................................488
9.3 Kernelschutz.........................................................................................488
9.3.1 Bedrohungsanalyse und Modellierung.................................................489
9.3.2 Kernelschutzmechanismen...................................................................490
9.3.3 Vertrauen in den Kernel......................................................................... 491
9.4 Virtualisierung......................................................................................496
9.4.1 Die Sicherheit des Hypervisors.............................................................496
9.4.2 Sicherheit des Gastkernels...................................................................498
9.5 Zusammenfassung................................................................................498

Stichwortverzeichnis........................................................................501
11

Vorwort

Als ich gefragt wurde, ob ich ein Vorwort zu diesem Buch schreiben wollte, habe ich mich
zuerst geweigert, da ich mich nicht gegenüber den Menschen in den Vordergrund spielen
wollte, denen Sie dieses Buch zu verdanken haben. Nachdem ich einige Kapitel Korrek-
tur gelesen hatte, erkannte ich jedoch, dass ich diese Gelegenheit nur ungern versäumen
wollte, da es eine große Ehre ist, einem Buch aus der Feder von zwei der weltweit besten
Entwickler von Kernelexploits einige Worte hinzufügen zu dürfen.
Bücher über Exploittechniken lese ich nur selten, die sie gewöhnlich nur wenig oder bereits
veraltete Kenntnisse vermitteln oder einfach von anderen Personen entwickelte Exploits
auflisten. Außerdem bieten Bücher nicht den gleichen Lerneffekt wie die Exploitentwick-
lung in der Praxis und auch nicht die Befriedigung, nach einem Tag harter Arbeit die
Eingabeaufforderung # zu sehen, insbesondere bei der Ausnutzung einer Kernelschwach-
stelle. Es ist an der Zeit, dass jemand dieses Gefühl zu Papier bringt und den Entwicklern
Zeit, eine Menge Abstürze und Bauchschmerzen erspart.
Das Schreiben von Exploits und insbesondere von Kernelexploits besteht nicht nur aus
Tricks und Exploit-Kung-Fu, sondern ist Ingenieurskunst, die ein tiefes Verständnis der
Grundlagen von Betriebssystemen erfordert. Dafür ist dieses Buch sehr hilfreich. Es füllt
die Lücke zwischen all den Kernel- und Treiberprogrammierbüchern in einem Regal.
Ich bin mir sicher, wer die Menschen sind, die dieses Buch lesen werden, und ich hoffe,
dass sich unter dem Publikum eine Menge Kernel- und Treiberentwickler befinden. Mein
nächster Auftrag zur Überprüfung von Kernelcode wird kommen, und ich hoffe, dass ich
vorher schon meine gedruckte Ausgabe dieses Buchs in Händen halten werde.
Sebastian Krahmer
Systemprogrammierer und Exploit-Ingenieur
13

Einleitung

Über dieses Buch


Da es heutzutage mehr Sicherheitsmaßnahmen gegen Userlandexploits gibt, werden
Kernelexploits unter Angreifern und ganz allgemein unter Exploit-Autoren immer be-
liebter. Es kann jedoch ziemlich gefährlich sein, mit dem Herzen des Betriebssystems
eines Computers herumzuspielen. Dieses Buch deckt die Techniken und Vorgehens-
weisen ab, die erforderlich sind, um zuverlässige und wirkungsvolle Kernelexploits für
die verschiedenen Betriebssysteme zu schreiben – für UNIX-Derivate, Mac OS X und
Windows.
Die Entwicklung von Kernelexploits ist sowohl eine Kunst als auch eine Wissenschaft.
Jedes Betriebssystem weist seine Eigenheiten auf, weshalb ein Exploit so gestaltet werden
muss, dass er die Besonderheiten des Ziels vollständig ausnutzt. In diesem Buch sehen wir
uns die am weitesten verbreiteten Betriebssysteme an – UNIX-Derivate, Mac OS X und
Windows – und zeigen, wie Sie die Kontrolle darüber gewinnen können.
Die Prinzipien und Taktiken sind nach Kategorien geordnet. Selbst wenn eine bestimmte
Schwachstelle mit einem Patch korrigiert wurde, können die grundlegenden Informatio-
nen Ihnen immer noch helfen, einen neuen, besseren Angriff zu schreiben (wenn Sie ein
Hacker sind) bzw. ein besseres Design und eine bessere Schutzstruktur zu gestalten (wenn
Sie Pen-Tester, Prüfer o. Ä. sind).

Der Aufbau dieses Buchs


Dieses Buch ist in vier Teile und neun Kapitel gegliedert. In Teil I, »Eine Reise ins Kernel-
land«, stellen wir unser Ziel und die theoretischen Grundlagen für den Rest dieses Buchs
vor. Hier finden Sie die folgenden Kapitel:
•• Kapitel 1, »Von Userland- zu Kernelland-Angriffen«, gibt eine Einführung in die Welt
der Exploits und erklärt, warum Sicherheitsforscher und Angreifer ihr Augenmerk von
Userlandanwendungen zum Herzen des Betriebssystems, dem Kernel, verlagert haben.
14 Einleitung

•• Kapitel 2, »Klassifizierung von Kernelschwachstellen«, teilt die verschiedenen Arten


von Schwachstellen und Bugs in Kategorien ein und stellt ihre Gemeinsamkeiten sowie
Möglichkeiten zu ihrer Ausnutzung vor. Je besser wir die verschiedenen Bugklassen
modellieren können, umso besser können wir auch zuverlässige und wirkungsvolle
Techniken entwerfen. Diese Klassifizierung ist auch praktisch für die Verteidigung,
denn je mehr wir über die verschiedenen Arten von Bugs wissen, umso besser können
wir Schutzvorrichtungen und Gegenmaßnahmen entwickeln.
•• In Kapitel 3, »Der Weg zum erfolgreichen Kernel-Hacking«, analysieren wir die Bau-
steine eines Exploits und beschreiben Techniken und empfohlene Vorgehensweisen
für die einzelnen in Kapitel 2 vorgestellten Klassen von Bugs. Die verschiedenen Be-
triebssysteme implementieren ihre Teilsysteme zwar jeweils auf ihre eigene Weise, aber
in diesem Kapitel stellen wir einige Vorgehensweisen vor, die sich auf verschiedene
Kernels und sogar auf verschiedene Architekturen anwenden lassen.

Mit der praktischen Arbeit beginnen wir in Teil II, »Die UNIX-Familie, Mac OS X und
Windows«. Wir sehen uns hier die Einzelheiten der verschiedenen Betriebssysteme an
und schreiben Exploits für sie. Außerdem schauen wir uns die Werkzeuge und Vorge-
hensweisen für das Debugging an, die in den einzelnen Betriebssystemen zur Verfügung
stehen und beim Schreiben von Exploits äußerst nützlich sind. Nach Möglichkeit stellen
wir jeweils Exploits für »echte« Schwachstellen statt künstlich hingebogener Beispiele vor.
Dieser Teil enthält folgende Kapitel:
•• In Kapitel 4, »Die UNIX-Familie«, geht es um Systeme auf der Grundlage von UNIX,
vor allem Linux und (Open)Solaris. Ein Teil des Kapitels ist dem Debugging mit den
wichtigsten Werkzeugen gewidmet, die diese Betriebssysteme anbieten (dynamische
Ablaufverfolgung, interner Kerneldebugger usw.).
•• Kapitel 5, »Mac OS X«, deckt die Version Leopard des Betriebssystems Mac OS X ab.
Neben den wichtigsten Klassen von Schwachstellen (die z. B. Stack- und Heapexploits
ermöglichen) stellen wir hier Möglichkeiten vor, um mithilfe von Reverse Engineering
in den Closed-Source-Teilen des Betriebssystems nach Schwachstellen zu suchen.

In Kapitel 6, »Windows«, geht es um das am weitesten verbreitete Betriebssystem der


Welt, nämlich Microsoft Windows. Im Gegensatz zu den Betriebssystemen aus den
vorherigen Kapiteln steht uns bei Windows der Quellcode nicht zur Verfügung. Un-
sere Kenntnisse der internen Mechanismen (und damit auch der Schwachstellen und
der möglichen Exploits dafür) beruhen auf einem Reverse Engineering der einzelnen
Kernelbestandteile. Die Debugging- und Reverse-Engineering-Tools sind hier noch
wichtiger als in Kapitel 4 und 5, weshalb wir diesem Thema einen großen Teil des
Kapitels widmen.

In Teil III, »Remote-Exploits«, verlagern wir unsere Aufmerksamkeit von lokalen Angrif-
fen (der üblichen Situation bei Kernelexploits) zu Angriffen über das Netzwerk. Damit
begeben wir uns auf weit kniffligeres Terrain, da viele der Techniken, die wir für die lokale
Vorgehensweise gelernt haben, hier nicht mehr anwendbar sind. Wir haben zwar immer
Einleitung 15

noch mit den gleichen Arten von Schwachstellen zu tun, aber wir benötigen ganz neue
Angriffsmöglichkeiten. Dieser Teil besteht aus zwei Kapiteln, von denen das eine eher
theo­retischer und das andere eher praktischer Natur ist:
•• In Kapitel 7, »Die Herausforderung durch Remote-Kernelexploit«, beginnen wir mit
der Theorie. Wir sehen uns an, warum und wie sich unsere Vorgehensweisen ändern,
wenn wir den Angriff über das Netzwerk vortragen, und stellen neue Techniken vor, um
Probleme bei Remote-Exploits zu überwinden. Trotz der theoretischen Natur dieses
Kapitels erhalten Sie hier auch einige praktische beisiele, insbesondere für Windows, da
wir UNIX (Linux) das ganze folgende Kapitel widmen.
•• Kapitel 8, »Anwendung in der Praxis am Beispiel von Linux«, stellt Schritt für
Schritt die Entwicklung eines zuverlässigen direkten Remote-Exploits für eine echte
Schwachstelle vor, nämlich einen Bug im SCTP-Teilsystem des Linux-Kernels (http://
cve.mitre.org/cgi-bi/cvename.cgi?name=CVE-2009-0065).

Mit Teil IV, »Schlusswort«, beenden wir unsere Erörtung der Kernel-(Un)Sicherheit. Dieser
Teil besteht aus lediglich einem Kapitel:
•• Kapitel 9, »Die Entwicklung des Kernels: Angriff und Verteidigung in der Zukunft«,
baut auf dem auf, was wir bis dahin über Kernelexploits gelernt haben, und versucht
einen Ausblick auf die Zukunft. Um die vielen verschiedenen Aspekte der Angriffs-
und Verteidigungstechniken geordnet betrachten zu können, greifen wir hier auf das
Grundprinzip der Computersicherheit zurück: die Steuerung des Informationsflus-
ses. Unter diesem Gesichtspunkt untersuchen wir die grundlegenden Merkmale von
Schwachstellen und Exploits, sodass wir uns ein Bild davon machen können, in welche
Richtung sie sich in Zukunft entwickeln werden.

Der Quellcode aller in diesem Buch vorgestellten Exploits und Tools steht auf der Begleit-
website www.attackingthecore.com zur Verfügung, die auch die Hauptanlaufstelle darstellt,
um Fehler zu melden, zusätzliches Material zu finden und mit uns Kontakt aufzunehmen.

Abschließende Bemerkung
Ein Buch zu schreiben, ist eine fantastische und gleichzeitig anspruchsvolle Erfahrung.
Es bietet die Gelegenheit, die vielen Ideen zu dokumentieren, die einem zu seinem Lieb-
lingsthema im Kopf herumschwirren, aber für uns war es auch in vieler Hinsicht eine
Herausforderung. Wir haben uns bemüht, in unseren Erklärungen so klar und korrekt wie
möglich zu sein, die Leidenschaft und den Spaß zu vermitteln, die das Austüfteln von We-
gen mit sich bringt, etwas kaputt zu machen (bzw. davor zu schützen), und Informa­tionen
zu vermitteln, die nicht nur bei Drucklegung des Buchs nützlich sind, sondern auch später
noch. Wir hoffen, dass Sie mit dem Ergebnis unserer Bemühungen so viel Freude haben
wie wir beim Schreiben.
17

Danksagung

Dieses Buch ist all denen gewidmet, die immer noch der Überzeugung sind, dass die
Beherrschung eines Codeeditors (und der Shell) im Bereich der Sicherheit wichtiger ist
als der Umgang mit einem E-Mail-Client.
Mehrere Personen haben uns geholfen und unterstützt und das Manuskript bis zur end-
gültigen Fassung betreut. Ohne sie wäre das, was Sie gerade in Ihren Händen halten (oder
auf Ihrem PDF-Reader betrachten) nicht möglich gewesen. Wir möchten insbesondere
folgenden Personen danken:
•• Matthew Cater, Rachel Roumeliotis, Graham Speake, Audrey Doyle und Julie Ochs
dafür, dass sie es (wieder einmal) mit einem wackeligen Zeitplan und unseren ständi-
gen Bitten aufgenommen haben, die Anzahl der Seiten gegenüber der ursprünglichen
Schätzung erhöhen zu dürfen.
•• Nemo für den erstaunlichen Stoff in Kapitel 5 und seine ständigen Rückmeldungen.
•• Ruggiero Piazzolla für die Hilfe mit der Website und vor allem für deren angenehme
Gestaltung.
•• Marco Desiati und Michele Mastrosimone für die Illustrationen. Unsere ersten Versuche
sahen im Vergleich zu ihren fertigen Bildern wie Kinderzeichnungen aus.
•• Abh für das unermüdliche und zeitintensive Korrekturlesen, Korrigieren und Ver-
bessern der Inhalte und der Codebeispiele in diesem Buch.
•• Sebastian Krahmer für das Vorwort, die Überprüfung vieler der Kapitel und die end-
losen Diskussionen über Techniken und Ideen.
•• (Ohne bestimmte Reihenfolge) Andrea Lelli, Scott Rotondo, xorl (netter Blog üb-
rigens!), Brad Spengler, Window Snyder, Julien Vanegue, Josh Hall, Ryan Austin,
Bas ­Albert, Igor Falcomata’, clint, Reina Alessandro, Giorgio Fedon, Matteo Meucci,
­Stefano Di Paola, Antonio Parata, Francesco Perna, Alfredo Pesoli, Gilad Bakas, David
Jacoby und Ceresoni Andrea für die Rückmeldung und die Ideen zu diesem Buch und
für die Hilfe dabei, seine Qualität insgesamt zu verbessern (und gelegentlich auch da-
für, ein Bett oder eine Couch zur Verfügung zu stellen, um darauf zusammenzubre-
chen). Wir sind sicher, dass wir einige Personen vergessen haben (der Satz »ihr wisst,
wer gemeint ist« war noch nie so angebracht wie hier.) Tut uns Leid!

Zu guter Letzt haben wir noch einige besondere Danksagungen auszusprechen, die aber
mehr persönlicher Natur sind.
18 Danksagung

Enrico möchte Mike Pogue und Jan Setje-Eilers für so ziemlich alles danken, was sie getan
haben, und Lalla, Franco und Michela dafür, dass sie eine so fantastische Familie sind.
Ein besonderes Dankeschön gilt den Anrufen um 9.00 Uhr und 22.30 Uhr, die das Leben
Tausende von Meilen von zu Hause entfernt wie Zuhause wirken ließen.
Massimiliano möchte folgenden Personen danken:
•• »Halfdead« für die Erkenntnis, dass es immer noch möglich ist, in der fantastischen
Welt der Sicherheit viel Spaß zu haben.
•• Meiner wunderbaren Familie: Noemi, Manuela, Giuseppe, Stefano (Bruce) und vor
allem Irene, die viele Wochenenden geopfert hat, um mich in all den Monaten zu
unterstützen, in denen ich dieses Buch schrieb. Ich liebe dich wirklich.
19

Die Autoren

Enrico Perla arbeitet als Kernelprogrammierer bei Oracle. 2007 hat er einen Bachelorgrad
in Informatik an der Universität von Turin erworben, 2008 einen Mastergrad in Informatik
am Trinity College in Dublin. Seine Interessen reichen von maschinennaher Systempro-
grammierung über maschinennahe Angriffe und Exploits bis zu Schutzmaßnahmen gegen
Exploits.
Massimiliano Oldani arbeitet als Sicherheitsberater bei Emaze Networks. Zu seinen
wichtigsten Forschungsgebieten gehören Betriebssystemsicherheit und Kernelschwach-
stellen.

Der Fachgutachter

Graham Speake (CISSP 56073, M. Inst. ISP) ist leitender Systemarchitekt bei der Yokogawa
Electric Corporation, einem großen Anbieter für industrielle Automatisierungsprodukte.
Er bietet Sicherheitsberatung und Lösungen für interne Entwickler sowie für Kunden in
vielen Ländern an. Zu seinen Fachgebieten gehören Industrieautomatisierung, Sicherheit
der Prozesssteuerung, Penetrationstests, Netzwerksicherheit und Netzwerkdesign. Er tritt
häufig als Redner bei Sicherheitskonferenzen auf und hält Sicherheitsschulungen für Kun-
den in aller Welt ab. Er war unter anderem als Sicherheitsberater bei BP und ATOS/Origin
und als Ingenieur bei der Ford Motor Company tätig.
Graham Speake hat einen Bachelorgrad der Swanse University in Wales und ist Mitglied
der ISA. Er wurde in Großbritannien geboren, lebt heute aber mit seiner Frau Lorraine
und seiner Tochter Dani in Houston, Texas.
Teil 1
Eine Reise ins Kernelland

Willkommen! Unsere Reise in die Welt des Kernel-Hackings beginnt hier. In diesem Teil
des Buchs sehen wir uns an, was der Kernel überhaupt ist, warum die Sicherheitsbranche
ihm so viel Aufmerksamkeit schenkt, wie Bugs auf Kernelebene aussehen und wie man
sie ausnutzen kann. Anstatt uns gleich mit den Einzelheiten der verschiedenen Betriebs­
systeme und den Exploits dafür zu beschäftigen, bauen wir zunächst ein solides Grund-
verständnis über den Kernel und die Methodik zur Ausnutzung seiner Schwachstellen
auf. Das macht es nicht nur leichter, uns später mit den kniffligen Details der in diesem
Buch behandelten Betriebssysteme auseinanderzusetzen (vor allem in Teil III), sondern
vereinfacht auch die äußerst komplizierte Aufgabe, sich über den ständig weiterentwi-
ckelten Kernel stets auf dem neuesten Stand zu halten.
1
Von Userland- zu
Kernelland-Angriffen

1.1 Einführung
In diesem Kapitel stellen wir unser Ziel vor, den Kernel. Nach einer kurzen Besprechung
der Grundlagen sehen wir uns an, warum Exploit-Autoren ihre Aufmerksamkeit von
Userland-Anwendungen auf den Kernel verlagert haben, und zeigen die Unterschiede
zwischen Userland- und Kernelland-Exploits auf. Danach konzentrieren wir uns auf die
Unterschiede zwischen den einzelnen Kernels. Dabei sehen wir uns nicht nur an, wie sich
Windows-Kernels von UNIX-Kernels unterscheiden, sondern auch, welche wichtige Rolle
die Architekturvarianten bei der Entwicklung von Kernel-Exploits spielen. Beispiels­weise
kann ein und derselbe Code auf einem 32-Bit-System angreifbar sein, aber nicht auf ei-
nem 64-Bit-System, oder nur auf einem x86- und nicht auf einem SPARC-Computer.
Zum Abschluss des Kapitels besprechen wir kurz die Unterschiede von Kernel-Exploits für
Open-Source- und Closed-Source-Systeme.
24 1  Von Userland- zu Kernelland-Angriffen

1.2 Der Kernel und die Welt des Kernel-Hackings


Unsere Reise in die Welt des Kernel-Hackings beginnen wir mit einer offensichtlichen
Maßnahme: Wir erklären, was der Kernel überhaupt ist und was es bedeutet, ihn zu ha-
cken. Wenn Sie sich einen Computer vorstellen, denken Sie wahrscheinlich vor allem an
die miteinander verbundenen physischen Geräte (Prozessor, Motherboard, Arbeitsspei-
cher, Festplatte, Tastatur usw.), mit denen Sie einfache Aufgaben durchführen können,
z. B. eine E-Mail schreiben, einen Film ansehen oder im Web surfen. Zwischen diesen
Hardwaregeräten und den Anwendungen, die Sie nutzen, befindet sich jedoch noch eine
Softwareschicht, die dafür sorgt, dass diese gesamte Hardware effizient funktioniert, und
eine Infrastruktur aufbaut, auf der die Anwendungen laufen können. Diese Software-
schicht ist das Betriebssystem, und ihr Kern ist der Kernel.
In einem modernen Betriebssystem ist der Kernel für die Dinge verantwortlich, die Sie
gewöhnlich als selbstverständlich hinnehmen: virtueller Speicher, Festplattenzugriff,
Ein-/Ausgabe usw. Diese vielschichtige und faszinierende Software ist im Allgemeinen
größer als die meisten Benutzeranwendungen und gewöhnlich in einer Mischung aus ei-
ner maschinennahen Assemblersprache und C geschrieben. Außerdem nutzt der Kernel
einige Eigenschaften der zugrunde liegenden Architektur, um sich vom Rest der laufen-
den Programme abzugrenzen. Die meisten Anweisungssatzarchitekturen (Instruction Set
Architecture, ISA) bieten mindestens zwei Ausführungsmodi, nämlich den privilegierten
Modus, in dem alle Anweisungen auf Maschinenebene voll zugänglich sind, und einen
unprivilegierten Modus, in dem nur eine Teilmenge dieser Anweisungen zur Verfügung
steht. Außerdem schützt sich der Kernel vor Benutzeranwendungen, indem er eine Tren-
nung auf Softwareebene durchsetzt. Wird ein virtuelles Speicherteilsystem eingerichtet,
sorgt der Kernel dafür, dass er auf den Adressraum (also den Bereich der virtuellen Spei-
cheradressen) jedes Prozesses zugreifen kann, während kein Prozess in der Lage ist, direkt
auf den Kernelspeicher zu verweisen. Den Arbeitsspeicher, der nur für den Kernel sichtbar
ist, nennen wir Kernelland-Speicher, während der Arbeitsspeicher, den die Benutzerpro-
zesse sehen können, der Userland-Speicher ist. Im Kernelland ausgeführter Code läuft mit
allen Rechten und kann auf jede gültige Speicheradresse im System zugreifen, wohingegen
Userlandcode den zuvor genannten Einschränkungen unterliegt. Diese sowohl hardware-
als auch softwarebasierte Trennung ist notwendig, um den Kernel vor versehentlicher Be-
schädigung oder Manipulationen durch unartige oder schädliche Userlandanwendungen
zu schützen.
Der Schutz des Kernels vor anderen laufenden Programmen ist der erste Schritt zu ei-
nem sicheren und stabilen System, ist aber offensichtlich nicht ausreichend: Es muss
auch einen gewissen Grad an Schutz zwischen den einzelnen Userland-Anwendungen
geben. Betrachten Sie als Beispiel eine typische Multiuser-Umgebung. Hier erwarten die
einzelnen Benutzer einen »privaten« Bereich im Dateisystem, in dem sie ihre Dateien
speichern, und sie erwarten auch, dass eine von ihnen gestartete Anwendung, z. B. ein
Mailreader, nicht von anderen Benutzern beendet, verändert oder ausspioniert werden
kann. Damit ein System nutzbar sein kann, muss es auch eine Möglichkeit geben, Be-
nutzer zu erkennen, hinzuzufügen und zu entfernen sowie den Einfluss einzugrenzen,
den sie auf gemeinsam genutzte Ressourcen haben. Beispielsweise darf ein böswilliger
1.2  Der Kernel und die Welt des Kernel-Hackings 25

Benutzer nicht in der Lage sein, den gesamten verfügbaren Platz im Dateisystem oder die
gesamte Bandbreite der Internetverbindung zu verbrauchen. Es wäre zu aufwendig, diese
Abstraktion in der Hardware zu realisieren, weshalb sie auf der Softwareebene bereitge-
stellt wird – eben durch den Kernel.
Die Benutzer werden anhand eines eindeutigen Werts identifiziert – gewöhnlich eine
Nummer –, der als Benutzer-ID (userid) bezeichnet wird. Einer dieser Werte dient dazu,
einen besonderen Benutzer mit höheren Rechten zu bezeichnen, der für alle anstehenden
administrativen Aufgaben verantwortlich ist, z. B. die Verwaltung anderer Benutzer, die
Festlegung von Verbrauchsgrenzwerten, die Konfiguration des Systems usw. In Windows
ist dieser Benutzer der Administrator, in der Welt von UNIX dagegen wird er als root be-
zeichnet und erhält gewöhnlich die uid (Benutzer-ID) 0. Im weiteren Verlauf dieses Buchs
werden wir für diesen Benutzer den gebräuchlichen Begriff Superuser verwenden.
Der Superuser ist auch ermächtigt, Änderungen am Kernel selbst vorzunehmen. Der
Grund dafür ist einleuchtend: Wie jede andere Software muss auch der Kernel aktuali-
siert werden, z. B. um Bugs zu reparieren oder Unterstützung für neue Geräte hinzuzufü-
gen. Eine Person mit Superuser-Status hat die volle Kontrolle über den Computer. Daher
besteht eines der Ziele von Angreifern darin, diesen Status zu gewinnen.

Hinweis
Der Superuser wird vom »Rest der (unprivilegierten) Welt« durch eine herkömm-
liche Architektur der »getrennten Rechte« unterschieden. Das funktioniert nach
dem Alles-oder-nichts-Prinzip: Wenn ein Benutzer die privilegierte Operation X
durchführen muss, so muss er zum Superuser ernannt werden, und damit kann
er neben X auch andere privilegierte Operationen durchführen. Die Sicherheit
dieses Modells lässt sich verbessern, indem die Rechte getrennt werden, sodass der
Benutzer nur diejenigen erhält, die er für die erforderliche Aufgabe benötigt. In
einer solchen Situation bedeutet es nicht unbedingt, dass man die volle Kontrolle
über das System bekommt, wenn man zum Superuser ernannt wird, denn was ein
bestimmtes Userland-Programm tun kann und was nicht, wird dann durch die
ihm zugewiesenen Rechte bestimmt.

1.2.1 Die Kunst der Ausnutzung


»Ich hoffe, ich habe beweisen können, dass die Ausnutzung von Pufferüberläufen eine
Kunst sein sollte.«
– Solar Designer1

1 Solar Designer, »Getting around non-executable stack (and fix)«. E-Mail an die Bugtraq- Mailingliste,
http://marc.info/?l=bugtraq&m=87602746719512; 1997 (abgerufen am 18.07.2010).
26 1  Von Userland- zu Kernelland-Angriffen

Unter den verschiedenen Möglichkeiten, mit denen ein Angreifer den angestrebten Status
eines Superusers erreichen kann, ist die Entwicklung eines Exploits die spannendste. Für
Neulinge mag dies wie Magie wirken, doch in Wirklichkeit ist dafür keine Magie nötig,
sondern nur Kreativität, Geschick und sehr viel Engagement. Anderes ausgedrückt: Es
handelt sich um eine Kunst. Das Grundprinzip ist erstaunlich einfach: Software weist Bugs
auf, und Bugs veranlassen die Software, sich falsch zu verhalten oder eine Aufgabe, die sie
korrekt durchführen soll, fehlerhaft auszuführen. Einen Bug auszunutzen bedeutet, dass
der Angreifer dieses Fehlverhalten als Vorteil für sich verwendet. Nicht alle Bugs lassen
sich ausnutzen. Diejenigen, bei denen es möglich ist, werden als Schwachstellen bezeich-
net. Die Überprüfung einer Software auf Schwachstellen umfasst Folgendes:
•• Lesen des Quellcodes des Anwendung, falls verfügbar
•• Reversieren der Binärdatei der Anwendung, d. h. Lesen der Disassemblierung des
kompilierten Codes
•• Verwirren der Anwendungsschnittstelle, d. h. zufällige oder einem Muster gehorchende,
automatisch generierte Eingaben an die Anwendung senden

Die Überprüfung kann manuell oder mithilfe statischer und dynamischer Analysewerk-
zeuge erfolgen. Eine ausführliche Beschreibung dieses Vorgangs können wir in diesem
Buch nicht leisten, aber wenn Sie mehr darüber erfahren wollen, finden Sie in den Litera-
turhinweisen am Ende dieses Kapitels Bücher zu diesem Thema.
Schwachstellen werden gewöhnlich in eine Handvoll Kategorien eingeteilt. Wenn Sie hin
und wieder zum Thema Sicherheit in Mailinglisten, Blogs oder E-Zines schauen, haben Sie
sicherlich schon von Pufferüberläufen (Stack- und Heap-Überläufen), Integer-Überläufen,
Formatierungsstrings und Race Conditions gehört.

Hinweis
Eine ausführlichere Beschreibung dieser Kategorien von Schwachstellen erhalten
Sie in Kapitel 2.

Die meisten dieser Begriffe sollten selbsterklärend sein, und eine genaue Kenntnis ih-
rer Bedeutung ist an dieser Stelle auch gar nicht nötig. Wichtig ist zu wissen, dass alle
Schwachstellen derselben Kategorie einen gemeinsamen Satz von Mustern und Angriffs-
wegen aufweisen. Diese Muster und Wege (die Techniken zur Ausnutzung) zu kennen,
ist bei der Entwicklung eines Exploits von großer Hilfe. Dies kann sehr einfach, aber
auch erstaunlich schwierig sein, und das ist die Stelle, an der die Kreativität des Exploit-
Autors den Vorgang zu einer Kunst macht. Erstens muss ein Exploit zuverlässig genug
sein, um ihn für eine angemessen breite Palette von angreifbaren Zielen anwenden zu
können. Ein Exploit, der nur in einer ganz speziellen Situation funktioniert oder der die
Anwendung zum Absturz bringt, hat keinen großen Nutzen. Eine solche Machbarkeits-
studie ist im Grunde genommen eine unfertige und gewöhnlich schnell geschriebene
Arbeit, die nur dazu dient, die Schwachstelle aufzuzeigen. Neben dieser Zuverlässigkeit
1.2  Der Kernel und die Welt des Kernel-Hackings 27

muss ein Exploit auch Effizienz aufweisen. Mit anderen Worten, der Autor sollte sich so
wenig wie möglich auf Brute-Force-Techniken verlassen, insbesondere wenn dadurch
Alarm auf dem Zielcomputer ausgelöst werden kann.
Exploits können auf lokale Dienste, aber auch auf Dienste im Netzwerk abzielen:
•• Für einen lokalen Exploit muss der Angreifer bereits Zugang zum Zielcomputer haben.
Der Zweck dieses Exploits besteht darin, die Rechte des Angreifers zu erhöhen und
ihm die volle Kontrolle über das System zu geben.
•• Ein Remote-Exploit zielt auf einen Computer ab, auf den der Angreifer keinen Zugriff
hat, den er aber über das Netzwerk erreichen kann. Diese Art von Exploit stellt eine
größere Herausforderung dar (bietet aber auch in gewissem Maße mehr Möglichkei-
ten). Wie Sie in diesem Buch noch sehen werden, besteht der unverzichtbare erste
Schritt für eine erfolgreiche Ausnutzung darin, so viele Informationen wie möglich
über das Ziel zu sammeln. Diese Aufgabe lässt sich leichter lösen, wenn der Angreifer
bereits Zugriff auf den Computer hat. Das Ziel eines Remote-Exploits besteht darin,
dem Angreifer Zugriff auf den fremden Computer zu geben. Wenn die Zielanwendung
mit erhöhten Rechten läuft, kann eine Anhebung der eigenen Rechte als Bonus hin-
zukommen.

Wenn Sie einen generischen Exploit genauer unter die Lupe nehmen, werden Sie feststellen,
dass er drei Hauptbestandteile aufweist:
•• Vorbereitungsphase: Der Angreifer sammelt Informationen über das Ziel und richtet
eine für ihn günstige Umgebung ein.
•• Shellcode: Hierbei handelt es sich um eine Folge von Maschinenanweisungen, deren
Ausführung gewöhnlich zu einer Erhöhung der Rechte und/oder der Ausführung
eines Befehls führt (z. B. einer neuen Instanz der Shell). Wie Sie in dem folgenden
Codeausschnitt erkennen können, sind die Maschinenanweisungen im Hexformat an-
gegeben, sodass sie vom Exploit-Code leicht manipuliert und im Arbeitsspeicher des
Zielcomputers abgelegt werden können.
•• Auslösungsphase: Der Shellcode wird im Arbeitsspeicher des Zielprozesses platziert
(z. B. über die Einspeisung von Eingaben) und die Schwachstelle wird ausgelöst.
Dadurch wird der Ausführungsfluss des Zielprogramms zum Shellcode umgeleitet.

char kernel_stub[] =
"\xbe\xe8\x03\x00\x00" // mov $0x3e8,%esi
"x65\x48\x8b\x04\x25\x00\x00\x00\x00" // mov %gs:0x0,%rax
"\x31\xc9" // xor %ecx, %ecx (15
"\x81\xf9\x2c\x01\x00\x00" // cmp $0x12c,%ecx
"\x74\x1c" // je 400af0
<stub64bit+0x38>
“\x8b\x10” // mov (%rax),%edx
“\x39\xf2” // cmp %esi,%edx
“\x75\x0e” // jne 400ae8
28 1  Von Userland- zu Kernelland-Angriffen

<stub64bit+0x30>
“\x8b\x50\x04” // mov 0x4 (%rax),%edx
“\x39\xf2” // cmp %esi,%edx
“\x75\x07” // jne 400ae8
<stub64bit+0x30>
“\x31\xd2” // xor %edx,%edx
“\x89\x50\x04” // mov %edx, 0x4(%rax)
“\xeb\x08” // jmp 4 00af0
<stub64bit+0x38>
“\x48\x83\xc0\x04” // add $0x4,%rax
“\xff\xc1” // inc %ecx
“\xeb\xdc” // jmp 400acc
<stub64bit+0x14>
“\x0f\x01\xf8” // swapgs (54
“\x48\xc7\x44\x24\x20\x2b\x00\x00\x00” // movq $0x2b, 0x20(%rsp)
“\x48\xc7\x44\x24\x18\x11\x11\x11\x11” // movq $0x11111111, 0x18(%rsp)
“\x48\xc7\x44\x24\x10\x46\x02\x00\x00” // movq $0x246,0x10(%rsp)
“\x48\xc7\x44\x24\x08\x23\x00\x00\x00” // movq $0x23, 0x8 (%rsp)/* 23
32-bit , 33 64-bit cs */
“\x48\xc7\x04\x24\x22\x22\x22\x22” // movq $0x22222222,(%rsp)
“\x48\xcf”; // iretq

Eines der Ziele eines Angreifers besteht darin, die Wahrscheinlichkeit für die erfolgreiche
Umleitung des Ausführungsflusses zu dem Speicherbereich mit dem Shellcode so weit
wie möglich zu erhöhen. Eine naive (und ineffiziente) Vorgehensweise besteht darin,
alle möglichen Speicheradressen auszuprobieren. Jedes Mal, wenn der Angreifer dabei
auf eine falsche Adresse stößt, stürzt das Programm ab. Der Angreifer versucht es dann
mit dem nächsten Wert, bis er schließlich den Shellcode auslöst. Dies ist eine sogenannte
Brute-Force-Technik, die sehr zeit- und gewöhnlich auch ressourcenintensiv ist. (Stellen
Sie sich einmal vor, über das Netzwerk so vorzugehen!) Außerdem ist sie unelegant. Wie
bereits gesagt greift ein guter Exploit-Autor nur dann auf Brute-Force-Methoden zurück,
wenn es notwendig ist, um ein Maximum an Zuverlässigkeit zu erzielen, und dabei ver-
sucht er die Höchstzahl der erforderlichen Versuche, um den Shellcode auszulösen, so weit
wie möglich zu reduzieren. Eine gängige Vorgehensweise in diesem Fall besteht darin, die
Anzahl der »guten Adressen« zu erhöhen, zu denen der Angreifer springen kann, indem
dem eigentlichen Shellcode eine Folge von NOP- (No Operation) oder NOP-ähnlichen
Anweisungen vorangestellt werden. Wenn der Angreifer den Ausführungsfluss zur Adresse
einer dieser NOP-Anweisungen umleitet, führt die CPU sie ohne Protest eine nach der
anderen aus, bis der Shellcode erreicht ist.
1.2  Der Kernel und die Welt des Kernel-Hackings 29

Tipp
In allen modernen Architekturen gibt es eine NOP-Anweisung, die nichts tut.
Auf x86-Computern wird sie durch den hexadezimalen Opcode (Operati-
on Code) 0x90 dargestellt. Eine NOP-ähnliche Anweisung ist eine Anweisung,
die das Verhalten des Shellcodes nicht ändert, wenn sie mehrere Male vor dem
Shellcode ausgeführt wird. Nehmen wir beispielsweise an, der Shellcode löscht
ein allgemeines Register vor der Verwendung. Jegliche Anweisungen, die dieses
Register ändern, können beliebig oft vor diesem Shellcode ausgeführt werden,
ohne die korrekte Ausführung des Shellcodes selbst zu beeinträchtigen. Wenn
alle Anweisungen von derselben Größe sind, wie es bei RISC-Architekturen der
Fall ist (Reduced Instruction Set Computer), dann kann jede Anweisung, die
keine Auswirkungen auf den Shellcode hat, als NOP verwendet werden. Sind
die Anweisungen dagegen wie bei CISC-Archiekturen (Complex Instruction Set
Computer) von variabler Größe, dann muss eine NOP-ähnliche Anweisung die-
selbe Größe haben wie die eigentliche NOP-Anweisung (also gewöhnlich die
kleinstmögliche Größe). Mithilfe von NOP-ähnlichen Anweisungen lassen sich
einige Sicherheitseinrichtungen umgehen (z. B. manche Intrusion-Detection-
Systeme [IDS]), die einen Exploit dadurch entdecken, dass sie einen Musterver-
gleich an den Daten vornehmen, die die zu schützende Anwendung erreichen.
Sie können sich leicht vorstellen, dass eine Folge von Standard-NOPs eine solche
Prüfung nicht besteht.

Wahrscheinlich ist Ihnen aufgefallen, dass wir bei unserer bisherigen Erörterung eine sehr
gewagte Annahme getroffen haben: Wenn die Opferanwendung erneut ausgeführt wird,
ist ihr Status genau der gleiche wie vor dem Angriff. Ein Angreifer kann zwar den Status
einer Anwendung gut vorhersagen, wenn er das angegriffene Teilsystem gut genug kennt,
aber das ist im Allgemeinen nicht der Fall. Ein erfahrener Exploit-Angreifer versucht
daher stets, die Anwendung in der Vorbereitungsphase in einen bekannten Zustand zu
versetzen. Ein gutes Beispiel dafür zeigt sich in der Ausnutzung von Speicherallokatoren.
Höchstwahrscheinlich unterliegen nicht alle Variablen, die die Abfolge und das Ergebnis
der Speicherzuweisung in einer Anwendung bestimmen, der Kontrolle des Angreifers. In
vielen Situationen kann ein Angreifer jedoch eine Anwendung dazu zwingen, einem be-
stimmten Pfad zu folgen, der zu bestimmen Anforderungen führt. Durch die mehrfache
Ausführung dieser Folge von Anweisungen kann der Angreifer immer mehr Informatio-
nen gewinnen, um das genaue Layout der Speicherallokatoren herauszufinden.
Betrachten wir diesen Vorgang jetzt aber von der anderen Seite: Um es einem Exploit-
Autor so schwer wie möglich zu machen, schreiben Sie Software, die die Ausnutzung einer
angreifbaren Software verhindern soll. Darin können Sie die folgenden Gegenmaßnah-
men umsetzen:
30 1  Von Userland- zu Kernelland-Angriffen

•• Sorgen Sie dafür, dass die Bereiche, in denen ein Angreifer Shellcode speichern könn-
te, nicht ausführbar sind. Wenn diese Bereiche Daten enthalten sollen, dann gibt es
schließlich keinen Grund für die Anwendung, dort irgendwelchen Code auszuführen.
•• Machen Sie es für den Angreifer schwer, die geladenen ausführbaren Bereiche zu
finden, da er dann zu einer für ihn interessanten Folge von Anweisungen in Ihrem
Programm springen kann. Erhöhen Sie dazu die Anzahl der Zufallsvariablen, mit
denen er sich auseinandersetzen muss, sodass Brute-Force-Angriffe so wirkungsvoll
werden wie der Wurf einer Münze.
•• Machen Sie Anwendungen ausfindig, die innerhalb kurzer Zeit mehrmals abstürzen
(was ein deutliches Indiz für einen Brute-Force-Angriff darstellt) und verhindern Sie
den Neustart dieser Anwendungen.
•• Versehen Sie die Grenzen von sensiblen Strukturen (z. B. die Speicherabschnitte der
Speicherallokatoren, die Stackframes usw.) mit Zufallswerten und prüfen Sie die Integ-
rität dieser Werte, bevor Sie sie nutzen (bei Stackframes also, bevor Sie den vorherigen
zurückgeben). Um an die dahinter gespeicherten sensiblen Daten zu gelangen, muss ein
Angreifer diese Werte überschreiben.
Das ist nur ein Ausgangspunkt für all das, was die Software tun soll. Aber wo bringen
Sie diese Maßnahmen unter? Welche Einheit hat so viel Kontrolle und Einfluss über alle
anderen Antworten? Die Antwort lautet: der Kernel!

1.3 Warum funktioniert mein Userland-Exploit nicht mehr?


Diejenigen, die Systeme vor Userland-Exploits zu schützen versuchen, haben sich die
im vorherigen Abschnitt aufgeführten Gegenmaßnahmen (und noch viele weitere!)
ebenfalls überlegt und festgestellt, das der Kernel der Platz ist, an dem sich diese Ge-
genmaßnahmen am wirkungsvollsten umsetzen lassen. Um sich eine Vorstellung davon
zu machen, wie sehr die Hürden für die Entwickler von Userland-Exploits angehoben
wurden, müssen Sie sich nur die Liste der Leistungsmerkmale von Projekten wie PaX/
grsecurity (www.grsecurity.net), ExecShield (http://people.redhat.com/mingo/exec-shield)
oder Openwall (www.openwall.com) für den Linux-Kernel oder die Sicherheitsmerkmale
von beispielsweise OpenBSD (W^X, Adress Space Layout Randomization [ASLR]) oder
Windows (Datenausführungsverhinderung, ASLR) ansehen.
1.3  Warum funktioniert mein Userland-Exploit nicht mehr? 31

Verteidigen Sie sich!


Verteidigung findet auf mehreren Ebenen statt
Alle Schutzmaßnahmen an einer einzigen Stelle zu konzentrieren, war noch nie
eine gute Idee, und dieses Prinzip gilt auch die Verteidigung gegen Exploits. Pat-
ches auf Kernelebene gehören zwar zu den wirkungsvollsten Maßnahmen, doch
Sicherheitsvorkehrungen lassen sich auch auf anderen Ebenen treffen. Compi-
ler sind ebenfalls sehr geeignete Orte für die Anbringung von Patches, denn wie
können Sie Ihren Code besser schützen als dadurch, die Verteidigung gleich in
ihn selbst aufzunehmen? Beispielsweise enthalten neuere Versionen der GNU
Compiler Collection (GCC, http://gcc.gnu.org) bereits Fortify Source2 und Optio-
nen für Stack Smashing Protector, auch bekannt als ProPolice (www.trl.ibm.com/
projects/security/sspl/). Allzweck-Bibliotheken können Patches ebenfalls gut ge-
brauchen, denn sie gehören zu dynamisch verknüpften Bibliotheken und können
sensible Teilsysteme wie Speicherallokatoren enthalten. ExecShield von Red Hat/
Fedora ist ein Beispiel für ein Projekt, dass all diese Arten von Patches einschließt.

Sie können ein System nicht nur dadurch schützen, dass Sie angreifbaren Code gegen
Ausnutzung absichern, sondern auch dadurch, dass Sie die Auswirkungen einer Ausnut-
zung verringern. In der Einführung zu diesem Kapitel haben wir bereits das klassische
Benutzermodell erwähnt, das die meisten in diesem Buch behandelten Betriebssysteme
verwenden. Die Stärke dieses Modells, nämlich seine Einfachheit, ist auch seine Schwäche:
Es deckt sich nicht mit dem Nutzungsmodell der Anwendungen, die auf einem System
laufen. Um zu verdeutlichen, was das bedeutet, sehen wir uns ein einfaches Beispiel an.
Zu den üblichen privilegierten Operationen gehören das Öffnen eines niedrigen TCP-
oder UDP-Ports (1 bis 1023 einschließlich) und das Löschen eines Benutzers vom Sys-
tem. Bei dem zuvor beschriebenen naiven Benutzermodell müssen beide Operationen mit
Superuser-Rechten ausgeführt werden. Allerdings ist es ziemlich unwahrscheinlich, dass
eine Anwendung beide Aktionen ausführen muss. Es gibt keinen Grund dafür, dass ein
Webserver Logik für die Verwaltung von Benutzerkonten enthält. Eine Schwachstelle in
der Webserveranwendung würde einem Angreifer aber die volle Kontrolle über das Sys-
tem geben. Das Prinzip der Rechtetrennung besteht darin, die Menge des mit sämtlichen
Rechten ausgeführten Codes so weit wie möglich zu reduzieren. Bei unserem Webserver
sind Superuser-Rechte nur notwendig, um das Socket zu öffnen, das an dem traditionel-
len HTTP-Port (80) lauscht. Nachdem er diese Operation ausgeführt hat, ist es für ihn
nicht mehr erforderlich, den Superuser-Status aufrechtzuerhalten. Um die Auswirkungen
der Ausnutzung einer Schwachstelle zu verringern, müssen Anwendungen wie HTTP den
Superuser-Status verwerfen, sobald sie ihre privilegierten Operationen ausgeführt haben.
Andere Daemons, beispielsweise sshd, zerlegen die Anwendung auf der Grundlage der ver-
schiedenen Arten von erforderlichen Operationen in mehrere Teile. Vollständige Rechte

2 Beispielsweise kennt der Compiler zur Kompilierungszeit die Größe bestimmter Puffer und kann diese
Information nutzen, um den Aufruf einer unsicheren Funktion wie strcpy zu einer sicheren Funktion wie
strncpy umzuleiten.
32 1  Von Userland- zu Kernelland-Angriffen

werden nur den Teilen zugewiesen, die sie auch benötigen, und diese Teile werden so klein
gefasst wie möglich. Die einzelnen Teile kommunizieren während der Lebensdauer der
Anwendung daher über eine Form von IPC-Kanal (Interprocess Communication).
Können wir es noch besser machen? Nun, wir können das Prinzip der geringstmöglichen
Rechte auf das gesamte System ausweiten. Mandatory Access Control (MAC), Zugriffs-
steuerungslisten (Access Control Lists, ACL) und rollengestützte Zugriffssteuerung (Role-
Based Access Control, RBAC) wenden dieses System auf die eine oder andere Weise auf
das Gesamtsystem an und verwerfen damit das Superuser-Prinzip. Jedem Benutzer wird
die geringstmögliche Menge an Rechten zugeteilt, die er zur Erledigung seiner Aufgaben
benötigt. Beispiele für solche Systeme sind Solaris Trusted Extensions, Linux grsecuri-
ty und Patches für NSA SELinus (www.nsa.gov/research/selinux/index.shtml, enthalten
im Linux-Mainstreamkernel seit Version 2.6) sowie Windows Vista Mandatory Integrity
Control.
Einen erfolgreichen und zuverlässigen Userland-Exploit zu schreiben, der diese Schutz-
vorkehrungen umgeht, stellt eine große Herausforderung dar, und dabei müssen wir
außerdem noch voraussetzen, dass der Autor bereits eine Schwachstelle in seinem Ziel
gefunden hat. Zum Glück (oder leider, je nachdem, wo Sie stehen) sind die Hürden auch
hier heraufgesetzt worden. Während der letzten beiden Jahrzehnte wurden Angriffe mit-
hilfe von Exploits immer weiter verbreitet. Als Folge davon wurde jegliche bedeutende
Userland-Software mehrfach von verschiedenen Hackern und Sicherheitsforschern in
aller Welt überprüft. Software entwickelt sich weiter, und es wäre naiv anzunehmen, dass
sich bei dieser Evolution keine neuen Bugs einschleichen würden. Allerdings lassen sich
Schwachstellen heute längst nicht mehr so leicht finden wie noch vor zehn Jahren.

Warnung
Wir konzentrieren uns hier auf die Verteidigung gegen Exploits mithilfe von
Software, doch auch hardwareseitig ist ein gewisser Schutz möglich. Beispiels-
weise gibt es in der x86-64-Architektur (der 64-Bit-Weiterentwicklung der x86-
Architektur) ein NX-Bit3 für physische Seiten. Moderne Kernels können dieses
Bit nutzen, um Bereiche im Adressraum als nicht ausführbar zu kennzeichnen,
was die Anzahl der Stellen verringert, an denen Angreifer Shellcode unterbrin-
gen können. Mehr darüber (und wie diese Schutzvorkehrung umgangen werden
kann) erfahren Sie in Kapitel 3.

3 Das NX-Bit (nonexecutable) kann auch auf 32-Bit-x86-Computern aktiviert werden, die die physische Ad-
resserweiterung (Physical Address Extension, PAE) unterstützen. Mehr darüber erfahren Sie in Kapitel 3.
1.3  Warum funktioniert mein Userland-Exploit nicht mehr? 33

1.3.1 Kernelland- und Userland-Exploits im Vergleich


Wir haben den Kernel bereits als die Einheit bezeichnet, in der viele Sicherheitsmaßnah-
men gegen Exploits implementiert sind. Angesichts der zunehmenden Verbreitung von
Sicherheitspatches und des derzeitigen Rückgangs an Userland-Schwachstellen ist es nicht
überraschend, dass Exploit-Autoren ihre Aufmerksamkeit zum Kern des Betriebssystems
verlagert haben. Im Vergleich zu Userland-Exploits stellt das Schreiben von Kernelland-
Exploits jedoch einige zusätzliche Herausfoderungen an den Autor:
•• Der Kernel ist die einzige Software, die für das System unverzichtbar ist. Solange der
Kernel ordnungsgemäß läuft, gibt es keine nicht behebbare Situation. Bei Userland-
Exploits sind Brute-Force-Methoden durchaus anwendbar, denn die einzige Sorge,
die sich ein Angreifer machen muss, wenn die Zielanwendung wiederholt abstürzt,
besteht darin, dass dies deutliche Spuren in den Protokollen hinterlässt. Beim Kernel
ist das jedoch nicht mehr so: Ein Fehler im Kernel versetzt das System in einen in-
konsistenten Zustand, wobei gewöhnlich ein manueller Neustart erforderlich ist, um
den Rechner wieder in einen ordnungsgemäß funktionierenden Zustand zurückzu-
versetzen. Tritt der Fehler in einem der sensiblen Bereiche des Kernels auf, stürzt das
Betriebssystem einfach ab, was als Panik bezeichnet wird. Einige Betriebssysteme, z. B.
Solaris, schreiben auch die Informationen über die Panik zur nachträglichen Analyse
in eine Crashdump-Datei.
•• Der Kernel ist sowohl durch Software als auch durch Hardware vom Userland ge-
trennt. Informationen über den Kernel zu gewinnen, ist sehr viel schwieriger. Auch die
Anzahl der Variablen, die nicht mehr der Kontrolle des Angreifers unterliegen, steigt
exponentiell. Beispielsweise befindet sich der Speicherallokator bei einem Userland-
Exploit innerhalb des Prozesses und ist gewöhnlich über eine gemeinsam genutzte Sys-
tembibliothek verknüpft. Das Ziel ist der einzige Verbraucher und das Einzige, was
sich darauf auswirkt. Dagegen können sich sämtliche Prozesse auf dem System auf das
Verhalten und den Status eines Kernel-Speicherallokators auswirken.
•• Der Kernel ist ein ausgedehntes und vielschichtiges System. Sein Umfang ist erheb-
lich und liegt in der Größenordnung von Millionen Codezeilen. Er muss sich um
die gesamte Hardware auf dem Computer und die meisten maschinennahen Soft-
wareabstraktionen kümmern (virtueller Speicher, Dateisysteme, IPC-Einrichtungen
usw.). Das führt zu einer Menge hierarchisch geordneter, miteinander verbundener
Teilsysteme, die ein Angreifer erst einmal genau verstehen muss, bevor er in der Lage
ist, eine bestimmte Schwachstelle auszulösen und erfolgreich auszunutzen. Da ein so
kompliziertes System selten fehlerfrei ist, kann sich diese Eigenschaft jedoch auch als
vorteilhaft für die Entwickler von Exploits erweisen.

Der Kernel bietet Angreifern jedoch auch einige Vorteile gegenüber dem Userland. Da
er der Code mit den höchsten Rechten auf dem System ist (ohne Berücksichtigung von
Virtualisierungslösungen; siehe den folgenden Hinweis), lässt er sich auch am schwie-
rigsten schützen. Außer der Hardware gibt es keine andere Einheit mehr, auf die dieser
Schutz gestützt werden kann.
34 1  Von Userland- zu Kernelland-Angriffen

Hinweis
Zur Zeit der Abfassung dieses Buchs werden Virtualisierungssysteme immer
beliebter. Es wird nicht mehr lange dauern, bis virtualisierte Kernelschutzvor-
kehrungen auftreten. Allerdings müssen bei dieser Art von Schutz auch Leis-
tungseinbußen berücksichtigt werden. Um eine große Verbreitung zu genießen,
dürfen die Virtualisierungslösungen den von ihnen geschützten Kernel nicht zu
stark beeinträchtigen.

Viele der beschriebenen Schutzvorkehrungen führen allerdings auch zu Leistungseinbu-


ßen. Bei einigen Userland-Anwendungen mögen sie zwar vernachlässigbar sein, doch wenn
die Maßnahmen auf den Kernel (und damit auf das gesamte System) angewendet werden,
haben sie viel stärkere Auswirkungen. Die Leistung ist für Endbenutzer ein entscheidendes
Kriterium, und es ist gar nicht einmal so unüblich, auf Sicherheit zu verzichten, wenn sie
eine Verschlechterung der Leistung nach sich zieht.
Tabelle 1.1 gibt einen Überblick über die wichtigsten Unterschiede zwischen Userland-
und Kernelland-Exploits.

Tabelle 1.1: Unterschiede zwischen Userland- und Kernelland-Exploits

Zweck Userland-Exploit Kernelland-Exploit


Brute-Force-Angriff auf Führt zu mehreren Abstürzen Führt zu einem inkonsistenten
eine Schwachstelle der Anwendung, die dann neu Zustand des Computers und
gestartet werden kann (oder im Allgemeinen zu einer Panik
automatisch neu gestartet oder einem Neustart.
wird, z. B. über inetd in Linux).
Einfluss auf das Ziel Der Angreifer hat großen Der Angreifer konkurriert bei
nehmen Einfluss (vor allem lokal) auf seinem Versuch, »Einfluss« auf
die Zielanwendung (beispiels- den Kernel zu nehmen, mit al-
weise kann er die Umgebung len anderen Anwendungen. Alle
sehen, in der sie läuft). Die Anwendungen sind Verbraucher
Anwendung ist der einzige der Kernel-Teilsysteme.
User des Bibliothekssystems,
das sie nutzt (z. B. des Spei-
cherallokators).
Shellcode ausführen Der Shellcode kann Kernel- Der Shellcode wird mit höheren
Systemaufrufe über User- Rechten ausgeführt und muss
land-Zugänge aufrufen, die die Steuerung korrekt ans
Sicherheit und Korrektheit Userland zurückgeben, ohne
garantieren. eine Panik des Systems hervor-
zurufen.
1.4  Der Kernel aus der Sicht eines Exploit-Autors 35

Zweck Userland-Exploit Kernelland-Exploit


Exploit-Schutz um- Dies erfordert immer kompli- Die meisten Schutzvorkehrun-
gehen ziertere Vorgehensweisen. gen befinden sich auf Kernel­
ebene, schützen aber nicht den
Kernel selbst. Angreifer können
die meisten davon ausschalten.

Die Anzahl der »Tricks«, die Sie auf Kernelebene ausführen können, ist praktisch unbegrenzt.
Das ist ein weiterer Vorteil der Vielschichtigkeit des Kernels. Wie Sie in diesem Buch noch se-
hen werden, ist es viel schwieriger, Kernelland-Schwachstellen zu klassifizieren als diejenigen
im Userland. Es ist zwar möglich, einige gängige Angriffswege ausfindig zu machen (was wir
auch tun werden), aber jede Kernel-Schwachstelle stellt eine eigene Geschichte dar.
Aber lehnen Sie sich ruhig zurück und entspannen Sie sich. Wir stehen erst am Anfang
unserer Betrachtung.

1.4 Der Kernel aus der Sicht eines Exploit-Autors


Im vorherigen Abschnitt haben wir uns die Unterschiede zwischen Userland- und Ker-
nelland-Exploits angesehen, doch von nun an wollen wir uns auf den Kernel konzent-
rieren. In diesem Abschnitt beschäftigen wir uns etwas stärker mit einigen theoretischen
Grundlagen, die für das weitere Verständnis sehr nützlich sind. Später besprechen wir
dann Kernel-Schwachstellen und -Angriffe. Da dies kein Buch über Betriebssysteme ist,
haben wir uns entschieden, die Prinzipien der Ausnutzung vor diesem Abschnitt vorzu-
stellen, sodass die entsprechenden Informationen klar hervorstechen. Allerdings gilt, dass
Sie ein Betriebssystem umso besser gezielt angreifen können, je mehr Sie darüber wissen.
Das Studium von Betriebssystemen ist nicht nur faszinierend, sondern zahlt sich auch aus,
wenn es an einen Angriff geht. (Um mehr über Betriebssysteme zu erfahren, beachten Sie
die in den Literaturhinweisen am Ende des Kapitels angegebenen Quellen.)

1.4.1 Userland-Prozesse und der Scheduler


Eine der Eigenschaften von Betriebssystemen, die wir als gegeben hinnehmen, ist die
Fähigkeit, mehrere Prozesse gleichzeitig auszuführen. Sofern ein System nicht über meh-
rere CPUs verfügt, kann offensichtlich immer nur ein Prozess auf einmal aktiv sein. Der
Kernel vermittelt dem Benutzer die Illusion des Multitasking, indem er jedem Prozess
Zeit auf der CPU einräumt und schnell zwischen den Prozessen wechselt. Dazu spei-
chert der Kernel für jeden laufenden Prozess einen Satz von Informationen über dessen
Status: an welcher Stelle in der Ausführung er sich befindet, ob er aktiv ist oder auf eine
Ressource wartet, in welchem Zustand sich der Computer befand, als der Prozess wieder
von der CPU entfernt wurde usw. All diese Informationen werden als Ausführungskon-
text bezeichnet und der Austausch eines Prozesses in der CPU gegen einen anderen als
36 1  Von Userland- zu Kernelland-Angriffen

Kontextwechsel. Das Teilsystem, das für die Auswahl des nächsten Prozesses und die Ver-
mittlung der CPU-Ressourcen für die verschiedenen Aufgaben zuständig ist, wird Sche-
duler genannt. Zur Ausnutzung von Race Conditions ist es sehr wichtig, Einfluss auf die
Entscheidungen des Schedulers nehmen zu können.
Neben den erforderlichen Informationen für einen ordnungsgemäßen Kontextwechsel
verfolgt der Kernel auch andere Einzelheiten zu den Prozessen, z. B. welche Dateien sie
öffnen, welche Berechtigungsnachweise sie verwenden und welche Speicherbereiche sie
nutzen. Der erste Schritt bei der Entwicklung von Kernel-Shellcode besteht gewöhnlich
darin, die Strukturen ausfindig zu machen, in der diese Angaben gespeichert sind. Wenn
Sie die Strukturen kennen, in denen die Berechtigungsnachweise des laufenden Prozesses
festgehalten werden, dann können Sie ganz einfach Ihre Rechte erhöhen und damit Ihre
Möglichkeiten erweitern.

1.4.2 Virtueller Arbeitsspeicher


Jeder Exploit-Entwickler muss auch mit dem Kernel-Teilsystem vertraut sein, das die
Abstraktion des virtuellen Arbeitsspeichers für die Prozesse und den Kernel selbst bereit-
stellt. Computer verfügen über einen festen Betrag an physischem Arbeitsspeicher (Ran-
dom Access Memory, RAM), in dem flüchtige Daten festgehalten werden. Der physische
Adressraum besteht aus den Adressen von 0 bis RAM-Größe -1. Allerdings vermitteln
moderne Betriebssysteme den laufenden Prozessen und den verschiedenen Kernel-Teil-
systemen die Illusion eines großen, privaten Adressraums, der nur ihnen allein gehört.
Dieser virtuelle Adressraum ist gewöhnlich größer als der physische und wird durch die
Architektur begrenzt: Bei einer n-Bit-Architektur reicht er gewöhnlich von 0 bis 2n -1.
Das Teilsystem für den virtuellen Speicher ist für diese Abstraktion zuständig, verwaltet
die Übersetzung von virtuellen in physische Adressen (und umgekehrt) und setzt die
Trennung zwischen verschiedenen Adressräumen durch. Wie bereits im vorherigen Ab-
schnitt gesagt, besteht einer der wichtigsten Aspekte eines sicheren Systems in der Tren-
nung zwischen Kernel und Prozessen und zwischen den einzelnen Prozessen. Um das zu
erreichen, teilen fast alle Betriebssysteme (und alle, die wir in diesem Buch behandeln)
den physischen Adressraum in Abschnitte fester Größe auf, sogenannte Seitenframes,
und den virtuellen Adressraum in Abschnitte derselben Größe, die als Seiten bezeich-
net werden. Wenn ein Prozess eine Speicherseite benötigt, weist ihm das Teilsytem für
den virtuellen Speicher einen physischen Frame zu. Die Übersetzung von physischen
Seitenframes zu virtuellen Seiten erfolgt mithilfe von Seitentabellen, die angeben, wel-
che physische Seite einer gegebenen virtuellen Adresse zugeordnet ist. Wenn alle Sei-
tenframes zugeordnet sind und ein neuer benötigt wird, wählt das Betriebssystem eine
Seite aus, die gerade nicht benutzt wird, kopiert sie in einen gesonderten Bereich der
Fest­platte – den Auslagerungsbereich – und macht damit einen physischen Frame frei, der
dem Prozess zurückgegeben wird. Wird die ausgelagerte Seite wieder gebraucht, kopiert
das Betriebssystem eine andere Seite auf die Festplatte und holt die vorherige zurück.
Diese Auslagerung wird auch als Swapping bezeichnet. Da der Zugriff auf die Festplatte
ein langsamer Vorgang ist, erstellt das Teilsystem für den virtuellen Speicher zur Verbes-
1.4  Der Kernel aus der Sicht eines Exploit-Autors 37

serung der Leistung zunächst einen virtuellen Adressbereich für den Prozess und weist
ihm erst dann einen physischen Seitenframe zu, wenn zum ersten Mal auf diese Adresse
verwiesen wird. Diese Vorgehensweise wird als Paging bei Bedarf bezeichnet.

Werkzeuge und Fallstricke


Den virtuellen Adressraum eines Prozesses beobachten
Sie wissen jetzt, was virtueller Arbeitsspeicher ist und wie er funktioniert. Um
ihn sich in Aktion anzusehen, können Sie einige Werkzeuge verwenden, die das
Betriebssystem zur Verfügung stellt. Auf Linux-Computern können Sie dazu den
Befehl cat /proc/<Pid>/maps geben (wobei <Pid> für die gewünschte Prozess-ID
steht). Daraufhin wird Ihnen eine Liste des gesamten Arbeitsspeichers angezeigt,
der dem Prozess zugewiesen ist (also alle virtuellen Adressbereiche, die der Pro-
zess angefordert hat), wie das folgende Beispiel zeigt:

luser@katamaran:~$ cat /proc/3184/maps


00400000-004c1000 r-xp 00000000 03:01 703138 /bin/bash
006c1000-006cb000 rw-p 000c1000 03:01 703138 /bin/bash
006cb000-006d0000 rw-p 006cb000 00:00 0
00822000-008e2000 rw-p 00822000 00:00 0 [heap]
7f7ea5627000-7f7ea5632000 r-xp 00000000 03:01 809430
/lib/libnss_files-2.9.so
7f7ea5632000-7f7ea5831000 ---p 0000b000 03:01 809430
/lib/libnss_files-2.9.so
[...]

Wie Sie sehen, erhalten Sie dabei eine Menge von Informationen, darunter die
Adressbereiche (links), die Seitenschutzeinstellungen (rwxp für Lesen/Schrei-
ben/Ausführen/privat) und die Datei, die letzten Endes hinter dieser Zuord-
nung steht. Ähnliche Informationen können Sie auf fast allen Betriebssystemen
erhalten. Bei OpenSolaris verwenden Sie den Befehl pmap, beispielsweise als pmap
-x <Pid>, auf Mac OS X dagegen vmmap in der Form vmmap <Pid> oder vmmap
<Prozessname>. Wenn Sie auf Windows arbeiten, sollten Sie sich die Sysinter-
nals-Suite von Mark Russinovich herunterladen (http://technet.microsoft.com/
en-us/sysinternals/bb842062.aspx), die neben vmmap noch viele weitere nützliche
System- und Prozessanalysewerkzeuge enthält.

Je nach Architektur steht zur Umsetzung dieses Vorgangs mehr oder weniger Unterstüt-
zung durch die Hardware bereit. Wenn wir die kniffligen Einzelheiten (die in jedem Buch
über Computerarchitektur und Betriebssysteme ausführlich beschrieben werden) einen
Augenblick lang außer Acht lassen, können wir sagen, dass der innere Kern der CPU den
physischen Arbeitsspeicher ansprechen muss, während Exploit-Autoren fast immer mit
dem virtuellen Speicher herumspielen.
38 1  Von Userland- zu Kernelland-Angriffen

Wie bereits gesagt, erfolgt die Übersetzung zwischen dem virtuellen und dem physischen
Adressraum mithilfe einer Datenstruktur, die als Seitentabelle bezeichnet wird. Für jeden
Prozess wird eine eigene Seitentabelle erstellt, und bei jedem Kontextwechsel wird die
entsprechende Tabelle geladen. Da es für jeden Prozess eine eigene Tabelle und einen eige-
nen Satz von Seiten gibt, sieht jeder Prozess einen umfangreichen, zusammenhängenden
virtuellen Adressraum ganz für sich allein. Dadurch wird auch die Trennung der Prozesse
erreicht. Besondere Seitenattribute ermöglichen es dem Kernel, seine Seiten gegenüber
dem Userland zu schützen und seine Gegenwart zu verbergen. Je nachdem, wie dies um-
gesetzt wird, gibt es zwei verschiedene Situationen: Kernelraum oberhalb des Benutzer-
raums oder getrennte Kernel- und Benutzeradressräume. Im Folgenden sehen wir uns an,
warum das für die Ausnutzung ein sehr interessantes Merkmal ist.

1.4.3 Benutzerraum oberhalb des Kernelraums im Vergleich mit


getrennten Adressräumen
Aufgrund des User/Supervisor-Seitenattributs können Sie vom Userland aus kaum etwas
vom Kernellayout sehen. Sie kennen nicht einmal die Adressen, denen der Kerneladress-
raum zugewiesen ist. Allerdings können Sie einen Angriff nur vom Userland aus ausführen.
Wie bereits erwähnt, können Sie dabei zwei verschiedene Situationen antreffen:
•• Kernelraum oberhalb des Benutzerraums    Hier ist der virtuelle Adressraum in zwei
Abschnitte aufgeteilt, einen privaten für den Kernel und einen zweiten für Userland-
Anwendungen. Um das zu erreichen, werden die Einträge der Kernelseitentabelle in
die Seitentabellen aller Prozesse übernommen. Beispielsweise befindet sich der Kernel
auf einem 32-Bit-x86-Computer mit Linux im Bereich von 0xc00000000 bis 0xffffffff
(also im »oberen« Gigabyte des virtuellen Speichers), wohingegen die einzelnen Pro-
zesse sämtliche Adressen unterhalb dieses Bereichs verwenden können (also in den
unteren 3 GB des virtuellen Speichers).
•• Getrennte Adressräume für Kernel und Prozesse    Hier erhalten sowohl der Kernel
als auch die Userland-Anwendungen jeweils vollständige, unabhängige Adressräume.
Beide können also den kompletten Bereich virtueller Adressen nutzen.

Für einen Exploit bietet die erste Situation eine Menge Vorteile gegenüber der zweiten.
Um das genau zu verstehen, müssen wir uns jedoch zunächst mit dem Prinzip des Ausfüh-
rungskontexts beschäftigen. Im Supervisormodus der CPU (also beim Ausführen eines
Kernelpfads) befindet sich die Ausführung im sogenannten Interruptkontext, wenn mit
ihr kein unterstützender Prozess verbunden ist. Eine solche Situation tritt beispielsweise
als Folge eines von der Hardware hervorgerufenen Interrupts auf, etwa wenn ein Paket auf
der Netzwerkkarte eingeht oder wenn eine Festplatte das Ende einer Operation meldet.
Die Ausführung wird an eine Interruptdienstroutine übertragen, und was immer zurzeit
auf der CPU läuft, wird abgebrochen. Code im Interruptkontext kann nicht blockieren
(indem er z. B. darauf wartet, dass durch das bedarfsweise Paging eine referenzierte Seite
zur Verfügung gestellt wird) oder in den Ruhezustand übergehen, denn der Scheduler
weiß nicht, wann er den Code schlafen legen (oder aufwecken) sollte.
1.4  Der Kernel aus der Sicht eines Exploit-Autors 39

Dagegen sprechen wir davon, dass ein Kernelpfad im Prozesskontext ausgeführt wird,
wenn es einen zugehörigen Prozess gibt. Gewöhnlich handelt es sich dabei um denjenigen,
der den Kernelcodepfad ausgelöst hat (z. B. als Folge eines Systemaufrufs). Solcher »Code«
unterliegt nicht allen Einschränkungen, die Code im Interruptkontext betreffen. Dies ist
der häufigste Ausführungsmodus innerhalb des Kernels. Damit sollen die Aufgaben, die
die Interruptdienstroutine durchführen muss, so weit wie möglich verringert werden.
Wir haben gerade kurz erklärt, was es bedeutet, »einen unterstützenden Prozess zu ha-
ben«, nämlich dass eine Menge prozessspezifischer Informationen bereitstehen und vom
Kernelpfad genutzt werden können, ohne dass er sie ausdrücklich laden oder danach Aus-
schau halten muss. Das heißt, dass eine Variable mit den Informationen über den aktuellen
Prozess innerhalb des Kernels unterhalten und jedes Mal geändert wird, wenn ein Prozess
auf der CPU eingeplant wird. Diese Variable wird von vielen Kernelfunktionen genutzt,
sodass sie aufgrund der Informationen über den unterstützenden Prozess handeln können.
Da Sie den unterstützenden Prozess steuern können (indem Sie z. B. einen bestimmten Sys-
temaufruf ausführen), haben Sie damit Einfluss auf den unteren Teil des Adressraums. Neh-
men wir des Weiteren an, dass Sie eine Kernelschwachstelle gefunden haben, die es Ihnen
erlaubt, den Ausführungsfluss beliebig umzuleiten. Wäre es nicht schön, diese Umleitung
zu einer Adresse im Userland vorzunehmen, die Sie kennen und beeinflussen können? Das
ist genau das, was in Systemen mit Kernalraum oberhalb des Benutzerraums möglich ist. Da
die Einträge der Kernelseitentabellen in die Prozessseitentabellen übernommen werden,
ist ein einziger virtueller Adressraum aktiv, der aus dem Kernelanteil und den Userland-­
Zuordnungen Ihres Prozesses besteht, sodass Sie die Zuweisung eines darin befindlichen
Zeigers aufheben können. Natürlich müssen Sie sich dazu im Prozesskontext befinden, da
Sie im Interruptkontext meistens keinen Hinweis darauf haben, welcher Prozess unterbro-
chen wurde. Die Kombination der Benutzer- und Kerneladressräume bietet viele Vorteile:
•• Sie müssen nicht raten, wo sich Ihr Shellcode befindet. Außerdem können Sie ihn in C
schreiben, da sich der Compiler um die Assemblierung kümmert. Das ist ein Gottes­
geschenk, wenn der Code zum Auslösen der Schwachstelle viele Kernelstrukturen
verändert und daher eine sorgfältige Wiederherstellungsphase erforderlich macht.
•• Sie haben nicht mit dem Problem zu kämpfen, einen großen, sicheren Platz zur Spei-
cherung des Shellcodes zu finden, denn schließlich stehen Ihnen 3 GB Adressraum
unter Ihrem Einfluss zur Verfügung.
•• Sie müssen sich keine Sorgen um den Ausführungsschutz von Seiten machen. Da Sie den
Adressraum steuern, können Sie ihn im Arbeitsspeicher nach Ihrem Gusto zuweisen.
•• Sie können einen Großteil des Adressraums im Arbeitsspeicher zuordnen und mit
NOPs oder NOP-ähnlichem Code/Daten füllen, wodurch Sie Ihre Aussichten auf Er-
folg erhöhen. Wie Sie noch sehen werden, können Sie manchmal nur einen Teil der
Rückgabeadressen zu überschreiben, weshalb die einzige Möglichkeit für einen zuver-
lässigen Exploit darin besteht, für eine möglichst große »Landefläche« zu sorgen.
•• Sie können Dereferenzierungsbugs im Userspache (und die Dereferenzierung von
NULL-Zeigern) leicht ausnutzen. Darüber werden wir uns in Kapitel 2 noch genauer
unterhalten.
40 1  Von Userland- zu Kernelland-Angriffen

All diese Vorgehensweisen sind in Umgebungen mit getrenntem Benutzer- und Kernelad-
ressraum nicht anwendbar. Auf solchen Systemen kann ein und dieselbe virtuelle Adres-
se im Kernel- und im Userland unterschiedliche Bedeutungen haben. Für einen Exploit
können Sie daher keine Zuweisungen innerhalb Ihres Prozessadressraums nutzen. Man
könnte sagen, dass die Vorgehensweise mit kombiniertem Benutzer- und Kerneladress-
raum die bessere ist, denn um effizient zu sein, benötigen getrennte Adressräume die Hilfe
der zugrunde liegenden Architektur. Das ist beispielsweise bei den Kontextregistern von
UltraSPARC-Computern der Fall. Dies bedeutet jedoch nicht, dass es unmöglich ist, ein
solches Design auf einer x86-Architektur zu verwirklichen. Das Problem besteht in den
Leistungseinbußen, die dadurch entstehen.

1.5 Open-Source- und Closed-Source-Betriebssysteme


In den beiden letzten Abschnitten haben wir uns einige allgemeine Prinzipien der
Kernel­implementierung angesehen, die für die verschiedenen in diesem Buch behan-
delten Betriebssysteme gelten. Im weiteren Verlauf konzentrieren wir uns auf drei Ker-
nelfamilien: Linux (als klassisches Beispiel für ein UNIX-Betriebssystem), Mac OS X
(mit seinem hybriden Microkernel/UNIX-Design) und Windows. In den Kapiteln 4 bis
6 sehen wir uns diese Betriebssysteme ausführlicher an. Zum Abschluss dieses Kapi-
tels wollen wir jedoch einige Anmerkungen zum Gegensatz zwischen Open Source und
Closed Source machen.
Ein Grund für die Beliebtheit von Linux ist seine quelloffene Natur: Der gesamte Quell-
code des Betriebssystems ist unter der Lizenz GPL (GNU Public Licence) veröffentlicht,
die die freie Verteilung des Kernelquellcodes erlaubt. Tatsächlich ist die Sache jedoch etwas
komplizierter, denn es gibt genaue Vorschriften, was Sie mit dem Quellcode tun dürfen
und was nicht. Beispielsweise ist festgelegt, dass Sie bei der Verwendung von GPL-Code
im Rahmen eines umfassenderen Projekts das gesamte Projekt ebenfalls mit der Lizenz
GPL veröffentlichen müssen. Andere UNIX-Derivate sind ebenfalls (ganz oder zum Teil)
quelloffen, wobei jedoch andere (und gewöhnlich entspanntere) Lizenzen verwendet wer-
den. FreeBSD, OpenBSD, NetBSD, OpenSolaris und (auch wenn es sich um einen Hybrid-
kernel handelt) Mac OS X erlauben es Ihnen, sich den kompletten Quellcode des Kernels
oder zumindest einen großen Teil davon anzusehen. Dem stehen die Microsoft Windows-
Familie und einige kommerzielle UNIX-Derivate wie IBM AIX und HP-UX gegenüber.
Den Quellcode zur Verfügung zu haben, hilft dem Exploit-Entwickler, da er dadurch
die Interna des von ihm ins Visier genommenen Teilssystems oder Kernels besser verste-
hen und leichter nach Angriffswegen suchen kann. Die Überprüfung auf Schwachstellen
ist in einem Open-Source-Systems im Allgemeinen auch einfacher als in einem Closed-
Source-System. Reverse-Engineering eines geschlossenen Systems ist viel zeitaufwendi-
ger und erfordert es, große Teile des Assemblercodes zu lesen, um das Gesamtbild zu
betrachten. Andererseits gelten Open-Source-Systeme als »robuster«, da der Code von
viel mehr Personen unter die Lupe genommen wird, die Fehler und Schwachstellen mel-
den können, wohingegen Probleme in geschlossenem Quellcode lange Zeit unbemerkt
1.6 Zusammenfassung 41

bleiben können (oder einfach nur nicht gemeldet werden). Eine solche Diskussion ist
vergebliche Liebesmüh. Systeme sind immer nur so gut und sicher wie die Qualität ihrer
Entwicklungs- und Testprozesse, es ist immer nur eine Frage der Zeit, bis Schwachstellen
von einem geschickten Forscher oder Hacker gefunden und ausgenutzt werden.

1.6 Zusammenfassung
In diesem Kapitel haben wir unser Ziel – den Kernel – vorgestellt und erklärt, warum
viele Exploit-Entwickler daran interessiert sind. Kernel-Exploits haben sich nicht nur als
möglich, sondern auch als äußerst leistungsfähig und effizient erwiesen, insbesondere auf
Systemen mit modernsten Sicherheitspatches. Für diese Leistungsfähigkeit muss der Ent-
wickler des Exploits jedoch ein breites und tiefes Verständnis des Kernelcodes mitbringen
und sich stark anstrengen. Unsere Fahrt ins Land des Kernel-Hackings haben wir damit
begonnen, uns einige allgemeine, unverzichtbare Prinzipien des Kernels anzusehen: wie
sich der Kernel über Prozesse auf dem Laufenden hälft, wie er die auszuführenden Pro-
zesse auswählt und wie der virtuelle Speicher es den einzelnen Prozessen ermöglicht, so
zu laufen, als stünde ihnen ein großer, zusammenhängender, privater Adressraum zur
Verfügung. Das war natürlich nur eine Einführung. Im Rest dieses Buchs werden wir uns
genauer mit den Einzelheiten der Teilsysteme beschäftigen. Wenn Sie mehr über Ausnut-
zung, Codeprüfung und die Entwicklung von Shellcode wissen möchten, schauen Sie sich
unter den Literaturhinweisen am Ende dieses Kapitels um.
In diesem Kapitel haben wir uns auch die Unterschiede zwischen kombiniertem Benut-
zer- und Kerneladressraum und einem Design mit getrennten Adressräumen angesehen.
Da dies große Auswirkungen darauf hat, wie Sie Exploits schreiben, haben wir diesem
Thema einen eigenen Abschnitt gewidmet. Auf Systemen mit kombiniertem Adressraum
stehen uns viel mehr Waffen zur Verfügung. In dem Prozessadressraum, den wir steuern,
können wir praktisch jede Adresse dereferenzieren.
Am Ende dieses Kapitels sind wir noch kurz auf die Unterscheide zwischen Open-Source-
und Closed-Souce-Systemen eingegangen. Der Quellcode der meisten Betriebssysteme,
die wir behandeln (mit der großen Ausnahme der Windows-Familie), steht offen zum
Download zur Verfügung. Sie können sich vorstellen, dass das für die Entwicklung von
Exploits und die Suche nach Schwachstellen eine große Hilfe ist.
Nachdem Sie hier erfahren haben, wie anspruchsvoll, faszinierend und leistungsvoll Ker-
nel-Exploits sein können, besprechen wir in Kapitel 2, wie Sie diesen Vorgang effizient
und vor allem zuverlässig durchführen können. Los geht’s!
42 1  Von Userland- zu Kernelland-Angriffen

1.6.1 Literatur

Codeüberprüfung
Dowd, M., McDonald, J., und Schuh, J. 2006. The Art of Software Security Assessment:
Identifying and Preventing Software Vulnerabilities (Addison-Wesley Professional).

Allgemeine Grundlagen von Betriebssystemen


Tanenbaum, A. 2016. Moderne Betriebssysteme (Pearson Studium).
Silberschatz, A., Galvin, P., und Gagne, G. 2008. Operating System Concepts, 8. Auflage
(Wiley).

Design und Implementierung einzelner Betriebssysteme


Bovet, D., und Cesati, M. 2005. Understanding the Linux Kernel, 3. Auflage (O’Reilly).
Singh, A. 2006. Mac OS X Internals (Addison-Wesley Professional).
Russinovich, M.E., und Solomon, D., mit Ionescu, A. 2009. Microsoft Windows Internals,
5. Auflage (Microsoft Press).
Mauro, J., und McDougall, R. 2006. Solaris Internals, 2. Auflage (Prentice Hall PTR).
501

Stichwortverzeichnis

Symbole
32-Bit-Architektur 80 asm() 410
64-Bit-Architektur 82 Asynchrone Prozeduraufrufe 442
!analyze 376 Aufrufgate 370
!process 345 Aufrufkonvention 171, 366, 407
!pte 433 Ausführungsfluss 406
__try/__except 332 Ausführungskontext 35, 464 Siehe
auch Kontextwechsel
Interruptkontext 425
A Prozesskontext 423
Verzögerter Kontext 425
Ablaufverfolgung 247 Ausführungsmodi 24
Access Control Lists 32 Ausführungsschritt
ADD_TO_ZONE() 308 Mac OS X 273
Administrator 25 UNIX 157
Adressraum Windows 338
Getrennt 38 Auslagerungsbereich 36
Größe 332 Ausnahmen 74
Kernelraum oberhalb Benutzerraum Auslösen 381
38 Handler 378
Physisch und virtuell 36 Interrupts 424
Privater virtueller Adressraum 77 Registrierung 379
Anweisungszeiger 74 Seitenfehler 384
Arbeitsspeicher __try/__except 332
Virtuell 36 Autorisierung
Willkürlich überschreiben 454 Integritätsebenen 342
Zwei aufeinanderfolgende Bytes über- Rechte 343
schreiben 458 Sicherheitskennung 339
Architektur Verweigerung 342
Informationen aus der Architektur Windows 338
nutzen 122 Zugriffstoken 345
RISC/CISC 73
x86-32 80
x86-64 82
502 Stichwortverzeichnis

B C
Bedrohungsanalyse 489 CALL 406
Benutzer Callgate 372
Administrator 25 Capabilitys 166
Benutzermodell 31 C-Funktionen 50
ID 25 chill() 152
Mac OS X 273 CISC 73
proc 273 Code Red 380
Rechtetrennung 31 copyin() 302
Root 25 CPU
Superuser 25 Betriebsarten 75
ucred 273 Einführung 73
uid 25 Ein- und Mehrprozessorsysteme 74
Benutzerkontenverwaltung 490 Präemptives Multitasking 114
Berechtigungsnachweise 93 Scheduler 114
Mac OS X 273 CrashReporter 243
ucred 273 CVE-2009-0065 446
Betriebssysteme CVE-2009-1046 200
BSD 132 CVE-2009-1235 275
BSD-Derivate 157 CVE-2009-3234 223, 231
Informationen aus dem Betriebs­ CVE-2009-3281 260
system 119
Ladeadressen des Kernelhaupt-
moduls 402 D
Linux 58, 131, 133, 445
Mac OS X 237 Datentypen 45
OpenSolaris 132, 144 dd 383
Open Source/Closed Source 40 Debugging
Solaris 144 Ablaufverfolgung 247
UNIX 131 CrashReporter 243
Userlandpuffer 334 DTrace 146
Versionsinformationen 324 GDB 249
Windows 322 JProbe 139
Brute-Force-Techniken 27 kdump 243
BSD 132, 157, 240 KGDB 144
BugCheck 376 Kmdb 153
Bugs 43 Kprobes 139
Ausnutzung erkennen/verhindern 493 Linux 137
Ausschließen 492 Mac OS X 243
Emulationsbugs 497 OpenSolaris 146
Hypervisor 496 Optionen 245
Poison-Zeiger 486 Race Conditions 152
TOCTOU 485 showcurrentthreads 248
WinDbg 335
Stichwortverzeichnis 503

Windows 335 Remote-Exploits 27, 399


XNU 243 Rücksprungadresse überschreiben
DEP 439 287
Dereferenzierung SCTP 446, 453
Beschädigter Speicher 47 Slab-Überlauf 182
Nicht initialisierte Zeiger 44 Softwarefehlerisolierung 493
Nicht validierte Zeiger 47 Speicherallokator 304
NULL-Zeiger 44 Stacküberläufe im Linux-Kernel 219
Determinismus 57 Steuerstrukturen überschreiben 368
Diagnosewerkzeuge 122 Symbole auflösen 300
Dienst-SID 343 Teilweises Überschreiben 210
Direkte E/A 229, 331 Tokendiebstahl 360
DTrace 146 Überschreiben des angrenzenden
DVWD 328 Objekts 454
Überschreiben eines einzelnen Bytes
368
E Userland-Exploits 30, 34
Verteidigung 31
E/A-Anforderungspakete 331 vmmon 269
Eintrittsinvarianz 484 Wiederherstellungsphase 94
Emulationsbugs 497 Zonenüberlauf 308
EPROCESS 345, 351, 369 Zwei aufeinanderfolgende Bytes über-
Exceptions 74 schreiben 458
Exploits
Arbeitsspeicher überschreiben 98,
275, 454 F
Ausführungsschritt 84, 157, 273, 338
Auslöseschritt 98 Far Call 374
Bearbeitung der SID-Liste 349 Far Return 372
Bearbeitung von Rechten 353 Fat-Binärformat 238
Bedingungen 26 Fortify Source 31
Eigenschaften 71 free_elements 307
Einführen 26 Freiliste 308
Funktionszeiger überschreiben 272 Füllbytes 430
Gegenmaßnahmen 29 Fuzzing 52
Heap 446
Heapallokator steuern 101
Informationsgewinnung 118, 324 G
Kernel 34
Kernel- und Userland-Exploits 33 Gates
Linux 445 Aufrufgate 370
Lokal 27 Callgate 372
Machbarkeitsstudien 26 Einführung 368
Phasen 27 Interruptgates 441
Rechte gewinnen 84 Trapgate 370
504 Stichwortverzeichnis

Gates, Bill 321 Interruptgates 441


GDB 249 Interruptkontext 425
Gepufferte E/A 331 Interruptsichere Funktionen 425
Geräte-E/A-Steuerung 330 Interruptstacks 50
Gerätetreiber 259 Interrupt-Vektortabelle 75
GetKernelBase() 327 Synchron 424
GetVersionEx() 324 Verschachtelte ISRs 425
IOCTLs 265, 275
IOKit
H Einführung 240
Geräteregistrierung 260
HAL 324 Kernelerweiterungen 259
HalDispatchTable 365 ISR 424
Hardwareabstraktionsschicht 324, 365
Harter Seitenfehler 225
Heap J
Beschädigen 101
Exploits 304 JMP 406
Implementierungen 172 JProbe 139
Layout anpassen 455
Schwachstellen 51
SCTP-FWD-Abschnitt 446 K
Slab-Allokator von OpenSolaris 173
SLUB-Allokator 197 kalloc 318
Toter Heap 51 kdump 243
Überlauf 103, 410 KEP 423
Hypervisor 496 Kernel
Abfertigungstabellen 365
APCs 442
I Benutzerdefinierte Kompilierung 402
Binärdateien 326
IDA Pro 260 Dateinamen 324
Implizite Vertrauensstellung 206 Debugging 137, 243, 335
Infoleaks 88, 124, 483 Einführung 24
Info.plist 254 Exploits 34
Informationsgewinnung 118 Gastbetriebssystem 498
Integer Heap 51, 172
Integerüberläufe 53 Informationen gewinnen 324
Vorzeichenfehler 55 Kernelallokator 318
Integrität 484, 494 Kernelausführungspfad 423
Integritätsebenen 342 Kernelerweiterungen 253
Interrupts 74 Kernel Executive 323, 365
Asynchron 424 Kernelland-Speicher 24
Interruptdienstroutinen (ISRs) 424 Ladeadressen des Hauptmoduls 402
Stichwortverzeichnis 505

Modulliste 326 Vom Interrupt- in den Prozesskontext


Rechte 420 435
Rechte erhöhen 354 Vom Interruptkontext ins Userland
Remote-Exploits 399 439, 464
Rücksprung in den Text 417 Vom Prozesskontext ins Userland
Schutz 24 437
Schutzmaßnahmen 488, 490 KPCB 351
Shellcode 87 KPCR 351
Stack 49 Kprobes 139
Steuerstrukturen überschreiben 368 KPROCESS 369
Überschreiben des Kernelarbeits­ kstat-Statistiken 186
speichers 419 KUSER_SHARED_DATA 434, 440
Vertrauen 491
Virtualisierung 498
Windows 322, 323 L
XNU 239
Zugriff auf Userlandpuffer 331 libkvm 415
Zustand wiederherstellen 94 Linux
Kernelerweiterungen Ab Version 2.6.29 168
Erstellen 255 Capabilitys 166
Gerätetreiber 259 Debugging 137
IOKit 259 Distributionen 135
kextstat 258 Einführung 133
Laden 258 Exploit 445
Überprüfen 260 JProbe 139
vmmon 260 KGDB 144
XNU 253 Kprobes 139
Kernelmodus-Codesignierung 344 open() 229
Kernelprozessor-Steuerungsblock 351 Race Conditions 58
Kernelprozessor-Steuerungsregion 351 Rechte 158
Kext Siehe Kernelerweiterungen Rechte anheben 170
kextstat 258 Remote-Exploits 453
KGDB 144 SCTP 446
KGDTENTRY 370 Seitenfehlerhandler 60
KLD 258 SLUB-Allokator 197
KMCS 344 Speicherbeschädigung in
Kmdb 153 set_selection() 200
kmem_slab_t 174 Stacküberlauf im Linux-Kernel 216
Kompatibilitätsmodus 471 vDSO 464
Kontextwechsel 36 Vor Version 2.6.29 158
Mehrstufiger Shellcode 427 Load-store-Architektur 73
Stager 426 Logikbugs 63
Verzögert 426 LUID_AND_ATTRIBUTES 344, 348
506 Stichwortverzeichnis

M O
Mach Objekttabelle 387
Einführung 239 open() 229
Mach-O-Dateien 238 OpenSolaris 132, 144, 173
mach_trap_table 242 OSVERSIONINFO 324
Systemaufrufe 242
Machbarkeitsstudien 26
Mac OS X P
Arbeitsspeicher überschreiben 275
Berechtigungsnachweise 273 Paging 76
BSD 240 Paging bei Bedarf 37
Debuggingoptionen 245 Panik 118
Fat-Binärformat 238 pattern_create.rb 271
Gerätetreiber 240, 259 pattern_offset.rb 272
IOKit 240, 259 pda_from_op() 160
Kernelerweiterungen 253 perf_copy_attr() 223
Race Conditions 319 Physische Adresserweiterung 32
Rechte 273 Physischer Adressraum 36
Snow Leopard 319 POP 78
Stacküberlauf 287 Post-mortem-Analyse 156
Systemaufruftabellen 241 Präemptives Multitasking 114
XNU 239 Prinzipale 338
Mandatory Access Control 32 Prinzip der geringstmöglichen Rechte 32
Mehrfachmappings 432, 439 Privilegierter Modus 24, 75
memcpy() 460 proc 273
Metasploit 271 Programmzeiger 74
Multitasking 35, 114 Prozesskontext 39, 423
Musalouiu, Razvan 275 PUSH 78
PUSH, RET 406

N
R
Non-Uniform Memory Access 199
NOP 29, 415 Race Conditions
NOP-ähnliche Anweisungen 29 Auslösen 63
NOP-Landezone 86 Ausnutzen 115
Ntoskrnl.exe 323 Debugging 152
NTQuerySystemInformation() 326 Einführung 57
NUMA-Knoten 199 Linux 58
NX 32, 464 Mac OS X 319
Sperren 113
TOCTOU 485
Verhindern 58
XNU 319
Stichwortverzeichnis 507

Rechte Mangelnder Einfluss auf das Ziel 403


Anheben 170 Payloads 421, 426
Array 344, 348 Rechteerhöhung 422
Bearbeiten 353 Rücksprung in den Text 417
Berechtigungsnachweise 93 Schwierigkeiten 401
Bitmap 344 SCTP 453
Capabilitys 166 Shellcode installieren 464
Erhöhen oder verschaffen 84 Shellcode platzieren 469
Hypervisor 497 Trampolinsequenzen 406
Kernelland 354 Überschreiben des Kernelarbeitsspei-
Linux 158 chers 419
Linux ab Version 2.6.29 168 REMOVE_FROM_ZONE() 307
Linux vor Version 2.6.29 158 Reverse Engineering 260
LUID_AND_ATTRIBUTES 344, Ring 0 323
348 RISC 73
Mac OS X 273 Role-Based Access Control 32
Prinzip der geringstmöglichen Rechte Rollengestützte Zugriffssteuerung 32
32 Root 25
proc 273 RtlCopyMemory() 391
Rechteerhöhung mit Userlandshell- Rücksprungadresse 111, 287
code 473 Rücksprung in den Text 417
Rechtetrennung 31
Remote-Exploits 422
Ring 0 323 S
Schreibrechte für Bereiche 429
Seitentabellen 430 Scheduler 35, 114
Superrechte 339 Schnelle virtuelle Systemaufrufe 467
ucred 273 Schreibschutz 429
Userland 355 Schutz
Windows 339, 343 Allgemeine Maßnahmen 488
WP 429 Ausnutzung von Bugs erkennen/ver-
Zugriffstoken 344 hindern 493
Redzoning 52 Bedrohungsanalyse 489
Referenzzählerüberlauf 63, 495 Benutzerkontenverwaltung 490
Register 80 Bugs ausschließen 492
Relative virtuelle Adresse 328 Code isolieren 491
Remote-Exploits 27 Dereferenzierung von Userlandzei-
Direkte Umleitung des Ausführungs- gern 495
flusses 406 Exploits 29
Einführung 399 Gemeinsam genutzte Speicherseg-
Erste Anweisung 405 mente deaktivieren 471
Heaplayout anpassen 455 Integrität 494
Mangel an offengelegten Informa­ Kernel 24, 490
tionen 401 Leistungseinbußen 495
Nicht ausführbare Seiten 494
508 Stichwortverzeichnis

Prinzip der geringsmöglichen Rechte SCTP 213


32 Anfälliger PFad 449
Referenzzählerüberläufe 495 Datenabschnitt 447
Unterstützung durch Architektur 495 Einführung 446
Vertraulichkeit 495 FWD-Abschnitt 448
Schwachstellen FWD-Abschnitte 458
Angriff über das Netzwerk 400 Heaplayout über SCTP-Nachrichten
Beschädigter Arbeitsspeicher 49 ändern 455
Bugs 43 Nachrichten erstellen 455
CVE-2009-0065 446 Pakete analysieren 462
CVE-2009-1046 200 Remote-Exploit 453
CVE-2009-1235 275 ssnmap-Objekt 452
CVE-2009-3234 231 Zwei aufeinanderfolgende Bytes über-
CVE-2009-3281 260 schreiben 458
Dereferenzierung beschädigter Spei- sctp_ssnmap 213
cher 47 Seiten 36, 76, 107
Dereferenzierung nicht initialisierter Seitencache 225
Zeiger 44 Seitenfehler 225, 384
Dereferenzierung nicht validierter Seitenfehlerhandler 60
Zeiger 47 Seitenframes 36, 51
Dereferenzierung von NULL-Zeigern Seitentabellen 36, 76, 420, 430
44 set_selection() 200
DVWD 328 Shadow-Mapping 468
Integerprobleme 53 SharedUserData 433, 439
IOCTL-Aufrufstack 275 Shellcode
IOCTLs 265 Ausführen 472
Kernelerweiterungen 260 Bearbeitung der SID-Liste 349
Kernelheap 51 Bearbeitung von Rechten 353
Kernelstack 49 Berechtigungsnachweise 93
Logikbugs 63 Dreiphasig 435
Referenzzählerüberlauf 63 Einführung 27
SCTP-FWD-Abschnitt 446 Gemischt/mehrstufig 88
SLUB 202 Gestalten 92
Software auf Schwachstellen über­ Größer als der gekaperte System­
prüfen 26 aufruf 469
Speicherbeschädigung in Installieren 464
set_selection() 200 Kernelland 87
TIOCGWINSZ 275 Mehrstufig 427, 435
Validierung der Eingaben von physi- Platzieren 84, 429
schen Geräten 65 Remote-Exploits 429
vmmon 260 Rückwärtige Verbindung 474
VMware Fusion 260 Stager 426
Vom Kernel hervorgerufene User- Stufen finden 427
land-Schwachstellen 66 Tokendiebstahl 360
Write-What-Where 362 Userland 85
Stichwortverzeichnis 509

Vsyscall 469 Rücksprungadresse überschreiben


Zweiphasig 439 287
showcurrentthreads 248 Schwachstellen 49
Sichere C-Funktionen 50 Stackframe 79
Sicherheitsbeschreibung 338 Stacküberlauf 110, 287
Sicherheitskennung Stacküberlauf im Linux-Kernel 216
Anmelde-SID 342 Toter Stack 46
Aufbau 339 Stack-Canary 112, 374, 377
Bearbeitung der SID-Liste 349 Stager 426
Dienst-SID 343 Vom Interrupt- in den Prozesskontext
Einführung 338, 339 435
Eingeschränkt 341 Vom Prozesskontext ins Userland
Gruppen-SID manipulieren 353 437
Integritätsebenen-SID 342 stdcall 407
Verweigerungs-SID 342 strcpy() 50
SID Siehe Sicherheitskennung Superrechte 339
Slabs 51, 410 Superuser 25
SLUB-Allokator 197 Swapping 36
SMP 57, 74 Symmetric Multiprocessing 57, 74
Snow Leopard 319 Synchronisierung 58
Softwarefehlerisolierung 493 SYSCALL/SYSRET 441, 465
Solaris 144, 415 sysent 241, 278
Speicherallokatoren SYSENTER/SYSEXIT 441, 465
Einführung 29 Systemaufrufe
Exploits 304 Dispatcher 438
Freiliste 308 Kapern 436
Heapallokator 101 Schnelle virtuelle Systemaufrufe 467
kalloc 318 Systemaufruftabellen 241
Kernelallokator 318
Kernelebene 51
Slab-Allokator von OpenSolaris 173 T
SLUB-Allokator 197
Zonenallokator 304 TIOCGWINSZ 275
Zonenüberlauf 308 TLB 76
Sperren 113 TOCTOU 485
sprintf() 50 Tokendiebstahl 360
ssnmap-Objekt 452 Torwalds, Linus 133
Stack Toter Speicher 46
Aufbau 78 Trampolinsequenzen 406
Bereinigung 407 Translation Lookaside Buffer 76
Beschädigen 110 Trapgate 370
Interruptstacks 50 Trapping 49
IOCTL-Aufrufstack 275 Traps 74
Pufferüberlauf 374
510 Stichwortverzeichnis

U Unprivilegierter Modus 24, 75


Unsichere C-Funktionen 50
Überlauf Userland
Benachbartes Objekt überschreiben Dereferenzierung von Userland­
104 zeigern 495
Heapüberläufe 103, 410 Exploits 30, 34
Integerüberläufe 53 Prozesse 35
Lokale Variable überschreiben 112 Puffer 331
Metadaten eines freien Objekts 204 Rechte erhöhen 355
Referenzzähler 63, 495 Schwachstellen aufgrund des Kernels
Rücksprungadresse überschreiben 66
111 Shellcode 85
Seiten überschreiben 107 Speicher 24
Slabs 182, 410 Zugriff auf Userlandpuffer vom
Stackpufferüberlauf 374 Kernel 331
Stacküberlauf 110, 287
Stacküberlauf im Linux-Kernel 216
Steuerstrukturen überschreiben 105
V
Teilweises Überschreiben 210
Trampolinsequenzen 406 Validierung 65
Überschreiben des angrenzenden vDSO 464
Objekts 454 Verfügbarkeit 488
Überschreiben über die Rücksprung- Verteidigung 31
adresse hinaus 408 Vertraulichkeit 482, 495
Zonen 308 Verzögerte Prozeduren 425
Zwei aufeinanderfolgende Bytes über- Verzögerter Kontext 425
schreiben 458 Virtualisierung 34, 335, 496
Überschreiben Virtueller Adressraum 36
Angrenzendes Objekt 454 vmmon 260
Arbeitsspeicher 454 VMware Fusion 260
Einzelnes Byte 368 Vorzeichenfehler 55
Kernelarbeitsspeicher 419 Vsyscall
Kernelsteuerstrukturen 368 Deaktivieren 471
Über die Rücksprungadresse hinaus Einführung 466
408 Schnelle virtuelle Systemaufrufe 467
Vsyscall 469 Shadow-Mapping 468
Zwei aufeinanderfolgende Bytes über- Shellcode platzieren 469
schreiben 458 Überschreiben 469
ucred 273 vDSO 468
Uid 25 Wiederherstellen 476
Umbrechende Streampaare 460
UNIX
Einführung 131
UNIX-Familie 133
Stichwortverzeichnis 511

W X
WDK 329 Xcode 255
Weicher Seitenfehler 225 XNU
Wiedereintrittsfähigkeit 484 BSD 240
Wiederherstellungsphase 94 Debugging 243
Objekttabelle reparieren 387 kalloc 318
Vsyscall 476 Kernelerweiterungen 253
WinDbg Mach 239
!analyze 376 Race Conditions 319
Befehlsarten 337 Systemaufruftabellen 241
dd 383
Einführung 335
Konfiguration 337 Z
!process 345
!pte 433 zalloc() 307
Windows Zeiger
APCs 442 Beschädigt 47
Autorisierung 338 Dereferenzierung von Userland­
BugCheck 376 zeigern 495
Debugging 335 Funktionszeiger mit Userlandadresse
Geschichte 322 überschreiben 365
Integritätsebenen 342 Funktionszeiger überschreiben 272
Kernel 323 Funktionszeiger von globalen Struk­
Kernel-Dateinamen 324 turen überschreiben 98
Kernel Executive 323 Größe 46
Objekte 338 Nicht initialisiert 45
Prinzipale 338 Nicht validiert 47
Rechte 339, 343 NULL-Zeiger 44
SharedUserData 433 Poison-Werte 486
Sicherheitsbeschreibung 338 zfree() 308
Sicherheitskennung 338, 339 zinit() 307
Superrechte 339 Zonenallokator 304
WinDbg 335 Zufallszahlenbarriere 112, 374
Windows Driver Kit 329 Zugriffssteuerungslisten 32
Windows Server 2003 377 Zugriffstoken
Windows Server 2008 390 Adresse 345
Write-What-Where 362 Bearbeiten 352
Zugriffstoken 339, 345 Einführung 339, 345
Wireshark 462 Finden 352
WP 429 Rechte 344
W^X 419 Tokendiebstahl 360
Enrico Perla / Massimiliano Oldani
Aus dem Inhalt:
• Kernelland- und Userland-
Exploits im Vergleich
• Open-Source- und Closed-Source-
Betriebssysteme

Kernel • Der Kernel aus der Sicht


eines Exploit-Autors

Hacking
• Klassifizierung von
Kernelschwachstellen
• Der Weg zum erfolgreichen
Kernel-Hacking
• Speicherbeschädigung

Jede Anwendung benötigt zum Ablauf ein Betriebssystem. • Die UNIX-Familie, Mac OS X
und Windows
Wenn ein Angreifer Vollzugriff auf das Betriebssystem hat,
• Exploits für den
gelangt er auch an die Anwendungsdaten. Diesen Zugriff
Speicherallokator
verschaffen sich Hacker über Schwachstellen im Kernel.
• Windows-Hacking in der Praxis
Die Entwicklung von Kernelexploits ist sowohl eine Kunst
• Remote-Exploits
als auch eine Wissenschaft. Jedes Betriebssystem weist seine
Eigenheiten auf, weshalb ein Exploit so gestaltet werden • Schwachstellen über das Netz
angreifen
muss, dass er die Besonderheiten des Ziels vollständig aus-
• Remote-Payloads
nutzt. In diesem Buch werden die am weitesten verbreiteten
Betriebssysteme behandelt – UNIX-Derivate, Mac OS X und • Kernelschutzmechanismen
Windows – und es wird gezeigt, wie man die Kontrolle dar- • Virtualisierung
über gewinnen kann.

Betriebssystem-Interna werden nicht nur mit nachvollziehbaren Beschreibungen erklärt, sondern auch mit anschaulichen Diagrammen.

Für alle, die Kernelarchitekturen verstehen wollen


„Das Schreiben von Exploits und insbeson-
Im ersten Kapitel widmet sich das Autorenteam dem Betriebssys-
dere von Kernelexploits besteht nicht nur
temkernel aus Sicht eines Exploit-Entwicklers. Danach werden aus Tricks und Exploit-Kung-Fu, sondern ist
die verschiedenen Arten von Schwachstellen und Bugs in Kate- Ingenieurskunst, die ein tiefes Verständnis
gorien eingeteilt und ihre Gemeinsamkeiten vorgestellt. Die ver- der Grundlagen von Betriebssystemen er-
schiedenen Betriebssysteme implementieren ihre Teilsysteme fordert. Dafür ist dieses Buch sehr hilfreich.
Es füllt die Lücke zwischen all den Kernel-
zwar jeweils auf ihre eigene Weise, aber in diesem Kapitel stellen
und Treiberprogrammierbüchern in einem
die Autoren Vorgehensweisen vor, die sich auf verschiedene Ker- Regal.“
nels und sogar auf verschiedene Architekturen anwenden lassen. (Sebastian Krahmer,
Systemprogrammierer
Schritt für Schritt wird die Entwicklung eines zuverlässigen di- und Exploit-Ingenieur)

rekten Remote-Exploits für eine echte Schwachstelle vorgestellt,


nämlich eines Bugs im SCTP-Teilsystem des Linux-Kernels.

40,– EUR [D] / 41,20 Besuchen Sie


EUR [A] unsere Website
ISBN 978-3-645-60503-8 www.franzis.de

You might also like