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 3Vorlesung 2: „Kontrolle von Hackerangriffen“
Teil 1 /
Teil 2 /
Teil 3 Wir haben also einen Puffer, über den wir den „Kanarienvogel“ legen. Darüber befindet sich der gespeicherte
EBP- Wert des gespeicherten Haltepunktzeigers und die Rücksprungadresse wird darüber platziert. Wenn Sie sich erinnern, geht der Überlauf von unten nach oben. Bevor Sie also zur Absenderadresse gelangen, wird zuerst der "Kanarienvogel" zerstört.
Teilnehmerin: Warum wirkt sich das auf den "Kanarienvogel" aus?
Professor: Weil angenommen wird, dass der Angreifer nicht weiß, wie er willkürlich in das Gedächtnis „springen“ soll. Herkömmliche Speicherüberlaufangriffe beginnen mit einem Hacker, der die Puffergrößenbeschränkung untersucht. Danach beginnt der Überlauf unter dem Strich. Aber Sie haben Recht - wenn ein Angreifer direkt in die Absenderadressleiste gelangen kann, hilft uns kein „Kanarienvogel“. Bei einem herkömmlichen Pufferüberlaufangriff sollte jedoch alles genau so ablaufen - von unten nach oben.
Die Hauptidee bei der Verwendung eines „Kanarienvogels“ ist daher, dass ein böswilliger Exploit den Speicherpuffer überlaufen lässt. Wir haben einen Laufzeitcode, der bei der Rückkehr von einer Funktion den "Kanarienvogel" überprüft, um sicherzustellen, dass er den richtigen Wert hat.
Zielgruppe: Kann ein Angreifer die Absenderadresse umschreiben und den „Kanarienvogel“ ändern? Wie kann er überprüfen, ob es geändert wurde, aber weiterhin seine Funktion erfüllt?
Professor: Ja, vielleicht. Daher sollten Sie einen Code haben, der dies tatsächlich überprüft, bevor die Funktion zurückkehrt. Das heißt, in diesem Fall ist die Unterstützung eines Compilers erforderlich, der die
Aufrufkonvention tatsächlich
aufruft . Dieser Teil der Rückgabesequenz erfolgt also, bevor wir die Gültigkeit dieses Werts prüfen, um sicherzustellen, dass der „Kanarienvogel“ nicht zerstört wurde. Erst danach können wir über etwas anderes nachdenken.
Teilnehmerin: Kann ein Angreifer nicht wissen oder erraten, was "Kanarienvogel" bedeutet?
Professor: Genau darüber werde ich sprechen! Was ist das Problem mit dieser Schaltung? Was ist, wenn wir zum Beispiel den Wert A in jedes Programm setzen? Oder ein ganzer Zweig von 4 Werten von A? Natürlich kann jeder Hacker die Größe des Puffers und seine Kapazität herausfinden und so die Position des "Kanarienvogels" in jedem System bestimmen. Daher können wir verschiedene Arten von Mengen verwenden, die wir in unseren „Kanarienvogel“ geben, um dies zu verhindern.
Eines können Sie mit unserem „Kanarienvogel“ tun. Es wird eine sehr lustige Art von "Kanarienvogel" sein, die C-Programmfunktionen verwendet und Sonderzeichen verarbeitet, die sogenannte deterministische Art von "Kanarienvogel".

