Ken Thompson Hack
Ken Thompson beschrieb 1984 eine Methode zur Beschädigung einer Compiler-Binärdatei (und anderer kompilierter Software, z. B. eines Anmeldeskripts auf einem * nix-System). Ich war gespannt, ob die moderne Kompilierung diese Sicherheitslücke geschlossen hat oder nicht.
Kurze Beschreibung:
Schreiben Sie den Compiler-Code neu, um 2 Fehler zu enthalten:
- Beim Kompilieren seiner eigenen Binärdatei muss der Compiler diese Fehler kompilieren
- Beim Kompilieren eines anderen vorgewählten Codes (Login-Funktion) muss eine beliebige Backdoor kompiliert werden
Somit funktioniert der Compiler normal - wenn er ein Anmeldeskript oder ähnliches kompiliert, kann er eine Sicherheits-Backdoor erstellen, und wenn er in Zukunft neuere Versionen von sich selbst kompiliert, behält er die vorherigen Fehler bei - und die Fehler existieren nur im Compiler binär sind also extrem schwer zu erkennen.
Fragen:
Ich konnte im Internet keine Antworten auf diese Fragen finden:
- In welcher Beziehung steht dies zur Just-in-Time-Kompilierung?
- Werden Funktionen wie das Programm, das Anmeldungen auf einem * nix-System verarbeitet, kompiliert, wenn sie ausgeführt werden?
- Ist dies immer noch eine gültige Bedrohung, oder hat es seit 1984 Entwicklungen in der Sicherheit der Zusammenstellung gegeben, die verhindern, dass dies ein bedeutendes Problem darstellt?
- Betrifft das alle Sprachen?
Warum will ich es wissen?
Ich bin darauf gestoßen, als ich einige Hausaufgaben gemacht habe, und es schien interessant, aber mir fehlt der Hintergrund, um konkret zu verstehen, ob es sich um ein aktuelles oder ein gelöstes Problem handelt.
Antworten:
Dieser Hack muss im Kontext verstanden werden. Es wurde zu einer Zeit und in einer Kultur veröffentlicht, in der Unix auf allen Arten von Hardware das vorherrschende System war.
Was den Angriff so beängstigend machte, war, dass der C-Compiler das zentrale Softwareelement für diese Systeme war. Fast alles im System durchlief bei der Erstinstallation den Compiler (Binärdistributionen waren aufgrund der heterogenen Hardware selten). Jeder hat die ganze Zeit Sachen zusammengestellt. Die Leute haben den Quellcode regelmäßig überprüft (sie mussten oft Anpassungen vornehmen, um ihn überhaupt kompilieren zu können), sodass das Einfügen von Hintertüren durch den Compiler eine Art "perfektes Verbrechensszenario" zu sein schien, in dem man nicht gefasst werden konnte.
Heutzutage ist Hardware viel kompatibler und Compiler spielen daher eine viel geringere Rolle im täglichen Betrieb eines Systems. Ein kompromittierter Compiler ist nicht mehr das gruseligste Szenario - Rootkits und ein kompromittiertes BIOS sind noch schwerer zu erkennen und loszuwerden.
quelle
Der Zweck dieser Rede bestand nicht darin, eine Schwachstelle hervorzuheben, die behoben werden muss, oder sogar eine theoretische Schwachstelle vorzuschlagen, deren wir uns bewusst sein müssen.
Der Zweck war, dass wir in Bezug auf Sicherheit niemandem vertrauen müssen, aber das ist leider unmöglich. Man muss immer jemandem vertrauen (daher der Titel: "Reflections On Trusting Trust")
Selbst wenn Sie der paranoide Typ sind, der seine Desktop-Festplatte verschlüsselt und es ablehnt, Software auszuführen, die Sie nicht selbst kompiliert haben, müssen Sie Ihrem Betriebssystem dennoch vertrauen. Auch wenn Sie das Betriebssystem selbst kompilieren, müssen Sie dem verwendeten Compiler vertrauen. Und selbst wenn Sie Ihren eigenen Compiler kompiliert, Sie noch brauchen zu vertrauen , dass die Compiler! Und das erwähnen nicht einmal die Hardware-Hersteller!
Man kommt einfach nicht damit davon, niemandem zu vertrauen . Das ist der Punkt, an dem er versucht hat rüberzukommen.
quelle
Nein
Der Angriff war, wie ursprünglich beschrieben, niemals eine Bedrohung. Während ein Compiler dies theoretisch tun könnte, müsste der Compiler dafür programmiert werden, um den Angriff tatsächlich auszuführen
Dazu muss man aus dem Quellcode herausfinden, wie der Compiler funktioniert, damit er ihn ohne Unterbrechung ändern kann.
Stellen Sie sich zum Beispiel vor, dass das Verknüpfungsformat die Datenlängen oder den Versatz des kompilierten Maschinencodes irgendwo in der ausführbaren Datei speichert. Der Compiler müsste selbst herausfinden, welche davon aktualisiert werden müssen und wo, wenn er die Exploit-Payload einfügt. Nachfolgende Versionen des Compilers (harmlose Version) können dieses Format beliebig ändern, sodass der Exploit-Code diese Konzepte effektiv verstehen müsste.
Dies ist eine selbstgesteuerte Programmierung auf hoher Ebene, ein hartes KI-Problem (zuletzt habe ich überprüft, dass der Stand der Technik Code generiert, der praktisch durch seine Typen bestimmt wird). Schauen Sie: Nur wenige Menschen können das überhaupt; Sie müssten zuerst die Programmiersprache lernen und die Code-Basis verstehen.
Selbst wenn das KI-Problem gelöst ist, werden die Leute bemerken, dass das Kompilieren ihres winzigen Compilers zu einer Binärdatei mit einer großen KI-Bibliothek führt.
Analoger Angriff: Bootstrapping-Vertrauen
Eine Verallgemeinerung des Angriffs ist jedoch relevant. Das Grundproblem ist, dass Ihre Vertrauenskette irgendwo beginnen muss, und in vielen Bereichen könnte ihr Ursprung die gesamte Kette auf eine schwer zu erkennende Weise untergraben.
Ein Beispiel, das sich im wirklichen Leben leicht umsetzen lässt
Ihr Betriebssystem, beispielsweise Ubuntu Linux, stellt die Sicherheit (Integrität) von Updates sicher, indem heruntergeladene Update-Pakete anhand des Signaturschlüssels des Repositorys überprüft werden (mithilfe von Public-Key-Kryptografie). Dies garantiert jedoch nur dann die Echtheit der Aktualisierungen, wenn Sie nachweisen können, dass der Signaturschlüssel einer legitimen Quelle gehört.
Woher hast du den Signaturschlüssel? Beim ersten Herunterladen der Betriebssystemverteilung.
Sie müssen darauf vertrauen, dass die Quelle Ihrer Vertrauenskette, dieser Signaturschlüssel, nicht böse ist.
Jeder, der MITM die Internetverbindung zwischen Ihnen und dem Ubuntu-Download-Server herstellen kann - dies könnte Ihr ISP sein, eine Regierung, die den Internetzugang kontrolliert (z. B. China), oder Ubuntus Hosting-Anbieter -, könnte diesen Prozess gekapert haben:
Von da an erhalten Sie Ihre Updates sicher vom Server des Angreifers. Updates werden als Root ausgeführt, sodass der Angreifer die volle Kontrolle hat.
Sie können den Angriff verhindern, indem Sie sicherstellen, dass das Original authentisch ist. Dies setzt jedoch voraus, dass Sie das heruntergeladene CD-Image mit einem Hash validieren (nur wenige tun dies tatsächlich ) - und der Hash selbst muss sicher heruntergeladen werden, z. B. über HTTPS. Und wenn Ihr Angreifer ein Zertifikat auf Ihrem Computer hinzufügen kann (wie in einer Unternehmensumgebung üblich) oder eine Zertifizierungsstelle (z. B. China) kontrolliert, bietet auch HTTPS keinen Schutz.
quelle
Erstens, meine Lieblingsbeschreibung dieses Hacks heißt Strange Loops .
Dieser bestimmte Hack könnte heute sicherlich (*) in einem der großen Open-Source-OS-Projekte durchgeführt werden, insbesondere in Linux, * BSD und dergleichen. Ich würde erwarten, dass es fast identisch funktionieren würde. Beispielsweise laden Sie eine Kopie von FreeBSD herunter, die über einen ausgenutzten Compiler zum Ändern von openssh verfügt. Von da an werden Sie das Problem bei jedem Upgrade von openssh oder dem Compiler nach Quelle fortsetzen. Angenommen, der Angreifer hat das System ausgenutzt, das zum Packen von FreeBSD verwendet wurde (wahrscheinlich, da das Image selbst beschädigt ist oder der Angreifer tatsächlich der Packager ist), dann wird das Problem jedes Mal neu erzeugt, wenn das System FreeBSD-Binärdateien neu erstellt. Es gibt viele Möglichkeiten, wie dieser Angriff fehlschlagen kann, aber sie unterscheiden sich nicht grundlegend davon, wie Kens Angriff fehlgeschlagen sein könnte (**). Die Welt hat sich wirklich nicht so sehr verändert.
Natürlich könnten ähnliche Angriffe von ihren Besitzern genauso einfach (oder einfacher) in Systeme wie Java, das iOS SDK, Windows oder ein anderes System eingeschleust werden. Bestimmte Arten von Sicherheitslücken können sogar in die Hardware eingearbeitet werden (insbesondere die Schwächung der Zufallszahlengenerierung).
(*) Aber mit "sicher" meine ich "im Prinzip". Sollten Sie erwarten, dass diese Art von Loch in einem bestimmten System existiert? Nein, ich würde es aus verschiedenen praktischen Gründen für ziemlich unwahrscheinlich halten. Mit der Zeit, wenn sich der Code ändert und ändert, steigt die Wahrscheinlichkeit, dass diese Art von Hack seltsame Fehler verursacht. Und das erhöht die Wahrscheinlichkeit, dass es entdeckt wird. Weniger geniale Hintertüren würden Verschwörungen erfordern, um aufrechtzuerhalten. Natürlich wissen wir, dass in verschiedenen Telekommunikations- und Netzwerksystemen "rechtmäßige Abhör-Hintertüren" installiert wurden, so dass in vielen Fällen diese Art von aufwändigem Hack unnötig ist. Der Hack ist offen installiert.
Also immer Verteidigung in die Tiefe.
(**) Unter der Annahme, dass Kens Angriff jemals tatsächlich stattgefunden hat. Er hat gerade besprochen, wie es gemacht werden kann. Er hat nicht gesagt, dass er es tatsächlich getan hat, soweit ich weiß.
quelle
Betrifft das alle Sprachen?
Dieser Angriff betrifft hauptsächlich Sprachen, die sich selbst hosten. Das sind Sprachen, in denen der Compiler in der Sprache selbst geschrieben ist. C, Squeak Smalltalk und der PyPy Python-Interpreter wären davon betroffen. Perl, JavaScript und der CPython-Python-Interpreter würden dies nicht tun.
In welcher Beziehung steht dies zur Just-in-Time-Kompilierung?
Nicht sehr viel. Es ist die Selbst-Hosting-Natur des Compilers, die es ermöglicht, den Hack zu verbergen. Ich kenne keine selbsthostenden JIT-Compiler. (Vielleicht LLVM?)
Werden Funktionen wie das Programm, das Anmeldungen auf einem * nix-System verarbeitet, kompiliert, wenn sie ausgeführt werden?
Nicht gewöhnlich. Die Frage ist aber nicht, wann es kompiliert wird, sondern von welchem Compiler . Wenn das Anmeldeprogramm von einem fehlerhaften Compiler kompiliert wurde, ist es fehlerhaft. Wenn es von einem sauberen Compiler kompiliert wird, ist es sauber.
Ist dies immer noch eine gültige Bedrohung, oder hat es seit 1984 Entwicklungen in der Sicherheit der Zusammenstellung gegeben, die verhindern, dass dies ein bedeutendes Problem darstellt?
Dies ist immer noch eine theoretische Bedrohung, aber nicht sehr wahrscheinlich.
Eine Möglichkeit, dies zu verringern, besteht darin, mehrere Compiler zu verwenden. Ein LLVM-Compiler, der selbst von GCC kompiliert wurde, passiert beispielsweise keine Hintertür. Ebenso passiert ein von LLVM kompilierter GCC keine Hintertür. Wenn Sie also über diese Art von Angriff besorgt sind, können Sie Ihren Compiler mit einer anderen Art von Compiler kompilieren. Das bedeutet, dass der böse Hacker (bei Ihrem Betriebssystemhersteller?) Beide Compiler schädigen muss, um sich gegenseitig zu erkennen. Ein viel schwierigeres Problem.
quelle
Es gibt eine theoretische Chance dafür. Es gibt jedoch eine Möglichkeit zu überprüfen, ob ein bestimmter Compiler (mit verfügbarem Quellcode) durch die Diverse-Doppelkompilierung von David A. Wheeler kompromittiert wurde .
Verwenden Sie grundsätzlich sowohl den verdächtigen Compiler als auch einen anderen unabhängig entwickelten Compiler, um die Quelle des verdächtigen Compilers zu kompilieren. Dies gibt Ihnen SC sc und SC T . Kompilieren Sie nun die verdächtige Quelle mit diesen beiden Binärdateien. Wenn die resultierenden Binärdateien identisch sind (mit Ausnahme einer Reihe von Dingen, die durchaus legitimerweise variieren können, z. B. verschiedene Zeitstempel), hat der verdächtige Compiler das Vertrauen tatsächlich nicht missbraucht.
quelle
Als spezifischer Angriff ist er so bedrohlich wie nie zuvor, was so gut wie überhaupt keine Bedrohung darstellt.
Ich bin mir nicht sicher, was du damit meinst. Ist ein JITter dagegen immun? Ist es anfälliger? Nicht wirklich. Als Entwickler ist IHRE App anfälliger, weil Sie nicht bestätigen können, dass sie nicht ausgeführt wurde. Beachten Sie, dass Ihre noch nicht entwickelte App grundsätzlich gegen diese und alle praktischen Variationen immun ist. Sie müssen sich nur um einen Compiler kümmern, der neuer ist als Ihr Code.
Das ist nicht wirklich relevant.
Es gibt keine wirkliche Sicherheit der Kompilierung und kann es auch nicht sein. Das war wirklich der Punkt seines Gesprächs, dass man irgendwann jemandem vertrauen muss.
Ja. Grundsätzlich müssen Ihre Anweisungen irgendwann in etwas verwandelt werden, das der Computer ausführt, und diese Übersetzung kann falsch ausgeführt werden.
quelle
David Wheeler hat einen guten Artikel: http://www.dwheeler.com/trusting-trust/
Ich mache mir mehr Sorgen um Hardware-Angriffe. Ich denke, wir brauchen eine VLSI-Design-Toolchain mit FLOSS-Quellcode, die wir selbst modifizieren und kompilieren können, damit wir einen Mikroprozessor bauen können, bei dem die Tools keine Hintertüren einfügen. Die Werkzeuge sollten uns auch den Zweck eines Transistors auf dem Chip erläutern. Dann könnten wir eine Probe der fertigen Chips öffnen und sie mit einem Mikroskop untersuchen, um sicherzustellen, dass sie die gleichen Schaltkreise haben, die die Werkzeuge angeblich haben sollten.
quelle
Auf Systemen, auf denen die Endbenutzer Zugriff auf den Quellcode haben, müssten Sie diese Art von Angriff verbergen. Das wären Open-Source-Systeme in der heutigen Welt. Das Problem ist, dass, obwohl für alle Linux-Systeme die Abhängigkeit von einem einzigen Compiler besteht, der Angriff für alle wichtigen Linux-Distributionen auf die Build-Server gelangen muss. Da diese die Compiler-Binärdateien nicht direkt für jede Compiler-Version herunterladen, musste sich die Quelle für den Angriff in mindestens einer früheren Version des Compilers auf ihren Build-Servern befinden. Entweder diese oder die allererste Version des Compilers, die sie als Binärdatei heruntergeladen haben, müsste kompromittiert worden sein.
quelle
Wenn man Quellcode für ein Compiler / Build-System hat, dessen Ausgabe von nichts anderem als dem Inhalt der bereitgestellten Quelldateien abhängen sollte, und wenn man mehrere andere Compiler hat und weiß, dass sie nicht alle den gleichen Compiler-Hack enthalten, kann man das Stellen Sie sicher, dass Sie eine ausführbare Datei erhalten, die von nichts anderem als dem Quellcode abhängt.
Angenommen, man hat Quellcode für ein Compiler / Linker-Paket (z. B. die Groucho Suite), der so geschrieben ist, dass seine Ausgabe weder von einem bestimmten Verhalten noch von etwas anderem als dem Inhalt der Eingabequelldateien abhängt, und man kompiliert / Verknüpft diesen Code mit einer Vielzahl von unabhängig erstellten Compilern / Linker-Paketen (z. B. der Harpo Suite, der Chico Suite und der Zeppo Suite), sodass für jedes eine andere Gruppe von ausführbaren Elementen (G-Harpo, G-Chico und G-Zeppo). Es wäre nicht unerwartet, wenn diese ausführbaren Dateien unterschiedliche Befehlsfolgen enthalten würden, sie sollten jedoch funktional identisch sein. Der Nachweis, dass sie in allen Fällen funktionsgleich sind, wäre jedoch wahrscheinlich ein unlösbares Problem.
Glücklicherweise ist ein solcher Beweis nicht erforderlich, wenn man die resultierenden ausführbaren Dateien nur für einen einzigen Zweck verwendet: das erneute Kompilieren der Groucho-Suite. Kompiliert man die Groucho-Suite mit G-Harpo (ergibt GG-Harpo), G-Chico (GG-Chico) und G-Zeppo (GG-Zeppo), so ergeben sich alle drei Dateien, GG-Harpo, GG-Chico und GG-Zeppo sollten alle Byte für Byte identisch sein. Wenn die Dateien übereinstimmen, bedeutet dies, dass alle in ihnen vorhandenen "Compiler-Viren" identisch vorhanden sein müssen (da alle drei Dateien byteweise identisch sind, können sich ihre Verhaltensweisen in keiner Weise unterscheiden Weg).
Abhängig vom Alter und der Abstammung der anderen Compiler kann möglicherweise sichergestellt werden, dass ein solcher Virus in ihnen nicht plausibel vorhanden ist. Wenn man beispielsweise einen antiken Macintosh verwendet, um einen Compiler, der 2007 von Grund auf neu geschrieben wurde, durch eine Version von MPW zu führen, die in den 1980er Jahren geschrieben wurde, wissen die Compiler der 1980er Jahre nicht, wo sie einen Virus in den 2007er Compiler einfügen sollen. Es mag heute für einen Compiler möglich sein, eine Code-Analyse so gut wie möglich durchzuführen, um dies herauszufinden, aber der für eine solche Analyse erforderliche Rechenaufwand würde den zum einfachen Kompilieren des Codes erforderlichen Rechenaufwand bei weitem überschreiten und hätte nicht unbemerkt bleiben können in einem Markt, in dem die Kompilierungsgeschwindigkeit ein Hauptverkaufsargument war.
Ich würde davon ausgehen, dass, wenn man mit Kompilierungswerkzeugen arbeitet, bei denen die Bytes in einer ausführbaren Datei, die erzeugt werden sollen, in keiner Weise von etwas anderem als dem Inhalt der eingereichten Quelldateien abhängen sollten, es möglich ist, eine einigermaßen gute Immunität von einem Thompson zu erreichen -Stil-Virus. Leider scheint aus irgendeinem Grund der Nichtdeterminismus bei der Zusammenstellung in einigen Umgebungen als normal zu gelten. Ich erkenne, dass es auf einem Multi-CPU-System möglich sein kann, dass ein Compiler schneller läuft, wenn bestimmte Aspekte der Codegenerierung variieren, je nachdem, welcher der beiden Threads zuerst eine Arbeit beendet.
Andererseits bin ich mir nicht sicher, warum Compiler / Linker keinen "kanonischen Ausgabemodus" bieten sollten, bei dem die Ausgabe nur von den Quelldateien und einem "Kompilierungsdatum" abhängt, das vom Benutzer möglicherweise überschrieben wird . Selbst wenn das Kompilieren von Code in einem solchen Modus doppelt so lange gedauert hätte wie das normale Kompilieren, würde ich vorschlagen, dass es einen erheblichen Wert hätte, in der Lage zu sein, jeden "Release-Build" Byte für Byte vollständig aus Quellmaterialien wiederherzustellen, selbst wenn dies dies bedeutete Release-Builds würden länger dauern als "normale Builds".
quelle