MIT-Kurs "Computer Systems Security". Vorlesung 3: Pufferüberläufe: Exploits und Schutz, 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

Interessanterweise kann ein Angreifer nicht zu einer bestimmten Adresse springen, obwohl wir hauptsächlich fest codierte Adressen verwenden. Was er tut, wird als "Haufenangriff" bezeichnet, und wenn Sie ein schlechter Mensch sind, wird es Ihnen ziemlich viel Spaß machen. Bei einem solchen Angriff beginnt ein Hacker, Tonnen von Shell-Code dynamisch zuzuweisen und ihn einfach zufällig in den Speicher einzugeben. Dies ist besonders effektiv, wenn Sie dynamisch Hochsprachen wie JavaScript verwenden. Somit befindet sich der Tag-Reader in einer engen Schleife und generiert einfach eine große Anzahl von Zeilen Shell-Code und füllt dann eine Reihe davon.

Der Angreifer kann die genaue Position der Zeilen nicht bestimmen, er wählt einfach 10 MB Zeilen Shell-Code aus und macht einen beliebigen Sprung. Und wenn er irgendwie einen der Ret- Zeiger steuern kann, besteht die Möglichkeit, dass er im Shell-Code „landet“.



Sie können einen Trick verwenden, der als NOP-Rutsche , NOP-Schlitten oder NOP-Rampe bezeichnet wird , wobei NOP Anweisungen ohne Bedienung oder leere Leerlaufbefehle sind. Dies bedeutet, dass der Ausführungsfluss des Prozessorbefehls immer dann zu seinem endgültigen, gewünschten Ziel „rutscht“, wenn das Programm an eine beliebige Stelle auf der Folie zur Speicheradresse wechselt.

Stellen Sie sich vor, wenn Sie eine Shell-Codezeile haben und an eine zufällige Stelle in dieser Zeile gehen, funktioniert dies möglicherweise nicht, da Sie den Angriff nicht richtig einsetzen können.

Aber vielleicht das Zeug , dass Sie in einem Stapel gelegt, meist nur eine Tonne des NOP, aber in der sehr Sie einen Shell - Code am Ende haben. Das ist eigentlich ziemlich klug, weil es bedeutet, dass Sie jetzt tatsächlich an den richtigen Ort gelangen können, an dem Sie springen. Denn wenn Sie in eines dieser NOPs springen , passiert es einfach "Boom, Boom, Boom, Boom, Boom, Boom, Boom", und dann gelangen Sie in den Shell-Code.

Es scheint, als hätten sich die Leute das ausgedacht, was Sie wahrscheinlich in unserem Team sehen. Sie erfinden so etwas, und das ist das Problem. Dies ist also eine andere Möglichkeit, einige zufällige Dinge zu umgehen, indem Sie einfach die Randomisierung Ihrer Codes robust machen, wenn dies sinnvoll ist.

Daher haben wir einige Arten von Zufälligkeiten besprochen, die Sie verwenden können. Es gibt einige dumme Ideen, die auch bei Menschen entstanden sind. Jetzt wissen Sie also, dass Sie beim Tätigen eines Systemaufrufs, beispielsweise mit der Funktion syscall libc , grundsätzlich eine eindeutige Nummer übergeben, die den gewünschten Systemaufruf darstellt. Vielleicht ist die Gabelfunktion 7, der Schlaf ist 8 oder so ähnlich.

Dies bedeutet, dass ein Angreifer, wenn er die Adresse dieser Syscall- Anweisung herausfinden und irgendwie darauf zugreifen kann, einfach die Systemrufnummer ersetzen kann, die er direkt verwenden möchte. Sie können sich vorstellen, dass Sie jedes Mal, wenn das Programm ausgeführt wird, eine dynamische Zuordnung von Syscall- Nummern zu gültigen Syscalls erstellen , um die Erfassung durch den Angreifer zu erschweren.



Es gibt sogar einige avantgardistische Vorschläge, die Hardware so zu ändern, dass das Gerät den xor- Verschlüsselungsschlüssel enthält, der für xor- dynamische Funktionen verwendet wird. Stellen Sie sich vor, dass jedes Mal, wenn Sie ein Programm kompilieren, alle Anweisungscodes einen bestimmten xor- Schlüssel erhalten. Dieser Schlüssel wird im Geräteregister gespeichert, wenn Sie das Programm zum ersten Mal herunterladen. Danach führt das Gerät bei jeder Ausführung des Befehls automatisch eine XOR- Operation damit aus, bevor Sie mit diesem Befehl fortfahren. Das Gute an diesem Ansatz ist, dass ein Angreifer diesen Schlüssel selbst dann nicht erkennt, wenn er Shell-Code generieren kann. Daher wird es für ihn sehr schwierig sein, herauszufinden, was genau in Erinnerung bleiben muss.

