MIT-Kurs "Computer Systems Security". Vorlesung 7: Native Client Sandbox Teil 2

Massachusetts Institute of Technology. Vorlesung # 6.858. "Sicherheit von Computersystemen." Nikolai Zeldovich, James Mickens. 2014 Jahr


Computer Systems Security ist ein Kurs zur Entwicklung und Implementierung sicherer Computersysteme. Die Vorträge behandeln Bedrohungsmodelle, Angriffe, die die Sicherheit gefährden, und Sicherheitstechniken, die auf jüngsten wissenschaftlichen Arbeiten basieren. Zu den Themen gehören Betriebssystemsicherheit, Funktionen, Informationsflussmanagement, Sprachsicherheit, Netzwerkprotokolle, Hardwaresicherheit und Sicherheit von Webanwendungen.

Vorlesung 1: „Einführung: Bedrohungsmodelle“ Teil 1 / Teil 2 / Teil 3
Vorlesung 2: „Kontrolle von Hackerangriffen“ Teil 1 / Teil 2 / Teil 3
Vorlesung 3: „Pufferüberläufe: Exploits und Schutz“ Teil 1 / Teil 2 / Teil 3
Vorlesung 4: „Trennung von Privilegien“ Teil 1 / Teil 2 / Teil 3
Vorlesung 5: „Woher kommen Sicherheitssysteme?“ Teil 1 / Teil 2
Vorlesung 6: „Chancen“ Teil 1 / Teil 2 / Teil 3
Vorlesung 7: „Native Client Sandbox“ Teil 1 / Teil 2 / Teil 3

Zielgruppe: Warum sollte der Speicherbereich des Adressbereichs von vorne beginnen?

Professor: Weil es in Bezug auf die Leistung effizienter ist, den Zielsprung zu verwenden, wenn Sie wissen, dass eine gültige Adresse ein fortlaufender Satz von Adressen ist, die bei Null beginnen. Denn dann können Sie dies mit einer einzelnen UND- Maske tun, bei der alle hohen Bits eins sind und nur ein Paar niedriger Bits Null ist.

Teilnehmerin: Ich dachte, die UND- Maske sollte für die Ausrichtung sorgen.

Professor: Richtig, die Maske sorgt für Ausrichtung, aber warum fängt sie von vorne an? Ich denke, sie verlassen sich auf Segmentierungshardware, segmentierte Hardware. Im Grunde genommen könnten sie es verwenden, um den Bereich in Bezug auf den linearen Raum nach oben zu verschieben. Oder es hängt nur damit zusammen, wie die Anwendung diesen Bereich „sieht“. Tatsächlich können Sie es an verschiedenen Offsets in Ihrem virtuellen Adressraum platzieren. Auf diese Weise können Sie bestimmte Tricks mit segmentierter Hardware ausführen, um mehrere Module im selben Adressraum auszuführen.



Teilnehmerin: Vielleicht liegt das daran, dass sie den Empfangspunkt des Nullzeigers „fangen“ möchten?

Professor: Ja, weil sie alle Empfangspunkte erreichen wollen. Aber Sie haben einen Weg, es zu tun. Weil sich der Nullzeiger auf das Segment bezieht, auf das zugegriffen wird. Wenn Sie das Segment verschieben, können Sie am Anfang jedes Segments eine nicht verwendete Nullseite anzeigen. Dies wird also helfen, einige Module zu erstellen.

Ich denke, einer der Gründe für diese Entscheidung - den Bereich von 0 zu starten - ist der Wunsch, das Programm auf die x64- Plattform zu portieren, die ein etwas anderes Design aufweist. Aber ihr Artikel sagt das nicht. Beim 64-Bit-Design wurde das Gerät selbst von Segmentierungshardware befreit, auf die es sich aus Effizienzgründen stützte, sodass ein softwareorientierter Ansatz erforderlich war. Für x32 ist dies jedoch immer noch kein guter Grund, den Speicherplatz von vorne zu beginnen.

Wir setzen also die Hauptfrage fort: Was möchten wir aus Sicherheitsgründen sicherstellen? Gehen wir diese Angelegenheit etwas „naiv“ an und sehen, wie wir alles ruinieren können, und versuchen dann, sie zu beheben.

