Professional Documents
Culture Documents
wichtigen
Betriebssysteme:
Linux, Unix,
Mac OS X und
Windows
Kernel
Hacking
Exploits verstehen, schreiben und abwehren: Schwachstellen in
Kernel-Architekturen erkennen und Gegenmaßnahmen ergreifen
Kernel
Hacking
Exploits verstehen, schreiben und abwehren: Schwachstellen in
Kernel-Architekturen erkennen und Gegenmaßnahmen ergreifen
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
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.
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
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
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
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 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
theoretischer 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 Informationen
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. Beispielsweise
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
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 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!
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
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.
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.
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.
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.
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.
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.
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).
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
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
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
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.