Software-Sicherheit – Teil 3 Exploits mit Security-Funktionen wie ASLR, DEP und Bounds Checking stoppen

Autor / Redakteur: Marcell Dietl / Stephan Augsten

Mit dem Security Development Lifecycle (SDL) setzte Microsoft ein klares Zeichen: Sicherheit ist ein Prozess und kein Produkt. Für Windows Vista wurden erstmals Richtlinien angewendet und Techniken wie ASLR und DEP eingesetzt. In diesem Beitrag betrachtet Security-Insider.de verschiedene Methoden zur Abwehr von Exploits und zeigt, wo deren Grenzen liegen.

Anbieter zum Thema

Bereits im Mai 2000 formulierte Bruce Schneier in seinem Newsletter „Crypto-Gram“ einen Satz, der erst Jahre später zum Leitsatz für die Entwicklung sicherer Systeme werden sollte: „Security is a process, not a product.“ Dahinter steckt vor allem die Überzeugung, dass es keine hundertprozentig sicheren Systeme gibt und vermutlich auch nie geben wird.

Prozesse wie SDL können die Menge an Software-Fehlern zwar deutlich reduzieren, jedoch nicht komplett ausschließen. Um die Hürde anzuheben, einen funktionsfähigen Angriffscode zu schreiben, setzen moderne Betriebssysteme zusätzlich auf Technologien wie ASLR (Address Space Layout Randomization) und DEP (Data Execution Prevention).

Auch im Umgang mit Bug Huntern hat sich jüngst einiges getan. Unternehmen wie Google setzen verstärkt auf Zusammenarbeit und loben Prämien aus. Wer kritische Fehler in Webservices des Konzerns findet und meldet, erhält je nach Art des Problems bis zu 3133,70 US-Dollar. Die Mozilla Foundation folgte diesem Vorbild und erweiterte ihr eigenes Prämienprogramm ebenfalls auf Webdienste.

Ob durch faires Verhalten gegenüber Personen, die Fehler melden, bei der Wahl der Programmiersprache oder beim Einsatz diverser Schutzmechanismen gegen Exploits: Sicherheit ist immer ein Prozess, der aus vielen kleinen Teilen besteht. Einige davon werden in den folgenden Abschnitten weiter vertieft.

Unsichere Bibliotheksfunktionen mit Libsafe abfangen

Aufgrund ihrer Komplexität gilt die Programmiersprache C als zweischneidiges Schwert. Für systemnahe bzw. hochperformante Anwendungen auf mobilen Endgeräten ist sie häufig die erste Wahl. Leider sind viele Funktionen extrem fehleranfällig. Von der Benutzung manch einer wird sogar offen abgeraten:

„Never use gets().“ – Auszug aus der gets(3)-Manpage

Funktionen wie strcpy(), strcat(), scanf(), sprintf() und weitere haben alle eines gemein: Fehlendes Bounds Checking (Grenzwert-Kontrolle). Ankommende Daten lassen sich nicht direkt begrenzen und werden auch dann in den Zielpuffer kopiert, wenn dieser viel zu klein ist. Das Ergebnis ist ein klassischer Buffer Overflow.

Kritisch wird es jedoch dann, wenn die Daten vom Anwender stammen und dieser sie so manipuliert, dass der Programmfluss gezielt verbogen wird. Um derartige Fehler abzufangen, bedient sich Libsafe der Umgebungsvariable LD_PRELOAD. In dieser lassen sich dynamisch ladbare Bibliotheken angeben, deren Funktionen beim Start einer ELF-Datei Vorrang genießen sollen.

Mit Hilfe dieses Tricks „überschreibt“ Libsafe einzelne unsichere Standard-C-Funktionen mit Alternativen, welche den Prozess im Falle eines Pufferüberlaufs sofort stoppen. So lässt sich möglicher Schaden immerhin begrenzen; die Ursache des Problems bleibt jedoch weiterhin bestehen.

Inhalt

  • Seite 1: Unsichere Bibliotheksfunktionen mit Libsafe abfangen
  • Seite 2: Stack-Overflows mit Compiler-Erweiterungen unterbinden
  • Seite 3: Alternative Sprachen mit automatischem Bounds Checking

Stack-Overflows mit Compiler-Erweiterungen unterbinden

Durch die berühmt-berüchtigte Anleitung „Smashing The Stack For Fun And Profit“ von Aleph One, wurde 1996 erstmals einer breiten Öffentlichkeit das Problem von Buffer Overflows bewusst gemacht. Nur zwei Jahre später stellten Forscher auf dem USENIX Security Symposium eine Lösung vor: StackGuard. Wichtigster Bestandteil dieser Erweiterung des GNU C Compilers war die Einführung eines so genannten „Canary“.

Dieses wird zu Anfang einer jeden Funktion im Speicher platziert und vor dem abschließenden Rücksprung erneut überprüft. Ist der Wert des „Canary“ anders als anfangs gesetzt, deutet alles auf einen Pufferüberlauf hin und StackGuard beendet den Prozess.

Wegen diverser Schwächen wurde das Projekt nie offiziell Teil von GCC. Stattdessen wurde der Stack-Smashing Protector (SSP) von IBM mit Version 4.1 der GNU Compiler Collection auf breiter Basis etabliert. Dieser beseitigte viele Schwächen der ursprünglichen StackGuard-Implementation der Canary-Methode und führte weitere Schutzmechanismen ein.

Was bei Linux „Canary“ heißt, nennt sich bei Microsoft „Security Cookie“ und wird über die Compiler-Option /GS aktiviert. Technisch betrachtet sind sich die beiden Konzepte sehr ähnlich. Ihr Hauptziel ist der Schutz der auf dem Stack abgelegten Verwaltungsinformationen wie der Rücksprungadresse. Vor anderen Angriffstechniken wie Heap- oder BSS-Overflows schützt jedoch weder das „Canary“ noch das „Security Cookie“.

Nur zusammen mit ASLR bietet DEP ausreichend Schutz

Die bislang vorgestellten Maßnahmen versuchten stets zu erkennen, ob ein Puffer übergelaufen war, um den Prozess anschließend zu stoppen. Das von Microsoft Data Execution Prevention (DEP) genannte Konzept verfolgt einen komplett anderen Ansatz. Anstatt den Buffer Overflow zu verhindern, sorgt DEP dafür, dass vom Angreifer eingeschleuster Shellcode nicht ausgeführt werden kann.

Um ein Speichersegment als nicht ausführbar zu markieren, verfügen moderne CPUs über ein spezielles Bit, welches bei AMD NX- (No eXecute) und Intel XD-Bit (eXecute Disable) heißt. Versucht ein Angreifer dennoch eigenen Code im Speicher zu starten, führt dies zu einem Hardware-Interrupt, der den Prozess stoppt.

Zunächst schien die Gefahr von Buffer Overflows tatsächlich gebannt. Es dauerte jedoch nicht allzu lange bis Exploits auftauchten, die auch ohne das Einschleusen eigenen Codes reibungslos funktionierten. Dazu legten sie präparierte Werte auf dem Stack ab und verbogen den Programmfluss dergestalt, dass Standard-C-Funktionen wie system() gestartet werden. Diese fanden dann die vom Angreifer hinterlegten Parameter vor und verwendeten sie.

Gegen diese als Return-into-libc bezeichnete Technik ist der von DEP gewählte Ansatz leider machtlos. Nicht jedoch ASLR! Wie es der Name schon vermuten lässt, sorgt Address Space Layout Randomization dafür, dass der Adressbereich des Prozesses (möglichst) zufällig gewählt wird. So wird es für einen Angreifer schier unmöglich den genauen Ort einer Funktion wie system() zu kennen und der Exploit verläuft im Sande.

Inhalt

  • Seite 1: Unsichere Bibliotheksfunktionen mit Libsafe abfangen
  • Seite 2: Stack-Overflows mit Compiler-Erweiterungen unterbinden
  • Seite 3: Alternative Sprachen mit automatischem Bounds Checking

Alternative Sprachen mit automatischem Bounds Checking

Ein vollkommen anderer Ansatz, um Pufferüberläufe und weitere C-typische Fehler zu vermeiden, ist der Wechsel zu einer moderneren Sprache. Java- oder .NET-Programme führen automatisch Bounds Checking durch, sodass Buffer Overflows sofort abgefangen werden. Bei Skriptsprachen wie PHP, Perl, Python und anderen sieht die Lage ähnlich positiv aus.

Leider kann die Programmiersprache gerade bei größeren Projekten selten frei und willkürlich gewählt werden, sondern ist vielmehr an Abhängigkeiten und Bedingungen geknüpft. Darüber hinaus hat jede Sprache eigene Stolpersteine, die sich als genauso gefährlich erweisen können wie übergelaufene Puffer.

Mit dem Java Native Interface (JNI) ist es sogar möglich, von Bibliotheksfunktionen außerhalb der virtuellen Maschine Gebrauch zu machen. Über diesen Umweg könnten sich dann auch wieder alle möglichen Bugs einschleichen.

Und letzten Endes sind die Interpreter und virtuellen Maschinen der vielen modernen Programmiersprachen meist selbst in C oder C++ geschrieben und somit auch vor den genannten Risiken nicht gefeit.

Weiterführende Literatur

All jene, welche die Themen dieses Artikels bzw. der gesamten Artikelreihe noch weiter vertiefen wollen, finden in den Büchern „Aus dem Tagebuch eines Bughunters“ sowie „Buffer Overflows und Format-String-Schwachstellen“ von Tobias Klein eine Vielzahl detailliert und spannend beschriebener Beispiele.

Inhalt

  • Seite 1: Unsichere Bibliotheksfunktionen mit Libsafe abfangen
  • Seite 2: Stack-Overflows mit Compiler-Erweiterungen unterbinden
  • Seite 3: Alternative Sprachen mit automatischem Bounds Checking

(ID:2050475)