Ich glaube, dass ein naiver Plan darin besteht, nach verbotenen Anweisungen zu suchen, indem einfach die ausführbare Datei von Anfang bis Ende gescannt wird. Wie können Sie diese Anweisungen erkennen? Sie können einfach den Programmcode in eine riesige Zeile einfügen, die je nach Größe Ihres Codes von null bis 256 Megabyte reicht, und dann die Suche starten.



Diese Zeile kann zuerst das NOP- Befehlsmodul, dann das ADD- Befehlsmodul, NOT , JUMP usw. enthalten. Sie suchen nur, und wenn Sie eine schlechte Anweisung finden, sagen Sie, dass es sich um ein schlechtes Modul handelt, und verwerfen Sie es. Wenn für diese Anweisung kein Systemaufruf angezeigt wird, können Sie den Start dieses Moduls aktivieren und alles im Bereich von 0 bis 256 ausführen. Glaubst du, das wird funktionieren oder nicht? Worüber machen sie sich Sorgen? Warum ist es so schwer?

Teilnehmerin: Sind sie besorgt über die Größe der Anweisungen?

Professor: Ja, Tatsache ist, dass die x86- Plattform Anweisungen mit variabler Länge enthält. Dies bedeutet, dass die genaue Größe des Befehls von den ersten Bytes dieses Befehls abhängt. Tatsächlich können Sie sich das erste Byte ansehen, um zu sagen, dass der Befehl viel größer ist, und dann müssen Sie möglicherweise ein paar weitere Bytes betrachten und dann entscheiden, welche Größe er benötigt. Einige Architekturen wie Spark , ARM und MIPS verfügen über Anweisungen mit fester Länge. ARM hat zwei Befehlslängen - entweder 2 oder 4 Bytes. Auf der x86- Plattform kann die Länge der Anweisungen 1, 5 und 10 Byte betragen, und wenn Sie es versuchen, können Sie sogar eine ziemlich lange Anweisung von 15 Byte erhalten. Dies sind jedoch komplexe Anweisungen.

Infolgedessen kann ein Problem auftreten. Wenn Sie diese Codezeile linear scannen, ist alles in Ordnung. Aber vielleicht gehen Sie zur Laufzeit in die Mitte einer Anweisung, zum Beispiel NICHT .



Es ist möglich, dass dies ein Multibyte-Befehl ist. Wenn Sie ihn ab dem zweiten Byte interpretieren, sieht er völlig anders aus.

Ein weiteres Beispiel, in dem wir mit Assembler "spielen" werden. Angenommen, wir haben Anweisung 25 CD 80 00 00 . Nachdem Sie sich das 2. Byte angesehen haben, interpretieren Sie es als Fünf-Byte-Befehl. Das heißt, Sie müssen 5 Byte vorwärts betrachten und feststellen, dass auf den AND% EAX- Befehl 0x00 00 80 CD folgt, beginnend mit dem AND- Operator für das EAX- Register mit einigen definierte Konstanten, zum Beispiel 00 00 80 CD . Dies ist eine der sicheren Anweisungen, die der native Client einfach durch die erste Regel zum Überprüfen von binären Anweisungen zulassen sollte. Wenn die CPU jedoch während der Ausführung des Programms entscheidet, dass sie mit der Ausführung des Codes von der CD beginnen soll , werde ich diese Stelle des Befehls mit einem Pfeil markieren, dann bedeutet der Befehl % EAX, 0x00 00 80 CD , der eigentlich ein 4-Byte-Befehl ist, die Ausführung von INT $ 0x80 , mit dem Sie unter Linux einen Systemaufruf durchführen können.



Wenn Sie diese Tatsache übersehen, lassen Sie das unzuverlässige Modul in den Kernel "springen" und führen Sie Systemaufrufe durch, dh tun Sie, was Sie verhindern wollten. Wie können wir das vermeiden?

Vielleicht sollten wir versuchen, den Versatz jedes Bytes zu betrachten. Weil x86 eine Anweisung nur in Byte- und nicht in Bitgrenzen interpretieren kann. Sie müssen sich also den Versatz jedes Bytes ansehen, um zu sehen, wo der Befehl beginnt. Halten Sie dies für einen realisierbaren Plan?