Teilnehmerin: Wenn er den Code erhalten kann, kann er auch xor verwenden , um den Code wieder in eine Anweisung umzuwandeln .

Professor: Ja, das ist das kanonische Problem, richtig. Dies ist etwas ähnlich wie bei BROP- Angriffen, wenn wir den Ort des Codes zufällig zu bestimmen scheinen, der Angreifer ihn jedoch "fühlen" und herausfinden kann, was passiert. Man kann sich vorstellen, dass ein Angreifer, wenn er beispielsweise eine Teilsequenz von Code kennt, die er in einer Binärdatei zu finden erwartet, versucht, die xor- Operation für diese Datei zu verwenden, um den Schlüssel zu extrahieren.

Im Wesentlichen haben wir alle Arten von Randomisierungsangriffen besprochen, von denen ich Ihnen heute erzählen wollte. Bevor wir mit der Programmierung fortfahren, sollten wir diskutieren, welche dieser Schutzmethoden in der Praxis verwendet werden. Es stellt sich heraus, dass sowohl GCC als auch Visual Studio standardmäßig den Stack Canaries- Ansatz enthalten. Dies ist eine sehr beliebte und sehr berühmte Gemeinde. Wenn Sie sich Linux und Windows ansehen, nutzen sie auch Dinge wie nicht ausführbaren Speicher und Adressraum-Randomisierung. Zwar ist das Baggy-Bounds- System bei ihnen nicht so beliebt, wahrscheinlich aufgrund der Kosten für Speicher, Prozessor, Fehlalarme usw., über die wir bereits gesprochen haben. Im Grunde haben wir untersucht, wie das Pufferüberlaufproblem verhindert werden kann.

Jetzt sprechen wir über ROP , Reverse-Oriented Programming. Ich habe Ihnen heute bereits gesagt, was es bedeutet, den Adressraum zufällig zu ordnen und die Ausführung von Daten zu verhindern - es wird gelesen, geschrieben und ausgeführt. Das sind eigentlich sehr mächtige Dinge. Weil die Randomisierung verhindert, dass ein Angreifer versteht, wo sich unsere fest codierten Adressen befinden. Die Möglichkeit, die Ausführung von Daten zu verhindern, stellt sicher, dass ein Angreifer nicht einfach darauf springen und ihn ausführen kann, selbst wenn Sie den Shell-Code auf den Stapel legen.

All dies sieht ziemlich fortschrittlich aus, aber Hacker entwickeln ständig Angriffsmethoden gegen solche fortschrittlichen Verteidigungslösungen.

Was ist also die Essenz der rückwärtsgerichteten Programmierung?

Was wäre, wenn ein Angreifer, anstatt nur während eines Angriffs neuen Code zu erstellen, die vorhandenen Codeteile kombinieren und sie dann auf abnormale Weise miteinander kombinieren könnte? Schließlich wissen wir, dass das Programm Tonnen solchen Codes enthält.



Zum Glück oder leider hängt alles davon ab, auf welcher Seite Sie stehen. Wenn Sie einige interessante Codeausschnitte finden und miteinander kombinieren können, erhalten Sie so etwas wie die Turing- Sprache, in der der Angreifer im Wesentlichen tun kann, was er will.

Schauen wir uns ein sehr einfaches Beispiel an, das Ihnen zunächst bekannt vorkommt, sich dann aber schnell in etwas Verrücktes verwandelt.

Nehmen wir an, wir haben das folgende Programm. Lassen Sie uns also eine Art Funktion haben, und was für den Angreifer praktisch ist, hier ist diese nette Run-Shell- Funktion. Dies ist also nur ein Aufruf an das System. Es führt den Befehl bin / bash aus und endet. Als nächstes haben wir einen kanonischen Pufferüberlaufprozess oder leider eine Funktion, die die Erstellung eines Puffers ankündigt und dann eine dieser unsicheren Funktionen verwendet, um den Puffer mit Bytes zu füllen.



