 Wer von euch hat eigentlich schon mal versucht und welches Software zu reversen? Wow! Florian, Florian beschäftigt sich mit dem Thema und geht so über die normalen Standardgeschichten hinaus, erzählt was über Konzepte, wie man Dinge vielleicht ein bisschen besser machen kann, wie man auch schwierige Sachen herausfinden kann und dann halt eben seine eigene Open Source Software danach schreiben kann. Florian studiert in Darmstadt Informatik, arbeitet im Moment bei der ERNW, beschäftigt sich halt eben mit den Werkzeugen fürs reversen und erzählt uns jetzt was dazu. Viel Spaß und Applaus für Florian. Ja, die Who Am I Slide kann ich mir dann im Grunde skippen. Was ich in Darmstadt noch tue, ist das CTF Team organisieren zusammen mit noch ein, zwei anderen. Ja, die Firma, für die ich arbeite, wie veranstaltende Konferenz, wir haben den Blog, Technische Write-Ups oder sowas zu Themen landen, wo ich auch ab und zu mal irgendwie detailliertere Blog-Posts zu solchen Tooling-Kram schreibe, die einfach angenehmer deswegen zu lesen sind. Aber so zum Thema. So ein paar Leute haben hier anscheinend schon mal versucht irgendwie Dinge zu reversen, von Prinzip her, was man macht, was das? Ja, okay. Von Prinzip her, was man ja macht, ist man hat im weitesten Sinne irgendwie einen Haufen Daten, bei dem man irgendwie die Bedeutung dahinter verstehen will. Ich werde mich insgesamt relativ stark darauf fokussieren, was passiert, wenn wir irgendwie mit auswöbarem Code uns irgendwie rumschlagen, der vor allem auf Linux-Systemen läuft, aber generell die Konzepte funktionieren eigentlich für wirkliche Sorte Code auf jeglichen Betriebssystemen. Wenn man am Anfang dasteht und erst mal irgendwie gar nicht weiß, was man tun soll, das menschliche Gehirn, das prima Muster zu finden, aber in den meisten Fällen hat man ja irgendwie Kontext mit, was man eigentlich arbeitet. Also beispielhaft, man findet irgendwo eine Datei auf irgendeinem seiner Server, die irgendwie ausgeführt wird, ganz viel Traffic generiert, man hat eigentlich keine Ahnung, was die tut und man ist sich nicht so ganz sicher, ob das jetzt Mallware ist oder ob das irgendwie so gehört, weil der Admin von dem Server irgendwie schon lange nicht mehr bei der Firma arbeitet. In dem Kontext weiß man, man hat eine Ausführerbeide, man weiß welchen Server man hat und man kann sich die anschauen. Generell auf den ganzen Slides die Konvention ist, ich weiß nicht, ob man das Dunkelblau erkennen kann, wenn das irgendein Toolname ist, ist der irgendwie Dunkelblau unterlegt und wenn der nicht so ganz offensichtlich zu finden ist oder Teil vom Standard Linux ist, steht unten links nochmal der Link irgendwie zum GitHub oder zu der offiziellen Seite davon. Ja, so prinzipiell vom Anfang an, wir gehen mal davon aus, dass wir irgendein Ausführerbeidencode haben. Der ist typischerweise auf einem Mienungssystem in einem bestimmten Format. Mit Pech haben wir aber auch irgendwie Firmware Images. Ich werde auf Firmware jetzt nicht so weit eingehen. Die haben nochmal ein paar eigene Tücken. Prinzipiell, man weiß nicht, mit welcher Architektur man sich rumschlägt, wenn man nicht irgendwie genaure Infos dazu findet. Da gibt es dann zum Beispiel irgendwie das Datasheet, das man sich anschauen kann bzw. man kann so ein paar Tricks anwenden. Einer der Tricks zum Beispiel ist, dass man sich anschaut, wieso typischer Code auf so einer Architektur aussehen würde. Was für das Ganze ist eigentlich noch relevant ist, wie viele Leute hier können überhaupt irgendwas mit Assembler anfangen? Okay, dann relevanter vielleicht das Inverse, wie viele Leute haben sich irgendwie noch nicht mit Assembler beschäftigt und können mit dem Thema jetzt irgendwie nichts eher anfangen, sind mehr für die Konzepte hier, weil ich setze so je nachdem, wie viel Wissen ich voraussetzen soll. Das waren jetzt gerade die meisten, aber es wird später vielleicht ein paar Sachen geben, die dann schwerer zu verstehen sind. Aber von Prinzip hat man ja irgendwie auf jeder Architektur irgendwelche Calling Conventions, die am Anfang von jeder Funktion die immer die gleichen Instruction haben. Und wenn man zum Beispiel jetzt bei der Firmware irgendwie die Architektur rausfinden kann, kann man einfach nach denen suchen. Andere Probleme, die man bei Firmware hat, sind Memory Layout. Aber da ist generell das Datasheet dazu finden und da weiter suchen, das ist im Grunde nochmal ein Thema für sich. Prinzipiell gehen wir mal davon aus, dass wir irgendeine Linux Binary haben für den Anfang und uns die anschauen wollen. Und da bringt Linux schon ziemlich viel mit, was Tooling angeht. Die folgende Tools sind jetzt eigentlich alles, was man auf so einem typischen Linux Installation finden kann, was man den Paketmanager noch findet, was viel ursprünglich für Debugging konzipiert wurde. Das erste, was man sich bei dem Ganzen rumschlagen muss, ist Pausing. Man weiß ja nicht unbedingt, wie diese Datei jetzt aufgebaut ist. Elf Dateien haben den standardisiertes Format. Die meisten ausführbaren Dateien haben immer ein standardisiertes Format. Auf Linux ist das das sogenannte Executable in Linking Format. Auf Windows ist das PE oder MZ, was im Heller steht. Auf Mac ist das das Macho Format und im Allgemeinen enthält das immer Metadaten über das Programm beziehungsweise wie das Programm in den Speicher geladen werden soll, was das Programm für Abhängigkeiten hat, also welche Libraries dafür noch benötigt werden und welche Funktionen das Programm irgendwie sich aus externen Libraries reinziehen will. Die können oft schon ein erster wichtiger Anhaltspunkt sein, wenn man jetzt irgendein fremde Binary hat, bei dem man sich nicht sicher ist, was sie tut, aber die importiert irgendeine Library namens Pass Malware und colter die Funktion Pass Malware drauf, dann kann man sich schon mal ungefähr vorstellen, was irgendwie wohl einer der internen Funktionalitäten davon sein wird. Wenn man Glück hat, ist noch die Symbol Table dabei, die gibt im Grunde die Namen von den internen Funktionen an, die nicht extern aufrufbar sind, was einem das Leben auch nochmal deutlich einfacher macht. Typischerweise kann man sowas aber eben entfernen und wird man irgendwie, wird man nicht immer dabei haben. Ja, für so grundlegendes Tooling, Linux bringt da irgendwie etliche verschiedene mit, die sind meistens irgendwie in Bindutils, die machen auch alle irgendwie ungefähr das gleiche. Also es gibt ObjectDump, NM, ReadElf und auch etliche andere, mit denen man erst mal irgendwie dieses Header Format pausen kann und sich so grundlegende Dinge anzeigen lassen kann. Im Allgemeinen, wenn man eigentlich später mit fortgeschrittenem Tooling irgendwie auf sowas losgeht, ist eigentlich immer irgendwo ein Paser drin enthalten, da muss man sich selbst wenig mit rumschlagen. Wenn man aber einfach nur mal irgendwie schnell über was drüber gucken will, ohne jetzt irgendwie die komplette Formen mit dem Tooling zu booten, reichen solche grundlegenden Sachen oft. Ja, das nächste, was Linux noch alles so mitbringt sind Tracing Utilities. Was bedeutet, man schaut sich während ein Programm ausgeführt wird an, was es so für Dinger aufruft. Eine der Funktionen, die Linux damitbringt ist, oder die oft in Linux Umgebung verfügbar ist, je nachdem wie minimal sein System ist, ist Ltrace. Das macht nichts anderes als, dass jede einzelne Library Funktion, die das Programm aufruft, wird einem als Nutzer einfach angezeigt, also was der Funktionsname ist, was dafür Argumentereien übergeben werden, an welcher Stelle, die gerade aufgerufen wurde und was das Ganze zurück gibt. Das kann bereits relativ viel Aufschluss über ein Programm geben und kann oft mehr so für eine Initialübersicht reichen. Konzeptionell gibt es das gleiche noch für System Calls. Das nennt sich dann einfach S Trace. Das macht das gleiche jedes Mal, wenn ein Programm irgendwie Kernel Funktionalitäten aufruft, kann man sich die anzeigen lassen und erfährt dann, was da irgendwie übergeben wurde und was da zurückkam. Das kann oft für simple Dinge schon irgendwie reichen. Angenommen, das hat irgendwie Stringopius, ein Programm versteckt irgendwie die Namen von Strings oder zum Beispiel die Namen von einem Server, zu dem es sich verbindet. Irgendwann wird sich das raus verbinden müssen und das muss über den Kernel passieren und da wird dann drin stehen zu welchem Hostname oder mindestens mal zu welcher IP sich das Ganze verbindet. Genauso für Debugging Purpose ist das auch schon ziemlich mächtig. Wenn sich irgendwann ein Programm aufhängt und irgendwie nicht so ganz verhält, wie man erwartet, kann man sich da auch wieder dran hängen und oft kann man schon irgendwie sehen, was es zuletzt gemacht hat, was eventuell schief gegangen ist oder dass es gerade hundertmal versucht, ein Fall zu öffnen, das nicht existiert. Aber trotzdem einfach nicht aufhört. Was Linux oder das GNU Tool Set auch noch mitbringt, ist der GNU Debugger, den wahrscheinlich auch viele kennen werden. Der Ursprünglich dafür designed wurde, dass man irgendwie Programme debuggt, zu denen man den Source Code und Debugging Symbols hat. Ich sehe oft genug irgendwie Leute, die dann erfahren, man kann mit Blue Debugger auch reversen. Die nehmen den dann so ohne jegliche Skripte und Erweiterungen und versuchen damit irgendwie auf eine fremde Binary loszugehen, die sie zu sehr keine weitere Informationen haben. Kann man machen, ist fürchterlich aufwändig. Es gibt mindestens mal drei Plugins, die relativ bekannt sind, die einem das Leben da deutlich einfacher machen. Prinzipiell, was man mit dem Debugger immer machen kann, ohne weitere Skripte, ist, man führt einfach Programme aus und kann sich einfach zu jedem Zeitpunkt des Programms, an dem man das Programm anhält, den aktuellen Zustand anschauen und weitere Punkte mit der Zukunft festlegen, zu denen man wieder abbricht. Und die ganzen Plugins, die das erweitern, geben einem dann teilweise einfach netteriere Übersichten über den aktuellen Zustand des Programms, irgendwie fortgeschrittene Fähigkeiten wie, dass man den Hieb durchsuchen kann oder ähnlich ist. Als kleines Beispiel ist das mal hier, links ist so Standard GDB, das einem erst mal die riesige Lizenz prüntet und wenn man dann eben versucht, das Programm zu starten, das keine Symbole hat, dann meint GDB, dass man die Hauptfunktion nicht finden kann, führt es deswegen einfach mal aus und man hat jetzt keine Ahnung, was das Programm eigentlich tut. Der gleiche Befehl in GDB-Peda, was eben eine dieser Erweiterungen ist, findet erst mal irgendwie mit Heuristiken raus, wo wahrscheinlich die Mainfunktion von dem ganzen Programm ist, da die typischerweise immer einer bestimmten Stelle im Code ist, hält dann automatisch an und gibt einem so eine schöne Übersicht über den kompletten aktuellen Zustand mit allen Registern. Was alles Informationen sind, die GDB eigentlich schon hätte, die man sich aber per Hand anzeigen lassen müsste. Was man auch noch an grundlegenden Dingen machen kann, ist die LD Preload Funktionalität von Linux. Vorhin bei den Metadaten des Programms habe ich gesagt, man hat eine Liste von Funktionen, die importiert werden und eine Liste an Libraries, aus die die importiert werden, aber es wird nirgends wo spezifiziert, wo man eigentlich diese Funktionen herbekommt. Was man jetzt also machen kann, ist, man definiert sich eine eigene Library, schreibt da Code rein, wie zum Beispiel man kann diesen XKCD 1 zu 1 kopieren, man muss nur die Funktion umbenennen, kompiliert das Ganze einfach als Library-Objekt und gibt der Binary das zuerst mit. Und wenn die Binary jetzt versucht, irgendwie Zufallszahlen zu generieren, wird sich wasch, entweder die Ziefunktion random aufrufen oder andere, lädt dann aber jetzt stattdessen erst mal unseren Code, der halt eben nicht zufällige Zahlen zurückgibt, sondern immer konstant die Gleiche. Das kann bereits sinnvoll sein, wenn man sich einfach ein Programm anschauen, deterministisch anschauen will, weil man damit eben den Zufall rausbekommt. Wenn man sowas prinzipiell irgendwie für DefUrandom auch noch machen will, da man beliebige Funktionen überschreiben kann, kann man auch einfach sagen, dass jedes Mal, wenn eine Datei geöffnet wird, prüft man, ob das jetzt irgendwie DefUrandom, also das Zufalls-Device von Linux ist. Und wenn ja, gibt man einfach irgendwie den File-Handle zu DefZero zurück. Und wenn nicht, kann man immer noch aus diesem preloaded Code die eigentlichen Lipsi-Funktionen aufrufen. Man könnte sich zum Beispiel auch so was wie das Library-Tracing von vorher damit einfach selber implementieren. Indem man sozusagen jede einzelne Funktion überschreibt, sich vorher die Argumente ausgeben lässt und dann die echte Funktion aufruft. Das funktioniert nicht für SetUAD-Binaries, das wäre auch zu mächtig. Funktioniert aber sonst auf den eigentlich allen Linux-Systemen und ist relativ leicht, wenn man irgendwie sonst keinen Tooling hat. Und ist eben eine grundlegende Fähigkeit, die einfach immer da ist. Ja, so, aber das grundlegende Linux-Tooling kannten jetzt wahrscheinlich die meisten schon. Ich weiß es nicht, wie viele von Leuten haben jetzt gerade irgendein neues Tool kennengelernt, dass ich einschätzen kann, auf was ich mich hier fokussieren muss. Ah, prima, das sind schon mal durchaus ein paar Leute. Ja, aber irgendeinem Punkt hat man aber das Problem, dass man irgendwie einfach nicht mehr auf Linux ist und all diese ganzen coolen Tools einfach nicht mehr hat. Windows hat sowas wie Ltrace und Strace nicht nativ. Oder man hat irgendwie das Problem, dass man sich mit Programmen rumschlägt, die einfach nicht mehr so schön mit dem Nutzer kooperieren. Die zum Beispiel nachschauen, ob man gerade irgendwie in einem Debugger ausgeführt wird, ob man gerade irgendwie mit Strace oder Ltrace ausgeführt wird, und dann eventuell was völlig anderes machen. Und eventuell will man auch spezialisierte Tools oder verstehen, was eigentlich Tools so intern machen. Das erste wichtige Konzept bei dem Ganzen ist Disassembly. Das eigentliche, was ja an den, was vom CPU ausgeführt wird, ist ja nichts anderes als eine Sequenz von Bytes, die für den Menschen jetzt in den meisten Fällen erst mal nicht lesbar ist. Wenn man das lang genug macht, schafft man das bestimmt. Aber es ist deutlich angenehmer, diese Repräsentationen irgendwie zu sehen. Jeder, der irgendwie in der Uni oder privat sich mal irgendwie Semmler angeschaut hat, wird wahrscheinlich diese Befehle dann irgendwie kennen. Für die Leute, die jetzt noch nicht gesehen haben, das bedeutet ein, man kann sich ja ein bisschen erschließen, da steht ADD, das wird wahrscheinlich irgendwas addieren. Wir addieren da eins auf AIX. AIX in dem Fall ist einfach nur ein Register, was mehr oder minder eine interne Variable des CPUs ist. Dass man das Ganze aber sozusagen im Rechner auch nicht beschreiben muss, gibt es noch das Konzept, das sich Lifting nennt. Was man da tut, ist man übersetzt nicht mehr von diesen Maschinenbytes zu irgendeinem menschenlesbaren String, sondern sozusagen zu einer anderen Sprache, die klar ausdrückt, was dieser Befehl eigentlich auf einem CPU machen würde. Nämlich, dass wir hier irgendwas addieren, und zwar einen direkten Wert auf ein Register. Und danach, nachdem dieser Befehl ausgeführt wird, ist der Inhalt dieses Registers um diesen immediate Wert einfach größer. Auf X86 gibt es dann auch noch diese Flaggen, und es ändern sich noch ein paar kleine Details. Und das Ziel von der Intermediate Representation ist eben komplett zu repräsentieren, was da alles passieren wird. Was einmal als Mensch ganz praktisch sein kann, wenn man das auf fremden Architekturen macht, aber später für Tooling auch extrem wichtig wird, da man ja für jeden einzelnen Befehl die komplette Semantik kennen will, falls man da irgendwie Analyse darauf machen will. Das typische Tool für einfach nur Disassembly ist Capstone. Das kam jetzt vor ein paar Jahren raus. Ist mindestens mal auf dem Source. Ich glaube auch unter einer sehr freien Lizenz. Und im Grunde jedes Tool, das in den letzten Jahren rauskam, das intern irgendwo Disassembly verwendet, called eigentlich nur in diese Library rein. Die unterstützt eigentlich fast alle Architekturen. Ich glaube, es sind also die man typischerweise irgendwie verwendet, und den man begegnet. Genauer gesagt, glaube ich, alle, die LLVM unterstützt. Und hat Bindings in allen möglichen Sprachen. Also das typische wie Python, C, aber auch irgendwie sowas wie Haskell, Clojure, JavaScript, in was man halt alles so arbeiten will. Das ImWRse davon, also das ist im Grunde ein Projekt, die haben noch zwei andere Tools. Das eine andere Tool ist Keystone. Das macht das gleich einfach nur rückwärts. Man kann einfach einen Assembler-String angeben und den dann für eine bestimmte Architektur übersetzen lassen. Zu dem dritten Tool in dem Ganzen komme ich später noch. So, einfach nur disassemblen reicht aber eigentlich nicht. Man musste erst mal wissen, was eigentlich Code ist. Man hat da so ein paar Startpunkte, vor allem eben den Entry Point in einer Binary, wo man weiß, irgendwo wird dieser Code mal ausgeführt. Man weiß also, das ist auf jeden Fall Code. Eventuell, wenn man noch Symbole dabei hat, weiß man an welchen Stellen Funktionen anfangen und weiß auch, dass der ausführbare Code lebt, der sich überhaupt Sinn gibt, anzuschauen. Das Problem an der Sache ist, wenn man sich jetzt mal den String rechts anschaut, dann gibt es einfach nur einen Haufen Bytes. Von denen man jetzt vielleicht weiß, das ist irgendwie ausführbarer Code. Man fängt einfach mal stumpf von Anfang an, zerlegt jede Instruktion und wenn die Instruktion fertig ist, schaut man sich die nächste an. Es funktioniert, gibt dann aber ein bisschen komischen Code. Das Ganze nennt sich einfach Linear Sweep, ist genauso simpel, wie es irgendwie klingt und zahlt sich jede Instruktion an und macht dann einfach beim nächsten Byte weiter. Das Problem ist, X86 ist eine fürchterlich verwirrende Architektur, die verstiehenlange Instruktionen zulässt, die nicht erzwingt, die sind immer an einer schönen, leinten Adresse liegen müssen. Und der Code rechts scheint auf den ersten Blick vielleicht irgendwie korrekt zu sein, ist aber auf den zweiten Blick so ein bisschen komisch. Also so ein Return Statement und direkt danach nen Jump, der irgendwie vorher eigentlich hätte was vergleichen sollen. Am Ende irgendwie ein Haufen 2 Null Bytes, die irgendwie disassembled worden ist, in den meisten Fällen komisch. Sieht schon irgendjemand, was da passiert ist? Okay. Was X86 nämlich zulässt, ist dieses erste Jump 3 springt 3 Bytes nach vorne. Dieses eine Byte dazwischen ist also nichts anderes als unnötige Informationen, die niemals ausgeführt werden, die aber einen simplen Disassembler schon völlig verwirren können. Um das Ganze anzugehen, gibt es eben einen anderen Approach, das nennt sich Recursive Descent. Das bedeutet, das ist ein genereller Algorithmus, das bedeutet nichts anderes als das jedes Mal, wenn man irgendwie zu einer neuen Stelle Code springt, fängt man von dort an weiter an zu disassemblen. Was bedeutet dieses B8 Byte, das wird niemals wirklich angeschaut. Das Problem an der Sache ist, man braucht plötzlich Verständnis, von was eigentlich diese Instruktionen machen. Man kann sich nicht mehr einfach irgendwie den Text dem User ausgeben lassen, oder man muss verstehen, was eigentlich wirklich passiert, wenn diese Instruktion ausgeführt wird. Dafür ist es eben insgesamt genauer. Problematisch wird das zum Beispiel hier schon bei der letzten Instruktion. Man springt zu irgendwie der Adresse, die in RX gerade drinnen steht, aber es ist nicht sofort offensichtlich, denn in dem Fall könnte man rückwärts laufen und das sehen. Aber das ist dann schon eine Grunde, die nächste größere Problemstellung, nämlich was sind so die Möglichkeiten, wie man durch ein Programm durchlaufen kann. Jeder, der schon mal Eider oder ein anderes System laufen hat, kennt diese schönen Grafen, die einem das Leben finde ich doch deutlich einfacher machen. Da man nicht mal so einen riesigen Block aus Text vor sich hat, sondern so ein bisschen Überblick bekommen kann, wie dieses Programm von der Struktur her aufgebaut ist. Um so was eben sinnvoll zu bauen, muss man sich jede Instruktion anschauen. Man fängt auch wieder am Anfang an. Und jedes Mal, wenn das Programm irgendwo hinspringen kann, kann man sich sozusagen entscheiden. Also die Entscheidung muss man vorher treffen, wie genau man sich das jetzt anschauen will. Wenn da eine direkte Adresse steht, weiß man auf jeden Fall, wo man hingeht. Falls da jetzt irgendein Wert in einem Register steht, kann man beliebig genau vielleicht zurückrechnen, was da irgendwie passieren könnte. Das ist schlicht und einfach eine Entscheidung zwischen Genauigkeit und Machbarkeit. Wenn man das irgendwie schlimm genug obfiskiert einen Code hat oder einfach nur C++, kann das schon füchterlich aufwendig werden, an jeder Stelle rauszufinden, was eigentlich jetzt der nächstmögliche Code ist. Das reicht meistens, um irgendwie simple Disassembler völlig zu überfordern, damit der Code irgendwie, falls der ursprüngliche Auto dieses Programms tatsächlich daran gedacht hat, dass sich das jemand mal anschaut, bereits das Leben deutlich schwerer machen kann. Generell, wenn man sich irgendwie damit beschäftigt, sollte man irgendeinen Tool haben, das es generiert und einem anzeigt. Es hilft beim intuitiven Verständnis von Programmübersicht halt deutlich, was menschliche Gehirn es einfach viel besser mit Bildern und Mostern als mit stumpfem Text. Ja, ein bisschen was zu executable parsing noch. Im Allgemeinen, diese grundlegende Tools kann man sich immer schon irgendwas mitbauen, aber jetzt irgendwie Object-Dump in Grab zu pipen, um irgendwie die Dependencies rauszufinden und im seltenen System kann man machen, es ist halt nicht schön. Dafür gibt es dann auch wieder dedizierte Tools. So ein Simplis ist PyElefTools, das ist einfach eine reine Python-Implementierung von einem Elf-Paser, den man irgendwie Standalone auf irgendeinem System schieben kann und sich dann einfach, sozusagen Python-Objekt aus jeder Beine regenerieren kann, dass man sich anschaut. Falls man irgendwie kompliziere Dinge haben will, gibt es LEAF. Das ist vom Corkslab geschrieben. Das kann dann auch irgendwie verschiedene Formate noch wie PE-Bineries und Macho-Bineries und kann dann sogar noch fortgeschrittenere Dinge wie, dass man die Datei dann ändern kann, zum Beispiel Neulibrations hinzuzufügen oder den Codefluss teilweise zu verändern. So, aber auf irgendeinem Punkt, wenn man sich nicht mehr einfach nur anschauen, was das Programm eventuell machen könnte, sondern was das Programm tatsächlich tut, wenn man das ausführt. Die Skiz rechts ist so eine Vereinfachung, wie sowas irgendwie aussehen kann. Man hat im Grunde irgendwo den Prozess, den man sich anschaut, nachdem das Programm irgendwie in den Speicher geladen ist. Der läuft in irgendeiner Form von Umgebung. Der läuft auf irgendeinem Fallsystem in den meisten Fällen. Da liegen irgendwelche Libraries, die das Ganze importiert. Das läuft auf irgendein Betriebssystem, das einen Kernel mitbringt mit System Calls. Und das Ganze ist auf irgendeiner Form von CPU ausgeführt. Einer der Techniken ist jetzt Emulation, wo man im Grunde sagt, deshalb irgendein Punkt, man in diesen Stack sozusagen reinschneidet und alles unten drunter die Traherung, die man mitbringt. Zum Beispiel, falls jemand Coemo schon kennt, normalerweise, wenn man in Coemo-Nobinary ausführt, führt er einfach nur diesen Code aus, aber auf dem eigenen Hostsystem und ignoriert, dass der CPU eigentlich nicht dazu passt, dass darum kümmert sich Coemo. Das funktioniert in den meisten Fällen nicht, weil man auf seinem X86-Hostsystem wahrscheinlich keine ARM-Libraries rumliegen hat. Die kann man aber zum Beispiel an einer bestimmten Stelle irgendwie zur Verfügung stellen und dann würde das so halbwegs funktionieren. Generell bei den Slides das Blaue bedeutet, dass man sozusagen als Analyst irgendwie in Kontrolle unter diesen Bereichen sind sozusagen in dieser Stelle die Abstraktionsgrenze zieht. Man kann mit Coemo auch ein bisschen tricksen. Die Linux Changewood Funktionalität macht es zum Beispiel möglich, dass man, wenn man angenommen hat, ein Raspberry Pi Image, man kann dieses Image mounten über die üblichen Linux Funktionalitäten und dann einem sozusagen einen eigenen Unterprozess starten, der glaubt, dass dieses Image jetzt das Route Verzeichnis von sich selbst wäre. Zusammen mit der Fähigkeit vom Linux Kernel, dass man dem Kernel erklären kann, dass bestimmte Binarys, die zum Beispiel mit dem Architekturheader für ARM anfangen, nicht mehr nativ ausgeführt werden sollen, sondern mit Coemo ausgeführt werden sollen, kann man damit zum Beispiel ein Raspberry Pi Image mounten, Reinchange roten und man hat eine Shell, die man in diesem Image hat bedienen, man läuft in Wirklichkeit aber immer noch auf dem Kernel, den man eigentlich gerade vorher verwendet hat. So heißt der Kernel bekommt immer noch Cisco als wie zum Beispiel Open, handelt die dann selbst und gibt es dann entsprechend zurück. Und der eigentliche CPU, auf dem wir das ausführen, ist immer noch eigentlich Coemo. Der Coemo System Modus ist dann, wenn man nur noch den eigentlichen CPU emuliert und zum Beispiel ein Raspberry Pi Image hat und tatsächlich den wirklichen Raspberry Pi Kernel dabei lädt. So, aber warum macht man das Ganze? Man ist zwar langsamer als die echte Maschine, aber man ist jetzt komplett und hat die komplette den kompletten CPU, sozusagen unter Kontrolle. Man kann jetzt dafür sorgen, dass Instruktionen plötzlich was völlig anderes tun. Man kann mal schauen, wie weit sein System eigentlich kommt, wenn der Increment Befehl plötzlich eine Zahl dekrimentiert. Man kann einfach Instruktionen komplett oder man kann sich eigenen Code registrieren, der jedes Mal, wenn dieser Emulator einen bestimmten Befehl ausführt, erst mal den eigenen vom Nutzer definierten Code ausführt, um sich zum Beispiel die Stelle zu merken, an die einem gerade einen Call Befehl ausgeführt wurde oder wohin gerade gesprungen wurde. Das Problem, was man bei Emulation aber hat, man abstrahiert halt und vereinfacht. Auf Linux geht das noch ganz gut. Die komplette Windows API zu emulieren ist halt extrem schwer, da das so viele Features bietet und sowas kann dann wieder genutzt werden, um Emulation zu erkennen. Malware wird eventuell irgendwie komische Aufrufe auf die Windows API machen, um bestimmte Fehlermeldungen zu erwarten, um damit zu erkennen, ob man immer in einer Emulationsumgebung läuft. Das kann man dann wieder mit, da man im Zweifelsfall immer, sozusagen unter dem Privilegienlevel über der eigentlichen Binary ist, kann man sowas immer rauspatschen, da man die komplette Kontrolle über das System hat, in das diese Binary gerade ausgeführt wird. Man muss diese Stellen dann einfach nur irgendwie finden und der Binary dann weiter weismachen, dass sie in der Umgebung läuft, so sehr erwarten würde. Coemo hatte ich ja bereits erwähnt, das ist so das Emulator-Tool, das auf Linux irgendwie verbreitet ist. Das kann auch Geräte emulieren, also wir können damit irgendwie virtuelle Netzwerkarten emulieren. Diese Fähigkeit wird gerne verwendet, um bei Hypervisern, wie also Open Source Hypervisern, wie KVM und Xen, aber auch generell das komplett sozusagen als CPU-Ersatz verwenden, um fremde Architekturen auszuführen. Das unterstützt auch wieder eigentlich alle Architekturen, die wahrscheinlich irgendwie man aufzählen kann, vor allem eben alle Gängigen, also MIPS, Spark, XX86, ARM, was man so kennt. Das Ganze ist relativ schnell, die haben da internen paar Tricks, dass sie sich irgendwie merken, wie man Blöcke ausführt. Im Allgemeinen ist das Tool aber eigentlich auch schon zu groß. Das ist noch Unicorn, das ist eines von den drei Tool, das wurde zusammen mit Capstone und Keystone, glaube ich, von ähnlichen Leuten, unter dem gleichen Team entwickelt. Das ist im Grunde nichts anderes als QEMU, aber nur der eigentliche CPU-Emulator, Kern von QEMU und als Framework. Ihr könnt ihn also irgendwie von Python aus importieren, euch sozusagen virtuellen CPUs starten, die ihm irgendwie einen Speicher-Layout geben, da irgendwie Code reinladen, jetzt einfach mal sagen, dass der an einer bestimmten Stelle anfangen soll, die nächsten 10 Instruktionen ausführen und das Programmzustand haben. Nachteil ist, man kann keine Geräte emulieren, und falls das Programm versucht, einen Cys-Corn aufzurufen, muss man sich da auch irgendwie drum kümmern, da da ja kein echter Kernel unten drunter läuft. Das versteht aber eben sozusagen die Semantik von jeder Instruktion. Und damit kann man zum Beispiel einfach kleine Code Snaps ausführen, wenn man einfach nur wissen will, was der zuständige Register danach ist. Das wird auch wieder in vielen anderen Tools intern verwendet, die die API in allen möglichen Sprachen hat. Und auch wieder eine freie Lizenz. Es gibt noch ein paar komplizitere Tools, die auf ähnlichen Konzepten aufbauen. Pyrebox ist von Cisco Talos. Das ist auch wieder QEMU, aber QEMU erweitert als Framework, dass man sich im Grunde mit einer iPython-Shell daran hängen kann, um sich sozusagen zur Laufzeit der Fokus auf Windows das Betriebssystem irgendwie anzuschauen, irgendwie eigene Python-Funktionen zu schreiben, die an bestimmten Stellen aufgerufen werden, was die dort zum Malware-Analyse-Primär verwenden. Der Linux-Support ist aber soweitig weiß aktuell in Entwicklung und funktioniert so halbwegs. Was es auch noch in der Richtung gibt, ist Panda, das ist auch im Grunde wieder nur ein weit modifiziertes QEMU. Das ist auch erlaubt, ein komplettes System aufzuzeichnen, um dann an allen möglichen Stellen in diesem Emulationsprozess sich mit eigenem Code reinzuhängen, um damit dann irgendwie Analyse zu machen. Das ist das Gefühl, mehr in Akademia irgendwie verwendet. Ich habe noch von keinem wirklichem realen, also Industrieverwendungszweck, davon gehört, aber das hat mich unbedingt was zu heißen. Das zweite wichtige Konzept, das es da irgendwie gibt, ist Dynamic Binary Instrumentation. Was bedeutet eigentlich einfach nur, dass man den Prozess in seiner natürlichen Umgebung ausführt, aber den Code umschreibt beziehungsweise sozusagen noch eigenen Code dazu lädt. In dem Fall gibt es zwei Ansätze. Entweder man lädt sich sozusagen dazwischen, man führt sozusagen dieses Instrumentation Framework auf dem eigentliches System aus, das man analysieren will in dem Kontext, und das lädt dann erst den Prozess, den man sich anschauen will. Oder was jetzt mit Frieda, was jetzt ein relativ neues Tool ist, man lädt den einfach irgendwie ein paar Tricks, was dafür sorgt, dass man plötzlich sozusagen auch den Prozess in seiner natürlichen Umgebung irgendwie unter Kontrolle hat. Das Prinzip, das dahinter ist, Assembler wird immer in so Atomahnblöcken ausgeführt. Da man ja nur bestimmte Instruktionen hat, um irgendwie Kontrollfluss zu ändern, woanders hinzuspringen, kann man immer sagen, wenn man an einer bestimmten Stelle anfängt, läuft man immer so lange durch, das ist dann Basic Block. Und die sind im Grunde einfach nur zusammenverkettet, man kann zwischendrin hin und her springen. Zum Beispiel, wenn man irgendwie Bedingungen hat und dann gibt es eben zwei mögliche nachfolgende Blöcke, zu denen man springen kann. Was jetzt so ein Instrumentation Framework macht, vom Prinzip her, ist, dass es dafür sorgt, indem es einen Block lädt, sich den Code davon anschaut. Zu dem Zeitpunkt kann man dann den eigenen Analysecode irgendwie reinhängen, der sich sschadessen zurück zum Framework springt, dass Framework sich jetzt anschauen kann, was der nächste Block ist und sich dann eben wieder von Anfang an diesen anschaut, darauf Analyse macht, den entsprechend umpatched, dass man zurückkommt. Was man damit machen kann, ist diese Funktionalitäten, die Linux selbst mitbringt, bereits schon, wie S Trace und L Trace, einfach Betriebssystem unabhängig zu implementieren. Für eines der Tools gibt es zum Beispiel dann ein S Trace oder L Trace einfach für Windows, das die Windows-Betriebssysteme benötigt. Damit kann man also wieder Jekie-Funktion Tracing machen, Coverage Tracing heißt, man interessiert sich nur dafür, welche Blöcke ausgeführt wurden, was bei großen Programmen relativ hilfreich sein kann, wenn man eben wissen will, ob dieser Code, den man sich gerade anschaut, überhaupt in der verwendet wurde, das letzte Mal, als man das Programm bedient hat und sich anschauen wollte, welcher Code dahinter liegt. So was ist für Fuzzing relativ wichtig, dass man diese Abdeckung haben will. Ein paar Beispiele, wie man das verwenden kann, was ich mal gesehen habe, eine App, die irgendwie ihre kryptografischen Keys versteckt hat, indem sie das in der eigene Library ausgelagert haben, damit irgendwie ein bisschen obfiskiert im Code generiert hat, aber dann einfach die Signierfunktion wieder woanders aufgerufen hat, man konnte sich also einfach mit einem Tool da reinhängen, hat auf diesen Funktionscall gewartet und hat sich einfach diesen Key aufgerufen. Das Tool, was da viele inzwischen für benutzen, ist Frieda. Da will ich jetzt gar nicht so sehr drauf eingehen, soweit ich das gesehen habe, gibt es Samstag, da noch mal ein dedizierten Vortrag oder Workshop zu. Vom Prinzip her ist es aber genau das, was ich gerade erklärt habe. Man lädt sich in einen einen existierenden Prozess rein, übernimmt den sozusagen und hat dann eine API, mit der man reden kann, um einfach bestimmte Analysefunktionen oder das ist vor allem super einfach, um einfach nur mal irgendeine bestimmte Funktionen zu hucken. Wurde ursprünglich primär dafür entwickelt, soweit ich weiß, um sich mit Mobile zu beschäftigen, funktioniert da entsprechend gut. Soweit wenn ihr irgendeine Android-App habt, hat das glaube ich ADB Support, dass man sich da entsprechend irgendwie reinhängen kann, um rauszufinden, was die App macht. Oder zum Beispiel, um irgendwie der App weiß zu machen, sie läuft gerade nicht in eine gejailbreakten Umgebung, so grob sieht das so aus. Eines der Probleme an dem Tool ist es, man kann es nur in Javascript, es hat Bindings in verschiedenen Sprachen, aber den eigentlichen Kern programmiert man mit Javascript oder C, weil sie die komplette V8-Engine dazu laden, ist ein bisschen fragwürdig, finde ich, aber es funktioniert anscheinend relativ gut und wird in letzter Zeit immer bekannter. Es gibt noch zwei andere Tools, die relativ verbreitet sind, das ist es wieder unter einer freien offenen Lizenz verfügbar. Leider nur irgendwie C programmierbar und Intel Pin, was proprietär ist, aber frei verwendbar, man darf es nur nicht ohne weiteres irgendwie Toolzieh darauf basieren weitergeben. Bei denen muss man dann aber einfach von entweder existierenden Tool oder Plugins dafür finden oder sich selber welche schreiben, die so was wie Funktionshooking oder welche Analyse man da auch immer ja, wenn einem das nicht mehr reicht. Das ganze Thema kann man auch noch formaler angehen, beziehungsweise vor allem unter dem Gesichtspunkt Automatisierung. Das ganze nennt sich dann Binary oder Program Analysis und es ist so im Grunde das akademische Unterfeld, das sich mit diesen Themen beschäftigt. Das ist in den letzten Jahren um einiges größer geworden, vor allem durch die DARPA Cyber im Namen ist, aus dem man was Gutes auskam. Was effektiv ein Wettbewerb war, bei dem es darum ging, ein komplett autonomes Framework zu schreiben, das im Grunde CTF spielt. Das alleine in der Lage ist, ein Programm nach Schwachstellen zu durchsuchen, diese Schwachstellen zu patchen und Exploits zu generieren, um die dann eben bei den anderen Teams auszunutzen. Das Finale davon war dann auf der DEF CONS in Las Vegas. Das lief dann auf diesen riesigen Maschinen, die daneben standen mit, ich glaube, ein paar Hundert Kernen und ein paar Terabyte RAM. Und aus dem sind eben ein paar Frameworks entstanden, die das Ganze im Grunde auf die Ebene des Machbarhands geholt haben. Teilweise die Konzepte sind 40 Jahre alt, aber da ist heutzutage irgendwie die Rechenleistung für da und dadurch jetzt auch das Tooling, dass man das diese Konzepte ist Symbolic Execution, wo man sich nicht mehr einen konkreten Programmverläufe anschaut, sondern sozusagen den alle möglichen Programmverläufe in Abhängigkeit von einem bestimmten Wert. Angenommen, man hat irgendein Programm, dass sich ein bestimmtes Protokoll spricht und eben in einem Buffer dann das Protokoll stehen hat. Was man jetzt machen kann ist, das ist jetzt Pseudocode, der prüft eventuell, ob das Paket mit einem bestimmten magischen Header anfängt und dann statt, dass man jetzt irgendwie den richtigen Header raten muss schaut sich das Framework an was die Bedingungen auf diesen Wert sind und findet in dem Fall zwei mögliche weitere Pfade, einmal, dass der Wert nicht korrekt ist und das Programm sich beendet und einmal, dass ein Switch Statement kommt. Was man jetzt weiß, ist, dass eine bestimmte Bedingung, nämlich dass dieser Wert dort stehen muss, immer erfüllt sein muss, wenn man zum Beispiel in diesem Switch Statement landet. Und mit dem Prinzip geht man irgendwie ein Handler, der dann verschiedene Pakettypen irgendwie verarbeitet, kann man entsprechend weitergehen und bekommt dann eben einen bestimmten Programmzustand und eine Liste an Bedingungen, die gelten müssen für einen bestimmten Input, damit man zu diesem Zustand im Programm kommen kann. Da gibt es dann irgendwie nochmal die Theorie dahinter, nennt sich SMT. Da gibt es dann Solver, für den man eben diese Liste an Bedingungen geben kann und die dann versuchen, eine Eingabe zu finden um zu diesem Programmpunkt zu kommen. Wenn man damit dann eben korrekt irgendwie das modelliert, was ein interessiert, kann man sich in dem Fall zum Beispiel ein Paket generieren lassen, das zu einer bestimmten Stelle im Programm führt. Man kann das aber auch noch weiter machen, indem man zum Beispiel Schwachstellen oder irgendwelche anderen Bedingungen irgendwie modelliert, um sich dafür dann Inputs generieren zu lassen, die zu dieser Sorte Systemzustand führen. Eines der Verwendmaßen meiner Meinung nach Tools für dieses komplette ist Anger. Das ist ein komplettes Analyse Framework, das unter anderem solche Funktionalitäten bereitstellt. Das intern damit funktioniert, dass es im Grunde wieder zu seiner Zwischensprache übersetzt und dann auf dieser Zwischensprache entweder Code direkt emulieren kann. Das ist symbolischer Execution darauf machen kann. Aber auch alle typischen Features, die man irgendwie von einem Analyse Framework erwarten würde, wie CFG Generierung, Funktionserkennung und ähnliches übernimmt. Das wurde in der DABOS TGC von dem drittplatzierten Team intern verwendet. Zusammen mit noch vielen anderen Komponenten ist komplett Open Source auf GitHub und ist in Python geschrieben, was den Code relativ leicht verständlich macht. Die Idee dahinter ist, dass man sich dieses Framework in ein Python-Programm irgendwie reinlädt und damit dann sozusagen mit einer IPython Shell sich das interaktiv irgendwie anschauen kann. Es gibt auch ein GUI dafür, damit ist man aber automatisch irgendwie limitierter, mit dem habe ich mich noch nicht so viel befasst. Ja, ich bin relativ gut in der Zeit. So insgesamt, das richtige Tooling macht einem das Leben deutlich einfacher, wenn man sowas insgesamt öfter macht, ist aber oft auch irgendwie initialer overhead, erstmal zu lernen, wie man irgendwie ein bestimmtes Tool bedient. In meiner Erfahrung ist es das oft irgendwie wert, da man damit plötzlich irgendwie Probleme lösen kann, mit denen man vorher sich gar nicht bewusst war, dass man sie überhaupt so lösen kann. Man kann teilweise existierendes Tooling zusammenbauen auf irgendwie Wege, wo jetzt einfach noch nichts existiert, um so eine Problemstellung irgendwie anzugehen. Oder einfach irgendwie selbst neue Tools schreiben, die dann auf existierendem Tooling irgendwie aufbauen. So, ich liege gut in der Zeit. Falls irgendjemand Fragen hat zu all diesem Überblick, kann ich jetzt gerne noch irgendwie Fragen beantworten. Ich habe aber auch noch ein paar Slides zu so paar Themen, die ich bis jetzt irgendwie noch nicht irgendwie, die konzeptionell noch nicht so ganz reingepasst haben. Ja, wenn die Frage sind, ich habe hier das Mikrofon. Hallo, gibt es auch irgendwelches Tooling, um zum Beispiel, wenn executable, also binary, zu runtime, zum Beispiel über ein Time Event oder sonst ein Interrupt modifiziert wird. Du meinst, wenn du Signalhandler oder sowas modifizierst? Nee, nicht Signal. Also wenn zum Beispiel irgendein Interrupt nebenläufig zum normalen Prozess, also dem User-Modeprozess, den du analysieren willst, dann den Code umbiegt. Wenn der User-Modeprozess nämlich anschaut, den ursprünglich irgendwo setzt, wofür ich jetzt ausgehen würde, also multi-threaded Programme sind generell schwer irgendwie zu analysieren. Das ist auch ein interessantes Stichwort, ja. Was ja effektiv das ist. Man hat plötzlich mehrere Programme, die nebeneinander laufen. Je nachdem, was man macht, wenn man sowas wie binary instrumentation macht, dann muss ja das Problem sein, solange dieser Interrupt-Handler, der den Code verändert, den Code immer noch an der Stelle findet, an dem er es erwartet. Bei Emulation existiert das Konzept von irgendwie nebenläufigen Interrupts nicht. Außer, man emuliert sie selber und ab dem Punkt hat man sie wieder komplett unter Kontrolle und kann entsprechend entscheiden, ob dieser Interrupt irgendwas tun soll, ob der das Programm modifiziert, wie es erwartet wurde oder ob er einfach niemals ausgeführt wird. Das hängt dann von dem Modell ab, dass man hat. Dass das Co-emo so was wahrscheinlich unterstützen wird und wahrscheinlich irgendwo intern modifizierbar ist, dass man das irgendwie behandeln kann, wie man will. Ein verwandtes Thema ist nach der ganzen Analyse De-Kompilation. Gibt es da irgendein Tool, wo dann so etwas wie C-Code wieder rausfällt, wenn es ursprünglich mal C war? Wenn du genug Geld auf das Problem wirfst, ja, es gibt da so ungefähr genau eins. Das ist das pro Tool, das gängigste ist. Da blecht man dann aber auch gerne mehrere Tausende Euro für pro De-Compiler. Da gibt es aktuell mehr Forschung in der Richtung. Also es gibt eine Forschungs-Branch von Anger, wo sie versuchen so was zu implementieren. Wobei da auch nicht immer unbedingt die Idee ist, dass man C zurück will, wenn es eigentlich wirklich ist. Typische Informationen, die verloren gehen, sind was eigentlich Variablen sind, wo eigentlich ein Waffe angefangen hat und aufgehört hat. Da gibt es dann schlicht und einfach verschiedene Analyseverfahren, um so was rauszufinden. Zum Beispiel, wenn man einen Start-Pointer irgendwo hin hat und weiß, man kann einfach Indexe von 0 bis 1000 irgendwie nur da reinadressieren, kann man davon ausgehen, dass das wahrscheinlich ein Waffe mit der Länge 1000 ist das ist aber auch wieder fehleranfällig und selbst die guten modernen De-Compiler brauchen sozusagen noch Feedback vom Nutzer, dass man die Typen mehr angeben kann. Damit sind die aber in meiner Erfahrung relativ gut verwendbar, wenn das Kottos der nicht aktiv dagegen versucht das zu machen, machen einem das Leben deutlich einfacher. Es gab mal eine Studie dazu, die ich gelesen habe, dass man mit solchen Tools leider den kommerziellen, im Grunde den kompletten Anfangsteil überspringen kann und auch die Stufe von einem mittelmäßig erfahrenen, der das in so ein, zwei Jahre schon macht, bekommt. Dafür bezahlt man aber entsprechend den Preis und man versteht nicht unbedingt, was eigentlich unten drunter passiert ist. Also es gibt es, würde ich zum Anfang nicht empfehlen was es gibt, es gibt einen Open Source De-Compiler, der von der Weile veröffentlicht wurde, der ganz gut ist, das nennt sich Red Deck für Retargetable De-Compiler der war über eine längere Zeit closed Source, ist aber inzwischen so weit ich weiß auf GitHub und man kann sich irgendwie anschauen, wie der funktioniert und wie gut der Kottos der Letzt zurückkommt. Fragen? Weitere? Gut, dann dürfte ja alles klar sein. Florian, herzlichen Dank, spannend Applaus für Florian.