Teilnehmerin: Ich denke, wenn jemand tatsächlich AND verwendet , springt der Prozessor nicht an diesen Ort, sondern lässt das Programm einfach laufen.

Professor: Ja, weil er im Grunde nicht zu Fehlalarmen neigt. Wenn Sie es wirklich wollen, können Sie den Code ein wenig ändern, um ihn irgendwie zu vermeiden. Wenn Sie genau wissen, wonach das Testgerät sucht, können Sie diese Anweisungen möglicherweise ändern. Vielleicht indem Sie AND zuerst für eine Anweisung setzen und dann die Maske für eine andere verwenden. Es ist jedoch viel einfacher, diese verdächtigen Byte-Anordnungen zu vermeiden, obwohl dies eher unpraktisch erscheint.

Es ist möglich, dass die Architektur eine Compileränderung enthält. Grundsätzlich haben sie eine Art Komponente, die den Code tatsächlich korrekt kompilieren muss. Sie können GCC nicht einfach „entfernen“ und Code für den Native Client kompilieren. Im Grunde ist das also machbar. Aber wahrscheinlich denken sie nur, dass es zu viel Ärger verursacht, keine zuverlässige oder leistungsstarke Lösung sein wird und so weiter. Außerdem gibt es mehrere x86- Anweisungen, die verboten sind oder als unsicher angesehen werden sollten und daher verboten werden sollten. Da sie jedoch größtenteils ein Byte groß sind, ist es ziemlich schwierig, sie zu finden oder herauszufiltern.

Wenn sie nicht nur unsichere Anweisungen sammeln und sortieren können und auf das Beste hoffen, müssen sie einen anderen Plan verwenden, um ihn zuverlässig zu zerlegen. Was unternimmt der native Client , um sicherzustellen, dass er nicht über diese Codierung mit variabler Länge „stolpert“?

In gewisser Weise sind wir in guter Verfassung, wenn wir die ausführbare Datei wirklich von links nach rechts scannen und nach allen möglichen falschen Codes suchen. Wenn der Code auf diese Weise ausgeführt wird, sind wir in guter Verfassung. Selbst wenn es einige seltsame Anweisungen und eine gewisse Verzerrung gibt, wird der Prozessor dort immer noch nicht "springen", sondern das Programm in derselben Reihenfolge ausführen, in der die Anweisungen gescannt werden, dh von links nach rechts.



Das Problem bei der zuverlässigen Demontage entsteht daher aufgrund der Tatsache, dass irgendwo in der Anwendung "Sprünge" auftreten können. Der Prozessor kann ausfallen, wenn er zu einer Codeanweisung „springt“, die er beim Scannen von links nach rechts nicht bemerkt hat. Dies ist also ein Problem der zuverlässigen Demontage, das sich derzeit in der Entwicklung befindet. Und der Hauptplan ist zu überprüfen, wohin alle "Sprünge" führen. In der Tat ist es auf einer bestimmten Ebene recht einfach. Es gibt eine Reihe von Regeln, die wir in einer Sekunde berücksichtigen werden. Der ungefähre Plan sieht jedoch vor, dass Sie sicherstellen müssen, dass der Zweck des „Sprungs“ früher erkannt wurde, wenn Sie eine Sprunganweisung sehen. Um dies zu tun, reicht es in der Tat aus, von links nach rechts zu scannen, dh das Verfahren, das wir in unserer naiven Herangehensweise an das Problem beschrieben haben.

Wenn Sie in diesem Fall eine Sprunganweisung und die Adresse sehen, auf die diese Anweisung verweist, müssen Sie sicherstellen, dass dies dieselbe Adresse ist, die Sie bereits während der Demontage von links nach rechts gesehen haben.

Wenn eine Sprunganweisung für dieses CD-Byte gefunden wird, sollten wir diesen Sprung als ungültig markieren, da wir nie gesehen haben, dass die Anweisung im CD-Byte beginnt, aber wir haben eine andere Anweisung gesehen, die mit der Nummer 25 beginnt. Aber wenn alle Sprunganweisungen befohlen, zum Anfang der Anweisung zu gehen, in diesem Fall bis 25, dann ist bei uns alles in Ordnung. Das ist klar?

