Aus Linux-Magazin 01/2018

Das Kernel-Self-Protection-Projekt will Linux sicherer machen

Sicherheitslücken im Kernel bleiben oft überraschend lange unentdeckt. Die Kernelhacker-Initiative Kernel Self-Protection hat das Ziel, durch zusätzliche Features Angreifern ihr Tun zu vergällen, und wenn doch welche durchdringen, die Folgen abzumildern. Eine kleine Inspektionsreise ins Zentrum des Widerstands.

Jeder Black Hat, der eine bislang unbekannte Sicherheitslücke im Linux-Kernel findet, knackt den Jackpot. Potenziell Millionen von Servern und Embedded-Geräten liegen für ihn offen, in der Regel bekommt er die Rootrechte mit serviert. Es liegt in der Natur der Sache, dass die Benutzer das nicht wollen und die Kernelmacher solche Ereignisse zu verhindern trachten.

Nach der reinen Lehre sorgen strenge Coding-Standards und ein ausgeklügeltes Software-Qualitätsmanagement dafür, Lücken sofort zu finden und noch vor Veröffentlichung zu beheben. Ein ebenso leuchtendes wie seltenes Vorbild liefert Open BSD, das in 20 Jahren gerade mal zwei relevante Sicherheitslücken zu beklagen hatte [1]. Doch obwohl der Autor dieses Artikels auch ein Anhänger dieser radikalen Richtung ist, muss man realistisch bleiben: Der Kernel besteht aus Bergen an Code, die komplett niemand in angemessener Tiefe reviewen kann – die Abhängigkeiten sind vielfältig, die möglichen Angriffszenarien auch. Der Kasten “Harmloser Anfang” beschreibt ein komplexes Beispiel.

Daher wird der Kernel wohl auf unabsehbare Zeit Altlasten und Bugs mit sich herumschleppen. Jonathan Corbet hat Ende 2010 [2] mühsam einmal nachgeprüft, wie lange in diesem Jahr behobene sicherheitsrelevante Fehler mutmaßlich bis zu ihrer Entdeckung überdauert hatten: 22 von 80 untersuchten Lücken schlummerten dort länger als fünf Jahre!

Die Praxis spricht für einen Ansatz, der durch generell wirkende Techniken einerseits Angriffe zumindest erschwert und andererseits die Folgen ausnutzbarer Codeschwächen eindämmt. Genau das ist das Ziel des Projekts Kernel Self-Protection [3].

Harmloser Anfang

Manche Sicherheitslücken sind gut versteckt. CVE-2015-7547 nahm ihren Anfang mit einer recht harmlosen Bugmeldung zu Glibc 2.20 [4]: Ein Programmierfehler, der vermutlich seit Glibc 2.9 existierte, führte zu einem Programmabsturz. Ein halbes Jahr später stellte sich heraus, dass geschicktes Kombinieren von Zugriffen aus dem Fehler ein Angriff macht.

An der Stelle hatten die Glibc-Programmierer einen Buffer von 2048 Byte auf dem Stack für eine DNS-Antwort vorgesehen, den, falls die Antwort größer sein sollte, sie neu auf den Heap legen wollten. Das ging aber manchmal schief, denn die Funktion fragte eine IPv4- und eine IPv6-Adresse ab und versuchte bei der zweiten Antwort zunächst den Rest des Puffers zu nutzen. Nur wenn das nicht klappte, forderte sie auf dem Heap den größeren Puffer an. Doch die Variable, die den Pointer auf diesen Puffer hält, wurde nicht aktualisiert, sodass Zugriffe weiterhin auf den Stack erfolgen. Dass nun die Prüfung der Größe natürlich nicht mehr passte, machte einen Stack Overflow möglich.

Das Ausnutzen dieser (natürlich inzwischen behobenen) Fehlerkonstellation hält für den Angreifer einige Komplikationen bereit: So muss er DNS-Pakete schicken, die größer als 2 KByte sind. Laut den Entdeckern der Sicherheitslücke benötigt der Angreifer darum entweder die Kontrolle über einen DNS-Server oder zumindest die Möglichkeit, DNS-Pakete als Man in the Middle zu verfälschen [5]. Alternativ wäre ein Angriff auch durch geschicktes Timing auslösbar. Dazu muss die zweite Antwort erst nach dem Time-out eintreffen und die erste Antwort sollte schon größer als 2 KByte ausfallen. Für die zweite Antwort setzt die Glibc dann die Puffergröße falsch auf “groß”, während sie nur einen kleinen Puffer auf den Stack allokiert. Auch dies erlaubt einen Stack Overflow.