Stellen Sie sich vor, Sie haben das Zeichen 0 für den „Kanarienvogel“ verwendet. Der Binärwert Null ist das Nullbyte, das Nullzeichen in ASCII. Ein Wert von -1 bedeutet eine Rückkehr zur vorherigen Position und so weiter. Viele Funktionen stoppen oder ändern den Betrieb, wenn sie auf Zeichen oder Werte wie 0, CR, LF, -1 stoßen. Stellen Sie sich vor, Sie als Hacker verwenden eine String-Verwaltungsfunktion, um den Puffer zu erhöhen, treffen auf das Zeichen 0 im "Kanarienvogel" und der Prozess stoppt! Wenn Sie die Funktion „Wagenrücklauf“ -1 verwenden, die häufig als Zeilenabschluss verwendet wird, wird der Vorgang ebenfalls gestoppt. -1 ist also ein weiteres magisches Zeichen.
Es gibt noch eine Sache, die im „Kanarienvogel“ verwendet werden kann - dies sind zufällige Werte, die für den Angreifer schwer zu erraten sind. Die Stärke des Zufallswerts hängt davon ab, wie schwierig es für einen Angreifer ist, ihn zu erraten. Wenn ein Angreifer beispielsweise feststellt, dass Ihr System nur 3 Entropiebits enthält, kann er einen Brute-Force-Angriff ausführen. Daher sind die Möglichkeiten, Zufallszahlen zum Schutz vor Angriffen zu verwenden, sehr begrenzt.
Zielgruppe: Normalerweise lese ich aus einem anderen Puffer und schreibe das, was ich gelesen habe, in diesen Puffer dieses Stapels. In dieser Situation scheint der Zufallswert des "Kanarienvogels" nutzlos zu sein, da ich die Daten aus einem anderen Puffer lese und weiß, wo sich der "Kanarienvogel" befindet. Ich habe einen anderen Puffer, den ich kontrolliere und den ich nie überprüfe. Und in diesen Puffer kann ich ziemlich viel von dem setzen, was ich setzen möchte. Ich brauche keinen zufälligen "Kanarienvogel", weil ich ihn sicher umschreiben kann. Ich sehe also nicht, wie es wirklich funktioniert - in dem von Ihnen vorgeschlagenen Szenario, wenn die Funktion beim Lesen von Daten aus dem Puffer stoppt.
Professor: Ich verstehe Ihre Frage - Sie meinen, wir verwenden einen deterministischen "Kanarienvogel", verwenden jedoch keine der Funktionen der Standardbibliothek, die durch unsere Zeichen 0, CR, LF, -1 "getäuscht" werden können. Dann ja, in der von Ihnen beschriebenen Situation wird kein „Kanarienvogel“ benötigt.
Die Idee ist, dass Sie diesen Puffer von überall mit Bytes füllen können, aber alles, was es Ihnen ermöglicht, diese Werte zu erraten oder sie zufällig abzurufen, führt zu einem Fehler.
Teilnehmerin: Ist es möglich, so etwas wie die Anzahl der Sekunden oder Millisekunden als Zufallszahlen zu verwenden und sie in einem „Kanarienvogel“ zu verwenden?
Professor: Datenanrufe enthalten nicht so viele Unfälle, wie Sie denken. Weil das Programm Protokolle oder eine Funktion hat, die Sie aufrufen können, um herauszufinden, wann das Programm heruntergeladen wurde, und andere ähnliche Dinge. Aber im Allgemeinen haben Sie Recht - in der Praxis kann dieser Ansatz funktionieren, wenn Sie ein Hardwaregerät verwenden können, das normalerweise auf einem niedrigen Niveau mit besseren Systemzeiten arbeitet.
Zielgruppe: Auch wenn es uns gelingt, die Protokolle über den Beginn des Pufferüberlaufs anzuzeigen, ist es immer noch wichtig, wann wir die Anforderung ablehnen. Und wenn wir nicht kontrollieren können, wie lange die Anforderung eines Computers an den Server dauert, ist es zweifelhaft, ob die genaue Zeit deterministisch erraten werden kann.
Professor: Ganz richtig, ich habe bereits gesagt, dass das Böse im Detail liegt, das ist genau so ein Fall. Mit anderen Worten, wenn Sie beispielsweise den Typ des Zeitkanals bestimmen können, stellen Sie möglicherweise fest, dass die Entropie oder die Anzahl der Zufälligkeiten nicht einen ganzen Zeitstempel ausfüllt, sondern viel weniger. Daher kann ein Angreifer die Stunde und Minute bestimmen, zu der Sie dies getan haben, jedoch keine Sekunde.
Teilnehmerin: Für den Rekord ist es eine schlechte Idee, die eigene Zufälligkeit einzuschränken?
Professor: absolut richtig!
Zielgruppe: Das heißt, normalerweise müssen wir einfach alles verwenden, was unsere Systeme unterstützen, oder?
Professor: Ja, das stimmt. Dies ist wie die Erfindung unseres eigenen Kryptosystems, was eine weitere beliebte Sache ist, die unsere Absolventen manchmal tun möchten. Aber wir sind nicht die NSA, wir sind keine Mathematiker, also scheitert dies normalerweise. Da haben Sie also absolut Recht.
Aber selbst wenn Sie die Zufälligkeit des Systems verwenden, können Sie immer noch weniger Entropiebits erhalten, als Sie erwarten. Lassen Sie mich ein Beispiel für die Phasen-Randomisierung von Adressen geben. Nach diesem Prinzip
funktioniert der Ansatz der
Stapelkanarien . Da wir uns mit Computersicherheit beschäftigen, fragen Sie sich wahrscheinlich, in welchen Fällen die "Kanarienvögel" ihre Aufgabe nicht bewältigen können und ob es Möglichkeiten gibt, den "Kanarienvogel" zu scheitern.
Ein solcher Weg ist ein Angriff durch Umschreiben von Funktionszeigern. Denn wenn ein Schlag auf den Funktionszeiger geschlagen wird, kann der „Kanarienvogel“ nichts tun.
Angenommen, Sie haben einen Code der Form
int * ptr ... .. , den
auslösenden Zeiger, egal wie, dann haben Sie den
char buf- Puffer
[128] , die
get- Funktion
(buf) und ganz unten einen Zeiger, dem ein Wert zugewiesen ist :
* ptr = 5 .
Ich stelle fest, dass wir nicht versucht haben, die Rücksprungadresse der Funktion anzugreifen, die diesen Code enthält. Wie Sie sehen können, wird die darüber liegende Zeigeradresse beschädigt, wenn der Puffer überläuft. Wenn ein Angreifer diesen Zeiger beschädigen kann, kann er einer der von ihm kontrollierten Adressen 5 zuweisen. Kann jeder sehen, dass der „Kanarienvogel“ hier nicht hilft? Weil wir den Pfad, auf dem die Funktion zurückkehrt, nicht angreifen.
Zielgruppe: Kann sich der Zeiger unterhalb des Puffers befinden?
Professor: Das kann es, aber die Reihenfolge bestimmter Variablen hängt von vielen verschiedenen Faktoren ab, von der Art und Weise, wie der Compiler den Inhalt anordnet, von der Größe der Hardware-Spalte usw. Aber Sie haben Recht, wenn der Pufferüberlauf steigt und sich der Zeiger unter dem Puffer befindet, kann der Überlauf ihn nicht beschädigen.
Teilnehmerin: Warum können Sie den "Kanarienvogel" nicht wie die Absenderadresse mit der Funktion "Kanarienvogel" verknüpfen?
Professor: Das ist ein interessanter Moment! Sie können solche Dinge tun. In der Tat können Sie sich einen Compiler vorstellen, der immer dann, wenn er einen Zeiger hat, versucht, für einige Dinge ein Add-On hinzuzufügen. Das Auschecken all dieser Dinge wird jedoch zu teuer sein. Da Sie jedes Mal, wenn Sie einen Zeiger verwenden oder eine Funktion aufrufen möchten, einen Code benötigen, der überprüft, ob dieser "Kanarienvogel" korrekt ist. Grundsätzlich könnten Sie etwas Ähnliches tun, aber macht das Sinn? Wir sehen, dass die "Kanarienvögel" in dieser Situation nicht helfen.
Und eine weitere Sache, die wir zuvor besprochen haben, ist, dass zufällige „Kanarienvögel“ im Prinzip nicht funktionieren, wenn der Angreifer die Zufälligkeit erraten kann. Das Erstellen von Sicherheitsressourcen basierend auf Zufälligkeit ist ein separates, sehr komplexes Thema, daher werden wir nicht darauf eingehen.
Teilnehmerin: Canary enthält also weniger Bits als eine Absenderadresse? Weil Sie sich sonst nicht einfach an diese Adresse erinnern und prüfen könnten, ob sie sich geändert hat?
Professor: Mal sehen. Sie sprechen von diesem Schema, wenn sich der „Kanarienvogel“ über dem Puffer befindet, und Sie meinen, dass das System nicht sicher sein kann, wenn es unmöglich ist, die Absenderadresse zu überprüfen und zu überprüfen, ob sie geändert wurde.