Das einzige Problem ist, dass Sie nicht die Ziele jedes Sprungs im Programm überprüfen können, da es möglicherweise indirekte Sprünge gibt. In x86 könnten Sie beispielsweise so etwas wie einen Sprung zum Wert dieses EAX- Registers haben. Dies ist ideal für die Implementierung von Funktionszeigern.



Das heißt, der Funktionszeiger befindet sich irgendwo im Speicher, Sie halten ihn in einem Register und gehen dann zu einer beliebigen Adresse im Bewegungsregister.

Wie gehen diese Jungs mit indirekten Sprüngen um? Weil ich in der Tat keine Ahnung habe, ob dies ein "Sprung" zu Byte- CD oder zu Byte 25 sein wird. Was machen sie in diesem Fall?

Zielgruppe: Tools verwenden?

Professor: Ja, Instrumentierung ist ihr Haupttrick. Wenn sie sehen, dass der Compiler bereit ist, die Generierung durchzuführen, ist dies ein Beweis dafür, dass dieser Sprung keine Probleme verursacht. Dazu müssen sie sicherstellen, dass alle Sprünge mit einer Vielzahl von 32 Bytes ausgeführt werden. Wie machen sie das? Sie ändern alle Sprungbefehle in sogenannte "Pseudo-Befehle". Dies sind die gleichen Anweisungen, jedoch mit dem Präfix, wodurch die 5 niedrigen Bits im EAX- Register gelöscht werden. Die Tatsache, dass der Befehl 5 niedrige Bits löscht, bedeutet, dass der angegebene Wert ein Vielfaches von 32 von zwei auf fünf ist und dann bereits ein Sprung in diesen Wert ausgeführt wird.



Wenn Sie dies während der Überprüfung betrachten, stellen Sie sicher, dass dieses Anweisungspaar nur mit einer Vielzahl von 32 Bytes "springt". Und um sicherzustellen, dass es keine Möglichkeit gibt, in seltsame Anweisungen zu „springen“, wenden Sie eine zusätzliche Regel an. Es besteht in der Tatsache, dass Sie während der Demontage, wenn Sie Ihre Anweisungen von links nach rechts betrachten, sicherstellen, dass der Anfang jeder gültigen Anweisung auch ein Vielfaches von 32 Bytes ist.

Zusätzlich zu diesem Toolkit überprüfen Sie daher, ob jeder Code, der ein Vielfaches von 32 ist, die richtige Anweisung ist. Mit einer gültigen, gültigen Anweisung meine ich eine Anweisung, die von links nach rechts zerlegt wird.

Teilnehmerin: Warum wird die Nummer 32 gewählt?

Professor: Ja, warum haben sie 32 statt 1000 oder 5 gewählt? Warum ist 5 schlecht?

Teilnehmerin: weil die Zahl eine Potenz von 2 sein muss.

Professor: Ja, deshalb. Andernfalls sind zusätzliche Anweisungen erforderlich, um sicherzustellen, dass ein Vielfaches von 5 verwendet wird, was zu Overhead führt. Was ist mit acht? Ist acht eine gute Zahl?

Zielgruppe: Möglicherweise haben Sie Anweisungen, die länger als acht Bit sind.

Professor: Ja, dies ist möglicherweise die längste Anweisung, die auf der x86-Plattform zulässig ist. Wenn wir einen 10-Byte-Befehl haben und alles ein Vielfaches von 8 sein sollte, können wir ihn nirgendwo einfügen. Die Länge sollte also für alle Fälle ausreichend sein, da der größte Befehl, den ich gesehen habe, 15 Bytes lang war. 32 Bytes reichen also aus.