Da der Angriff eine ungewöhnliche Parameterkombination erfordert und komplexe Abhängigkeiten bestehen, ist er schwer zu entdecken. Maßnahmen wie ein Canary, Address Space Layout Randomisation oder auch ein Non Executable Stack hätten damals den Angriff neben den aufwändigen Voraussetzungen erschwert und somit das Risiko reduziert.

Einbruchstechnik für jedermann

Mit dem großen Abstand der Theoriebrille betrachtet, funktionieren die meisten Attacken auf Programme ähnlich: Ein Angreifer versucht einem laufenden Prozess neuen Programmcode unterzujubeln, den der gekaperte Prozess dann mit seinen Rechten ausführt. Das können SQL- oder Shellbefehle sein oder bei Angriffen auf den Kernel typisch Binärcode. Um den einzuschleusen, nutzen Angreifer Programmierfehler aus, über die sie einerseits Speicherinhalte bestimmen und andererseits auch den Program Counter manipulieren dürfen.

Der Programmzähler ist ein CPU-Register, das auf die nächste auszuführende Instruktion zeigt. Bei Intel-Prozessoren hieß es bei 16-Bit-Programmen IP (Instruction Pointer), in der 32-Bit-Welt EIP (Extended IP) und bei 64 Bit etwas morbide RIP (Relative Instruction Pointer). Fast alle Angriffe haben das Ziel, den Inhalt dieses Registers so zu verändern, dass es statt auf den nächsten vom Programmierer vorgesehenen Befehl auf einen vom Angreifer eingeschleusten zeigt.

Ein direkter Schreibzugriff auf dieses Register ist aber kaum möglich, was einen kleinen Umweg nötig macht: Ruft ein Programm eine Funktion auf, kopiert es die Rücksprungadresse, also jene Stelle, an der das Programm nach der Funktion weiterlaufen soll, auf den Stack. Da der Stack auch lokale Funktionsvariablen enthält, die der Angreifer vielleicht über Eingaben manipulieren kann, ist diese Rücksprungadresse ein beliebtes Ziel.

Rücksprung ins Verderben

Um die Adresse zu ändern, gibt es diverse Methoden. Etwas ruppig ist ein Buffer Overflow: Dabei überfüllt der Angreifer einfach eine Variable mit zu vielen Daten und überschwemmt so auch die Rücksprungadresse (Abbildung 1, [6]). Etwas subtiler gehen Angriffe über Format-String-Schwachstellen vor, mit denen sich der Stack nicht nur auslesen, sondern auch manipulieren lässt.

Eine weitere Möglichkeit bilden Integer Overflows, bei denen Angreifer zum Beispiel ausnutzen, dass vorzeichenbehaftete und vorzeichenfreie Integer unterschiedliche Wertebereiche haben. So wird etwa aus der vorzeichenfreien Zahl 128 mit Vorzeichen eine -128. Prüft das Programm nur, ob eine bestimmte Obergrenze eingehalten ist, weil beispielsweise maximal 100 Werte auf dem Stack Platz haben, ist -128 formal in Ordnung. Statt jetzt aber die Speicherstellen 0 bis 100 zu belegen, wie vom Programmierer gedacht, erstreckt sich der Bereich nun von -128 bis 100, was eine Overflow-Situation auslösen kann.

Abbildung 1: Gelingt es einem Angreifer, mehr Daten in einem Buffer auf dem Stack abzulegen, als der Programmierer vorgesehen hat, kann er beim Buffer Overflow die gültige Rücksprungadresse überschreiben.

Abbildung 1: Gelingt es einem Angreifer, mehr Daten in einem Buffer auf dem Stack abzulegen, als der Programmierer vorgesehen hat, kann er beim Buffer Overflow die gültige Rücksprungadresse überschreiben.

Schutz suchen