Wir wissen also, dass hier ein Pufferüberlauf ohne Probleme auftritt. Das Interessante ist jedoch, dass wir diese Run-Shell- Funktion haben, aber es ist schwierig, sie auf eine Weise zu erreichen, die auf Pufferüberläufen basiert. Wie könnte ein Angreifer kann den Befehl ausführen Shell auslösen?

Zunächst kann der Angreifer das Programm zerlegen, GDB starten und die Adresse dieses Dings in der ausführbaren Datei herausfinden. Sie kennen diese Methoden wahrscheinlich aus der Laborarbeit. Während eines Pufferüberlaufs kann ein Angreifer diese Adresse übernehmen, in den generierten Pufferüberlauf einfügen und überprüfen, ob die Funktion zur Ausführungsshell zurückkehrt .

Um es klar zu machen, werde ich es zeichnen. Sie haben also einen Stapel, der so aussieht: Unten befindet sich ein übergelaufener Puffer, darüber ein Indikator für gespeicherte Lücken und darüber die Rücksprungadresse für prosess_msg . Unten links haben wir einen neuen Stapelzeiger, der die Funktion initiiert, darüber einen neuen Unterbrechungszeiger, dann den Stapelzeiger, der verwendet wird, und noch höher ist der Unterbrechungszeiger des vorherigen Frames. Es kommt mir alles ziemlich bekannt vor.



Wie gesagt, während des Angriffs wurde GDB verwendet, um herauszufinden, wie die Adresse der Run-Shell lautet . Wenn der Puffer überläuft, können wir einfach die Adresse der Run-Shell hier rechts einfügen. Dies ist eigentlich eine ziemlich einfache Erweiterung dessen, was wir bereits wissen, wie man es macht. Im Wesentlichen bedeutet dies, dass wir, wenn wir einen Befehl haben, der die Shell startet, und wenn wir die Binärdatei zerlegen können, um herauszufinden, wo sich diese Adresse befindet, sie einfach in dieses Überlaufarray am unteren Rand des Stapels einfügen können. Es ist ziemlich einfach.

Dies war also ein äußerst leichtfertiges Beispiel, da der Programmierer aus irgendeinem verrückten Grund diese Funktion hier einsetzte und dem Angreifer damit ein echtes Geschenk machte.
Nehmen wir nun an, anstatt dieses Ding run_shell aufzurufen , nennen wir es run_boring und führen dann einfach den Befehl / bin / ls aus . Wir haben jedoch nichts verloren, da oben die Zeichenfolge char * bash_path angezeigt wird , die den Pfad zu diesem bin / bash angibt .



Das Interessanteste daran ist, dass ein Angreifer, der ls ausführen möchte, das Programm "analysieren" und den Speicherort von run_boring finden kann. Dies macht überhaupt keinen Spaß. Tatsächlich haben wir jedoch eine Zeile im Speicher, die auf den Pfad der Shell verweist. Außerdem wissen wir noch etwas Interessantes. Dies bedeutet, dass selbst wenn das Programm das System nicht mit dem Argument / bin / ls aufruft, dennoch eine Art Aufruf erfolgt.

So wissen wir, dass das System in irgendeiner Weise mit diesem Programm verbunden sein sollte - System ( „/ bin / ls “). Daher können wir diese beiden void- Operationen verwenden, um das System tatsächlich mit diesem char * bash_path-Argument zu verknüpfen . Als erstes gehen wir in GDB und finden heraus, wo sich dieses System ("/ bin / ls") im Bild des Binärprozesses befindet. Gehen Sie einfach zu GDB , geben Sie print_system ein und erhalten Sie Informationen zum Offset. Dies ist ziemlich einfach und Sie können dasselbe für bash_path tun . Das heißt, Sie verwenden einfach GDB , um herauszufinden, wo dieses Ding lebt.

Sobald Sie fertig sind, müssen Sie etwas anderes tun. Denn jetzt müssen wir wirklich irgendwie herausfinden, wie wir das System mit dem von uns gewählten Argument aufrufen können. Und die Art und Weise, wie wir dies tun, besteht im Wesentlichen darin, den aufrufenden Rahmen für das System zu verfälschen. Wenn Sie sich erinnern, verwenden sowohl der Compiler als auch die Hardware einen Frame, um den Stack-Aufruf zu implementieren.