Ja und nein. Bitte beachten Sie, dass bei einem Pufferüberlaufangriff alles darüber überschrieben wird, sodass dies weiterhin zu Problemen führen kann. Aber wenn diese Dinge in gewisser Weise unveränderlich wären, könnten Sie so etwas tun. Das Problem ist jedoch, dass die Manipulation der Absenderadresse in vielen Fällen ziemlich kompliziert ist. Weil Sie sich vorstellen können, dass eine spezielle Funktion von verschiedenen Orten aus aufgerufen werden kann, und so weiter. In diesem Fall laufen wir ein wenig voraus, und wenn am Ende der Vorlesung noch Zeit ist, werden wir darauf zurückkommen.
Dies sind Situationen, in denen ein „Kanarienvogel“ versagen kann. Es gibt andere Stellen, an denen ein Fehler möglich ist, beispielsweise beim Angriff auf das
Malloc und die
freien Funktionen. Die Malloc-Funktion weist einen Speicherblock einer bestimmten Größe in Bytes zu und gibt einen Zeiger auf den Blockanfang zurück. Der Inhalt des zugewiesenen Speicherblocks wird nicht initialisiert, sondern bleibt bei undefinierten Werten. Und die
freie Funktion gibt Speicher frei, der zuvor dynamisch zugewiesen wurde.
Dies ist ein einzigartiger Angriff im Stil von C. Mal sehen, was hier passiert. Stellen Sie sich vor, Sie haben hier zwei Zeiger, p und q, für die wir
malloc verwenden, um jedem dieser Zeiger 1.024 Byte Speicher zuzuweisen. Angenommen, wir machen die
strcpy- Funktion für p aus einem Pufferfehler, der von einem Angreifer gesteuert wird. Hier tritt ein Überlauf auf. Und dann führen wir den Befehl
free q und
free p aus . Das ist ziemlich einfacher Code, oder?