Dazu manipuliert der Angreifer den Stack oder auch den Heap. Beides sind aber Speicherbereiche, in denen sonst nur Daten stehen. Daher ist eine gern verfolgte Vermeidungsstrategie, die auch Kernel Self-Protection befürwortet, Speicherbereiche entweder als ausführbar oder als nicht-ausführbar zu markieren, also Bereiche als Code und andere als Daten zu deklarieren. Neuere CPUs verfügen dazu über ein Bit im Seitendeskriptor. AMD spricht vom No Execute (NX), Intel vom Execute Disable (XD) Bit. Der Linux-Kernel unterstützt in diesem Zusammenhang auch einen Schreibschutz.

Zwar bieten diese Zugriffsrechte keinen hundertprozentigen Schutz, aber sie erschweren zumindest den Angriff. Fortgeschrittene könnten sich zum Beispiel mit einem “Return to Libc”-Angriff trotzdem noch des Systems bemächtigen. Dafür schreiben sie als Rücksprungadresse etwa die Einsprungadresse von »execve()« [7] zusammen mit geeigneten Parametern auf den Stack ([6], [8]).

Erfolgreiche Angreifer schaffen es zunächst meist nur, den Speicher eines gekaperten Prozesses zu beeinflussen. Es trifft sich gut, dass moderne Prozessoren Funktionen enthalten, die den Kernel- vom Anwendungs-Speicher trennen. Dann darf eine Kernelfunktion keine in den Userspace eingeschleusten Befehle ausführen. Auch dies erschwert Angriffe.

Gelber Vogel gegen Überlauf

Ein Stack Overflow überschreibt Teile des Stack. Schutz kann der Canary spenden, eine Bitfolge vor der Rücksprungadresse, die der Angreifer nicht vorhersagen kann. Stimmen die Muster nicht mehr überein, signalisiert dies: Jemand hat den Stack überschrieben. Das warnt vor, wie der Kanarienvogel die Bergleute vor Grubengas, daher der Name.

Zwar lässt sich dieser Schutz beispielsweise durch das so genannte Trampolining umgehen, einer Technik, bei der Angreifer im Programm vorhandene Pointer so verbiegen, dass sie auf die Rücksprungadresse zeigen. Doch erfordert das einen erheblich höheren Aufwand und den Einsatz angreifbarer Pointer. Genauso könnte es ein Angreifer zunächst versuchen, den Canary-Wert auszulesen und seinen Buffer Overflow geeignet anzupassen, dem wirkt wiederum ein zufälliger Canary entgegen.

Ähnlich funktionieren Shadow Stacks, die im Hintergrund eine Kopie der Rücksprungadresse führen, an die der Angreifer voraussichtlich nicht gelangt. Vor dem Rücksprung prüft der Prozess, ob die Kopie mit dem Original übereinstimmt. Die Abhandlung [9] vergleicht die Techniken und deren Performance.

Nicht nur die Adressen auf dem Stack sind durch Overflows bedroht, überzählige Daten könnten auch auf dem Heap liegende wichtige Strukturen überschreiben. Der Speicherbereich ist zudem anfällig für Use-after-Free-Fehler: Dabei gibt der Programmierer Speicher zwar mittels »free()« frei, nutzt aber den Pointer anschließend wieder. Solche Fehler lassen sich durch striktere Checks beim Zugriff auf Speicherbereiche im Kernel abfangen.

Einladung zum Glücksspiel

Angreifer nutzen beim Einschleusen ihres Codes bekanntes Wissen über das Speicherlayout des Betriebssystems (Abbildung 2). Randomisiert der Kernel die Ausgestaltung der Adressen, wird ihr Angriff zum Glücksspiel. Zwar gibt es Attacken wie Heap Spraying, doch der Aufwand wächst. Wird nicht nur der Kernel an zufälligen Adressen geladen, sondern auch die Kernelmodule, eventuell noch in zufälliger Reihenfolge, erschwert das Angriffe auf die Kernelmodule. Genauso treiben zufällige Stack-Basisadressen den Aufwand in die Höhe.

