 werden wir dann an den Herald, den ich jetzt leider gerade gar nicht so gut höre. Ah, okay, jetzt ist das Signal drin. Unser Vortragende ist ein Unternehmer, der mit sieben Jahren bereits gelernt hat, MS-DOS zu benutzen. Und zu diesem Zeitpunkt hat er dann auch gelernt, den Computer seines Vaters zu nutzen. Und wie er auch während des Vortrags merken werdet, nimmt er gerne Sachen auseinander. Und er möchte uns auch mit seinem Vortrag zeigen, wie man mit LLVM Intermediate Representation fair fremden kann. Ein Runde Applaus für unseren Vortragenden. Applaus. Vielen Dank. Und ich bin etwas überrascht, dass tatsächlich Leute kommen für den Vortrag, weil die Arbeit eigentlich fünf Jahre früher gemacht wurde. Und es ist sehr warm hier. Und als Spanier fühle ich mich eigentlich ziemlich wohl hier mit diesen Temperaturen. Und ich möchte über meine Maßarbeit sprechen. Und in dem Projekt ging es darum, kurz zu fair fremden. Und damals, und das war damals halt ein großes Thema, weil im Gegensatz zu heute kann man nicht mal mit LLVM Sprachen, Javascript transpilen. Und das Ziel von meinem Projekt seinerzeit war es, Angriffe auf gepatchte Software zu verhindern. Hier ist zunächst einmal, hier sind erstmal die rechtlichen Rahmenbedingungen, die mir ermöglichen, diesen Vortrag tatsächlich auch zu halten. Ich möchte euch ein wenig darüber erzählen, wie LLVM funktioniert. Und so, dass ihr dann auch versteht, wie das alles funktioniert. Und ich möchte euch auch erklären, wie man das alles benutzen kann, wie man Angriffe verzögern kann oder auch verhindern kann. Und ich möchte auch eine komplette Demonstration des gesamten Projekts auf meinem Rechner zeigen. Ich hoffe, das funktioniert. Also LLVM ist ein Framework, also ein Rahmenwerk für Compilers. Und es kann Hochsprachen in Assembler übersetzen. Und das Coole ist, dass es sehr viele Werkzeuge und Programme gibt, die mit LLVM interagieren. Und LLVM hat eine Intermediate Representation, also eine Zwischensprache, die dann von allen Hochsprachen genutzt werden kann, um sozusagen eine gemeinsame Sprache auf der LLVM zu unterstützen. Und wenn man dann auch ein Verfremder schreibt, der auf der Intermediate Representation Ebene ansetzt, kann man den von jeder Programmiersprache, von jeder Hochsprache ausnutzen. Und wie man dann die Patches dann verteilt, wäre dann halt auch in dieser Intermediate Representation. Und es gibt verschiedene Frontends, also Programme, die halt Code in einer Sprache in die Intermediate Representation übersetzen können. Es gibt halt die LLVM Frontends für C-ähnliche Sprachen, zum Beispiel implementiert von Klang. Und es gibt halt auch verschiedene Verfremdungstechniken. Zum Beispiel wurde von Chengxi Wang eine Reihe von Techniken entwickelt. Und diese Techniken habe ich dann auch verwendet und weiterentwickelt. Und es gibt verschiedene Methoden. Das erste ist Control Flattening, also den Kontrollfluss durch das Programm abzuflachen und die Sprungpunkte zwischen den Blöcken umzusortieren und auch dynamisch zu berechnen. So, dass man halt nicht eine Gruppe von Ifs und Wilds hat, sondern dass man halt alle diese Code-Berstandteile in ein Bereich im finalen Produkt hat und dadurch halt auch weiß, wie die Sachen relativ zueinander angesorgt sind und entsprechend auch Sprünge direkt berechnen kann und sie nicht voraus kompiliert. Es gibt dann auch noch Konstanten-Verfremdung, so dass die Berechnung der Konstanten erst während der Laufzeit berechnet und sie nicht vorher im Code direkt integriert. Es gibt auch noch undurchsichtige Prädikate, also Kontrollbedingungen, die halt nicht nachvollziehbar sind und diese sind dann sehr komplex und sehr schwer nachzuvollziehbar. Ein weiterer Methode, die sehr alt ist, ist Register-Austausch, sodass man verfremdet, welche Register für eine Instruktion tatsächlich verwendet werden, indem man die Werte sehr viel erst mal getauscht werden, bis dann tatsächlich die richtigen Berechnungen durchgeführt werden. Es gibt einige Tricks, die man damit machen kann. Es gibt dann auch noch toten Code einzusetzen, dass man Code einfügt, der nie läuft, oder zumindest wenn er läuft, kein Ergebnis produziert, das den Rest des Programms beeinflusst und dadurch dann die Leute, die den Code analysieren, halt verwert. Und dann gibt es natürlich noch die Umsortierung von Code, die auch zufällig erfolgen kann. Ein Augenblick, Entschuldigung. Hier ist gerade was durcheinander geraten, wie ihr sehen könnt. Die können diese Verfremdung nutzen, um Patches zu schützen. Und wir können sie natürlich mit beliebigen Programmiersprachen benutzen, weil es beispielsweise C, Haskell, Rust und Swift, LLVM, Intermediate Representation nutzen und natürlich auch noch viele andere Programmiersprache existieren, die das nutzen. Und das Schöne ist, man kann halt sehr viele Architekturen und Sprachen verwenden mit dem LLVM-Framework. Man macht sich nur einmal die Arbeit und kann es dann halt für all diese Sachen wieder verwenden. Aber das macht es natürlich auch schwierig, Sachen zu verwenden, die sehr architekturals spezifisch sind, die in der Intermediate Representation nicht so gut abbildbar sind. Um jetzt über Control Flattening, also die Abflachung von Kontrollstrukturen, von Flussstrukturen durch den Code umzusetzen, erstellt man Fee Notes, die es ermöglichen in LVM Intermediate Representation Werte anzupassen während der Laufzeit und dann auch Berechnungen durchzuführen, die für die Opposition genutzt werden. Und so versucht man halt, die bedingte Sprünge im Programm zu eliminieren und durch unbedingte Sprünge im Programm zu ersetzen. Und das Entscheidende ist auch, dass eine Abhängigkeit zwischen den verschiedenen Blöcken besteht, sodass der Sprung vom aktuellen Block in den nächsten Block auch vom vorherigen Block abhängt. Es gibt immer einen großen Knoten in dem Aufrufgraf, wo alles durchgeht. Und ihr werdet wahrscheinlich sehen, dass es dort dann keine bedingten Sprünge gibt. Oder man dreht das um, man kann den Aufrufgraf berechnen und dadurch kann man einen neuen Graf erzeugen, mit dem man dann halt die Sprünge durchführt. Und ich habe dafür jetzt noch keinen Code, weil das nicht Teil meiner Arbeit war, aber es ist etwas, was man theoretisch machen kann. Ihr könnt auch bedingte Anweisung plus ein Sprung mit dieser Technik erzeugen. Ich möchte jetzt eine Demonstration kurz machen. Ich schaue hier gerade Augenblick. Hier könnt ihr sehen, ich mache es etwas größer. Hier könnt ihr den Unterschied zwischen einer Funktion sehen und der anderen. Auf der linken Seite habt ihr den verfremdeten Code, auf der rechten Code habt ihr den reinen und verfremdeten Code. Und ihr seht, viele Sprünge hier. Wenn wir jetzt fortsetzen, ja, also eine andere Technik ist dann halt Konstanten und Verfremdung. Es ist relativ einfach zu verstehen, dass 3,1415 p ist anstatt p dann tatsächlich zu berechnen. Und hier, also die Art, wie es funktioniert ist, dass wir uns eine Konstante aussuchen oder auswählen und dann wählen wir uns eine Technik aus. Ich habe zwei Methoden implementiert, indem ich beispielsweise ein Array habe, in dem all die Konstanten definiert sind. Und dann nutze ich einen Pointer, um auf diese Konstanten zuzugreifen. Und das macht es schwieriger, nachzuverfolgen, wie der Code funktioniert. Die Möglichkeit, wie man das aufdecken kann ist. Man sieht auch viele Speicherzugriffe mit konstanten Werten drin. Ein anderes Problem ist, dass ich echt nicht gewohnt bin, mit zwei Bitschämen zu arbeiten. Die Demo, die ich hier zeigen will, ist die hier, eine Sekunde, ich starte gerade. Hier seht ihr ein Beispiel für die Unterschiede zwischen den beiden. Wenn ihr hier einen Anfang geht, seht ihr, dass lokale Code relativ einfach ist. Und den multipliziert mit einem unterschiedlichen Satz an Konstanten. Also 1, 2, 3, 4, 5, 6, 7. Wenn ihr auf die linken Seite schaut, dann seht ihr zum Beispiel, um den Wert 2 zu berechnen, wir machen im Prinzip ein X-Ohr, um einen Wert zu kriegen, den wir dann multiplizieren mit der Konstante 1, was Multiplikat 2 ist. Ein super Weg, um einen unnützigen Code zu erzeugen. Versteht, aber die Performance wird natürlich leidender darunter. Deswegen muss man damit ein bisschen aufpassen, das kann man nicht zu sehr übertreiben. Um weiter zu machen, der nächste Teil, weil die nächste Methode ist. Die nächste Technik ist der Split, also das Aufteil in Basisblöcken. Für die, die das nicht kennen, ein Basisblock ist, ein Satz an Instruktionen, die keinerlei Sprünge zwischen haben. Also eine komplette Linie durch laufende Folge von Auenweisungen. Das heißt, der Control Flow fängt im Prinzip an vom Block an und läuft dann bis zum Ende durch, ohne irgendwie rauszuspringen. Das ist nicht 100% richtig, weil in LLWM manche Sachen können auch Teil von so einem Block betrachtet werden, aber was wir machen, ist, wir teilen diese Basisblöcke in zwei Teile, um mit extra Verzweigungen einzuführen. Und das verträgt sich schlecht mit Control Flattening. Das heißt, wenn man ein Basisblock mit 50 hat, dann teilt es das auf, wenn zum Beispiel 5 Blöcke mit 10 sein und es dauert halt sehr viel länger, um rauszufinden, was eigentlich wirklich passiert. Der Weg, wie man das rausfinden kann, ist, dass man eine Menge unkonditionale Sprünge zwischen verschiedenen Basisblöcken kriegt und die meisten davon sind nütz und nütz. Es gibt wenig Gründe, unkonditionale Sprünge zwischen zwei Blöcken. Und der einfachste Weg, das zurückzubauen, ist im Prinzip einfach, die Blöcke wieder zu merken. Das war jetzt auch nicht Teil meiner Arbeit, aber hoffentlich macht das jemand, das ist nicht weggeschwierig. Was man auch noch machen kann, ist, dass man alle die Blöcke verwürfelt, also in ihrer Reihenfolge zufällig anordnet. Das heißt, jeder Block ist im Prinzip unabhängig voneinander. Solange der erste Block derselbe ist, kann man alles andere durcheinanderwürfeln und es hat keinen Einfluss auf die Wirkungsweise. Das kann man natürlich erkennen, weil üblicherweise, wenn man Code mit einem Compiler bearbeitet, dann arbeitet er den Code im Prinzip von oben nach unten ab und es ist sehr unwahrscheinlich, dass man Returns relativ früh im Code findet. Man erwartet normalerweise, dass Return am Ende der Basisblöcke sitzt, am Ende der Funktion. Und dann, um das wieder umzukehren, muss man sie einfach nur wieder sortieren und in die originale Ordnung zurückzubringen, indem man die Sprünge einfach auflöst. Ich mache die Demos am Ende, das Vortrags damit schneller geht. Runt Ins ist eine andere Technik, die versucht, die Notes durcheinander zu würfeln. Die Idee ist, man nimmt eine Instruktion, die keinerlei Abhängigkeiten hat. Die nimmt man als die erste oder die letzte. Und dann markiert man alle anderen Instruktionen, die keine weiteren Abhängigkeiten haben und ordnet die so an. Und immer so weiter, bis man alle aufgelöst hat. Das ist im Prinzip etwas, was Performance Fee tut, weil man die Out of Order Execution durcheinander bringt. Es ist ein bisschen sehr schwieriger, das zu erkennen, weil der Compiler wird normalerweise den Code in einem Basisblöcke relativ zufällig angeordnet, aber man kann sehen, dass es entweder merkwürdige Optimierung ist oder sehr unoptimiert. Wenn ihr Code habt, der relativ weit weg sein sollte im Output und plötzlich sehr nah beieinander ist, dann könnte das ein Hinweis sein, dass ihr im Prinzip einen Random habt. Das kann man umkehren, indem man auch wieder die normale Ordnung versucht rauszulesen. Das wird immer noch nicht das Ganze wieder auffäben, weil es immer noch anders ist, als die, die es original sein wird, aber es wird trotzdem einfacher werden, den Code zu lesen. Und dann kann man natürlich auch Funktionen zufällig anordnen. Das ist normal nicht so wirklich hilfreich, aber wenn man es schwerer zum Lesen machen will, dann ist es relativ gut, weil normal hat man eine relativ gute Ordnung, so wie in der Quellcode in der Art und Weise, wie der Outer sie runtergeschrieben hat. Also zum Beispiel die Hauptmethode am Anfang und dann alle, die sie aufruft und immer so weiter, so dass sie quasi hierarchisch aufgebaut sind. Und das hebt es hier auf. Und man kann dasselbe machen mit globalen Variablen. Die kann man auch alle zufällig anordnen. Normalerweise werden sie angeordnet in der Art und Weise, wie sie benutzt werden, aber man kann sie natürlich auch einfach wieder zufällig anordnen. Das kann man nicht wirklich einfach erkennen, aber es ist, man kann es zum Beispiel einfach alphabetisch machen und das ist dann ein bisschen einfacher zu lesen, um es wieder umzukehren. Das wird dann nicht ganz so das sein wie vorher, aber immer noch besser. Dann gibt es noch SwapOps. Das ist das nächste an einem Register Swap, den wir vorhin besprochen haben, den wir praktisch umsetzen können. Die Idee ist, dass wenn man 1, 1 bis 2 addieren kann, dann kann man auch zu 2 plus 1 addieren. Das heißt, wir markieren alle Operationen, den operanten Mann austauschen kann und entscheiden uns zufällig, ob wir die umkehren wollen oder nicht. Und das wieder umzukehren ist, man kann versuchen, die auch wieder umzudrehen und sehen, ob das dann ordentlich ist. Das heißt, man kann zuerst die Konstanten wählen als operanten, dann bestimmte Register, die Register in einer, zum Beispiel die Register, die an der Funktion am nächsten sind, auch als erstes kommen. Und das bedeutet aber auch, dass verschiedene Versionen vom Code werden nicht die gleichen Output haben nach der Randomisierung sein, kann man das erkennen. So, jetzt sind wir bei diesem Teil des Codes, bei des Talks. Dann möchte ich mal die Transformationen zeigen, weil die sind für euch wahrscheinlich das interessanteste. Ich habe hier, ich wange an mit dem Control Flattening, bzw. was ich euch jetzt zeigen will, ist die anderen. Genau, das Basic Block Splitting machen wir mal am Anfang und dann das zufällige Anordnungen von den verschiedenen Funktionen operanten und was wir gesehen haben. Das heißt, wenn wir jetzt den Code hier aufmachen, das ist jetzt zum Beispiel der Funktion Split. Das heißt, hier war ein relativ gradliniger Code, dem wir haben keine Verzweigungen und jetzt wird es sehr viel schwieriger, dem zu folgen, weil man hier eine Verzweigung aller paar Instruktionen hat. Für sich selber ist das ziemlich nutzlos, weil das ist wirklich einfach wieder aufzulösen und es bringt keinen wirklichen Vorteil. Aber wenn man das Control Flattening nachher anwendet und die Person, die das wieder deopfuskieren will, entschlüsseln will, zwingt es dazu, immer sich den Main-Node anzugucken, um das wieder aufzulösen. Das ist natürlich sehr aufwendlich und sehr schwierig. Das ist nicht so, was ich euch zeigen würde, ist das zufällige Anordnungen von Basic Blocks, die man umtauscht in der Reihenfolge. So, das sehen wir jetzt hier. Also, ihr könnt hier sehen, der Ablauf ist mehr oder weniger derselbe. Sie haben mal die Blocke jetzt anders angeordnet, weil jetzt sind sie in der zufälligen Ordnung. Die Code ist der Originale. Sieht ihr, dass der Control Fluss mehr oder minder linear ist? Und hier, über links, muss man immer wieder zum einen ELS gehen und zum anderen ELS, und dann das erste F in dem linken Teil. Das erste ist in der ersten Helle auf der rechten Seite. Es ist jetzt am Ende bei der ersten Seite und auch andere Bereiche werden einfach umsortiert, wie ihr hier seht. Das heißt, es springt kreuz und quer im Code hin und her, badab, beim Durchlauf dieser Passage. Es wird natürlich schwieriger, den hier zu folgen, weil es wird sehr viel komplizierter, den Code wieder richtig anzuordnen. Auf eine ähnliche Weise, wenn man das Anweisungsumordnungen verwendet, also das Instruction Reordering, dann haben wir ein ähnliches Bild. Was ihr hier sehen könnt, ist, die Ausdrücke, die wir im Original hatten auf der rechten Seite, haben eine total andere Anordnung, als dem, den man links sieht, der ein bisschen office skated wurde. Was wir jetzt hier sehen können, der Code sieht deutlich anders aus, als der, der ursprünglich kompiliert wurde, was ja auch unser Ziel ist. Das hier sieht, hier hat man einen anderen office skated Key verwendet. Da sieht man, dass viele der Knoten wurden deutlich anders angeordnet und der Code, der gelb und grün und blau markiert ist, sind halt die korrespondierenden Teile. Das Ziel dieser Operation hier ist, dass man dem Angreifer sehr viel schwieriger macht, unseren Code zu analysieren, um zu verstehen, was der originale Console war. Was ihr hier sehen könnt, ist ein drittes Beispiel, was, wie viel Unterschiedes macht, wenn man den Key zwischendrin ändert und dann kommt ein ganz anderes Ergebnis bei rum. Jetzt kommen wir zum Swap Ops, also wenn man Funktionen und Globals austauscht in ihrer Reihenfolge. Das hat jetzt relativ wenig erstellen im Code, wo ich das eigentlich anwenden kann. So, hier. Hier könnt ihr es sehen. Wir werden es vergleicht. Wir verdrehen ein paar von den operanten, operanten bei den Additionen und bei den Multiplikationen, weil beide komutativ sind und deswegen kann ich die vertauschen. Wenn weitere, die komutativ sind, ist es XOR. Wir können sehen, dass das Ergebnis ziemlich anders ist, wenn man die Keys verändert, die wir bei der Verschüßlung verwenden. Also wie gesagt, wir verwenden dafür einen zuverzahlen Generator. Hier sieht man, dass man einen anderen Vertauschung kriegt, wenn man einfach andere Bits von dem Key hat. Und hier kann man sehen, wie es bei einem weiteren anderen aussieht. Jetzt sind nochmal andere Teile von dem Key vertauscht. Jedes Mal, wenn man einen Teil von dem Schlüssel, mit dem man verschlüsselt, ändert, wird jedes Mal ein anderer Output kommen. Das macht es für den Angreifer, bzw. der ist reverse ingenieren will, sehr viel schwieriger. Und am letzten möchte ich noch die Demonstration zeigen für das Reordering von globalen Funktionen und anderen Globals. Also das hier ist der Originalcode. Wir können das sehen, wir haben einen Haufen Globals am Anfang der Funktion und die sind alle sortiert alphabetisch, weil das so was im Originalcode. Und auf der rechten Seite können wir das sehen, dass sie zufällig angeordnet sind, sodass man eine komplett andere Ordnung kriegt. Jedes Mal, wenn man die Anwendung macht, hat man eine andere Ordnung, die man kriegt. Hier, das möchte ich euch jetzt zeigen, wie sich das hier mal ändert, wenn ich einen anderen Obfuscation Key verwende. Ihr könnt jetzt sehen komplett andere Anordnungen von der gleichen Version des Codes. Und jetzt zeichne ich noch das Dritte. Hier, der Code wechselt halt relativ auf die Position, je nachdem, wie oft man es anwendet. Das ist immer mit welchem Key. Das war jetzt der erste Beispiel, die erste Hälfte von der Demonstration. Nachher möchte ich euch hier noch alle zusammenzeigen. Perfekt. Jetzt will ich noch darüber reden, wie ich das gemacht habe. Und der Angreifer muss herausfinden, was geändert wurde. Unter Annahme, dass das nicht schon öffentlich bekannt ist. Und zum Beispiel, wenn es halt nur ein kleiner Patch ist, dann ist es relativ einfach, herauszufinden, was genau im Code geändert wurde. Und was dann auch die Sicherheitslücke ist. Und nun kommt der Teil, wo ihr lachen könnt, weil ich habe sehr viel Arbeit da reingesteckt. Weil ein Compiler normalerweise immer den gleichen Code produzieren sollte. Aber bei meinem Projekt kommt es halt auch darauf an, zufällige Ergebnisse zu produzieren, sodass bei jedem Compiler-Durchlauf ein anderes Ergebnis rauskommt. Deshalb aber natürlich trotzdem immer noch das gleiche Verhalten hat. Und um diese Zufallsergebnisse zu erlangen, verwenden wir Pseudo-Zufallszahlen. Und damit das Ganze funktioniert, muss dieser Zufallsgenerator gute Eigenschaften hat. Und das bedeutet, dass ... Also, man muss halt auch mal schauen, was will man genau verfremden, also den gesamten Codebase oder bestimmte Funktionen. Wahrscheinlich eigentlich nur den Teil, den man halt tatsächlich für den Patch geändert hat. Und der Code kann umsortiert werden. Und es ist immer noch möglich, die eigentlichen Veränderungen zu finden. Deswegen muss es halt echt wichtig, dass man die Verfremdung etwas schwerer, also etwas intensiver macht. Das Headset vom Speakers gerade runter gefallen. Yes, let's see. Okay. Also, ein anderes Problem ist, dass natürlich das Patches in der Regel klein sind. Und das Ding ist natürlich, wenn wir jetzt etwas neu kompilieren, dann ändern wir halt das gesamte Programm. Deswegen ist es sinnvoll, Verfremdungslüssel zu verwenden, so, dass wir dann wirklich sehr spezifisch sagen, was genau soll verfremdet werden. Und wir verwenden dann immer die gleichen Transformation für die Verfremdung. Das heißt, wenn wir den Codepads gepatched haben, kriegen wir halt den gleichen compiler-Output jedes Mal, und jetzt kommt die echte Demo. Und zwar haben wir ein Hallo-Welt-Beispiel. Und ich werde meine Konsole hier rüber verschieben. Und... ...unified. Also, das war's. Das war's. Und... ...unified. So, jetzt könnt ihr das gleiche sehen, was ich auf meinem Bildschirm sehe. Und einmal ein bisschen größer machen, damit es leichter zu lesen ist. Yes, I am one of these evil people that actually names his folders in Spanish, in case you are wondering. Ich bin Spanish. Okay, hier haben wir die Dateien. Und den Code, den wir verfremden wollen, ist hier. Wie ihr sehen könnt, gibt es ein kleines Array. Und hier haben wir die Anfrage nach dem Namen. Und wenn der Name halt leer ist, dann gibt das Programm halt Welt aus anstatt den Benutzernamen, der nicht angegeben wurde. Wenn wir das jetzt kompilieren, kriegen wir das hier. Der erste Schritt produziert die LVM-Repräsentation. Und... Und... Die zweite Zeile sorgt dann dafür die Verfremdung. Okay, also es funktioniert immer so, wie es sein soll. Und jetzt machen wir die Verfremdung. Und... Und... Und... Diese Zeile versucht, alle Transformationen durchzuführen. Zunächst einmal wählen wir ein Modulschlüssel. Und dieser Schlüssel wird verwendet, um den Zufallsgenerator zu initialisieren. Und dann teilen wir das Ganze in einfache Blöcke auf. Und dann haben wir hier die Kontrollflussabflachungstransformation. Und wir verwenden hier einen kleinen Parameter, um exponentielles Wachstum zu verhindern. Und am Ende werden wir alles zusammen kompilieren. Okay... Und wie ihr sehen könnt, funktioniert es momentan noch nicht ganz so, wie ich es vorgestellt habe. Und der Grund, warum es nicht... Ich glaube, ich weiß, woran es liegt. Yes, I can start one. Okay, wechsel zu einem anderen Mikrofon. Seht ihr, das passiert, wenn man die Demo wirklich sehr spät am Abend oder in der Nacht vorbereitet? Okay... Nein, leider nicht. Zum Glück habe ich den kompilierten Code hier. Also rufen wir den direkt auf. Das ist die verfrontete Version. Das ist die verfrontete Version des Codes. Und wie ihr sehen könnt, verhält es sich genauso wie das nicht verfrontete Programm. Wenn wir den gleichen Verfremdungsschlüssel verwenden, kriegen wir wieder exakt das gleiche Ergebnis. Also auch wenn wir zufallswerte verwendet haben durch den Modulschlüssel, haben wir am Ende das gleiche Endergebnis. Typing. Immer noch weiter tippen. Es ist natürlich nicht wichtig, wenn ihr die nonminare Files benutzt. Man kann sehen, dass es einige Unterschiede gibt zwischen dem nicht verfronteten Code und dem verfronteten Code. Der neue Code ist sehr langsamer. Es ist sehr langsamer. Es ist sehr langsamer. Es ist sehr langsamer. Der neue Code ist deutlich länger. Und wenn wir den Verfremdungsschlüssel ändern, sehen wir, dass es sehr viele Veränderungen oder Unterschiede gibt. Und das passiert, weil wir diesen Compiler verwenden. Und ich denke, es ist jetzt besser, zu den Fragen zu gehen, weil ich mir nicht sicher bin, ob ich noch auf die Schnelle die Sachen hier zum Laufen kriege. Ganz vielen Dank. Eine Runde Applaus, bitte. Wir haben drei Minuten für Fragen. Wenn ihr eine Frage habt, bitte, geht zu den Mikrofonen. Und dann könnt ihr Fragen fragen. Bitte, nach Möglichkeit fragen, und keine Kommentare. Vielen Dank für den Vortrag. Was ist der überlaut, die size und runtime overload? Was ist der zusätzliche Laufzeit nach der Verfremdung im Gegensatz zum nicht verfronteten Programm? Es hängt sehr von den Parametern ab, die man wählt. Wenn man niedrige Werte verwendet für den BBS-Split, dann ist der zusätzliche Inhalt oder zusätzliche Laufzeit eher gering. Und wenn man nicht verfrontet, dann ist der zusätzliche Inhalt und ich habe einige Tests laufen lassen und ich habe nicht wirklich große Unterschiede festgestellt. Etwas, was wichtig ist, dass die Intermediate representation immer noch durch Optimierer zurücktransformiert werden kann, sodass einige Verfremdungen verloren gehen. Gibt es weitere Fragen? Sieht nicht so aus, als wenn es noch Fragen gibt. Oh, Moment, da ist noch eine Frage. Frage bitte. Ist das Projekt Quell-offen oder Open-Source? Nein, etwas ironisch gesagt, ich bin eine böse Person. Okay, Übersetzungsfehler. Nein, es ist wirklich nicht Open-Source. Okay, Moment. Übersetzungsfehler ist Übersetzungsfehler. Also ja, der Code ist Open-Source und so hier ist der Link zu dem Code. Da ist der Link zu meiner Masterarbeit und auch der Link zum Code. Ich habe derzeit eine etwas veraltete Version veröffentlicht und aber ich habe auch in der Zwischenzeit ein bisschen weitergearbeitet auch im Hinblick auf diesen Vortrag, den ich heute halte. Aber der Code ist auf jeden Fall stabil und ihr könnt ihn ausprobieren. Ich hoffe, das beantwortet eine Frage. Ja, vielen Dank für die Frage. Ich hatte tatsächlich vergessen, das noch zu erwähnen. Vielen Dank. Noch einmal einen Runder Applaus, bitte. Ich möchte mich auch noch mal bedanken bei den Leuten von dem Bornhead Camp, weil ich bereits eine vorher dort ein Talk gegeben habe, der mir auch geholfen hat. Mich auf diesen Talk. Also, ja, ein warmer Applaus. Es ist immer sehr gut. Okay, das ging jetzt ein bisschen.