Wir haben 2 Zeiger, denen wir Speicher zugewiesen haben, wir verwenden einen für eine bestimmte Funktion, es tritt ein Pufferüberlauf auf und wir geben den Speicher beider Zeiger frei.
Angenommen, die Speicherleitungen von p und q befinden sich nebeneinander im Speicherbereich. In diesem Fall können schlimme Dinge passieren, oder? Weil die
strcpy- Funktion verwendet wird, um den Inhalt von
str2 nach
str1 zu
kopieren .
Str2 sollte ein Zeiger auf einen String sein, der mit Null endet, und
strcpy gibt einen Zeiger auf
str1 zurück . Wenn sich die Linien
str1 und
str2 überlappen, ist das Verhalten der Funktion
strcpy undefiniert.
Daher kann die
Strycpy- Funktion, die den Speicher
p verarbeitet, gleichzeitig den für
q zugewiesenen Speicher beeinflussen. Und das kann Probleme verursachen.
Es ist möglich, dass Sie so etwas versehentlich in Ihrem eigenen Code getan haben, als Sie eine seltsame Art von Zeigern verwendet haben. Und alles scheint zu funktionieren, aber wenn Sie die
freie Funktion aufrufen müssen, tritt ein solches Ärgernis auf. Und ein Angreifer kann es ausnutzen, ich werde erklären, warum dies passiert.
Stellen Sie sich vor, dass der hervorgehobene Block in der Implementierung der Funktionen
free und
malloc folgendermaßen aussieht.
Nehmen wir an, dass oben im Block sichtbare Anwendungsdaten vorhanden sind und unten die Größe der Variablen angegeben ist. Diese Größe ist nicht das, was die Anwendung direkt sieht, sondern eine Art „Abrechnung“, die von
Free oder
Malloc durchgeführt wird , sodass Sie die Größe des zugewiesenen Speicherpuffers kennen. Ein freier Block befindet sich neben dem markierten Block. Angenommen, ein freier Block enthält einige Metadaten, die folgendermaßen aussehen: Wir haben die Größe des Blocks darüber, darunter befindet sich freier Speicherplatz, der Rückzeiger und der Vorwärtszeiger darunter. Und ganz unten im Block wird die Größe erneut angezeigt.

Warum haben wir hier 2 Zeiger? Weil das Speicherzuweisungssystem in diesem Fall eine doppelt verknüpfte Liste verwendet, um zu verfolgen, wie freie Blöcke miteinander in Beziehung stehen. Wenn Sie einen freien Block auswählen, schließen Sie ihn daher von dieser doppelt verknüpften Liste aus. Und dann, wenn Sie es freigeben, werden Sie eine Arithmetik für den Zeiger durchführen und diese Dinge in Ordnung bringen. Danach fügen Sie es dieser verknüpften Liste hinzu, richtig?
Wann immer Sie von Zeigerarithmetik hören, sollten Sie denken, dass dies Ihr „Kanarienvogel“ ist. Weil es viele Probleme geben wird. Ich möchte Sie daran erinnern, dass wir einen Pufferüberlauf hatten. Wenn wir annehmen, dass
p und
q nebeneinander oder sehr nahe am Speicherplatz liegen, kann es letztendlich vorkommen, dass dieser Pufferüberlauf einige Größendaten für den zugewiesenen Zeiger
q überschreibt - dies ist der Boden unseres zugewiesenen Blocks. Wenn Sie meinen Gedanken von Anfang an weiter folgen, wird Ihnen Ihre Vorstellungskraft sagen, wo alles schief geht. Tatsächlich geschieht bei diesen Operationen im Wesentlichen das
freie q und das
freie p - sie betrachten diese Metadaten im ausgewählten Block, um alle erforderlichen Manipulationen mit dem Zeiger durchzuführen.