Je mehr Zufall ins Spiel kommt, desto hilfreicher ist es für Angriffslustige, durch Information Exposure etwas über den Speicheraufbau, den Wert von Canaries et cetera herauszufinden. Dabei nutzen sie aus, dass freigegebener Speicher in der Regel nicht überschrieben wird. Auch uninitialisierte Variablen oder Format-String-Schwachstellen helfen.

Um das zu vermeiden, empfiehlt es sich, Speicher nach einer Freigabe sofort zu überschreiben. Außerdem lassen sich Funktionen statt über ihre Adressen über IDs und eine Tabelle, analog der Interrupt-Vektor-Tabelle, anspringen. Damit werden die Adressen außerhalb des Kernels nicht bekannt und für Angreifer schwerer vorhersagbar.

Abbildung 2: Im Normalfall ist die Einteilung des Adressraums auf einem 32-Bit-System der I-386-Architektur festgelegt. Heap und Stack streben von zwei Seiten aufeinander zu, da ihre Größe dynamisch wächst.

Abbildung 2: Im Normalfall ist die Einteilung des Adressraums auf einem 32-Bit-System der I-386-Architektur festgelegt. Heap und Stack streben von zwei Seiten aufeinander zu, da ihre Größe dynamisch wächst.

Modul ist cool

Wem es trotz der aufwändigen Gegenwehr gelungen ist, Schadcode einzuschleusen, der will meist ein Rootkit installieren. Recht einfach gelingt das, indem er ein eigenes Kernelmodul in das System lädt. Um das zu verhindern, könnte der Admin den Kernel statisch – also ohne Modulsupport – kompilieren, was aber abseits von Embedded-Geräten unpraktisch ist. Alternativ schlägt das Self-Protection-Projekt vor, nur wenigen, lokal angemeldeten Nutzern das Laden von Modulen zu erlauben.

Fazit

Im Umgang mit Sicherheitslücken gibt es drei Philosophien: “Mich will schon keiner hacken”, “An der Wurzel beheben” und “Mich vor den Folgen schützen”. Dass die erste nichts taugt, steht außer Frage. Die Ursachen der Übel beseitigen, wie Open BSD dies erfolgreich tut, weckt Sympathien, funktioniert aber bei Linux in der Praxis offenbar nicht flächendeckend. Im Kernel schlummern sicher noch viel mehr Probleme als in den CVE-Datenbanken verzeichnet.

Auch wenn es mancher als widersinnigen Reparaturbetrieb ansehen mag. Es erscheint sinnvoll, dass Kernel Self-Protection Selbstverteidigungsfunktionen wie Address Space Layout Randomisation in Linux etabliert, die Angreifern ihre Arbeit stark erschweren. Und: Das eine zu tun, bedeutet ja nicht, das andere zu lassen. Gezielte Code-Reviews und ein intensives Qualitätsmanagement sollen und müssen ja weiterhin stattfinden. (jk)

Infos

  1. Security-Seite von Open BSD: https://www.openbsd.org/security.html

  2. Jonathan Corbet, “Kernel vulnerabilities: old or new?”: https://lwn.net/Articles/410606/

  3. Kernel Self-Protection: https://www.kernel.org/doc/html/latest/security/self-protection.html

  4. “In send_dg, the recvfrom function is NOT always using the buffer size of a newly created buffer”, CVE-2015-7547: https://sourceware.org/bugzilla/show_bug.cgi?id=18665

  5. Patch für CVE-2015-7547: https://www.sourceware.org/ml/libc-alpha/2016-02/msg00416.html

  6. Kunst, Quade, “Kern-Technik”, Folge 38: Linux-Magazin 03/08, S. 104

  7. The GNU C Library Reference Manual, “Executing a File”: https://www.gnu.org/software/libc/manual/html_node/Executing-a-File.html

  8. C0ntex, “Bypassing non-executable-stack during exploitation using return-to-libc”: http://infosecwriters.com/text_resources/pdf/return-to-libc.pdf

  9. Dang, Maniatis, Wagner, “The Performance Cost of Shadow Stacks and Stack Canaries”: https://people.eecs.berkeley.edu/~daw/papers/shadow-asiaccs15.pdf

Der Autor

Tobias Eggendorfer ist Professor für IT-Sicherheit in Ravensburg-Weingarten und freiberuflicher IT-Berater (http://www.eggendorfer.info).

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 4 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben