Umsetzung moderner Speicherschutztechniken in OpenBSD

In den vergangenen Jahren wurden immer neue Angriffe auf bestehende Speicherschutztechniken entwickelt. Zudem werden die bereits vorhandenen Schutztechniken nicht immer konsequent angewendet. Daher wurden in den vergangenen Veröffentlichungen des OpenBSD-Projektes neue Techniken zum Schutz vor Angriffen entwickelt und das Umgehen bestehender Schutztechniken erschwert. Alles zusammen führt nicht nur zu einer sicheren Plattform sondern wirkt sich positiv auf die Sicherheit von Dritt-Software aus, welche auf die OpenBSD-Plattform portiert wird.

1. Einleitung

Seit 20 Jahren ist das OpenBSD-Projekt bekannt für die Entwicklung des gleichnamigen und sicheren Betriebssystems OpenBSD sowie mehrerer dazugehöriger Anwendungen wie beispielsweise OpenSSH und OpenNTPd. Aber was macht dieses Betriebssystem so sicher? Welche Maßnahmen wurden in der Vergangenheit und werden aktuell ergriffen, um diesem Status gerecht zu werden? Diese und weitere Fragen wird dieser Beitrag klären.

Diese Arbeit richtet sich an alle Interessenten, welche sich über aktuelle Entwicklungen im Speicherschutzbereich informieren wollen. Zudem bietet es einen Einblick in die Anstrengungen, welche im Open-Source-Projekt OpenBSD unternommen werden, um sich gegen aktuelle Angriffe zu wappnen.

In den letzten zwei vergangenen Jahren wurden neue Schutztechniken, wie etwa Pledge [4] und ein neuer Mechanismus gegen S-ROP (Signal-Return-Oriented-Programming) [5], entwickelt, welche das Ausnutzen von Schwachstellen in Software wesentlich erschweren. Dafür werden die Angriffe grundlegend erklärt und detailliert gezeigt, mit welchen Mechanismen diese zukünftig unterbunden werden.

Zum Abschluss wird an konkreten Beispielen erläutert, wie die zuvor erwähnten Schutztechniken in Sicherheitsprodukten ihre Anwendung finden und welche Auswirkungen sie auf die Qualität von Dritt-Software haben.

2. Durchsetzung der Datenausführungsverhinderung

Die Datenausführungsverhinderung, welche auch unter den Namen Non-Executable-Bit oder Write-XOR-Execute (W^X) bekannt ist, ist ein etabliertes Verfahren um Angreifer daran zu hindern, Schadcode in laufende Prozesse einzuschleusen. Dieses wird sichergestellt, indem die Speicherseiten eines Prozesses nie zugleich schreibbar und ausführbar sind. Leider wird diese Technik nicht konsequent in allen Programmen angewendet. So muss dieser Sicherheitsmechnismus für Anwendungen wie Python oder den Chrome-Browser deaktiviert werden.

Bisher war es Anwendungen unter OpenBSD erlaubt, diesen Mechanismus auszuhebeln, indem sie die Rechte der Speicherseiten selbstständig auf schreibbar sowie ausführbar setzen. Dieses hatte den Vorteil, dass Just-in-Time-Compiler (JIT) schnell Programmcode kompilieren und ausführen konnten, bot aber zugleich eine potentielle Angriffsfläche.

Seit OpenBSD-Version 6.0 ist es nicht mehr uneingeschränkt möglich, dass eine Anwendung, die Scheib- und Ausführrechte einer Speicherseite unbemerkt vom Anwender setzen kann. Zunächst müssen ausführbare Dateien die neue Option "wxneeded" gesetzt bekommen. Dieses wird durch den Maintainer, der die jeweiligen Dritt-Software zu OpenBSD portiert, durchgeführt. Zusätzlich muss das Dateisystem mit der Mount-Option "wxallowed" eingebunden werden. Dieses ermöglicht dem Administrator eines Systems, die Verwendung solcher Anwendungen zu unterbinden. Durch diese zweistufige Vorgehensweise müssen zwei Instanzen auf Entwickler- und Anwenderseite zustimmen, dass eine Anwendung eine Ausnahme von der Datenausführungsverhinderung bekommt.

3. Return to LibC

Das Einschleusen von fremdem Programmcode in fehlerhafte Software ist durch die Verwendung bekannter Techniken wie den Stack-Canaries oder der zuvor beschriebenen Datenausführungsverhinderung nicht mehr ohne Weiteres möglich. Daher bedienen sich Angreifer alternativer Techniken, um in den Programmablauf einer fehlerhaften Software eingreifen zu können.

Return-to-LibC ist beispielsweise eine dieser Angriffstechniken. Bei diesem Verfahren schleust der Angreifer gar keinen eigenen Code ein, sondern lässt das Programm an einer beliebigen Stelle in der Standardbibliothek fortfahren, um so die gewünschte Ausführung von Code zu erreichen.

Um diesem entgegen zu wirken, sind Techniken wie beispielsweise die Address-Space-Layout-Randomization (ASLR) notwendig. Dadurch liegen eingebunden Programmbibliotheken nicht an einer festen, sondern an einer zufälligen Stelle im Programmspeicher. Einem Angreifer ist es damit nicht mehr möglich, trivial die Sprungadressen für einen gezielten Angriff zu erraten.

Das OpenBSD-Projekt hat eine einfache aber effektive Methodik entwickelt, um diese Schutztechnik weiter auszubauen. Bisher lag die Standardbibliothek als ein homogener Block im Speicher. Seit OpenBSD 6.0 werden die ca. 900 einzelnen Objektdateien der C-Standardbibliothek bei jedem Systemstart neu zusammen gelinkt und dabei auch neu geordnet. Somit ist es für einen Angreifer noch schwieriger, die genaue Position des gewünschten Programmcodes für das gezielte Ausnutzen einer Schwachstelle zu ermitteln.

Dieses Vorgehen hat den Nachteil, dass sich durch das Neu-Linken der C-Standardbibliothek der Boot-Vorgang um ca. 3 Sekunden verlängert. Konsequenterweise müsste man dieses Vorgehen auch auf alle anderen Programmbibliotheken ebenfalls anwenden, welches den Boot-Vorgang enorm verlängern würde.

Eine weitere Frage die sich stellt, ist die Integration dieses Verfahrens in Trusted-Execution-Systeme, die vor dem Ausführen von Programmen die Integrität der Programmdateien und ihrer Bibliotheken kryptografisch überprüfen. Da sich die Bibliotheken bei jedem Systemstart verändern, können diese nicht mehr signiert werden.

4. Einschränkung von System-Calls

Für die Interaktion über das Betriebssystem mit der Hardware, anderen Prozessen oder dem Netzwerk benötigt jedes Programm die Verwendung von System-Calls. Da nicht jedes Programm zu jeder Zeit auf alle Ressourcen zugreifen muss, gibt es schon länger Techniken, wie beispielsweise Systrace [6], die den Aufruf von System-Calls für Programme speziell einzuschränken.

Mit einer solchen präventiven Maßnahme lässt sich der Handlungsspielraum eines Angreifers nach der erfolgreichen Übernahme eines anfälligen Programms einschränken. Um diese Grundidee weiter zu treiben und die Einschränkung für den Angreifer dynamischer zu gestalten, wurde im Release 5.9 von OpenBSD eine Technik namens Pledge [7] eingeführt.

Bei Pledge handelt es sich um einen neuen System-Call, mit dem ein Programm dem Kernel zu Laufzeit mitteilen kann, welche System-Calls es in der restlichen Programmlaufzeit benutzen wird. Die Liste an erlaubten System-Calls kann dabei ausschließlich eingeschränkt, aber nicht erweitert werden. Sollte nun ein Angreifer eine Schwachstelle im Programm ausnutzen um Schadcode auszuführen, kann dieser nicht beliebig auf dem System agieren. Sobald er einen System-Call verwendet, welcher zuvor deaktiviert wurde, beendet der Kernel dieses Programm unverzüglich.

Die Vorteil gegenüber dem alten Systrace-Verfahren ist, dass ein Prozess sich selbst während der Laufzeit nachregulieren kann. So braucht ein Netzwerkdienst meist nur während der Initialisierung die Möglichkeit einen Socket zu öffnen. Später, bei der Abarbeitung der Client-Anfragen wird dieses nicht mehr benötigt. Sollte ein Angreifer nun eine bösartige Anfrage stellen, welche einen Fehler im Programm ausnutzt, steht dem Angreifer die Möglichkeit einen Socket zu öffnen nicht mehr zur Verfügung.

Ein anderer Vorteil liegt in der besseren Handhabung des Pledge-Verfahrens. Unter Systrace musste der Programmierer, Administrator oder Anwender noch extern vorgeben, welche System-Calls genau während der gesamten Laufzeit erlaubt sind und welche nicht. Bei Pledge sind die 330 System-Calls in 26 Gruppen zusammengefasst. Der Programmierer gibt beim Aufruf von Plegde jeweils nur die Gruppen an, welche die Anwendung benötigt, anstatt jeden System-Call einzeln. Diese Gruppen sind thematisch und von ihrer Auswirkung auf das System her aufgeteilt. So sind etwa alle System-Calls, welche nur lesend oder auch schreibend auf das Dateisystem zugreifen, in die beiden Gruppen "rpath" und "wpath" zusammengefasst.