Wenn Sie die Anweisungen zum Aufrufen oder Beenden der Prozessdienstumgebung anpassen möchten, benötigen Sie möglicherweise eine nicht triviale Menge an Code in einem 32-Byte-Steckplatz. Zum Beispiel 31 Bytes, weil 1 Byte eine Anweisung enthält. Sollte es viel größer sein? Sollten wir dies beispielsweise auf 1024 Bytes einstellen? Wenn Sie viele Funktionszeiger oder viele indirekte Sprünge haben, müssen Sie jedes Mal, wenn Sie eine Stelle erstellen möchten, an der Sie springen möchten, diese unabhängig vom Wert bis zum nächsten Rand fortsetzen. Mit 32 Bit ist es also eine ganz normale Größe. Im schlimmsten Fall verlieren Sie nur 31 Bytes, wenn Sie schnell zum nächsten Rand gelangen müssen. Wenn Sie jedoch eine Größe haben, die ein Vielfaches von 1024 Bytes beträgt, ist es möglich, ein ganzes Kilobyte Speicherplatz vergeblich für einen indirekten Sprung zu verschwenden. Wenn Sie kurze Funktionen oder viele Funktionszeiger haben, führt eine so große Größe der Vielzahl der Länge des "Sprungs" zu einer erheblichen Speicherverschwendung.

Ich denke nicht, dass die Nummer 32 ein Stolperstein für den Native Client ist . Einige Blöcke könnten mit einer Vielzahl von 16 Bit arbeiten, einige 64 oder 128 Bit, es spielt keine Rolle. Nur 32 Bit schienen ihnen der akzeptabelste und optimalste Wert zu sein.

Machen wir also einen Plan für eine zuverlässige Demontage. Daher sollte der Compiler beim Kompilieren von C- oder C ++ - Code in eine Native Client- Binärdatei etwas vorsichtig sein und die folgenden Regeln beachten.



Daher muss er bei jedem Sprung, wie in der oberen Zeile gezeigt, diese zusätzlichen Anweisungen in den unteren 2 Zeilen hinzufügen. Und unabhängig von der Tatsache, dass er eine Funktion erstellt, zu der er "springen" wird, springt unsere Anweisung, wenn der Zusatz AND $ 0xffffffe0,% eax angibt. Und es kann nicht einfach mit Nullen ergänzt werden, denn all dies muss die richtigen Codes haben. Daher ist die Hinzufügung notwendig, um sicherzustellen, dass jede mögliche Anweisung gültig ist. Und glücklicherweise wird auf der x86- Plattform keine einzelne Noop- Funktion durch ein einzelnes Byte beschrieben, oder zumindest gibt es kein einzelnes Noop- 1-Byte. So können Sie dem Wert einer Konstante jederzeit Dinge hinzufügen.

Was garantiert uns das? Stellen wir sicher, dass wir immer sehen, was in der Terminologie der Anweisungen passiert, die befolgt werden. Diese Regel gibt uns Folgendes: die Gewissheit, dass ein Systemaufruf nicht versehentlich erfolgt. Dies gilt für Sprünge, aber was ist mit Renditen? Wie gehen sie mit Retouren um? Können wir zu einer Funktion im Native Client zurückkehren ? Was passiert, wenn Sie den brandaktuellen Code ausführen?

Zielgruppe: Der Stapel kann überlaufen.

Professor: Es ist wahr, dass es ganz unerwartet auf dem Stapel auftaucht. Tatsache ist jedoch, dass der von den Native Client- Modulen verwendete Stapel tatsächlich einige Daten enthält. Daher sollten Sie sich beim Umgang mit Native Client keine Gedanken über einen Stapelüberlauf machen.

Teilnehmerin: Warten Sie, aber Sie können alles auf den Stapel legen. Und wenn Sie einen indirekten Sprung machen.

Professor: Es ist wahr. Die Rückkehr sieht fast aus wie ein indirekter Sprung von einer Stelle im Speicher, die sich oben im Stapel befindet. Daher denke ich, dass eine Sache, die sie für die Rückgabefunktion tun könnten, darin besteht, das Präfix auf die gleiche Weise wie bei der vorherigen Prüfung festzulegen. Und dieses Präfix prüft, was oben im Stapel angezeigt wird. Sie überprüfen, ob dies gültig ist, und wenn Sie den AND- Operator schreiben oder verwenden, überprüfen Sie, was sich oben im Stapel befindet. Dies scheint aufgrund des ständigen Datenwechsels etwas unzuverlässig zu sein. Wenn Sie beispielsweise auf die Oberseite des Stapels schauen und sicherstellen, dass dort alles in Ordnung ist, und dann etwas schreiben, kann der Datenstrom im selben Modul etwas am oberen Rand des Stapels ändern, woraufhin Sie auf die falsche verweisen Adresse