Wir wollen so etwas wie das, was ich in dieser Abbildung dargestellt habe, auf dem Stapel anordnen. Tatsächlich werden wir ein System fälschen, das auf dem Stapel hätte sein sollen, aber kurz bevor es seinen Code tatsächlich ausführt.

Hier haben wir also das Argument des Systems, dies ist die Zeile, die wir ausführen möchten. Unten haben wir eine Zeile, in die das System zurückkehren soll, wenn die Zeile mit dem Argument abgeschlossen ist. Das System erwartet, dass der Stapel unmittelbar vor Beginn der Ausführung so aussieht.



Früher haben wir angenommen, dass es beim Übergeben der Funktion keine Argumente gibt, aber jetzt sieht es etwas anders aus. Wir müssen nur sicherstellen, dass das Argument in dem von uns erstellten Überlaufcode enthalten ist. Wir müssen nur sicherstellen, dass sich dieser gefälschte Aufrufrahmen in diesem Array befindet. Daher wird unsere Arbeit wie folgt sein. Denken Sie daran, dass der Stapelüberlauf von unten nach oben verläuft.



Zuerst geben wir hier die Systemadresse ein. Und obendrein werden wir eine Junk-Absenderadresse platzieren . Dies ist der Ort, an dem das System nach Beendigung zurückkehrt. Diese Adresse ist eine zufällige Menge von Bytes. Darüber setzen wir die Adresse bash_path . Was passiert, wenn der Puffer jetzt überläuft?

Nachdem prosess_msg die Ziellinie erreicht hat, wird er sagen: "OK, dies ist der Ort, an den ich zurückkehren sollte"! Der Systemcode wird weiterhin ausgeführt, er wird höher und sieht den von uns erstellten gefälschten Anrufrahmen. Für das System wird nichts Erstaunliches passieren, es heißt: "Ja, hier ist das Argument, das ich ausführen möchte, ist bin / bash ", es führt es aus und es ist bereit - der Angreifer hat die Shell erobert!

Was haben wir jetzt gemacht? Ich nutzte das Wissen über die Aufrufkonvention , die Aufrufkonvention , als Plattform zum Erstellen von gefälschten Stapelrahmen oder gefälschten Rahmennamen, würde ich sagen. Mit diesen gefälschten Aufrufrahmen können wir jede Funktion ausführen, auf die verwiesen wird und die bereits von der Anwendung definiert wurde.

Die nächste Frage, die wir stellen sollten, lautet: Was ist, wenn das Programm diese Zeile char * bash_path überhaupt nicht hat? Ich stelle fest, dass diese Zeile fast immer im Programm vorhanden ist. Nehmen wir jedoch an, wir leben in einer umgekehrten Welt, und sie ist immer noch nicht da. Was könnten wir also tun, um diese Zeile in ein Programm aufzunehmen?

Das erste , was kann zu tun getan werden kann, ist die richtige Adresse für bash_path angeben, es höhere Platzierung, aber in diesem Bereich unser Stapel, Einfügen gibt es drei Elemente, von denen jedes eine Größe von 4 Bytes hat:

/ 0
/ pat
/ bin



Aber auf jeden Fall kommt unser Zeiger hierher und - boom! - Die Sache ist erledigt. Auf diese Weise können Sie jetzt Argumente aufrufen, indem Sie sie einfach in Ihren Shell-Code einfügen. Erschreckend, nicht wahr? Und das alles ist vor einem vollständigen BROP- Angriff aufgebaut. Bevor Sie jedoch auf einen vollständigen BROP- Angriff hinweisen , müssen Sie verstehen, wie Sie die bereits im Code enthaltenen Elemente einfach miteinander verketten . Wenn ich diese abgelegte Absenderadresse hier habe, möchten wir nur auf die Shell zugreifen. Wenn Sie jedoch ein Angreifer sind, können Sie diese Absenderadresse oder Absenderadresse an etwas weiterleiten, das wirklich verwendet werden kann. Und wenn Sie dies getan haben, können Sie mehrere Funktionen in einer Reihe zu einer Reihe zusammenfassen, mehrere Zeichen einer Funktion in einer Reihe. Dies ist in der Tat eine sehr mächtige Option.

Denn wenn wir einfach die Absenderadresse für den Sprung festlegen, stürzt das Programm danach normalerweise ab, was wir vielleicht nicht wollen. Daher lohnt es sich, einige dieser Dinge miteinander zu verknüpfen, um interessantere Dinge mit dem Programm zu tun.