Dieses Interface-Design steigert die Akzeptanz beim Programmierer, da der Aufwand beim Einsatz von Pledge im Gegensatz zum alten Systrace wesentlich geringer ist. Auch im Vergleich zu anderen ähnlichen Schutzmechanismen wie Seccomp und Capsicum läst sich Pledge mit einem geringeren Aufwand in bestehende Anwendungen integrieren [8].

5. Signal Return Oriented Programming

Bosman und Bos haben mit ihrem Beitrag "Framing Signals - A Return to Portable Shellcode" auf dem IEEE Symposium on Security and Privacy 2014 [1] einen Angriff offengelegt, welcher dazu führt, dass ein Angreifer ein fehlerhaftes Programm dazu verleiten kann, an einer beliebigen Stelle im Programmcode fortzufahren. Dafür bedienen sie sich des Signal-Return-System-Calls.

Verwendung von Sigreturn
Abb. 1. - Verwendung von Sigreturn

Wie in Abbildung 1 zu sehen, erhält der Kernel zunächst ein Signal für einen bestimmten Prozess. Daraufhin stoppt der Kernel den entsprechenden Prozess und pusht dessen Ausführungskontext auf dessen Stack in Form eines sog. Signal-Context und springt in den Signal-Handler. Nach der Ausführung des Signal-Handlers wird der System-Call sigreturn(2) aufgerufen. Dieser veranlasst den Kernel den Signal-Context vom Stack zu popen, um damit den Prozess-Context wiederherzustellen und mit der regulären Abarbeitung des Programms fortzufahren.

S-ROP-Angriff
Abb. 2. - S-ROP-Angriff

Ein Angreifer kann sich diesen Mechanismus zu eigen machen, indem er selbst durch das Ausnutzen einer Schwachstelle einen eigenen gefälschten Programmkontext auf dem Stack hinterlegt und dann an eine Stelle im Programmcode springt, welche den Signal-Return-System-Call auslöst. Der Kernel ist nicht in der Lage, einen falschen Signal-Context von einem echten zu unterscheiden und versetzt das Programm nun in den vom Angreifer beabsichtigten Zustand.

Neues Sigreturn
Abb. 3. - Neues Verhalten von Sigreturn

Ab Version 6.0 wird der OpenBSD-Kernel einen Programm-Kontext nur noch zusammen mit einem Secret auf dem Stack des Programms ablegen. Meldet sich das Programm danach mit dem Signal-Return zurück, kann der Kernel anhand des Secrets prüfen, ob es sich um eine validen Programmkontext handelt. Sollte diese Überprüfung fehlschlagen, wird das Programm durch den Kernel unverzüglich beendet. Der Kernel hält für jeden Prozess ein eigenes Secret bereit, welches bei jeder Verwendung modifiziert wird, sodass Replay-Attacken verhindert werden.

6. Verbesserung der ASLR durch "Fork and Exec"

Schon seit längerem gibt es Server-Prozesse, wie den SSHd (Secure Shell Daemon), die durch das Privilege-Separation-Design auf mehrere Prozesse mit unterschiedlichen Rechten aufgeteilt wurden oder generell zur Abarbeitung einer Anfrage einen separaten Prozess mittels fork(2) erzeugen.

Befindet sich in diesem Prozess nun ein Programmierfehler, kann dieser von einem Angreifer dazu verwendet werden, um beispielsweise mittels klassischen ROPs (Return Oriented Programming) zu versuchen, zur Adresse eines bestimmten Programmcodes zu springen. Praktisch wird der Angreifer aber nicht in der Lage sein, eine genaue Stelle zu treffen, da Mechanismen wie ASLR und die in Abschnitt 3 beschriebene Verbesserung an der C-Standardbibliothek dazu führen, dass Programmcode immer an zufälligen Adressen im Speicher liegt. Ein fehlerhafter Versuch führt dann zur sofortigen Terminierung des Prozesses.

Speicher-Layout nach einem Fork
Abb. 4. - Speicher-Layout nach einem Fork

Versucht der Angreifer es aber dennoch erneut und erzeugt mit einer neue Anfrage an den Elternprozess einen neuen Kind-Prozess, so hat dessen Speicher-Layout den gleichen Aufbau wie beim Versuch zuvor. Beim ausführen eines Forks, wird ein bestehender Prozess lediglich eins-zu-eins kopiert. Diesen Umstand kann sich ein Angreifer zunutze machen und nun durch gezielte Variation seines Angriffs an jedem neuen Kind-Prozess sich iterativ durch den immer gleichen Prozessspeicher durchprobieren, bis sein Angriff funktioniert.

