Ich habe angefangen, Notizen zu den von mir angesehenen Sicherheitsvideos aufzuschreiben (um sich schnell zu erinnern).
Diese könnten für Anfänger nützlicher sein.
Die Reihenfolge der Notizen hier ist nicht in der Reihenfolge der Schwierigkeit, sondern in umgekehrter chronologischer Reihenfolge, wie ich sie schreibe (dh zuerst zuerst).
Diese Arbeit ist im Rahmen einer Creative Commons Attribution-Noncommercial-Sharealike 4.0 International Lizenz lizenziert.
Geschrieben am 12. August 2017
Beeinflusst von Gynvaels Vertrauen CTF 2017 Livestreams hier und hier; und durch seine Google CTF Quals 2017 Livestream hier
Manchmal kann eine Herausforderung eine komplizierte Aufgabe durchführen, indem ein VM implementiert wird. Es ist nicht immer notwendig, die VM vollständig umzukehren und die Herausforderung zu lösen. Manchmal können Sie ein bisschen wieder ein wenig reagieren, und wenn Sie wissen, was los ist, können Sie sich in die VM anschließen und Zugriff auf Dinge erhalten, die Sie benötigen. Darüber hinaus werden in VMs zeitbasierte Nebenkanalangriffe einfacher (hauptsächlich aufgrund mehr "realer" Anweisungen, die ausgeführt werden.
Kryptografisch interessante Funktionen in Binärdateien können anerkannt und schnell nach den Konstanten gesucht und online gesucht werden. Bei Standard -Kryptofunktionen reichen diese Konstanten aus, um bei einer Funktion schnell zu erraten. Einfachere Kryptofunktionen können noch leichter erkannt werden. Wenn Sie viele XORS und solche Sachen sehen und keine leicht zu identifizierbaren Konstanten, ist es wahrscheinlich handgewandte Krypto (und möglicherweise auch gebrochen).
Manchmal ist bei der Verwendung von IDA mit Hexrays die Demontage -Sicht möglicherweise besser als die Dekompilierungsansicht. Dies gilt insbesondere dann, wenn Sie feststellen, dass in der Dekompilierungsansicht viele Komplikationen vorhanden zu sein scheint, aber Sie bemerken, dass sich wiederholte Muster in der Demontage -Sichtweise. (Sie können die zwei mit der Space Bar schnell b/w wechseln.) Wenn beispielsweise eine (feste) (feste) Big-Ineger-Bibliothek implementiert ist, ist die Dekompilierungsansicht schrecklich, aber die Demontage-Sicht ist leicht zu verstehen (und aufgrund der sich wiederholenden "mit wellenkorrigierten" Anweisungen wie adc leicht zu erkennen). Bei der Analyse wie diese ist die Verwendung der Funktion "Gruppenknoten" in der Graphansicht von IDA äußerst nützlich, um die Komplexität Ihres Diagramms schnell zu verringern, wie Sie verstehen, was jeder Knoten tut.
Für seltsame Architekturen ist es äußerst nützlich, einen guten Emulator zu haben. Insbesondere ein Emulator, mit dem Sie eine Mülldeponie des Speichers geben können, kann verwendet werden, um schnell herauszufinden, was vor sich geht, und interessante Teile zu erkennen, sobald Sie das Gedächtnis aus dem Emulator heraus haben. Die Verwendung eines Emulators, der in einer komfortablen Sprache (wie Python) implementiert ist, bedeutet außerdem, dass Sie Dinge genau so ausführen können, wie Sie möchten. Wenn es beispielsweise einen interessanten Teil des Codes gibt, den Sie möglicherweise mehrmals ausführen möchten (z. B. zu brutaler Kraft oder etwas), können Sie mit dem Emulator schnell etwas codieren, das nur diesen Teil des Codes tut, anstatt das vollständige Programm ausführen zu müssen.
Faul zu sein ist gut, wenn es wieder aufkommt. Verschwenden Sie keine Zeit damit, alles umzugehen, sondern verbringen Sie genügend Zeit mit Recon (auch in einer Re -Herausforderung!), Um die Zeit zu verkürzen, die für die tatsächliche Aufgabe der Wiederholung tatsächlich aufgewendet wird. In einer solchen Situation bedeutet Recon, nur schnelle Betrachtung verschiedener Funktionen zu betrachten, ohne zu viel Zeit damit zu verbringen, jede Funktion gründlich zu analysieren. Sie beurteilen nur schnell, worum es in der Funktion geht (zum Beispiel "sieht" wie ein Krypto -Ding aus "oder" sieht wie eine Speicherverwaltungssache usw. aus))
Für unbekannte Hardware oder Architektur verbringen Sie genügend Zeit mit Google. Sie haben möglicherweise Glück mit einer Reihe nützlicher Tools oder Dokumente, mit denen Sie Tools schneller erstellen können. Oft finden Sie implementierungen für Spielzeugemulator usw., die als kurzer Punkt nützlich sein können. Alternativ können Sie einige interessante Informationen (z. B. die gespeicherte Bitmaps oder die gespeicherten Zeichenfolgen oder so etwas) erhalten, mit denen Sie ein schnelles "Fix" -Skript schreiben können, und dann normale Tools verwenden, um zu sehen, ob interessante Dinge da sind.
GIMP (das Image Manipulation Tool) hat eine sehr coole Funktionsfunktion, um Rohpixeldaten anzuzeigen. Sie können dies verwenden, um schnell nach Vermögenswerten oder sich wiederholenden Strukturen in Rohbinärdaten zu suchen. Verbringen Sie Zeit damit, sich mit den Einstellungen herumzuspielen, um festzustellen, ob weitere Informationen daraus gewonnen werden können.
Geschrieben am 2. Juli 2017
Beeinflusst durch eine Diskussion mit @p4n74 und @h3rcul35 im Infoseciitr #Bin -Chat. Wir haben darüber diskutiert, wie manchmal Anfänger Schwierigkeiten haben, mit einer größeren Herausforderung binär zu beginnen, insbesondere wenn es gestrippt ist.
Um entweder die RE -Herausforderung zu lösen oder in der Lage zu sein, sie zu verhindern, muss man zunächst die angegebene Binärdehnung analysieren, um sie effektiv auszunutzen. Da die Binärdatei möglicherweise entzogen werden kann usw. (mit file gefunden) muss man wissen, wo man mit der Analyse beginnen soll, um sich auszubauen.
Es gibt einige Analysestile, wenn Sie nach Schwachstellen in Binärdateien suchen (und nach dem, was ich gesammelt habe, haben verschiedene CTF -Teams unterschiedliche Vorlieben):
1.1. Umzug vollständiger Code auf C.
Diese Art der Analyse ist eine Art selten, aber für kleinere Binärdateien sehr nützlich. Die Idee ist, in einem Reverse -Ingenieur im gesamten Code zu gehen. Jede Funktion wird in IDA (unter Verwendung der Dekompileransicht) geöffnet und umbenannt (Verknüpfung: n) und Retyping (Verknüpfung: y) werden verwendet, um den dekompilierten Code schnell lesbarer zu gestalten. Anschließend wird der gesamte Code in eine separate .c -Datei kopiert/exportiert, die zusammengestellt werden kann, um ein äquivalentes (aber nicht gleiches) Binärdatum zum Original zu erhalten. Anschließend kann die Analyse der Quellcode -Ebene durchgeführt werden, um Vulns usw. zu finden usw. Sobald der Anfälligkeitspunkt gefunden wurde, ist der Exploit auf der ursprünglichen Binärdatei aufgebaut, indem sie in der schön dekompilierten Quelle in IDA verfolgt werden, Seite an Seite an Seite mit der Demontageansicht (verwenden Sie die Registerkarte schnell zwischen den beiden.
1.2. Minimale Analyse der Dekompilierung
Dies geschieht ziemlich oft, da der größte Teil des Binärs relativ nutzlos ist (aus Sicht des Angreifers). Sie müssen nur die misstrauischen Funktionen analysieren oder Sie möglicherweise zum Vuln führen. Dazu gibt es einige Ansätze, um zu beginnen:
1.2.1. Starten Sie von Main
Normalerweise wird für ein gestreiftes binäres, selbst Main nicht beschriftet (IDA 6.9 markiert es jedoch für Sie), aber im Laufe der Zeit lernen Sie zu erkennen, wie man die Haupteingangspunkte vom Einstiegspunkt erreicht (wo sich die IDA standardmäßig mit standardmäßig öffnet). Sie springen dazu und beginnen von dort aus zu analysieren.
1.2.2. Finden Sie relevante Zeichenfolgen
Manchmal kennen Sie einige spezifische Zeichenfolgen, die möglicherweise ausgegeben werden usw., von denen Sie wissen, dass sie nützlich sein könnten (z. B. "Herzlichen Glückwunsch, Ihre Flagge ist %s" für eine RE -Herausforderung). Sie können zur Saitenansicht (Verknüpfung: Shift+F12) springen, die Zeichenfolge finden und mit XREFS (Verknüpfung: x) rückwärts arbeiten. Mit den XREFs können Sie den Funktionspfad zu dieser Zeichenfolge finden, indem Sie XREFs für alle Funktionen in dieser Kette verwenden, bis Sie das Haupt (oder einen Punkt, den Sie kennen).
1.2.3. Aus einer zufälligen Funktion
Manchmal ist keine bestimmte Zeichenfolge nützlich, und Sie möchten nicht von Main beginnen. Stattdessen durchblättern Sie schnell die gesamte Funktionsliste und suchen nach Funktionen, die misstrauisch aussehen (z. B. viele Konstanten oder viele XORS usw.) oder als wichtige Funktionen (XREFs von Malloc, kostenlos, kostenlos usw.).
1.3. Reine Demontageanalyse
Manchmal können Sie die Dekompilierungsansicht nicht verwenden (aufgrund der seltsamen Architektur oder der Anti-Dekompilierungstechniken oder der handgeschriebenen Versammlung oder der Dekompilation, die zu unnötig komplex aussieht). In diesem Fall ist es perfekt gültig, nur nach der Demontage zu suchen. Es ist äußerst nützlich (für neue Architekturen), automatische Kommentare einzuschalten, was einen Kommentar zeigt, in dem jede Anweisung erläutert wird. Darüber hinaus sind die Funktionen der Knotenfolorisierung und der Gruppenknoten immens hilfreich. Auch wenn Sie keine davon verwenden, hilft die regelmäßige Markierung von Kommentaren in der Demontage sehr. Wenn ich dies persönlich mache, schreibe ich lieber pythonartige Kommentare, damit ich schnell in Python eintrieben kann (besonders nützlich für RE-Herausforderungen, wo Sie möglicherweise Z3 usw. verwenden müssen).
1.4. Verwenden von Plattformen wie BAP usw.
Diese Art der Analyse ist (semi-) automatisiert und ist normalerweise für viel größere Software nützlicher und wird in CTFs selten direkt verwendet.
Fuzzing kann eine wirksame Technik sein, um schnell in die Vuln zu gelangen, ohne sie anfangs tatsächlich verstehen zu müssen. Durch die Verwendung eines Fuzzers kann man eine Menge niedrig hängender Fruchtstil von Vulns erhalten, die dann analysiert und tridiert werden müssen, um zum tatsächlichen Vuln zu gelangen. Weitere Informationen finden Sie in meinen Notizen zu den Grundlagen des Fuzzing und des genetischen Fuzzings.
Eine dynamische Analyse kann verwendet werden, nachdem eine Vuln unter Verwendung einer statischen Analyse gefunden wurde, um schnell Exploits aufzubauen. Alternativ kann es verwendet werden, um die Vuln selbst zu finden. Normalerweise startet man die ausführbare Datei in einem Debugger und versucht, Codepfade zu erreichen, die den Fehler auslösen. Durch Platzieren von Haltepunkten an den richtigen Stellen und der Analyse des Zustands der Register/Heap/Stack/usw. kann man eine gute Vorstellung davon bekommen, was vor sich geht. Man kann auch Debugger verwenden, um schnell interessante Funktionen zu identifizieren. Dies kann beispielsweise durchgeführt werden, indem vorübergehende Haltepunkte auf allen Funktionen anfänglich festgelegt werden. Dann machen Sie 2 Spaziergänge durch alle uninteressanten Codepfade; und einer durch nur einen einzigen interessanten Weg. Der erste Walk trifft alle uninteressanten Funktionen aus und deaktiviert diese Haltepunkte, wodurch die interessanten während des zweiten Spaziergangs als Haltepunkte auftauchen.
Mein persönlicher Stil für die Analyse ist es, mit der statischen Analyse zu beginnen, normalerweise von den Hauptanalysen (oder für nicht konsole basierte Anwendungen, von Saiten), und darauf hinzuarbeiten, schnell eine Funktion zu finden, die seltsam aussieht. Ich verbringe dann Zeit und verzweigte mich von hier aus vorwärts und rückwärts, schreibe regelmäßig Kommentare auf und mache kontinuierlich umbenannt und Variablen umbenannt, um die Dekompilierung zu verbessern. Wie andere verwende ich Namen wie Apple, Banane, Karotten usw. für scheinbar nützlich, aber bis hin zu noch unbekannten Funktionen/Variablen/usw. ist es für mich zu schwierig, die Namensstil von Func_123456 zu verfolgen). Ich verwende auch regelmäßig die Strukturenansicht in IDA, um Strukturen (und Enums) zu definieren, um die Dekompilierung noch schöner zu gestalten. Sobald ich den Vuln gefunden habe, wechsle ich normalerweise zum Schreiben eines Skripts mit PWNTOOLS (und benutze das, um einen gdb.attach() ). Auf diese Weise kann ich viel Kontrolle über das bekommen, was los ist. Im GDB verwende ich normalerweise einfaches GDB, obwohl ich einen peda hinzugefügt habe, der bei Bedarf Peda sofort lädt.
Mein Stil entwickelt sich jedoch definitiv weiter, da ich mich mit meinen Werkzeugen und mit benutzerdefinierten Tools, die ich geschrieben habe, um die Dinge zu beschleunigen, besser bequem gemacht habe. Ich würde mich freuen, von anderen Analysestilen sowie möglichen Änderungen meines Stils zu hören, die mir helfen könnten, schneller zu werden. Für alle Kommentare/Kritikpunkte/Lob, die Sie wie immer auf Twitter @jay_f0xtr0t haben, kann ich erreicht werden.
Geschrieben am 4. Juni 2017
Beeinflusst von diesem fantastischen Live -Stream von Gynvael Coldwind, wo er die Grundlagen von ROP diskutiert und ein paar Tipps und Tricks gibt
Return Oriented Programing (ROP) ist eine der klassischen Ausbeutungstechniken, mit denen der NX -Schutz (nicht ausführbarer Speicher) umgehen wird. Microsoft hat NX als DEP (Datenausführungsprävention) aufgenommen. Sogar Linux usw., haben Sie es effektiv, was bedeutet, dass Sie bei diesem Schutz Shellcode nicht mehr auf Heap/Stapel platzieren und es ausführen können, indem Sie darauf springen. Um Code auszuführen, steigen Sie nun in den bereits bestehenden Code (Hauptbinär- oder seine Bibliotheken-LIBC, LDD usw. unter Linux; Kernel32, Ntdll usw. unter Windows). ROP entsteht, indem Fragmente dieses Code wiederverwendet werden, der bereits da ist, und einen Weg herauszufinden, um diese Fragmente zu kombinieren, um das zu tun, was Sie tun möchten (was natürlich den Planeten hackt !!!).
Ursprünglich begann ROP mit RET2LIBC und wurde im Laufe der Zeit mit viel mehr kleinen Codestücken fortgeschritten. Einige mögen sagen, dass ROP jetzt "tot" ist, da zusätzliche Schutzmaßnahmen es mindern, aber es kann immer noch in vielen Szenarien ausgenutzt werden (und für viele CTFs definitiv notwendig).
Der wichtigste Teil von ROP sind die Geräte. Geräte sind "nutzbare Code -Teile für ROP". Das bedeutet normalerweise Codestücke, die mit einem ret enden (aber andere Arten von Geräten könnten auch nützlich sein; wie diejenigen, die mit pop eax; jmp eax usw. enden). Wir ketten diese Geräte zusammen, um den Exploit zu bilden, der als ROP -Kette bekannt ist.
Eine der wichtigsten Annahmen von ROP ist, dass Sie die Kontrolle über den Stapel haben (dh der Stapelzeiger zeigt auf einen Puffer, den Sie kontrollieren). Wenn dies nicht der Fall ist, müssen Sie andere Tricks (z. B. Stack Pivoting) anwenden, um diese Kontrolle zu erhalten, bevor Sie eine ROP -Kette bauen.
Wie extrahieren Sie Geräte? Verwenden Sie herunterladbare Tools (z. B. Ropgadget) oder Online -Tool (z. B. ROPSHELL) oder schreiben Sie Ihre eigenen Tools (können Sie manchmal für schwierigere Herausforderungen nützlicher sein, da Sie sie bei Bedarf an die spezifische Herausforderung optimieren können). Grundsätzlich brauchen wir nur die Adressen, auf die wir für diese Geräte springen können. Hier kann ein Problem mit ASLR usw. vorhanden sein (in diesem Fall erhalten Sie ein Leck der Adresse, bevor Sie zu ROP tatsächlich übergehen).
Wie verwenden wir diese Geräte jetzt, um eine Seilkette zu machen? Wir suchen zuerst nach "Basic Gadgets". Dies sind Geräte, die einfache Aufgaben für uns erledigen können (z. B. pop ecx; ret , mit deren Wert ein Wert in ECX geladen werden kann, indem das Gerät platziert wird, gefolgt vom zu geladenen Wert, gefolgt von der REST der Kette, die nach dem Wert des Wertes zurückgegeben wird). Die nützlichsten grundlegenden Geräte sind in der Regel "ein Register festgelegt", "Register -Wert an der Adresse, auf die durch Register hingewiesen wird" usw.
Wir können uns aus diesen primitiven Funktionen aufbauen, um eine Funktionalität auf höherer Ebene zu gewinnen (ähnlich wie bei meinem Beitrag mit dem Titel "Ausnutzungsabstraktion). Beispielsweise können wir mit den SET-Registrier- und Store-Wert-AT-Address-Geräten eine "Poke" -Funktion erstellen, mit der wir eine bestimmte Adresse mit einem bestimmten Wert festlegen können. Mit dieser Weise können wir eine "Poke-String" -Funktion erstellen, mit der wir eine bestimmte Zeichenfolge an einem bestimmten Ort im Speicher speichern können. Jetzt, da wir Poke-String haben, sind wir im Grunde genommen fast fertig, da wir alle Strukturen erstellen können, die wir im Speicher wollen, und auch alle Funktionen aufrufen können, die wir mit den gewünschten Parametern wollen (da wir Set-Registrieren und Werte auf Stack platzieren können).
Einer der wichtigsten Gründe, um von diesen Primitiven der niedrigeren Ordnung zu größeren Funktionen zu bauen, die komplexere Dinge tun, besteht darin, die Wahrscheinlichkeit zu verringern, Fehler zu machen (was sonst in ROP üblich ist).
Es gibt komplexere Ideen, Techniken und Tipps für ROP, aber das ist möglicherweise ein Thema für eine separate Notiz für eine andere Zeit :)
PS: GYN hat einen Blogpost zur returnorientierten Ausbeutung, die eine Lektüre wert sein könnte.
Geschrieben am 27. Mai 2017; Erweitert am 29. Mai 2017
Beeinflusst von diesem erstaunlichen Live -Stream von Gynvael Coldwind, wo er über die grundlegende Theorie hinter genetischem Fuzzing spricht und beginnt, einen grundlegenden genetischen Fuzzeller aufzubauen. Anschließend vervollständigt er die Implementierung in diesem Live -Stream.
"Advanced" Fuzzing (im Vergleich zu einem blinden Fuzzar, beschrieben in meiner "Grundlagen des Fuzzing"). Es modifiziert/mutiert auch Bytes usw., aber es macht es ein bisschen schlauer als der blinde "dumme" Fuzzer.
Warum brauchen wir einen genetischen Fuzzeller?
Einige Programme könnten für dumme Fuzzer "böse" sein, da es möglich ist, dass eine Anfälligkeit eine ganze Reihe von Bedingungen erfordern, um es zu erfüllen, um erreicht zu werden. In einem dummen Fuder haben wir eine sehr geringe Wahrscheinlichkeit, dass dies keine Ahnung hat, ob es Fortschritte macht oder nicht. Als spezifisches Beispiel, wenn wir den Code haben if a: if b: if c: if d: crash! (Nennen wir es den Crasher -Code), und in diesem Fall benötigen wir 4 Bedingungen, um das Programm zu stürzen. Ein dummer Fuzzer kann jedoch möglicherweise nicht in der Lage sein, den a -Zustand zu überwinden, nur weil es eine sehr geringe Chance gibt, dass alle 4 Mutationen a , b , c , d gleichzeitig auftreten. Auch wenn es nur a durchläuft, könnte die nächste Mutation zurückkehren !a Nur weil es nichts über das Programm weiß.
Warten Sie, wann zeigt sich diese Art von "schlechten Fall" -Programm?
Es ist in Dateiformat -Parser weit verbreitet, ein Beispiel zu nehmen. Um einige bestimmte Codepfade zu erreichen, muss man möglicherweise mehrere Überprüfungen überschreiten. "Dieser Wert muss dies sein, und dieser Wert muss das sein, und ein anderer Wert muss etwas von etwas anderem sein" und so weiter. Darüber hinaus ist fast keine reale Software "unkompliziert", und die meisten Software haben viele, viele mögliche Codepfade, von denen einige nur dann zugegriffen werden können, nachdem viele Dinge im Bundesstaat korrekt eingerichtet sind. Dadurch sind viele der Codepfade dieser Programme im Grunde genommen für dumme Fuzzer unzugänglich. Darüber hinaus sind einige Wege manchmal möglicherweise völlig unzugänglich (und nicht nur verrückt unwahrscheinlich), da nicht genügend Mutationen überhaupt getan werden. Wenn einer dieser Wege Fehler hat, könnte ein dummer Fuzzeller sie niemals finden.
Wie machen wir es besser als dumme Fuzzer?
Betrachten Sie den Steuerflussdiagramm (CFG) des oben genannten Crasher -Code. Wenn zufällig ein dummer Fuzzer plötzlich a korrekten hätte, dann würde es auch nicht erkennen, dass er einen neuen Knoten erreichte, aber dies würde weiterhin ignorieren und die Probe wegwerfen. Auf der anderen Seite erkennen sie dies als neue Informationen ("ein neu erreichter Pfad") und speichern diese Probe als einen neuen Anfangspunkt in den Korpus. Dies bedeutet, dass der Fuzzer jetzt aus dem a aus starten und sich weiter bewegen kann. Manchmal kann es manchmal zum !a von der a -Probe zurückkehren, aber die meiste Zeit wird es nicht und stattdessen in der Lage sein, b -Block zu erreichen. Dies ist wiederum ein neuer Knoten erreicht, also fügt das Korpus eine neue Probe hinzu. Dies setzt sich fort und ermöglicht es, immer mehr mögliche Wege zu überprüfen und schließlich den crash! .
Warum funktioniert das?
Durch das Hinzufügen mutierter Proben in den Korpus, in dem die Grafik mehr untersucht wird (dh zuvor nicht erforschte Teile), können wir bisher unerreichbare Bereiche erreichen und somit solche Bereiche verflcken. Da wir solche Bereiche verblassen können, können wir in diesen Regionen Fehler aufdecken.
Warum heißt es genetischer Fuzzing?
Diese Art von "intelligenter" Fuzzing ist wie genetische Algorithmen. Mutation und Überkreuzung von Proben verursacht neue Exemplare. Wir halten Exemplare, die besser zu den getesteten Bedingungen geeignet sind. In diesem Fall lautet die Bedingung "Wie viele Knoten im Diagramm haben sie erreicht?". Diejenigen, die mehr durchqueren, können aufbewahrt werden. Dies ist nicht genau wie genetische Algen, sondern eine Variation (da wir alle Exemplare halten, die das unerforschte Gebiet durchqueren und keine Crossover machen), ist aber ausreichend ähnlich, um denselben Namen zu erhalten. Grundsätzlich die Auswahl der bereits bestehenden Bevölkerung, gefolgt von Mutation, gefolgt von Fitnesstests (ob neue Bereiche) und wiederholt.
Warten Sie, also verfolgen wir einfach ungerückte Knoten?
Nein, nicht wirklich. AFL verfolgt eher die Kantenquellen im Diagramm als die Knoten. Darüber hinaus heißt es nicht nur "Edge reiste oder nicht", sondern verfolgt, wie oft eine Kante durchquert wurde. Wenn eine Kante 0, 1, 2, 4, 8, 16, ... Zeiten durchquert wird, wird er als "neuer Pfad" betrachtet und führt zu einer Ergänzung in den Korpus. Dies geschieht, da die Betrachtung von Kanten anstelle von Knoten eine bessere Möglichkeit ist, zwischen Anwendungszuständen zu unterscheiden, und die Verwendung einer exponentiell zunehmenden Anzahl der Kantenquellen mehr Informationen zu unterscheiden (ein Rand, der einmal durchquert wird, unterscheidet sich zweimal von durchquerten, aber durchquerte 10 unterscheidet sich nicht zu 11 Mal nicht zu 11 Mal).
Also, was und alles brauchst du in einem genetischen Fuzzeller?
Wir brauchen 2 Dinge, der erste Teil wird als Tracer (oder Tracing Instrumentation) bezeichnet. Grundsätzlich zeigt es Ihnen, welche Anweisungen in der Anwendung ausgeführt wurden. AFL macht dies auf einfache Weise, indem er zwischen die Kompilierungsphasen springt. Nach der Erzeugung der Montage, aber vor der Zusammenstellung des Programms sucht es nach grundlegenden Blöcken (durch die Suche nach Endungen, indem Sie nach Anweisungen nach Sprung/Zweig suchen) und fügt jedem Block Code hinzu, der den Block/die Kante wie ausgeführt markiert (wahrscheinlich in einen Schattenspeicher oder etwas). Wenn wir keinen Quellcode haben, können wir andere Techniken zur Verfolgung (wie PIN, Debugger usw.) verwenden. Es stellt sich heraus, dass sogar Asan Abdeckungsinformationen geben kann (siehe Dokumente dafür).
Für den zweiten Teil verwenden wir dann die vom Tracer angegebenen Abdeckungsinformationen, um neue Wege zu verfolgen, wie sie erscheinen, und fügen die generierten Stichproben in den Korpus für die zufällige Selektion in Zukunft hinzu.
Es gibt mehrere Mechanismen, um den Tracer zu machen. Sie können auf Softwarebasis oder Hardware basieren. Für Hardware -basierte Basis gibt es beispielsweise einige Intel -CPU -Funktionen, in denen ein Puffer im Speicher angegeben ist und Informationen aller grundlegenden Blöcke in diesen Puffer aufgezeichnet werden. Es ist ein Kernel -Funktion, daher muss der Kernel ihn unterstützen und als API bereitstellen (was Linux tut). Für Softwarebasis können wir dies tun, indem wir Code hinzufügen oder einen Debugger unter Verwendung von vorübergehenden Haltepunkten oder durch einzelnes Treten verwenden oder die Nachverfolgung von Adresseingängern verwenden oder Haken oder Emulatoren oder eine ganze Reihe anderer Möglichkeiten verwenden.
Eine andere Möglichkeit, die Mechanismen zu differenzieren, besteht darin, entweder durch Black-Box-Verfolgung (bei der Sie nur die nicht modifizierte Binärdatum verwenden können) oder der White-Box-Verfolgung von Software (wo Sie Zugriff auf den Quellcode haben, und den Code selbst zu ändern, um den Verfolgungscode hinzuzufügen).
AFL verwendet Software -Instrumente während der Kompilierung als Methode zur Verfolgung (oder durch QEMU -Emulation). Honggfuzz unterstützt sowohl Software- als auch Hardware -basierte Tracing -Methoden. Andere intelligente Fuzzers sind möglicherweise anders. Der, den Gyn hat, verwendet die Verfolgung/Abdeckung, die von Adresseinrichtung (ASAN) bereitgestellt wird.
Einige Fuzzer verwenden "Speedhacks" (dh Erhöhung der Fuzzing -Geschwindigkeit), beispielsweise durch Erstellen eines Forkservers oder anderer solcher Ideen. Könnte es wert sein, diese irgendwann in diese zu untersuchen :)
Geschrieben am 20. April 2017
Beeinflusst von diesem fantastischen Live -Stream von Gynvael Coldwind, wo er darüber spricht, worum es bei Fuzzing geht, und baut auch einen einfachen Fuzzeller von Grund auf neu!
Was ist in erster Linie ein Fuzzer? Und warum benutzen wir es?
Bedenken Sie, dass wir eine Bibliothek/ein Programm haben, das Eingabedaten benötigt. Der Eingang kann in irgendeiner Weise strukturiert sein (sagen wir ein PDF oder PNG oder XML usw., aber es muss kein "Standard" -Format sein). Aus Sicherheitsgründen ist es interessant, wenn zwischen der Eingabe und dem Prozess / der Bibliothek / des Programms eine Sicherheitsgrenze besteht, und wir können einige "spezielle Eingaben" übergeben, die ein unbeabsichtigtes Verhalten über diese Grenze hinaus verursachen. Ein Fuder ist eine solche Möglichkeit, dies zu tun. Dies geschieht durch "mutierende" Dinge in der Eingabe (dadurch möglicherweise zu verfälschen), um entweder zu einer normalen Ausführung (einschließlich sicher behandelter Fehler) oder einem Absturz zu führen. Dies kann geschehen, da die Logik der Edge -Fall nicht gut behandelt wird.
Das Absturz ist der einfachste Weg für Fehlerbedingungen. Es könnte auch andere geben. Beispielsweise kann die Verwendung von Asan (Adresse des Desinfektionsmittels) usw. dazu führen, dass auch mehr Dinge erfasst werden, was möglicherweise Sicherheitsprobleme sein können. Zum Beispiel kann ein einzelner Byte -Überlauf eines Puffers möglicherweise keinen Absturz alleine verursachen, aber durch Verwendung von Asan können wir dies möglicherweise sogar mit einem Fuzzeller fangen.
Eine weitere mögliche Verwendung für einen Fuder ist, dass Eingänge, die durch Fuzzing eines Programms generiert werden, möglicherweise auch in einer anderen Bibliothek/einem anderen Programm verwendet werden können, um zu prüfen, ob Unterschiede vorhanden sind. Zum Beispiel wurden einige Fehler mit hoher Präzisionsmathematikbibliothek wie diese festgestellt. Dies führt jedoch normalerweise nicht zu Sicherheitsproblemen, daher werden wir uns nicht auf so viel konzentrieren.
Wie funktioniert ein Fuzzer?
Ein Fuder ist im Grunde eine Mutate-Execute-Repeat-Schleife, die den Zustandsraum der Anwendung untersucht, um zu versuchen, Zustände eines Absturz- / Sicherheit "zu finden". Es findet keinen Exploit, nur einen Vuln. Der Hauptteil des Fuzzers ist der Mutator selbst. Mehr dazu später.
Ausgänge aus einem Fuzzar?
Im Fuzzer wird ein Debugger (manchmal) der Anwendung angehängt, um einen Bericht aus dem Absturz zu erhalten, um sie später als Sicherheit Vuln gegen einen gutartigen (aber möglicherweise wichtigen) Absturz zu analysieren.
Wie können Sie feststellen, welche Programmbereiche zuerst am besten sind?
Beim Fuzzing möchten wir uns normalerweise auf ein einzelnes Stück oder ein kleines Stück Programm konzentrieren. Dies geschieht normalerweise hauptsächlich, um die Ausführung zu verringern. Normalerweise konzentrieren wir uns nur auf das Parsen und die Verarbeitung. Auch hier ist die Sicherheitsgrenze sehr wichtig, welche Teile uns wichtig sind.
Arten von Fuzzern?
Eingabemodier, die dem Fuzzer gegeben wurden, werden als Korpus bezeichnet. In Oldschool -Fuzzern (auch bekannt als "blind"/"dumme" Fuzzzers) bestand eine Notwendigkeit für einen großen Korpus. Neuere (auch bekannt als "genetische" Fuzzer, zum Beispiel AFL) brauchen nicht unbedingt ein so großes Korpus, da sie den Staat alleine untersuchen.
Wie sind Fuzzer nützlich?
Fuzzer sind hauptsächlich nützlich für "niedrig hängende Früchte". Es findet keine komplizierten logischen Fehler, aber es kann leicht zu findende Fehler finden (die während der manuellen Analyse manchmal leicht zu übersehen sind). Während ich in dieser Notiz eingibt und normalerweise auf eine Eingabedatei bezieht, muss dies nicht genau das sein. Fuzzers können Eingänge verarbeiten, die möglicherweise STDIN- oder Eingabedatei oder Netzwerkbuchse oder viele andere sein. Ohne allzu viel Verlust der Allgemeinheit können wir uns vorerst als nur eine Datei betrachten.
Wie schreibe ich einen (grundlegenden) Fuzzer?
Auch hier muss es nur eine Mutate-Run-Repeat-Schleife sein. Wir müssen in der Lage sein, das Ziel häufig aufzurufen ( subprocess.Popen ). Wir müssen auch in der Lage sein, Eingaben in das Programm zu übergeben (z. B.: Dateien) und Abstürze erkennen ( SIGSEGV usw. verursachen Ausnahmen, die gefangen werden können). Jetzt müssen wir nur noch einen Mutator für die Eingabedatei schreiben und das Ziel in den mutierten Dateien weiter aufrufen.
Mutatoren? Was?!?
Es kann mehrere mögliche Mutatoren geben. Einfache (dh einfache Implementierung) könnten darin bestehen, Bits zu mutieren, Bytes zu mutieren oder zu "magischen" Werten zu mutieren. Um die Wahrscheinlichkeit eines Absturzes zu erhöhen, können wir mehrere (vielleicht ein parametrisierter Prozentsatz von ihnen?), Anstatt nur 1 Bit oder etwas zu ändern?). Wir können auch (anstelle von zufälligen Mutationen) Bytes/Wörter/DWords/etc in einigen "magischen" Werten ändern. Die magischen Werte könnten 0 , 0xff , 0xffff , 0xffffffff , 0x80000000 (32-Bit INT_MIN ), 0x7fffffff (32-Bit INT_MAX ) usw. Grundsätzlich ausgewählt, die gemeinsam sind, um Sicherheitsprobleme zu verursachen (weil sie einige Kantenfälle auslösen könnten). Wir können intelligentere Mutatoren schreiben, wenn wir mehr Informationen über das Programm kennen (z. B. für String -basierte ganze Zahlen, können wir etwas schreiben, das eine Ganzzahl -Zeichenfolge in "65536" oder -1 usw. verändert). Mutatatoren auf klobenden Basis können Teile bewegen (im Grunde genommen neu organisieren die Eingabe). Additive/Anhänger -Mutatoren funktionieren ebenfalls (z. B. verursachen größere Eingaben in Puffer). Kürzer können auch funktionieren (zum Beispiel werden EOF manchmal nicht gut behandelt). Probieren Sie im Grunde eine ganze Reihe kreativer Wege, um Dinge zu vermeiden. Die mehr Erfahrung in Bezug auf das Programm (und die Ausbeutung im Allgemeinen), desto nützlicher sind möglicherweise möglich.
Aber was ist das für das "genetische" Fuzzing?
Das ist wahrscheinlich eine Diskussion für eine spätere Zeit. Ein paar Links zu einigen modernen (Open -Source -Fuzzern) sind jedoch AFL und Honggfuzz.
Geschrieben am 7. April 2017
Beeinflusst von einer netten Herausforderung in Picoctf 2017 (Name der Herausforderung zurückgehalten, da der Wettbewerb noch im Gange ist)
WARNUNG: Diese Notiz mag für einige Leser einfach/offensichtlich erscheinen, aber es erfordert das Aussagen, da die Schichtung mir bis vor kurzem nicht kristallklar war.
Natürlich verwenden wir beim Programmieren alle Abstraktionen, ob Klassen und Objekte, Funktionen oder Metafunktionen oder Polymorphismus oder Monaden oder Funktoren oder all diesen Jazz. Können wir jedoch während der Ausbeutung wirklich so etwas haben? Offensichtlich können wir Fehler ausnutzen, die bei der Implementierung der oben genannten Abstraktionen gemacht werden, aber hier spreche ich über etwas anderes.
In mehreren CTFs war es ein Ad-hoc-Exploit-Skript, das eine Shell fallen lässt, wenn ich zuvor einen Exploit geschrieben habe. Ich benutze die erstaunlichen Pwntools als Framework (um eine Verbindung zum Dienst und die Konvertierung von Dingen und Dynelf usw.), aber das war es auch schon. Jeder Exploit war tendenziell ein Ad-hoc-Weg, um auf das Ziel einer willkürlichen Codeausführung hinzuarbeiten. Diese aktuelle Herausforderung und nach dem Nachdenken über meine vorherige Note zur Ausbeutung des "Advanced" -Fortenzeichenfolge habe ich festgestellt, dass ich meine Exploits auf konsequente Weise durchführen und durch verschiedene Abstraktionsschichten bewegen konnte, um endlich das erforderliche Ziel zu erreichen.
Betrachten wir beispielsweise die Sicherheitsanfälligkeit als einen logischen Fehler, mit dem wir nach einem Puffer 4 Bytes in einem kleinen Bereich lesen/schreiben können. Wir möchten dies bis hin zur Erlangung der Codeausführung und schließlich der Flagge missbrauchen.
In diesem Szenario würde ich diese Abstraktion als ein primitives short-distance-write-anything betrachten. Damit können wir natürlich nicht viel tun. Trotzdem mache ich eine kleine Python -Funktion vuln(offset, val) . Da es jedoch kurz nach dem Puffer einige Daten/Meta-Daten gibt, die nützlich sein könnten, können wir dies missbrauchen, um sowohl read-anywhere zu bauen als write-anything-anywhere . Dies bedeutet, dass ich kurze Python -Funktionen schreibe, die die zuvor definierte vuln() -Funktion aufrufen. Diese Funktionen von get_mem(addr) und set_mem(addr, val) werden einfach (in diesem aktuellen Beispiel) durchgeführt, indem sie einfach die Funktion vuln() verwenden, um einen Zeiger zu überschreiben, der dann an anderer Stelle in der Binärdatei derenferenziert werden kann.
Nachdem wir diese Abstraktionen get_mem() und set_mem() haben, erstelle ich eine Anti-Aslr-Abstraktion, indem ich im Grunde 2 Adressen von GOT- get_mem() und Vergleich mit einer LIBC-Datenbank (danke @niklasb für die Datenbank) ausnimmt. Die Offsets von diesen geben mir zuverlässig eine libc_base , die es mir ermöglicht, jede Funktion im Got mit einem anderen von libc zu ersetzen.
Dies hat mir im Wesentlichen die Kontrolle über EIP gegeben (sobald ich eine dieser Funktionen genau dann "auslösen" kann, wenn ich will). Jetzt ist nur noch übrig, dass ich den Auslöser mit den richtigen Parametern aufruft. Deshalb richte ich die Parameter als separate Abstraktion ein und rufe dann trigger() auf und habe Shell -Zugriff auf das System.
TL; DR: Man kann kleine Ausbeutungsimitive bauen (die nicht zu viel Kraft haben), und indem wir sie kombinieren und eine Hierarchie stärkerer Primitiven bauen, können wir vollständige Ausführung erhalten.
Geschrieben am 6. April 2017
Beeinflusst von diesem fantastischen Live -Stream von Gynvael Coldwind, wo er über Format -String -Ausbeutung spricht
Einfache Formatzeichenfolge Exploits:
Sie können den %p verwenden, um zu sehen, was sich auf dem Stapel befindet. Wenn sich die Formatzeichenfolge selbst auf dem Stapel befindet, kann man eine Adresse (sagen wir FOO ) auf den Stapel platzieren und dann nach dem Positionsspezifizierer n$ (z. B. AAAA %7$p möglicherweise AAAA 0x41414141 zurückgeben, wenn 7 die Position auf dem Stapel ist). We can then use this to build a read-where primitive, using the %s format specifier instead (for example, AAAA %7$s would return the value at the address 0x41414141, continuing the previous example). We can also use the %n format specifier to make it into a write-what-where primitive. Usually instead, we use %hhn (a glibc extension, iirc), which lets us write one byte at a time.
We use the above primitives to initially beat ASLR (if any) and then overwrite an entry in the GOT (say exit() or fflush() or ...) to then raise it to an arbitrary-eip-control primitive, which basically gives us arbitrary-code-execution .
Possible difficulties (that make it "advanced" exploitation):
If we have partial ASLR , then we can still use format strings and beat it, but this becomes much harder if we only have one-shot exploit (ie, our exploit needs to run instantaneously, and the addresses are randomized on each run, say). The way we would beat this is to use addresses that are already in the memory, and overwrite them partially (since ASLR affects only higher order bits). This way, we can gain reliability during execution.
If we have a read only .GOT section, then the "standard" attack of overwriting the GOT will not work. In this case, we look for alternative areas that can be overwritten (preferably function pointers). Some such areas are: __malloc_hook (see man page for the same), stdin 's vtable pointer to write or flush , etc. In such a scenario, having access to the libc sources is extremely useful. As for overwriting the __malloc_hook , it works even if the application doesn't call malloc , since it is calling printf (or similar), and internally, if we pass a width specifier greater than 64k (say %70000c ), then it will call malloc, and thus whatever address was specified at the global variable __malloc_hook .
If we have our format string buffer not on the stack , then we can still gain a write-what-where primitive, though it is a little more complex. First off, we need to stop using the position specifiers n$ , since if this is used, then printf internally copies the stack (which we will be modifying as we go along). Now, we find two pointers that point ahead into the stack itself, and use those to overwrite the lower order bytes of two further ahead pointing pointers on the stack, so that they now point to x+0 and x+2 where x is some location further ahead on the stack. Using these two overwrites, we are able to completely control the 4 bytes at x , and this becomes our where in the primitive. Now we just have to ignore more positions on the format string until we come to this point, and we have a write-what-where primitive.
Written on 1st April 2017
Influenced by this amazing live stream by Gynvael Coldwind, where he explains about race conditions
If a memory region (or file or any other resource) is accessed twice with the assumption that it would remain same, but due to switching of threads, we are able to change the value, we have a race condition.
Most common kind is a TOCTTOU (Time-of-check to Time-of-use), where a variable (or file or any other resource) is first checked for some value, and if a certain condition for it passes, then it is used. In this case, we can attack it by continuously "spamming" this check in one thread, and in another thread, continuously "flipping" it so that due to randomness, we might be able to get a flip in the middle of the "window-of-opportunity" which is the (short) timeframe between the check and the use.
Usually the window-of-opportunity might be very small. We can use multiple tricks in order to increase this window of opportunity by a factor of 3x or even up to ~100x. We do this by controlling how the value is being cached, or paged. If a value (let's say a long int ) is not aligned to a cache line, then 2 cache lines might need to be accessed and this causes a delay for the same instruction to execute. Alternatively, breaking alignment on a page, (ie, placing it across a page boundary) can cause a much larger time to access. This might give us higher chance of the race condition being triggered.
Smarter ways exist to improve this race condition situation (such as clearing TLB etc, but these might not even be necessary sometimes).
Race conditions can be used, in (possibly) their extreme case, to get ring0 code execution (which is "higher than root", since it is kernel mode execution).
It is possible to find race conditions "automatically" by building tools/plugins on top of architecture emulators. For further details, http://vexillium.org/pub/005.html
Written on 31st Mar 2017
Influenced by this amazing live stream by Gynvael Coldwind, where he is experimenting on the heap
Use-after-free:
Let us say we have a bunch of pointers to a place in heap, and it is freed without making sure that all of those pointers are updated. This would leave a few dangling pointers into free'd space. This is exploitable by usually making another allocation of different type into the same region, such that you control different areas, and then you can abuse this to gain (possibly) arbitrary code execution.
Double-free:
Free up a memory region, and the free it again. If you can do this, you can take control by controlling the internal structures used by malloc. This can get complicated, compared to use-after-free, so preferably use that one if possible.
Classic buffer overflow on the heap (heap-overflow):
If you can write beyond the allocated memory, then you can start to write into the malloc's internal structures of the next malloc'd block, and by controlling what internal values get overwritten, you can usually gain a read-what-where primitive, that can usually be abused to gain higher levels of access (usually arbitrary code execution, via the GOT PLT , or __fini_array__ or similar).