 So, für den nächsten talk of today, we're gonna talk about a new open source. Bis dann, schalten wir nochmal auf den Side Track. DBI means Dynamic Binary Instrumentation. Dynamics, well, you know, binary also. Instrumentation means to observe, monitor and modify some parts of the, some parts of a program. Also, der Talk wird gerade vorgestellt. Es geht nochmal darum, was der Titel eigentlich bedeutet. Binary Instrumentation bedeutet, dass man einmal die Instrumentation zur Laufzeit vornimmt und die Instrumentation bedeutet, Einsichten in die Anwendung zu erhalten. Ein warmer Applaus für den Vortragenden, die Vortragenden. Also, ich denke, die Harrods haben uns ganz gut vorgestellt. Wir arbeiten beide bei Quarkstop, was eine französische Security Consulting Firma ist. Und was wir bei Quarkstop machen, ist relativ viel Dynamic Binary Instrumentation, DBI, kurz genannt. Und im letzten Jahr haben wir uns damit sehr viel beschäftigt. Und was wir in diesem Vortrag erreichen wollen, ist, dass wir etwas aufklären, was eigentlich so ein Dynamic Binary Instrumentation framework ist, wie man sowas implementiert, wie man das benutzt und wie ihr das vielleicht auch an euren eigenen Projekten benutzen könnt. Ich fange jetzt erstmal bei ganz einfach an mit einer einfachen Einführung. DBI ist im Prinzip einfach nur eine Transformation des Programms von sich selbst in sein eigenes Messtool. Das heißt, man kann den Zustand des Programms zur Laufzeit feststellen und observieren. Man kann dann tatsächlich den Prozess des Einsammens, die Daten automatisieren. Und was kann man damit jetzt tun? Ja, die meisten von euch haben bestimmt schon mal C++ programmiert und vielleicht schon mal Valgrind benutzt und Valgrind zum Beispiel benutzt, um Memcheck auszuführen. Was Memcheck dann tut, ist, dass es einfach Memory Allocation und Deallocationen trackt und Speicher-Zucker für Tractor. Mit diesen Informationen ist es in der Lage, sowohl Use After Free als auch Memory Leaks Probleme zu detektieren und die dann, oder zum Beispiel Buffer Overflows oder Out-of-Bounce Accesses zu detektieren. Das ist also ziemlich nützlich. Ein anderer relativ populärer Eingangsfall ist Fuzing. Bei Fuzing generiert man zufällige Eingaben und sendet das in das Programm in der Hoffnung, dass es dann crashed. Und was man dabei nicht machen möchte, ist, interessante, zufällige Eingaben zu generieren. Wir möchten im Prinzip wissen, ob die Eingaben, die wir reinwerfen, verschiedene Abschnitte vom Programm ausführen oder zur Ausführung von verschiedenen Programmabschnitten führen. Das heißt, wir möchten letztendlich die Code Coverage messern und mit DBI-Frameworks kann man sowas erreichen. Es gibt dann noch spezielle Frameworks, mit denen man herausfinden kann, welche Instruktionen ausgeführt werden und zusammen mit symbolischen Informationen von der Komparzeit können wir herausfinden, was das Programm zur Laufzeit ausführt. Was man auch machen kann, ist Ausführungstraces aufzeichnen, also was letztendlich in der Anwendung zur Laufzeit passiert. Das heißt, man hat so eine Art Zeitdosen-Debagger, in dem man in der Zeit vorwärts und rückwärts kreuen kann und dann herausfinden kann, was in dem Programm passiert ist, ohne dass man es neu starten muss und weil Breakpoint-Zeiten muss oder sowas. Woran ich auch vorher noch gearbeitet habe, waren Software-Seitenkanäle, Seitenkanal-Attacken auf Krypto-Implementierung. Das heißt, man konnte sowas nutzen, um Krypto-Code-Seitenkanäle auszunutzen. Die große Frage, die sich überstellt, wieso sollte man jetzt nicht einfach ein Debugger benutzen, denn Debugger kann das alles tun. Also, das Problem mit Debuggern ist, dass sie für menschliche Interaktion gedacht sind und nicht für Maschinen, und sie sind halt ziemlich langsam. Das heißt, wenn wir uns vorstellen, dass ein Debugger an irgendeinem Ziel attacht ist und wer im Debugger continue drücken, dann passiert Folgendes im System. Der Debugger sagt dem Kernel, dass die Ausführung weitergehen soll. Der Kernel schedulet das Target erneut. Das heißt, wir haben den ganzen Scheduling-Prozess und die Routinen, die da dranhängen. Dann trappt das Target auf irgendein Breakpoint. Das heißt, wir trappen zurück an den Kernel und dann entscheidet der Kernel, dass der Interakt von dem Debugger behandelt werden soll. Das heißt, er wird dem Debugger ein Signal senden und den Debugger schedulen. Das heißt, wenn man einen Breakpoint tatsächlich trifft, dann übertreten wir viermal die Grenze zwischen Programmen oder dem Kernel. Also, es ist ziemlich langsam und wie genau langsam. Vielleicht habt ihr solche Attacken schon mal bei irgendwelchen CTFs gesehen. Das heißt, wenn ein Banderere-Reverse-Engineer sagt, dass ein Passwort überprüft, wo man dann die Ausführungszeit überprüfen soll. Vielleicht war es tatsächlich nicht die Ausführungszeit, sondern tatsächlich die Anzahl an Instruktionen, die ausgeführt wurde. Und man kann oft gesagt und weise ein Passwort Zeichen verzeichnen, dass man einfach schaut, wie lange der Vergleich dauert. Hier sehen wir die Anzahl an Instruktionen, die ausgeführt wird. Und GDB ist sehr langsam. Das heißt, Sie sind da oben, dass der Anzahl an ausgeführten Instruktionen sehr viel kleiner ist. Das heißt, wir wollen nicht einen Gatschen Debugger benutzen, sondern einen dynamischen Instrumentation. Das heißt, wir wollen irgendwie den Kernel loswerden. Und tatsächlich die einzige Lösung, um den Kernel loszuwerden, ist, die Instrumentationen nicht mehr über Breakpoints zu realisieren, sondern im Target selbst zu implementieren. Es gibt dann verschiedene Techniken. Die erste Technik ist aus dem Source Code. Jeder hat das schon mal gemacht. Man macht einfach ein Print Statement rein, um Informationen zu kriegen. Im Wesentlichen ist das auch Instrumentation. Aber man könnte zum Beispiel auch schlauer sein. Man könnte das automatisch machen mit einem Compiler Plug-in. Und die andere Möglichkeit ist, wenn man mit dem Binary arbeitet. Wenn man mit dem Binary arbeitet, dann gibt es im Prinzip zwei Möglichkeiten. Erst mal patcht man das Binary und baut da Hux rein. Und das andere ist ein Dynamic Binary Instrumentation. Das erste ist ganz langweilig im Source Code. Und Static Binary Patching ist ganz grob und barbarisch eigentlich. Man muss vorher genau, wo man das einfügen muss und wie man das einfügen muss. Das geht manchmal auch nicht. Und deswegen geht es hier um Dynamic Binary Instrumentation. Es gibt einige schon existierende Frameworks. Das erste ist Valgrind. Das habe ich schon kurz erwähnt. Seit 2000 gibt es das schon. Das ist Open Source. Aber es gibt nur auf Unix und ist sehr komplex. Nicht zu benutzen, aber die Instrumentation ist schon früher. Dann gibt es da Memorio. Das gibt es seit 2002. Ist auch Open Source und ist Cross-Plattform. Und Cross-Architecture. Aber es ist sehr schwierig zu benutzen. Und im Wesentlichen muss man die Instructions in Assembler selber zusammenbauen. Und das ist sehr fehlerrechtlich. Das andere ist Intel Pin. Das ist das Populärste eigentlich. Denn das ist sehr benutzerfreundlich. Aber es läuft nur auf Intel. Ist Cross Source. Warum haben wir unsere eigene gemacht? 2015 haben wir überlegt, was wir davon wollen. Cross-Plattform und Cross-Architecture. Alle diese Frameworks wurden schon vor langer Zeit entwickelt. Es gibt neue Sachen, die man probieren will. Wir wollen uns spezialisieren auf Mobile und Embeddedargets. Weil wir damit vor allem arbeiten in unserem täglichen Arbeit. Wir wollen ein einfacheres Design und ein modulares Design. So dass wir es zusammen mit anderen Reverse Engineering Tools zusammen benutzen können. Und auch weil wir wollen auch uns spezialisieren auf Heavy Instrumentation. Also ganz viele Sachen instrumentieren, viele Daten kriegen können. Um das Folgende verstehen zu können, muss man wissen, wie die AI eigentlich funktioniert. Das heißt, ich versuche das jetzt mal zu erklären, wenn ich es zu richtigen kriege. Die einfache Idee ist, Dynamic Binary Instrumentation dynamisch die Instrumentation in den Code einzufügen im Binary zur Laufzeit. Das heißt, wir gucken uns diesmal an. Wenn man disassembliert den Code, der ausgeführt werden soll, dann gibt man dem Instrumentation Tool die Anweisung und dann baut die Instrumentation und die Instrumentation wird dann in den Ausführungsfluss eingebaut und dann sieht man den Packman zum größten Vergleich hier. Die sieht ganz einfach aus, aber es gibt einige Probleme mit dieser Grafik. Problem ist nicht Packman, sondern was anderes. Das erste ist Disassembling. Wenn man das Binary kriegt, weiß man gar nicht, der Code ist Daten und Daten sind Code und es gibt sehr unvorhergesehenere Sprünge und man kann nicht einfach das Ganze disassemblieren schon vorher. Also man muss rausfinden, was der Code ist von dem Binary während der Ausführung. Was das bedeutet ist, man führt ein kurzes Stück Code aus, eine Reihe Instruktionen die ausgeführt werden und das fährt dann auf mit einem Control Float mit einem Sprung zum Beispiel dass man nicht auflösen kann und guckt nur diesen Block an und dann guckt man wo es hingeht, was als nächstes ausgeführt werden soll und da entdeckt man dann den nächsten Code Block und führt dann den nächsten Code Block aus und es ist ein kurzer Zyklus von der Ausführung das ist für die Instrumentation den Instrumentation Code den man erzeugt denn dieser ist viel größer als der Original Code es ist die Summe von dem Original Code plus dem Instrumentation den man anzubaut und der Compiler versucht das möglichst kompakt da reinzubauen und lässt nicht viel raubend kein freier Platz übrig im Code von dem Binary also kann es nicht direkt an derselben Stelle instrumentieren man kann es nicht direkt im Binary machen lässt nicht genug Platz das heißt man muss es irgendwo anders hinschreiben und das bedeutet man muss den Original Code relocaten und relocating ist das Problem damit ist die Speicheradressen zum Teil anders sind zum Beispiel hat man relative Sprüngung sagt 20 bytes vorwärtsspringen oder wir müssen jetzt Daten adressieren die eine bestimmte Anzahl bytes entfernt sind von meiner Position und wenn man das durch die Gegend schiebt dann werden diese Referenzen alle ungültig das bedeutet wir müssen den Original Code neu schreiben um diese Referenzen wirklich gerade zu ziehen und in unserer Engine die wir hier zeigen in diesem Vortrag das nennen wir das patching das heißt wenn man alles zusammenfasst das ist der Lebenszyklus von einer DPI Engine man fängt mit dem Disassemblieren an das erste Stückchen dann patchen wir das damit das Relocierbar wird dann kommt die Instrumentation dazu und dann assemble wir das wieder irgendwo anders hin und dann können wir es ausführen und wenn die Ausführung passiert ist dann gucken wir wo das nächste Stück Code ist und machen es dann weiter nachdem Cedric jetzt die Low Level Abstraktion mal erklärt hat schon mal wir möchten jetzt unser eigenes DPI Design das heißt und also und wir haben diese Basic Blocks und das ist die execution wir haben jetzt von Vortrag und mit dieser Struktur die Kontrollfläche geht einfach auf du beginnst eine Funktion und du hast einfach alle Basic Blocks und du nimmst keine Art zu interagieren also was wir wollen ist eine Art Unterkontrollfläche und durch das wir wollen, nach dem ersten Block die Kontrollfläche zu nehmen wir wollen, um den Jump zu skipen in unser eigenes Code da werden wir die Logik des Engines exekutieren und wir werden auf das nächste Basic Block und so weiter aber hier alles ist um die Kontrollfläche zu halten und das ist sehr schwierig, was wir erwarten weil was wir wollen ist es braucht zu modifizieren die original Instruktion der Beine aber wir wollen nicht zu modifizieren die original Beine der Software wir wollen um es zu exekutieren wie es enttäuscht war und das ist sehr schwierig und braucht viele low-level Tools also was brauchen wir eine sehr einfache Sache wir brauchen einen Multi-Architektur-Dies-Assembler und wir brauchen einen Multi-Architektur-Assembler und wenn es möglich ist es sollte auch cross-platform sein bitte und wir brauchen eine solche Intermediate-Repräsentation zu arbeiten und diese Repräsentation sollte ein Link zwischen der Dies-Assembler und der Assembler also diese Requirements sind ziemlich stark und was wir nicht wollen also wir haben nicht limited resources also wir wollen Multi-Architektur-Dies-Assembler und die Assembler und auch die Intermediate-Repräsentation wir wollen nicht zwischen der Präsentation jedes einzelne Manual und jedes einzelne Architektur wir wollen das support ja, ich mag den Entwickler-Manual aber es ist nicht so viel Spaß also wir haben nicht 10 Jahre zu spenden und passen aber die Dinge haben sich geändert seit ein paar Jahren es gibt einen neuen Spieler dieser Spieler ist called LLVM was ist LLVM? LLVM war basically created focus on compiler technology especially just in time engine but basically LLVM is the core foundation core framework of a very well known software today which is the Clang compiler I'm sure like everybody in this room know Clang compiler but it's more than it's like a world toolkit that provides tons of things to play with so, wir sind wieder wir müssen jetzt irgendwo wieder anfangen also, tatsächlich hat LLVM schon alle Features die wir brauchen das unterstützt alle wichtigen Architekturen die wir brauchen x86, x86, x64 es unterstützt es bietet sowohl ein Disassembler als auch ein Assembler und mehr als nur das denn es hat vor allem die Intermediate Representation also diese Zwischensprache die im Zwischen dem Code in dem Assembler steht manche von euch haben bestimmt schon von dieser Intermediate Representation gehört aber okay nein, das ist gar nicht genau das und es ist nicht die Intermediate Representation die im Compiler Pass verwendet wird sondern es ist eine noch etwas tiefer also näher am Assembler liegende die heißt LLVM Maschinencode also kurz MC und die ist die Sprache die zwischen das Assembler genutzt wird steht aus ja, schauen wir uns mal in dieser kleinen, einfachen Instruktion an bei DBI ist tatsächlich eine Instruktion einfach nur eine Sequenz von bytes und was LLVM uns bietet ist, dass wir dem einfach diese Diste können und LLVM bietet uns diese Struktur hier das ist tatsächlich schon eine Intermediate Representation das ist ziemlich einfach, man hat die Instruktion die Instruktion besteht aus einer Liste von Operanten aber man hat die ganzen interessanten Informationen die man braucht schon hier und damit können wir schon arbeiten LLVM MC ist sehr minimalistisch es gibt nur ein paar Strukturen und das ist ziemlich generisch das heißt alle Architekturen benutzen dieselbe Repräsentation das heißt, wir können dann Dinge auf eine generische Art und Weise tun aber das verrät uns schon viel über eine Instruktion zumindest alles was wir brauchen um damit zu arbeiten aber der Nachteil ist dass es dadurch dass es so einfach ist im relativ geordene Struktur ist es ist relativ schwer damit komplizierte Dinge zu tun und sie wollten es generisch halten und mussten deswegen einige Kompromisse eingehen es gibt zum Beispiel sehr viel Glucode und es macht die Benutzung von diesem Layer relativ kompliziert wir wollen zum Beispiel Instruktionen erstellen was wir bei die BI brauchen wenn jede Instruktion dieselbe Repräsentation nutzen würde dann wäre es ganz einfach aber tatsächlich ist das in Wirklichkeit alles ein bisschen unterschiedlich zum Beispiel haben wir dieses Beispiel hier ist eine Serie von Operanten die dann in einer Instruktion kopiert sind aber diese Repräsentation ist dokumentiert, sie ist nicht standardisiert das heißt jede Instruktion benutzt möglicherweise ihr eigenes encoding und um das zu wissen muss man tatsächlich auf den Glucode schauen und das ist eigentlich nicht dafür gemacht, dass man damit großartig arbeiten kann wir möchten Instruktionen auch patchen das heißt wir wollen die modifizieren wenn wir zum Beispiel auf den Jump schauen es ist ziemlich einfach wenn wir auf den Trans den Immediate ist das heißt wenn wir das modifizieren möchten dann müssen wir von dem hier auf das hier gehen es sieht total anders aus das seht ihr hier und wir möchten auch binary patches machen also eine Serie von Transformationen die wir auf den Code anwenden hier ist auch ein einfaches Beispiel das ist zum Beispiel eine Instruktion sein die wir patchen möchten zum Beispiel das hier das ist eine Referenz auf Speicher die den Programm counter involviert und das heißt wenn wir und das dadurch dass die auf den Instruction Counter auf dem Wert des Instruction Counters abhängt können wir die nicht einfach im Code verschieben weil sich dann eben der Wert vom Instruction Counter erinnern würde das heißt was wir machen müssen auch wenn wir noch andere Instruktionen haben wir müssen dann das Register mit dem Programm Counter ersetzen mit einem anderen Register in dem dann die originale Adresse steht so dass wenn wir dann diese Instruktion ausführen ist dann die Speicheradresse die wir jetzt laden dieselbe ist wie im originalen Programm aber das heißt wir verändern das Verhalten vom Programm tatsächlich denn wir verwenden jetzt auf einmal das R1 Register und dann nachdem wir das alles aus also diese modifizierte Instruktion dann ausgeführt haben müssen wir auch das Backup Register wieder restaurieren also auf den originalen Wert zurücksetzen vorher und das ist tatsächlich alles relativ kompliziert wenn man das dann alles auch noch in dieser Intermediate Sprache machen muss also das Encoding ist relativ direkt zu arbeiten und das ist aber jetzt tatsächlich jetzt nur ein ziemlich einfacher Patch und das kann relativ schnell sehr komplex werden wenn wir komplexere Programmabschnitte haben wir ihr habt ja auch gesehen dass wir Register backupen mussten und ihr könnt jetzt schon sehen dass das nicht unbedingt der einzige Patch sein wird den wir machen müssen wenn wir ein Register wissen sollen wir müssen eventuell eine ganze Reihe von Dingen da tun und das müssen wir irgendwie saubererabstrahieren um diese Komplexität händeln zu können ja und hier haben wir unsere magische Engine die Patch Engine so heißt die und als Eingaben bekommt es eine Instruktion die Originale Instruktion und dann transformieren wir die mit der durch eine Abstraktion die Ausgabe ist eine oder mehrere Befehle Instruktion und dafür wesentlichen haben dafür haben wir eine Vision diese verzerrte Vision vielleicht vielleicht vielleicht können wir die Schritte identifizieren die wir brauchen um die Transformation anzuwenden und sagen dieser Patch ist in Wirklichkeit nur eine Serie von Transformation einige davon sind völlig generisch und vielleicht können wir einfach die anders anordnen und die sozusagen integrieren in einer domain spezifischen Sprache die dann darauf spezialisiert ist Binarys zu Patchen und ein Teil der Idee war diese Instruktion diese diese will trotzdem alles architekturspezifisch sein das heißt der Patch wird architekturspezifisch sein aber vielleicht kann die Sprache trotzdem generisch sein und wir für mehr als eine Architektur brauchbar sein und nach einigen Kopfschmerzen haben wir dieses Schema gemacht und ich will hier nicht genau alles erklären aber was wir erklären können ist im Wesentlichen das Problem was wir haben ist wir haben zwei Vörter und das von dem DBI das heißt wir haben Interaktion zwischen den beiden das heißt die Idee der Abstraktion ist etwas besser zu organisieren und man seht die Beziehung zwischen den beiden Teilen und indem wir diese Beziehung identifizieren versuchen wir eine Abstraktion herzustellen die uns erlaubt dann einfacher zu arbeiten mit dem Binary um das ganz komplizierte Thema möglichst einfacher zu beschreiben und ich zeige euch was das ist zum Beispiel das ist ein Teil der Sprache und es ist etwas was ein temporäres Register ist ein temporäres Register wir haben keine temporäre Register wir haben jetzt in der Beschwindung ein Beispiel gesehen das ist das gleiche Beispiel wie vorher und dies war das temporäres Register und wenn man etwas wie dieses hier machen will, eine Serie von Befehlen dann kann man einfach nur sagen ich will ein temporäres Register haben ja, dann sagen wir einfach Temp und das Register wird ausgewählt als ein freies Register wird ausgewählt von der Engine und einen Namen eine ID, sodass man später darauf zugreifen kann und indem wir damit arbeiten wird das gesichert und ich muss dann die Original Instruktion modifizieren um diese Modifikation zu machen ist was wir tun im Wesentlichen wir ersetzen das Register in der Original Befehl das heißt wir ersetzen dieses Register durch das temporäre Register was der Programm Counter ist und ging um die temporäre die Referenz und variables wir haben Variablen und Keywords zum Beispiel und eine andere Idee ist, dass wir wesentlich nur eine Reihe von Regeln an auf das Binary um das zu patchen das heißt wenn man das patchen will dann heißt das es gibt eine Notwendigkeit oder eine Bedingung dafür zum Beispiel für die Beschränkung von Inputs und wir fänden eine Serie von Aktionen an die können zum Beispiel unsere Transformationen sein die wir anwenden wollen wir gucken uns dass man an hier was wir eine Regel nennen das ist einfach nur aus dem Original Code und das ist unser Patch das ist das Programm Counter wir ersetzen wir den Programm Counter mit einem temporären Register und man kann hier sehen es gibt hier eine Bedingung wenn es nur eine Regel und die Regel ist du musst hier ein Register benutzen und die Action ist nur einige davon ich gehe nicht weiter ins Detail aber hier gibt es zum Beispiel Ersetzten durch temporäres Register und eine schöne Sache von dieser Regel ist das völlig generisches und es ist dieselbe Regel die wir auf ARM benutzen und auf X8664 und ein anderes Beispiel ist nur AM und hier ersetzen wir das auf wir ersetzen durch ein temporäres Register etwas komplexer als vorhin aber wir haben hier eine Bedingung die andere Instruktion gilt aber wir haben das ähnliche wir haben Keywords und Variable und ähnliches das heißt was haben wir heraus gelernt LLVM es ist ein magisches Stück Software sehr robust und es hat uns eigentlich gerettet hierbei aber das Problem was wir hatten war die Intermediate Representation war so simple und so einfach, dass es sehr kompliziert wurde zu spielen und man kann zum Beispiel in komplizierten Switchcases funktioniert es nicht mehr das heißt wir müssen uns auf die Abstraktion konzentrieren und sehen wie kompliziert es war diese Abstraktion zu machen und es ist immer noch in der Arbeit und noch nicht fertig Transformation diese relativ komplexen Transformation was einfach was übersetzen ist also das einfache zu lesen ist so ich werde jetzt weitermachen mit dem nächsten Teil in dem es darum geht wie man Großarchitektur Support angeht viele DBI-Frameberg wie zum Beispiel Die von Intel hatten Probleme andere Architekturen wie zum Beispiel ARM zu targeten und das Problem ist wenn man dann nicht von Anfang an darüber nachdenkt dann wird das ziemlich komplex nachher drauf zu bauen und ich werde jetzt in folgendem zeigen wie wir dieses Problem angegangen sind wenn man darüber nachdenkt was in dem Prozess passiert dann kann man diesen Platz in zwei Entitäten aufteilen einmal den Haus der Haus enthält die DBI-Engine und das Instrumentationstool und der zweite Teil ist der Gast der Gast beinhaltet das originale Binary und den instrumentierten Code das heißt diese Thermologie haben wir so ein bisschen aus der vor allem Welt geklaut weil das in dem Kontext Sinn macht wie ist das ja keine virtuelle Maschine diese Kontext zerteilen sich ja den gleichen Speicher weil sie ja eben einfach der gleiche Prozess sind und das heißt wir müssen zwischen diesen Kontexten im Prinzip jeden Zyklos wechseln aber wir bekommen hier nicht die Hilfe vom Kernel wir haben keine Virtualisierungsinstruktion die wir geschenkt werden an der Stelle das heißt wir müssen im Prinzip den CPU Kontext jedes Mal wenn wir zwischen Gast und Host wechseln sichern und wieder herstellen und wir müssen dabei aber jede Art von Seiteneffekt auf den Gast vermeiden weil der Gast eben nichts von diesen DBI mitbekommen soll dass da im Prinzip ein separater Prozess läuft das heißt wir können den Stack nicht modifizieren wir können auch den Register Inhalt nicht modifizieren weil jetzt ansonsten ein Crashing könnte oder das Bewergen könnte und der einzige Weg wie man das irgendwie auf die Reihe bekommen kann ist dass der Gast relative Speicherzugriffe machen können muss wir können die Adresse nicht einfach in einem Register berechnen und dann auf dem Stack sprechen weil wir den Stack nicht modifizieren dürfen das heißt wir müssen direkte wir müssen direkte Speicherzugriffe aber relative Attressierung ist relativ eingeschränkt auf den meisten CPU-Architekturen bei x86 hat man relativ breite Sprungmöglichkeiten aber unter ARM hat man beispielsweise nur zwölf Bit das heißt wir sind plus minus 4.6 Bit limitiert auf ARM und das heißt in Summe dass wenn wir einen Context Switch implementieren wollen der auf verschiedenen Architekturen funktioniert dann haben wir letztendlich eine Situation wo der Host Speicher daran liegen muss den wir instrumentiert haben das andere Problem ist dass wir mit Data Execution Prevention also Ausführungsschutz klarkommen möchten und co-existieren möchten und das Problem ist dass für manche Speicherumgebungen eben aufgrund der Data Execution Prevention manche Speicherseiten nicht gleichzeitig Schreib- und Ausführer haben können und was wir machen ist deswegen dass wir einen Codeblock auf eine Page legen die nur lesbar und ausführbar ist und auf der anderen die Schreib- aber dafür nicht ausführbar ist das heißt wir haben hier oben die Read Execute und unten die Read Write Page und der erste Abschnitt in der Read Execute Seite ist der Prolog und der wird verwendet und was dabei passiert ist einfach nur den Host-Kontext zu sichern und den Gast-Kontext zu laden und das wird also mit relativer Attressierung zu machen und der Epilog macht einfach genau das Umgekehrte und zwischendurch läuft die Engine und die Idee dahinter ist dass man die Instrumentation Daten an den Instrumentierungscode findet, das heißt die Daten sind dann direkt attrassierbar und wir haben jetzt eben über eine Speicher-Seite gesprochen und das ist meistens dann 4 Kilobyte und das heißt das ist relativ viel Platz wenn wir so kleine Block haben und dann ganz viele davon brauchen dann könnten wir ziemlich leicht so viel Speicher verbrauchen und das heißt wir müssen auf irgendeine Art und Weise mehrere instrumentierte Basic Blocks in den Codeblock packen da muss man dann ein paar Tricks anwenden das heißt, Dinge werden komplexer das ist das was wir dann am Ende erreicht haben wir haben wir haben hier am Anfang einen sogenannten Selector der im Prinzip einfach nur ein Jump ist irgendwo in den Codeblock rein und das ist im Prinzip ein programmierbarer Jump aber wir können den Codeblock nicht modifizieren weil es ja eben nur readexecute ist das heißt was es tut ist ein Jump zu einer Adresse der im Data Block ist und jeder, naja wir springen jedenfalls dann zu einem Basic Block und jeder Basic Block springt aber am Ende wieder zu einem Epi-Lock von dem also wir haben hier Kontext und Shadows also die Instrumentations Konstante wir müssen den Programmzähler auf x86 funktioniert das sehr gut wenn man 64 Bit Immediate Adressierung hat aber auf ARM ist man sehr limitiert für die Größe der Immediate Adressierung die man hier noch Register laden kann und wir müssen das auf ARM anders machen und wir haben erst ein Stück Code dann ein Stück Daten und kann das dann adressieren und es funktioniert dann auf ARM genauso ohne zu viel Platz zu verschwenden das Interessantere was wir Instruktionsschatten Instruction Shadows das ist nicht ganz neu das ist an Valgrind angelehnt wie Valgrind Speicheralutzierung trägt ist das sie ein Speicherschatten ein Memory Shadow anlegen für jeden Speicherbereich den du dem Mann anfordert bekommt es einen zusätzlichen Schattenbereich für Bits die auf bestimmte Speicherbereiche zeigen und man hat dann einen Shadow von dem Speicher und das ist verbunden mit dem tatsächlich Speicher und wir wollten das Gleiche machen aber für Instruktionen und es ist eine Methode für uns diese Variablen zu abstrahieren in dem instrumentierten Code um Inline Instrumentierung einfach zu machen und der Haupt Use Case ist im Moment Speicherzugriffe aufzuzeichnen und das ist ein Problem das ist natürlich ein Speicherzugriffe nicht alles was in deinem Programm passiert aber um Speicherzugriffe aufzuzeichnen man muss das vor und nach Instruktionen instrumentieren, das heißt man muss sehr viel Instrumentierung dazu bauen und wenn man den eigenen Code hat, dann muss man wieder Context Switches machen und man macht dann pro Instruktion zwei Context Switches und dann wird die Instrumentierung auch sehr langsam das heißt die Lösung ist was wir Inline Instrumentation genannt haben und was man macht ist man man kudiert den Speicherzugriffe wieder in Assembler Neu ohne das Instrumentierungstool zu benutzen werden dafür benutzen für die Instruktion die Speicherzugriffe machen dafür erzeugen wir diesen Shadow und dort werden die Speicherzugriffe in dem Shadow gespeichert und man kann einfach diesen Basic Block ausführen und am Ende guckt man einfach im Schatten nach und weiß dann welche Adressen transferiert wurden und auf welche zugegriffen wurden und um das dann alles umzusetzen in einem Crossearch-Tutur-Context das wird alles nicht so ganz einfach denn wir brauchen zum Beispiel eine Abstraktion um Speicherseiten zu allokieren oder Page-Zugangs also Schreib- oder Leseberechtigung zu ändern und wir wollen das alles im Speicher mal wir wollen jetzt ganz letztlich Binary oder so was erstellen, dass dann Autofestplatte liegt und das ist leider nicht so einfach dass wir einfach zusätzlich Section-Sendungen einem Binary erstellen können aber tatsächlich hilft LLVM uns auch hierbei denn als LLVM tatsächlich gestartet wurde hieß es Low Level Virtual Machine das heißt, sie hatten da so eine Art Bytecode der im Prinzip auf verschiedenen Architekturen ausführbar war und dadurch, dass es eine Just-in-Time-Engine war es war es eigentlich gar nicht so weit weg von etwas, dass ein Dynamic Binary Instrumentation framework ist wir können jetzt natürlich nicht diese Engine genauso benutzen denn obwohl es einige Features hat die auch bei uns funktionieren passt nicht so ganz in unseren USKS aber LVM bietet uns all die Möglichkeiten um es genau so was zu implementieren es hat eben diese ganzen Crosshatch-Tour Abstraktion und die Low Level Intermediate Sprache die wir eben gesehen hatten das heißt, was haben wir gelernt dabei er vor allem ist sehr geeignet um ein Just-in-Time-Compiler zu bauen aber Dynamic Binary Instrumentation in ein Just-in-Time-Engine von Anfang an rein zu designen ist relativ schwierig und man muss dann tatsächlich auch über Portabilität, wenn man das haben möchte von Beginnern nachdenken man sollte es nicht nachher in einen drauf bauen also halt das da gibt es dann halt noch einen kleinen Teil von unserem Projekt, nämlich QBDE was Quark Step Dynamic Binary Instrumentation bedeutet also sehr kreativ von unserer Seite es ist Cross-Plattform und Crosshatch-Tour DBI-Framework und Cross-Plattform bedeutet in diesem Kontext das ist auf Linux, MacOS, Windows, Android und iOS läuft und es benutzt er freundlich ist in den letzten Monaten haben wir uns darauf konzentriert das Detail ist dabei, dass es ein relativ komplizierte Engine ist und relativ komplex ist und was wir eben bieten möchten und glauben uns das haben dass wir einfach zu benutzende APIs und gute DoCo bereitstellen und machen alles einfach an dem wir Binary-Pakete für die Betriebszimmer bereitstellen, die wir unterstützen die Grundphilosophie ist dass die Kern-Engine nur das allerwichtigste unterstützt was QBDE-Eye bereitstellt das heißt die Idee war dass die Sachen einfach gehalten werden soll einfach gehalten werden soll das heißt, dass man nicht zwingt Sachen genau auf unsere Art und Weise zu machen das heißt, wir haben nicht genau die Methoden die man benutzen muss um Binary zu indizieren und dass Nutzer dadurch limitiert werden sondern was wir am Ende haben ist eine Wirklichkeit unserer Modul einfach zu integrieren in euren USKs wir haben zum Beispiel Python-Bindings erstellt so dass man sehr schnell und wir haben eine sehr gute Integration in Freida ich bin mir sicher, viele von euch kennen das es ist ein sehr schönes Framework um bei Neustern instrumentieren auf eine etwas andere Art und Weise und QBDE-Eye und Freida passen sehr gut zusammen das zeigen wir nachher auch an einer Demo die aktuelle Roadmap die wir haben ist dass wir Arm noch besser unterstützen wollen aber im Prinzip ist es einfach nur, dass wir zusätzliche Regeln definieren müssen zum Beispiel für Arm gerade auch Arm 64 an der Stelle wir müssen bei Speicherzugriffen noch ein bisschen aufholen, zum Beispiel ich zeig jetzt schon Multiple Data Exces oder Multi Threading Support und Exception Handling das ist tatsächlich auch noch ein offenes Thema und es liegt daran dass wir die Kern-Engine wirklich es einfach halten wollten und wir uns deswegen noch nicht darum gekümmert haben also damit kommen wir jetzt zur Demo die erste Demo ist von der Engine selbst und die Python-Bindings das heißt wir müssen ja so ich hoffe ich habe nichts kaputt gemacht jetzt jetzt sollten wir das nochmal ja okay ich glaube das geht so es ist eine ganz einfache Demo ich wollte nur zeigen wie einfach und wie wir das gemacht haben die API und es ist ein einfaches Programm was ein Passwort checkt und wenn man einfach nur die Software anguckt kann man das nicht einfach finden und deswegen war ein Problem mein Kollege hat eine seltsame Testatur Belegung hier aber ja was wir versuchen wollen ist jeden Speicherzugriff zu protokollieren von diesem Programm und es passiert ganz viel hier wir gucken einfach nur die Schreibzugriffe an wir benutzen die Python-Bindings und dieses Callback wir machen einfach nur 2 Sachen wir machen den Speicherzugriff Callback und wir lassen die Engine von Anfang bis Ende laufen hier ist der Memory Callback und es ist eine Analyse der Instruktion gemacht und das ist Informationen über den Speicherzugriff und schreiben dann einfach nur den Wert und wunderbar und ja so ja ist da ein Passwort? Nein, aber vielleicht einfach nur starten jetzt so wir wollen es starten es ist nicht mehr in der History das wäre zu einfach und jetzt los geht's ah ja hier können wir es laufen lassen und man sieht es sind ganz viele Speicherzugriffe die hier vom Programm gemacht werden und hier ist noch nicht sehr gut lesbar sehr viele Zeiger und wir wissen es ist ein Passwort und wir wollen es nach Speicherzugriff filtern die nur ein Beit lang sind und wir gucken hier ob die Größe eins ist und jetzt lassen wir das laufen hier und das ist interessanter jetzt wir können hier sehen dass wir den Wert des Beits rechts und viele Informationen die viele Instruktionen die hier ausgegeben werden und was wir jetzt noch machen können ist, wir machen einfach das gleiche aber wir filtern auch ob das XOR Instruktionen sind und gucken wir was wir jetzt bekommen und ja und jetzt ist nicht mehr so viel sind nicht so viele XOR Instruktionen übrig geblieben es sieht etwas zufällig aus aber das Ende ist interessant denn die sind immer in dem selben Speicherbereich und das sieht nach ASCII aus und das letzte die letzte ID ist die Beits nacheinander in einen Puffer zu schreiben und das heißt man übergibt ein Parameter an das Callback und jede dieser Befehle wird einfach an ein Array zum Zugefügt und dann trockt man das aus am Ende und wenn wir das machen wir sehen, wir haben etwas Müll und am Schluss ein Passwort der aussieht wie Import Triton Aha das Passwort ist korrekt wir haben einfach damit das Passwort gefunden und man sieht wie einfach dieses Binding ist das ist ein BDI wir haben Zeit für noch ein Demo ich will aber erstmal vielleicht ich versuche das mal auf dem Remote Screen zu machen aber wenigstens wir haben noch ein Demo Binary das will ich mal starten das ist nur kurz aber wir machen es einfach hier ins Größere und dann können wir sehen wir wollen verstehen was das macht und versuchen das mit dem Frieda Framework und versuchen das mit dem Frieda Framework versuchen versuchen das mit dem Frieda Framework und ich mache das mit meinem Laptop hier zuerst laden laden wir Frieda und ja das Demo funktioniert nicht immer so und dann laden wir das Binary und das ist glaube ich hier das Frieda Framework und dann sagen wir ich will das Demo Binary laden jetzt haben wir Frieda gestartet hier und ich übernehme die Kontrolle und hier sind wir jetzt in Frieda das ist die Frieda Umgebung die meisten von euch kennen das vielleicht die Frieda benutzen was wir damit machen können ist zum Beispiel so was wie das Binary das reverse-engineered Binary und ich habe eine Funktion die heißt Secret und was ich mache ist ich rufe mit der Frieda API will ich die Adresse von Secret herausfinden und wir brauchen eine Eingabe zu dieser Funktion und im Wesentlichen werde ich einfach einen String als und das macht einen Remote Speicheraluzierung und so kann ich dann sagen ich möchte eine native Funktion und das ist wie eine JavaScript Funktion die eine Funktionsadresse aufruft die wir aufgelöst hatten hier und dann mache ich jetzt Sekfunk Sekfunk-Import das Frieda dann macht dann ruft es diese Remote Funktion auf mit diesem Input und was wir hier machen können wir haben jetzt mit der BDI Integration wir können einfach die DBI einfach dieses hier machen und wir haben jetzt ein VMObject und damit erzeugen wir einen CPU Status und dann erzeugen wir einen Stack wo wir so viel Zeit haben und was wir wesentlich gemacht haben wir haben eine virtuelle CPU und haben den Zustand initialisiert und einen virtuellen Stack und was wir jetzt machen müssen ist okay wir wollen instrumentieren wir wollen die Demo das Demo Binary instrumentieren und das hier ist dafür da um den Aufruf der externen Library zu unterbinden und wir wollen wir können aussuchen welchen Code wir instrumentieren wollen und wir wollen nur das Binary selbst und nicht die Library und wir rufen es einfach auf wir benutzen den Funktion-Pointer von Frieda und haben dann eine Liste von Argumenten und das ist Input und wir haben genau den selben Output den selben Rückabewert aber wir haben genau dasselbe Ding das heißt BDI funktioniert und was wir jetzt machen können ist jetzt bauen wir die wir haben jetzt nur den Original Code in der BDI ausgeführt und jetzt machen wir die Instrumentierung und dafür müssen wir nur etwas erzeugen wie wie ein Instruktionscallback ja, ganz einfach und was ich jetzt gemacht habe ist, ich habe eine JavaScript Funktion erzeugt die zur Laufzeit aufrufen wird für jeden Funktionsaufruf für jede Funktion und es macht einen Dump der Journal Purposes Register und macht eine komplette Analyse der Instruktion macht dann eine Disassimplierung auf die originale Adresse und es wird vom BDI gemanagt und es gibt Cash und eine Menge andere Sachen dahinter und wir wissen nicht genau was dahinter ist, aber das ist alles Magie und danach brauchen wir nur noch den Callback hinzuzufügen und was ich jetzt Frage ist einen Codecallback zu machen vor jeder Instruktion und da rufe ich meine JavaScript Funktion auf und jetzt ist der Callback hinzugefügt und wir können einfach zurückgehen und die wieder aufrufen die Inputfunktion nochmal aufrufen und schon fertig jetzt haben wir diese JavaScript Funktion die jedes Mal aufgerufen wird und für jeden Befehl haben wir die Adresse und die kompletten Kontext der Register und es ist ganz schön viel und dazwischen haben wir immer den Aufruf der externen Library und dann springen wir aus dem BDI raus und in die libc Funktion die irgendwas macht und dann haben wir ein Return Instruction und es gibt 0 zurück also funktioniert alles so wie vorher also QDBi ist ein Open Source Projekt wir haben es vor ein paar Tagen veröffentlicht und bitte zögert nicht das zumindest mal auszuprobieren es ist unter einer permissiven Lizenz freigegeben das heißt wir sind froh über PyrreQuest und es gibt auch einen Channel auf Free Note schaut es euch einfach mal an und kompensiert mit uns wir möchten unserem Kollegen bei Quarkstab danken für den ganzen Unterstützung und die Beta-Tests und besonders für Ann-Paul und Joe die sehr viel an diesem Release mitgewirkt haben und auch generell vielen Dank an Quarkstab unsere Firma die uns erlaubt hat diese Software unter einer permissiven Lizenz zu veröffentlichen vielen Dank wenn ihr Fragen stellen wollt ein bisschen Speed-Tating wir haben nur eine Minute Zeit ich versuch's mal könnt ihr das mal stellen zu D-Trace und wie D-Trace mit Userspace arbeitet D-Trace ist ganz cool aber es ist nicht wirklich Cross-Plattform und nicht Cross-Architecture der wichtigste Punkt ist hier gibt es die Granularität und die Granularität ist einzelne Befehle und man kann auch vor und nach jedem Befehl das machen und das kann nur an DBI machen und wenn man zu viel Zeit hat aber das ist die Haupt Hauptunterschied und die ergänzen sich ja gegenseitig Entschuldigung ich muss den Vortrag an dieser Stelle dann beenden, weil wir keine Zeiten haben der nächste Talk ist in 15 Minuten und der heißt growing up vielen Dank