Teilnehmerin: Gilt dies nicht für das Springen in gleichem Maße?

Professor: Ja, was passiert dort mit einem Sprung? Können unsere Rennbedingungen diesen Test irgendwie ungültig machen?

Teilnehmerin: Aber ist der Code nicht beschreibbar?

Professor: Ja, der Code kann nicht geschrieben werden, das ist wahr. Daher können Sie AND nicht ändern. Aber konnte nicht ein anderer Stream den Zweck des Sprungs zwischen diesen beiden Anweisungen ändern?

Teilnehmerin: Dies ist im Register, also ...

Professor: Ja, das ist eine coole Sache. Denn wenn ein Stream etwas im Speicher oder in dem, was von EAX geladen wird, ändert (von sich aus tun Sie dies vor dem Herunterladen), befindet sich dieser EAX in diesem Fall in einem schlechten Zustand, löscht dann jedoch fehlerhafte Bits. Oder er kann den Speicher danach ändern, wenn sich der Zeiger bereits in EAX befindet , sodass es keine Rolle spielt, dass er den Speicherort des Speichers ändert, aus dem das EAX- Register geladen wurde.

Tatsächlich teilen Threads keine Registersätze. Wenn ein anderer Thread das EAX- Register ändert, hat dies keine Auswirkungen auf das EAX- Register dieses Threads. Daher können andere Threads diese Befehlsfolge nicht ungültig machen.

Es gibt noch eine andere interessante Frage. Können wir dieses UND umgehen ? Ich kann überall in diesem Adressraum springen, wohin ich will. , AND .



, , , , , AND . . jmp , .



, , - , 1237. , 32. Native Client , , , . , , 1237 ?



- EAX , , , , . , ? ?

: NaCl , .

: , . x86 , , NaCl , 2 . , , : «, , !», 25 CD 80 00 00 . . , x86 .

, Native Client . , , , , NaCl . , .

: , , . , . , , , , .



: , . , . , , , EAX . , - . EAX , EBX . , . EAX EBX AND . , , EAX , . , - 64 . Jmp *% eax AND .



, , , , . Intel , , , , . , , . AND , EAX , «» .

, , . , . , , , . , , , .
, , C1 C7 .

C1 , , . , «» . , , . , , - . , .
2 , 0 64 . , , . , , .

3 , , , . , , .

4 , hlt . halt ? , C4 . , , - , .

, , ? , , - .
, , , , . , , , , . .



55:20

:

MIT-Kurs "Computer Systems Security". 7: « Native Client», 3


.

, . Gefällt dir unser Artikel? Möchten Sie weitere interessante Materialien sehen? Unterstützen Sie uns, indem Sie eine Bestellung aufgeben oder sie Ihren Freunden empfehlen. Habr-Benutzer erhalten 30% Rabatt auf ein einzigartiges Analogon von Einstiegsservern, das wir für Sie erfunden haben: Die ganze Wahrheit über VPS (KVM) E5-2650 v4 (6 Kerne) 10 GB DDR4 240 GB SSD 1 Gbit / s $ 20 oder wie teilt man den Server? (Optionen sind mit RAID1 und RAID10, bis zu 24 Kernen und bis zu 40 GB DDR4 verfügbar).

3 Monate kostenlos bei Bezahlung eines neuen Dell R630 für einen Zeitraum von sechs Monaten - 2 x Intel Deca-Core Xeon E5-2630 v4 / 128 GB DDR4 / 4 x 1 TB Festplatte oder 2 x 240 GB SSD / 1 Gbit / s 10 TB - ab 99,33 USD pro Monat , nur bis Ende August, Bestellung kann hier sein .

Dell R730xd 2 mal günstiger? Nur wir haben 2 x Intel Dodeca-Core Xeon E5-2650v4 128 GB DDR4 6 x 480 GB SSD 1 Gbit / s 100 TV von 249 US-Dollar in den Niederlanden und den USA! Lesen Sie mehr über den Aufbau eines Infrastrukturgebäudes. Klasse mit Dell R730xd E5-2650 v4 Servern für 9.000 Euro für einen Cent?

Source: https://habr.com/ru/post/de418225/


All Articles