Angenommen, unser Ziel ist es, das System beliebig oft aufzurufen. Wir wollen dies nicht nur einmal tun, wir werden es beliebig oft tun. Wie kann das gemacht werden?

Dazu verwenden wir zwei Informationen, die wir bereits erhalten können. Wir wissen, wie man die Systemadresse erhält - Sie müssen nur in GDB schauen und sie dort finden. Wir wissen auch, wie man die Adresse dieser Zeile, bin / bash, findet . Um diesen Angriff mit mehreren Aufrufen des Systems auszulösen, müssen Gadgets verwendet werden. Dies bringt uns näher an das, was in BROP passiert.

Was wir jetzt brauchen, ist die Adresse dieser beiden Codeoperationen zu finden: pop% eax und ret . Der erste entfernt die Oberseite des Stapels und legt ihn im eax- Register ab, und der zweite legt ihn in den eip- Befehlszeiger. Dies nennen wir das Gadget. Es sieht aus wie ein kleiner Satz von Montageanweisungen, mit denen ein Angreifer ehrgeizigere Angriffe ausführen kann.



Diese Gadgets sind Standardwerkzeuge, mit denen Hacker beispielsweise Binärdateien finden. Es ist auch einfach, eines dieser Gadgets zu finden, vorausgesetzt, Sie haben eine Kopie der Binärdatei, und wir haben uns nicht um die Randomisierung gekümmert. Diese Dinge sind sehr leicht zu finden, ebenso wie die Adresse des Systems und so weiter.

Wenn wir eines dieser Geräte haben, warum können wir es dann verwenden? Natürlich, um Böses zu tun! Dazu können Sie Folgendes tun.

Angenommen, wir ändern unseren Stapel so, dass er so aussieht. Der Exploit wird nach wie vor von unten nach oben gerichtet. Als erstes platzieren wir hier die Systemadresse und darüber die Adresse des Pop / Ret- Gadgets. Noch höher setzen wir die Adresse von bash_path und wiederholen dann alles: Von oben platzieren wir erneut die Adresse des Systems, die Adresse des Gadgets pop / ret und die Adresse von bash_path .



Was wird hier jetzt passieren? Es wird ein wenig kompliziert sein, daher sind die Notizen dieser Vorlesung im Internet verfügbar, und im Moment können Sie einfach hören, was hier passiert, aber als ich das zum ersten Mal verstand, war es wie zu verstehen, dass der Weihnachtsmann nicht existierte!

Wir beginnen an der Stelle, an der sich der Eintrag befindet, und kehren zu dem System zurück, in dem die ret- Anweisung das Element mit dem Befehl pop aus dem Stapel entfernen wird. Jetzt befindet sich der obere Rand des Stapelzeigers hier. Also entfernen wir das Element mit pop und geben dann die ret- Prozedur zurück, die die Steuerung an die vom Stapel ausgewählte Rücksprungadresse überträgt, und diese Rücksprungadresse wird dort mit dem Aufrufbefehl platziert . Wir rufen also erneut das System an und dieser Vorgang kann immer wieder wiederholt werden.



Es ist klar, dass wir diese Sequenz in Beziehung setzen können, um eine beliebige Anzahl von Dingen auszuführen. Im Wesentlichen erhält der Kernel eine sogenannte umgekehrte Programmierung. Bitte beachten Sie, dass wir auf diesem Stapel nichts ausgeführt haben. Wir haben alles getan, um die Ausführung von Daten zu verhindern, ohne etwas zu zerstören. Wir haben nur einen unerwarteten Sprung gemacht, um das zu tun, was wir wollen. Eigentlich ist es sehr, sehr, sehr klug.

Interessant ist, dass wir dieses neue Modell für die Datenverarbeitung auf hohem Niveau identifiziert haben. , , , . , , . , - . , , . , . . , . , , stack canaries.

, «» , . , , «» ret address saved %ebp , - , «». , ret , , «», , - . stack canaries .

, «». , . , «»?

, , , .
, , , «» , «» «».

, , , «» , , .
, - , «» , . , ? ?
, fork . , fork . , , , fork , , , , «» . , stack canaries .

«»? . , , , «». «» . .



, , – , «». , , 0. , «», . , :

«, «»! , 0. «»! 1 – «», 2 – . , 2- . , , «».



, , , .

«», , , . , , «».

57:10

:

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


.

Vielen Dank für Ihren Aufenthalt bei uns. 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).

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/de418093/


All Articles