Speicher-Layout nach einem Exec
Abb. 5. - Speicher-Layout nach einem Exec

Die "Fork and Exec"-Strategie besagt nun, dass immer nach einem Fork, auch ein Exec ausgeführt werden muss. Diese Vorgehensweise sorgt dafür, dass die Speicherseiten der unterschiedlichen Prozesse eines Dienstes auch jeweils immer ein unterschiedliches Layout haben. Also, dass sich Stack, Text und Daten-Segment an unterschiedlichen Adressen im virtuellen Speicher befinden. Dieses nimmt dem Angreifer die Möglichkeit den Speicherraum gezielt nach der gewünschten Adresse zu durchsuchen. Dieses Design-Prinzip wurde seit Version 6.0 in vielen wichtigen Systemdiensten übernommen.

7. Auswirkungen auf das Software-Ökosystem

Durch die Portierung der Büro-Software OpenOffice sind viele kleinere und größere Programmierfehler aufgefallen, da diese durch die vorhandenen Sicherheitsmechanismen die Anwendung zum Absturz brachten. Die daraus folgenden Sicherheitsverbesserungen [3] wurden an das Projekt zurückgegeben und haben daher auch Auswirkungen auf den Einsatz unter anderen Betriebssystemen wie etwa Linux und Windows haben.

Die in Abschnitt 2 vorgestellte verschärfte Datenausführungsverhinderung führte bereits zur Anpassung einiger Dritt-Software. So wurde der Webbrowser Firefox daraufhin angepasst, dass er selbst die Datenausführungsverhinderung nicht mehr umgehen muss [2] und damit dessen Speicherseiten strikt zwischen schreibbar und ausführbar getrennt sind.

Seit OpenBSD 6.0 wurde der Großteil der Anwendungen im Basis-System durch den Pledge-Mechnismus abgesichert. Durch dieses Vorgehen, sind bei 10% der Privileged-Separated Anwendungen, des OpenBSD-Basissystems, Design-Probleme aufgefallen, deren sich die Entwickler zuvor nicht bewusst waren [9]. Einige Teilprozessen haben beispielsweise Sockets geöffnet, obwohl es von ihrem Design her überhaupt nicht vorgesehen war. Diese Probleme sind nach der Detektion mittels Pledge behoben worden. Zunehmend wird auch immer mehr Dritt-Software ebenfalls durch ihre Maintainer mit Pledge-Aufrufen versehen.

8. Fazit und Ausblick

Wie die Angriffe, so sind auch die Verteidigungsmethoden im ständigen Wandel. Dieser Beitrag hat die aktuellen Entwicklungen im Feld der Speicherschutztechniken am Beispiel ihrer Umsetzung im OpenBSD-Projekt aufgezeigt. Das Projekt nimmt dabei immer wieder eine Vorreiterrolle bei Security-Themen ein, indem es diese Techniken früh und konsequent umsetzt und versucht damit auf das Software-Ökosystem positiven Einfluss zu nehmen.

Literaturhinweise

  1. Bosman, Erik, and Herbert Bos. "Framing signals - a return to portable shellcode." 2014 IEEE Symposium on Security and Privacy. IEEE, 2014.
  2. Jan de Mooij, "W^X JIT-code enabled in Firefox", Dec 29, 2015, https://jandemooij.nl/blog/2015/12/29/wx-jit-code-enabled-in-firefox/
  3. Mirko Lindner, "OpenOffice.org für OpenBSD fertig", 22. November 2006, https://www.pro-linux.de/news/1/10508/openofficeorg-fuer-openbsd-fertig.html
  4. Theo de Raadt, "Pledge: A new security technology in OpenBSD", Hackfest 2016 https://www.hackfest.ca/en/2015/
  5. Theo de Raadt, "SROP mitigation", May 09, 2016 https://marc.info/?l=openbsd-tech&m=146281531025185
  6. https://www.citi.umich.edu/u/provos/systrace/
  7. http://man.openbsd.org/OpenBSD-current/man2/pledge.2
  8. Felix von Leitner, "Check your privileges!", 2015, 32. Chaos Communication Congress, https://media.ccc.de/v/32c3-7284-check_your_privileges
  9. Theo de Raadt, "Privilege Separation and Pledge", 2016, dotSecurity, Paris, France http://www.thedotpost.com/2016/05/theo-de-raadt-privilege-separation-and-pledge