Das heißt, irgendwann in der Ausführung erhalten die
freien Funktionen einen bestimmten Zeiger basierend auf dem Größenwert:
p = get.free.block (Größe) , und die Größe wird vom Angreifer
gesteuert , da er einen Pufferüberlauf korrekt ausgeführt hat ?
Er hat eine Reihe von arithmetischen Berechnungen durchgeführt, sich die Funktion
back und die Zeiger dieses Blocks angesehen und wird nun so etwas wie die Zeiger "back" und "forward" aktualisieren - dies sind die beiden unteren Zeilen.

Aber in Wirklichkeit sollte dich das nicht stören. Dies ist nur ein Beispiel für den Code, der in diesem Fall stattfindet. Tatsache ist jedoch, dass er aufgrund der vom Hacker neu geschriebenen Größe jetzt diesen Zeiger steuert, der die
freie Funktion durchläuft. Aus diesem Grund sind die beiden Zustände hier in den unteren Zeilen tatsächlich Zeigeraktualisierungen. Und da der Angreifer dieses
p steuern konnte, steuert er tatsächlich diese beiden Zeiger. Hier kann ein Angriff stattfinden.
Wenn Sie also
frei laufen und versuchen, diese beiden Blöcke zu kombinieren, haben Sie eine doppelt verknüpfte Liste. Denn wenn Sie zwei Blöcke haben, die miteinander kollidieren und beide frei sind, möchten Sie sie zu einem großen Block kombinieren.
Wenn wir jedoch die Größe steuern, bedeutet dies, dass wir den gesamten Prozess über die vier obigen Zeilen steuern. Das heißt, wenn wir verstehen, wie Überlauf funktioniert, können wir Daten auf die von uns gewählte Weise in den Speicher schreiben. Wie gesagt, solche Dinge passieren oft mit Ihrem eigenen Code, wenn Sie mit einem Zeiger nicht schlau sind. Wenn Sie einen doppelten freien Fehler wie
free q und
free p oder etwas anderes machen, stürzt Ihre Funktion ab. Weil Sie die Metadaten, die in jedem dieser ausgewählten Blöcke vorhanden sind, durcheinander gebracht haben und diese Berechnung irgendwann eine Art "Müll" -Wert anzeigt, nach dem Sie "tot" sind. Wenn Sie jedoch ein Angreifer sind, können Sie diesen Wert auswählen und zu Ihrem Vorteil nutzen.
Fahren wir mit einem anderen Ansatz fort, um Pufferüberlaufangriffe zu verhindern. Dieser Ansatz dient dazu, Grenzen zu überprüfen. Mit der Begrenzungsprüfung soll sichergestellt werden, dass bei Verwendung eines bestimmten Zeigers nur auf ein Speicherobjekt verwiesen wird. Und dieser Zeiger befindet sich innerhalb der zulässigen Grenzen dieses Speicherobjekts. Dies ist die Hauptidee der Überprüfung. — . , C, . , : , , ?
, – . 1024 , :
char [1024] ,
char *y = & [108].
? ? Schwer zu sagen. , , . , , - .
- , , , . . , , , . , , . , , .
, ,
struct union . , . :
integer ,
struct ,
int .
,
union , . ,
integer ,
struct , .
, , - :
int p: & (u,s,k) , : u, s, k.

, , , , . , ,
union integer ,
struct . , , , . .
p' ,
p ,
p' , .

, , . , ,
union . , - -
union , , , . , , X. , , , , . , , . .
, . ,
p p' , . .
? Electric fencing – . , , , , .

, - , . , , . , . , , , .
- C C++, , , . - , , - . , . , «» — , , , . , , .
, guard page – ! , .
59:00
:
MIT « ». 2: « », 2Die Vollversion des Kurses finden Sie
hier .
, . ? ? ,
30% entry-level , : VPS (KVM) E5-2650 v4 (6 Cores) 10GB DDR4 240GB SSD 1Gbps $20 ? ( RAID1 RAID10, 24 40GB DDR4).
Dell R730xd 2 ? 2 Intel Dodeca-Core Xeon E5-2650v4 128GB DDR4 6x480GB SSD 1Gbps 100 $249 ! . c Dell R730xd 5-2650 v4 9000 ?