 So C++-Programmers kind of use typecasting like we just go for a beer at a bar. Jetzt hier, Programmierer benutzen typecasting, so wie wir einfach nur einen Trinken gehen. Und man muss mit den Typen sehr vorsichtig umgehen. Speilherzugriff kann ein Problem sein. Und das führt zu allen möglichen Problemen, insbesondere Typenverwirrung oder Type-Confusion. Matthias ist hier zu uns gekommen von Purdue University, um uns zu erzählen von einer Compiler-Erweiterung, die einige von diesen Problemen beheben sollte. Und jetzt übergebe ich an Matthias. Vielen Dank für die Einführung. Wir sprechen heute über Type-Confusion und verschiedene Arten von Typ-Sicherheit und Verwundbarkeiten, die ausgenutzt werden können von Angreifern, die da mit Code Execution erreichen können. Wir haben an die Zeit daran gearbeitet, die Typ-Hirarchie von C++ auf verschiedene Vulnerabilities zu untersuchen, die dann ausgenutzt werden können. Einmal melden bitte, wer Programmiert C++. Ja, wesentlichen jeder. Wer hat mehr als, sagen wir mal, 10.000 Zeilen C++ geschrieben? Das sind auch immer noch viele. Das ist eine gute Voraussetzung. C++ Programmierer, ihr kennt bestimmt die verschiedene Formen von Type-Casting, Static-Cast, Dynamic-Cast und noch einige andere Arten. Und interessanterweise hat C++ überhaupt keine Art von Typ-Sicherheit. Das heißt, dieser Vortrag könnte auch beschrieben werden folglich mit einer Chance für Taschenrechner. Und es gibt viele Möglichkeiten, diese Type-Confusion auszunutzen. Type-Confusion, damit kann man Remote Code Execution erreichen. Das heißt, dann angreifert kann diese Arten von Typ-Verwirrungen ausnutzen, um beliebigen Code auf deinem System auszuführen. Und insbesondere Browser, Hypervisor und Kernels sind Angriffsziele für diese Art von Type-Confusion und die auszunutzen. Als, wie man kürzlich gesehen hat, auf der Pound to Own Competition. Und hier sehen wir einige Arten von dieser Type-Confusion, die benutzt werden, um Taschenrechner aufbauten zu lassen überall. Der Taschenrechner zeigt einfach, dass man beliebigen Code ausführen kann. Und wenn man drüber nachdenkt, dann sieht man, dass die Angriffsfläche, die wir auf unserem System haben, ist riesengroß. Wir arbeiten nicht einfach nur mit Systemen von wenigen Tausend Code-Zeilen, sondern es geht um viele Millionen von Code-Zeilen. Das heißt, die Abstraktion ist riesig. Und wenn man Google Chrome zum Beispiel anguckt, wir haben mehr als 100 Millionen Code-Zeilen. Und das ist eine riesige Codebase, die wir haben. Und es ist sehr schwierig, sich zu verteidigen gegen Exploits hier. Und die Leute bei Google machen ganz tolle Sachen mit Code Reviews und Tests und alle verschiedenen Möglichkeiten zu testen. Aber es gibt immer noch ganz viele Möglichkeiten von Type-Confusion hier. Und es ist nicht nur Google Chrome mit 67 Millionen Code-Zeilen, sondern auch etliche andere Systeme. Da ist der Window-Manager. Es gibt noch die Standardbibliotheken, der Linux-Kernel, der Hypervisor usw. Und das sind dann mehr als 100 Millionen Code-Zeilen insgesamt. Und da gibt es natürlich viele Möglichkeiten für Type-Confusion. Wir untersuchen diese verschiedenen Arten von Type-Confusion und versuchen daraus zu finden, wie man solche Vulnerabilities finden kann. Wie man welche Möglichkeiten ein Angreifer hat durch diese Typverwirrung. Was ist der Attack-Vector? Was sind die Attack-Primitive? Wie kann man die Attack-Primitive bauen? Wie kann man das automatisch rausfinden, da Exploits wird zu finden? Das Angriffsmodell ist so, wir haben einen externen Benutzer. Ohne jede Möglichkeit, Code auszuführen. Man muss sich vorstellen, dass an einem Ende ein Programm ist, das auf Anfragen antwortet. Man schickt eine Anfrage und bekommt eine Antwort. Man hat bestimmte Arten von Berechnungsmöglichkeiten. Man hat eine Anfrage und eine Antwort. Zwischenfindet eine Berechnung statt. Man ist einfach limitiert, was man ausführen lassen kann. Wenn man ein Webserver anfragt, dann schickt man eine Anfrage hin und bekommt ein HTML-File zurück. Das kann man dann im Browser rendern. Ein Angreifer versucht, ein Request so zu bauen, dass er statt HTML dann zum Beispiel eine Shell bekommt. Und unter den Schritten sind dabei, dass man ein externer Benutzer ist, der eine Anfrage stellt, um ein Dokument bekommt für einen lokalen Benutzer, um letztendlich ein Administrator zu werden. Und diesen Schritten folgt man, um die Möglichkeiten zu erhöhen, die man hat, Schritt für Schritt. Und das Schöne daran ist, dass man ein externer Angreifer kann, einfach diese Angriffe auslösen durch ganz einfache Möglichkeiten, indem man einfach nur eine Anfrage hinschickt. Aktuelle Software, da geht es im Wesentlichen um Control Flow, das Übernahme von Control Flow. Und die Software Security Community hat in den letzten 20 Jahren oder so, haben wir ganz viele Mitigations, ganz viele Verteidigung dagegen gefunden, um solche Exploit-Möglichkeiten zu finden. Und wenn man jetzt diesen Angriff nimmt, den ich eben gezeigt habe, dann ist es sehr wahrscheinlich, dass eine große Menge Code Verwundbarkeiten enthält. Und wir arbeiten an Mechanismus, die Integrität und Verfügbarkeit sicherzustellen, selbst wenn es Vulnerabilities gibt. Das heißt, am Anfang müssen wir akzeptieren, dass da Fehler sind in unserem Code. Das heißt, alle Verteidigungen, die wir machen, sind vor allem konzentriert darauf, den kompletten Exploit auszuführen. Und in den letzten mehr als 20 Jahren haben wir mehrere Mitigations und Verteidigung erfunden, die es immer schwerer machen für Angreifer, wirklich Code auszuführen auf unseren System. Das heißt, wenn man den Control Flow übernehmen will auf einem System, muss man erst mal durch mehrere Reifen springen, um dann den Code ausführen zu können. Und dann kann man zum Beispiel den Taschenrechnerstaaten oder andere Kommandos ausführen. Ein Control Flow Highjack Attack beeinflusst den Adressraum von einem Prozess, von einer Applikation, um die verschiedenen Code Pointer und Daten zu beeinflussen, so dass etwas anderes ausgeführt wird. Also man kann sich vorstellen, dass der Code ist normalerweise nur der Web Server, aber anstatt dass er ein Webdokument ausliefert, willst du, dass er eine Schelle öffnet, die dann die vollen Berechnungsmöglichkeiten eröffnet, sodass man interagieren kann mit dem System und vielleicht noch die Privilegien erweitern kann auf Administrator. Und wir haben eben die Möglichkeiten sehr stark eingeschränkt, die einen Angreifer haben kann, selbst wenn es Verwundbarkeiten gibt, auch Typ-Sicherheitsverwundbarkeiten. Und diese Folie zeigt, das zeigt, das sind abstrahärter Formen. Das heißt, wir sehen hier den Code, den kann man nur lesen und ausführen. Und ein Angreifer, das ist nur das Einzige, was lesbar und ausführbar ist. Das heißt, ein Angreifer kann nicht einfach Code einfügen. Und das ist eine der Verteidigungsmöglichkeiten, die wir haben. Und dann gibt es dann eben noch den Hip und den Scrap, die kann man lesen und schreiben, aber nicht ausführen. Das heißt, die einzige Möglichkeit, wie ein Angreifer das beeinflussen kann, ist, dass er die Daten ändert, um dann den Code zu ändern. Und was wir haben, ist, wir haben eine große Menge Code-Pointer auf dem Hip und auf dem Stack, die zeigen auf eine Code-Region. Um den Code auszuführen oder um den Control Flow zu übernehmen, können wir einfach diese Code-Pointer überschreiben und dann die auf eine andere Stelle zeigen zu lassen. Und das ist genau das, was passiert beim Control Flow High Jack. Und das kann auch Code-Pointer sein, die direkt auf den Code zeigen. Oder wir können auch über Hip-Tables gehen. Das ist eine Art, wie C++ Inheritins Hand habt über virtuelle Funktionen. Das heißt, man erlaubt es, dass die Funktion überschrieben werden kann durch die Klassenhierarchie. Und wenn man eine virtuelle Funktion nimmt, dann übernimmt man einen Code-Pointer und lässt ihn auf eine bestimmte Klassenbasierte Implementation zeigen. Und als Angreifer kann man versuchen, diese ganzen Pointer umzubiegen und dann den Code irgendwie zusammenzustückeln, dass er das tut, was man möchte. Wie die Handzeichen vorhin gezeigt haben, sind die meisten von euch bereits C++-Programmierer, aber ich möchte doch einmal auf das C++-Casting-Verhalten eingehen. Selbst wenn ihr C++ eine Weile benutzt habt, seid ihr vielleicht nicht ganz klar, wie die verschiedenen Cast-Konstruktionen in einer tatsächlichen Maschinencode in der Applikation kompiliert werden. Wir haben verschiedene Cast-Operation, das ist eines der statische Cast oder dynamische Cast. Ein statischer Cast kann ein Pointer auf einem Objekt in einem anderen Typ umwandeln. Das Vorteil ist, es ist ein statischer Cast, es ist sehr, sehr schnell, aber er macht nichts. Er macht nur zur Compile-Zeit ein Check, ob die Umwandlung erlaubt ist. Und wenn es irgendein Fahrt in der Klassenheraiche gibt, der vom Quell-Typ, vom Ziel-Typ geht, dann ist der Cast erlaubt. Es braucht keinerlei Laufzeitenformation, es führt keinen Overhead ein, was wirklich gut ist für die Performance, aber es gibt einem keinerlei Sicherheitsgarantien. Ein dynamischer Cast auf der anderen Seite führt einen dynamischen Check zur Laufzeit aus. Es ist sehr ähnlich zu einem Cast in einer anderen Programmiersprache wie Java. Da wird tatsächlich erzwungen, dass das Objekt tatsächlich von dem Typ ist, auf den es gecastet wird. Auch in C++ führt ein dynamischer Cast zu einem Laufzeit-Check. Um tatsächlich einen Laufzeit-Check auszuführen, braucht man Laufzeit-Typinformationen. Wir müssen wissen, was ist der Typ der Speicherbereich, den wir gucken. Man muss den zugrunde liegenden Typ des Speicherobjekts irgendwie zur Verfügung haben. Das ist eines der Nachteile von C++. Von C++ ist im Wesentlichen eine Erweiterung von C, und in C ist alles im Prinzip ein ungetübter Speicherbereich. Alles ist einfach nur Haufen von bytes. Wenn man einen Zeichen-Array hat, ist es einfach nur eine Menge von bytes. Bei einem dynamischen Cast benutzt man einen eindeutigen Identifizierer für ein Objekt, um zu entscheiden, welchen Typ es hat. Da wird zum Beispiel der VTable-Pointer benutzt. Dass wirklich es einen eindeutigen Identifizierer für ein Objekt haben. Das erlaubt ihm zu sagen, dies ist der Laufzeit-Typ des Objekts. So kann man zwischen verschiedenen Objekttypen unterscheiden. Lass uns etwas detaillierter in das Casting-Verhalten gucken. Wenn man einen statischen Cast hat, wie Caste, ein Objekt B in einem Pointer of Greta, das wird in ein Load der Pointers B kompiliert und dann eine Speicheroperation in das X-Register dieser anderen Area. Es passiert keinerlei wirklicher Typüberprüfung. Der Compiler macht nur eine Möglichkeitstest-Check von B nach A beim Kompilieren. Wenn man einen dynamischen Cast ohne Optimierung mit O0 übersetzt, sieht man, dass ein ganzen Haufen mehr Code erzeugt wird. Man lädt den Pointer, macht einen Null-Check, dann laden wir den Pointer zu der Greta-Classe und zu der Basis-Klasse und dann führen wir einen vollen dynamischen Cast aus. Das ermöglicht es uns zu überprüfen, dass der tatsächliche Typ des Objekts zu dem erwarteten Typ entspricht und wir machen einen vollständigen Laufzeit-Check. Wenn wir das optimieren, dann laden wir die beiden Klassenpointer und wir testen, dass der Result des Casts wieder erlaubt oder die Programme dranzeigen. Vergleichen das mit dem Typ des Reicheners und führen dann gegenfalls den Cast aus. Ein statischer Cast optimiert nichts. Wir kriegen keine Instruktion. Ein statischer Cast führt überhaupt kein Laufzeit-Overhead und keinerlei Laufzeit-Überprüfungen. Erinnert euch an das? Ein statischer Cast führt nicht dazu, dass der Laufzeit irgendwelche Instruktionen ausgeführt werden. Kein Performance-Overhead, keinerlei Sicherheitsgarantien. Mit diesem Wissen können wir uns fragen, was ist Typverwirrung? Type Confusion folgt aus unzulässigem Downcast. Nehmen wir ein, wir haben eine Elternklasse und zwei abhängige Klassen, Kild 1 und Kild 2. Wenn wir jetzt ein Objekt des Types, Kild 1, also Kild 1, alluzieren und in dem Tip-Pointer speichern, können wir es zu dem Parent-Type Casten. Da diese beiden Klassen voneinander abhängig sind, ist das ein gültiger Cast. Wir können den Pointer auf P irgendwo hin speichern, und wir können das Parent-Objekt mit den Feldern des Parent-Objects benutzen. Als zweiten Step können wir das Parent-Objekt in einen Child-Objekt Casten. Aber dieser statische Cast macht keine Check, und das würde zur Laufzeit zur Typverwirrung. Das ist wirklich das, wo der ausnutzbare Verhalten kommt. Mit diesem statischen Cast, der nicht gecheckt wird, kann dieser statischen Cast missbraucht werden, um die zugrunde liegende Speicherbereich als einen anderen Typ zu interpretieren. Lass mich ein bisschen mehr Background über diese Typverwirrung geben. Wir haben die Parent-Glas und die Child-Glas. Wir nehmen nur die beiden, um es etwas einfacher zu machen. Das Parent-Objekt hat nur eine einzige Variable in sich, X vom Typ Int, und die Kind-Klasse hat einen zweiten Wert und eine virtuelle Funktion, die Print-Gemant genannt wird. Wenn wir ein P-Objekt haben, alluzieren wir nur die 4-byte für den integer. Wenn wir ein C-Objekt allocieren, haben wir den V-Table-Pointer, der auf den Speicherbereich zeichter, der den ganzen Code-Pointer enthält, und wir haben den X und den Y-Speicherbereich, der benutzt werden kann. Lass uns annehmen, wir alluzieren ein neues P-Objekt. Wenn wir einen statischen Cast auf C durchführen, dann steigt dieser C-Pointer oberhalb des tatsächlichen Objekts. Und das zugrunde liegende Objekt, die Daten, die an der Stelle sehen, würden als ein V-Table-Pointer neu interpretiert werden, zusammen mit dem Y-Integer, also Y-Integer, der gelesen und geschrieben werden kann, das den zugrunde liegenden Speicherbereich zu geriffen werden. Das führt zu Speichersicherheitsverletzung und zu Übernahme des Kontrollflusses. Das ist das Erste, was passiert. Es verletzt die Integrität der zugrunde liegenden Anwendung, und es ist der wichtigste Angriffsweck dafür, einen Angereifer diesen Fakt auszunuten. Es kann entweder als eine Speichersicherheitsverletzung benutzt werden oder um den Kontrollfluss zu übernehmen. Wie benutzen wir diesen Vulnerability-Type um eine Primitiv für den Ausdruck einer Sicherheitslücke zu bauen? Nun, wenn man da Type-Confusion, also Typ-Verwirrung ausnutzen will, versucht man zwei Pointer zu kontrollieren, die einen verschiedenen Typ haben, die beide zu der gleichen Speicherbereich zeigen. Diese beiden Pointer von verschiedenen Typen erlauben, ist ja die Felder des Speichers, als verschiedene Typen anzusprechen. Wir haben zum Beispiel zwei Pointer auf diese Speicherbereich, zum Beispiel auf dem Ersten, ist der erste Eintracht als ein V-Table-Pointer, und dem zweiten Typ ist es, als eine Long-Integer interpretiert. Und wenn man den Setter für diesen Long-Wert benutzt, dann kann man den V-Table-Pointer in der anderen Ansicht überschreiben. Also stellt ihr an, man mittels den ersten View, also den rechten View, um den Wert zu setzen, und den zweiten, um dann darauf zu dispatschen und den Code aufzurufen. Eine einfache Demonstration um die Mächtigkeit dieses Angriffs zu zeigen, nehmen wir an, wir haben eine Basisklasse, die eine bestimmte Funktionalität empfolgt, und wir haben zwei Unterklassen, die bestimmte Funktionalität implementieren. Und wir haben eine Art Executor as a Service. Beide sind als virtuelle Funktionen implementiert, und wir möchten unsere, einfach weil wir unser Framework auf dieser Funktionalität bauen wollen, und wir möchten diese Funktionalität überschreiben können. Also implement dieser Executor Service, eine einzige Funktion namens Exit, die dann an System übergeben wird, um es als ein Programm zu geben. Und die größte Funktion, drückt den String einfach nur als String aus. Es gibt keine Art, das zu verwirren, weil die haben verschiedene Namen. Richtig? Aber, wenn wir zwei Basisobjekte alluzieren, B1 und B2, der erste vom Typ Greater und den zweiten vom Typ Exec, dann können wir tatsächlich, können wir tatsächlich, diese Objekte dispatchen, wir alluzieren diese beiden Objekte vom Typ Greater und Exec, dann casten wir das erste into Greater, und wir rufen Greater Say Hi auf, und der Greater sagt Hi. Bei dem zweiten Objekt casten wir es nach Greater. Der Compiler führt einen Compiler zum Check aus und sagt, okay, ja, der Greater ist abhängig von, also abgeladete Klasse von BaseTag, also ist dieser statische Cast erlaubt, und dann können wir say hi mit diesem seltsamen String, Userbin Xcalc, ausrufen, und es funktioniert, der Compiler beschwert sich nicht. Und das ist wirklich lustig, wenn man sich das anguckt. Wir sehen, das ist genau der Code, den ich gerade gezeigt habe. Wir haben den statischen Cast nach Greater und wir haben den statischen Cast nach Greater hier, und wir rufen zweimal Say Hi aus. Also wir rufen Make auf, bauen das Programm, und dann wir alluzieren zwei Objekte vom Typ Greater und Exec, und wir rufen zweimal die Say Hi-Methode auf, und wir werden es ausführen. Der erste Aufruf von Say Hi sagt Hi, und der zweite Aufruf von Say Hi öffnet den Taschengleich nahm, was nicht das ist, was wir möchten. Wenn man guckt, wie das tatsächlich implementiert wird, nun warum passiert dieser Bug? Der zugrunde liegende Bug ist, dass der Compiler nicht davon abhalten kann, eine Base-Klasse in eine Greater-Klasse zu casten, obwohl es tatsächlich in Wirklichkeit eine Exec-Klasse ist. Wir haben die beiden V-Tables, die eine vielleicht auf den V-Table von Greater und der andere auf die V-Table von Exec, und wir können einfach zwischen diesen beiden hin und her casten, ohne dass das Typsystem in C++ nicht darüber beschwert. Und wenn man auf die tatsächliche Implementation anguckt und sich anguckt, was tatsächlich als kopiliert wird, wir de-referenzieren das erste Feld der V-Table, die erste Feld der Klasse, das ist die V-Table, dann den ersten Pointer in der V-Table und rufen den auf. Und egal ob Say Hi sagen oder Exec. In der Exec-Klasse. Das ist der gleiche, und im zweiten Fall rufen wir halt die Exec-Methode auf. Das ist eine lustige Art, Software auszubeuten, aus der Beziehung. Die Frage ist, wie finden wir diese Art von Vulnerabilität? Wie könnten wir das im Software finden? Der klassische Ansatz ist, den die Leute für die letzten paar Jahre benutzt haben, insbesondere um Vulnerabilitäten in Großbrausern zu finden, ist Fuzzing. Aber was das heißt halt, ja, wir machen Fuzzing, und wir versuchen diese Typverwirrung Vulnerabilitäten zu finden. Aber es ist relativ hart, die zu finden und sie zu trickern, weil man nicht den tatsächlichen Check erzwingen kann. Der einzige Möglichkeit, ein Fehler zu finden, ist, wenn man tatsächlich Korruption hat, wenn man in einen Segmentation-Fault läuft. Ansonsten, wenn kein Crash gibt, kann man nicht feststellen, dass die Typkonfusion vorhanden warst. Wenn man nur ein... Es kann viele Typkonfusionen geben, die ausgenutzt werden könnten, die man verletzt, wie man übersieht, weil sie nicht zu einer... einem Crash führt. Und die Frage ist, können wir diese fehlenden Typverwirrungen finden? Und das heißt, wie finden wir raus, wann diese illegalen Typcasting stattfinden? Und das zugrunde liegende Problem ist, in C++ ist ein statischer Cast wird nur zur Kompilierzeit überprüft. Es ist sehr schnell, aber gibt uns keine Garantien zur Laufzeit. Auf der anderen Seite haben wir dynamischen Cast, der zur Laufzeit überprüft wird. Und das ist aber viel overhead. Und es gibt nur für wenige Polymorphe-Klassen. Und warum sind dynamische Klassen auf Polymorphe-Klassen beschränkt? Wir brauchen eine Möglichkeit, individuelle Objekte zu identifizieren, oder den Typ der Objekte, und der V-Table-Pointer ist so ein Welt. Und das liegt am Design von C++. In C++ ist ein Struct eine Klasse, und eine Klasse ist ein Struct. Und wenn man ein Struct in C aluziert, dann weiß man nicht, was der Typ ist, der zugrunde liegt. Und C erinnert sich nicht daran, dass man ein Foo Struct hat, sondern nur ein, wenn man eine Type ID hat, nur dann kann man tatsächlich den Typ identifizieren. Sicherer, objektorientierte Sprachen wie C-Sharp und Java und so. Wenn man immer ein Objekt aluziert, dann hat man eine Type ID und die identifiziert dann den Typ. Und das fehlt in C++. Und deswegen können wir nicht alle Casts explizit überprüfen, sondern nur für Polymorphe-Objekte und Klassen. Und hier fehlt aber noch was. Wir müssen einen echten Type-Check machen können für alle diese Objekte. Das heißt, nach dem Motto des Kongresses müssen wir jetzt was tun und wir haben Type Safety für C++ eingeführt. Und die zugrunde liegende Idee ist, dass wir jeden Typecast checken wollen. Das heißt, wir machen einen dynamischen Check für jeden Typcast und dann entfernen wir aggressiv alle Casts, die wir können in unserem Design und in unsere Implementation. Das heißt, wir machen Typchecks explizit. Das heißt, wir forcieren den Laufzeit-Check in allen Casts für alle Casts, für alle dynamischen Casts, für staatliche Casts und reinterpreted Casts. Das hört sich an wie ein Widerspruch. Ich habe gerade erzählt, dass es gar nicht geht in dem existierenden Framework in C++. Denn wir haben keine Möglichkeit, die einen zugrunde liegenden Typ eines Objekts zu identifizieren. Also wie lösen wir dieses Problem? Wenn immer man ein Objekt baut und den Konstruktor baut und dann speichern wir diesen Speicherbereich und wissen, dass ein bestimmter Speicherbereich von einem bestimmten Typ ist und wir haben eine Mittattabelle im Hintergrund und dann können wir einfach nachgucken. Für jedes Beidem Speicher können wir nachgucken, welchen Typ hat dieses Stück Speicher und dann können wir diese Informationen verwenden in allen Casts. Das heißt, wir haben einen staatlichen Cast durch einen Laufzeitscheck und wir können dann immer Type-Confusion-Probleme direkt beim Cast erkennen und nicht erst später, wenn wir zum Beispiel Speicher-Corruption haben. Also wir bauen eine globale Typenhyrarchie während der Komplierung der Software und wir speichern alle Typen. Wir instrumentieren die Alluzierung des Speichers und in einem zweiten Schritt können wir dann für jeden Typecast zur Laufzeit diesen Check ausführen und feststellen, ob es tatsächlich passt. Wir haben dieses große System gebaut auf LLVM basierend und der Instrumentierungs-Sourcecode baut auf Klang auf und hat explizite Typchecks und während der Komplierung machen wir ein Object Tracing und verfolgen die Typhyrarchie und dann können wir zur Laufzeit feststellen, ob irgendwas passiert und haben dann ein Hardened Binary und ihr kennt vielleicht vorheriger Arbeiten und die das für Polymorphistrukturen überprüfen und wir können das jetzt für alle Typecasts machen, auch für statische Casts und dynamische Casts. Wir haben die Alluzierung von neuen Objekten, wir haben Placement New und wir haben Reinterpretcast und auch einige andere Sachen. Wir haben sehr hart daran gearbeitet, dass wir, wir haben auch versucht, echte große Software zu übersetzen wie Chrome und das Problem ist, sobald man volle Typechecks für jeden Cast infossiert, dann bekommt man einen riesigen Overhead. Das heißt, unser Haupttask war dann, den Overhead zu reduzieren, damit es benutzbar wird. Das heißt, wir haben Tracing auf Unsafe Objekte entfernt. Das heißt, wenn es nur in einem sicheren Kontext benutzt, wenn es niemals für Casting benutzt wird, dann brauchen wir es nicht zu instrumentieren, denn wir kennen den Typ des zugrunde liegenden Objekts und wenn es nie gecastet wird, dann brauchen wir es nicht und dann können wir dafür das Tracing entfernen, wenn es nie gecastet wird im ganzen Programm. Wir beschränken uns auf unsichere Casts. Wir machen eine statische Verifikation innerhalb des Scopes der Funktion, um herauszufinden, welche Teile des Codes tatsächlich auf sichere Art genutzt werden und dann können wir auch weitere der Casts entfernen. Wir ersetzen auch alle dynamischen Casts mit einem, unserem speziellen Cast und unser Cast, den wir entfernt haben mit den metadata Informationen, ist viel schneller als andere, als Casts, die durch die Art die Information gemacht werden, das durch den Original C++ Cast. Das heißt, dynamische Cast ist nie optimiert worden. People benutzen das kaum, weil es so viel Performance overhead hat und deswegen ist es nicht optimiert worden und deswegen wird es nicht benutzt. Das heißt, das ist ja eine Schleife. Wenn wir alle dynamischen Casts durch unser Cast ersetzen, dann können wir die Performance erhöhen. Interessanterweise, indem wir einfach nur dieses machen, haben wir schon vier neue Vulnerabilities in Apache Services gefunden. Das ist eine große XML-Verarbeitungsbibliothek und da waren Casts von einer DOM Texte Imlimitation zu einer DOM Element Imlimitation und damit konnten wir solche Typen anders interpretieren und wir haben auch Type Confusion gefunden in der QtBase Bibliothek, in der Node-Base zum NAP-Node. Und die waren einfach zu findende Sachen. Die haben wir einfach nur, die kompelliert und einfach nur benutzt. Und indem wir die einfach nur kompelliert haben mit unserem Type Checker, können wir bereits Vulnerabilities finden und Fehler in der Software, indem wir sie einfach nur in der ganz normal täglichen Umgebung laufen lassen. Das war der erste Schritt und wir haben einige verschiedene Verwundbarkeiten gefunden, aber wir wollten noch einen Schritt weitergehen. Und einige Wochen vor dem Kongress haben wir angefangen, alles zu fassen, Fashing zu machen auf allen Dingen und wir können zum Beispiel unsere Type Safety kombinieren mit AFL. Das heißt, wir können beliebige C++-Software kompilieren mit Hextite, wir haben baseierte Instrumentierung und dann können wir die über LLVM laufen lassen und finden dann verschiedene Arten von Type Confusion. Wir lassen einfach AFL seine Magie machen und dann haben wir alle Type Confusion Reports und können die dann behandeln und finden viele verschiedene Verwundbarkeiten. Und ich möchte gerne noch einen Shout-out machen zu den Studenten, die ganz viel Arbeit gemacht haben und diese Systeme gemacht haben und ganz viele der Verwundbarkeiten behandelt haben. Wir haben einige Zeit damit verbracht, unseren Ghetto Fashing Cluster zu benutzen, der auf beim Studenten unter dem Tisch sitzt. Wir haben relativ wenig Leistung, nur fünf Maschinen, die verschiedene Software laufen lassen. Aber trotzdem haben wir ziemlich viele interessante Fälle gefunden. Nach zwei Wochen Fashing haben wir zwei neue Type Confusion Bugs im QT Core gefunden, leider nicht ausnutzbar, aber die sind schon inzwischen gefixt und wir haben noch einen Buck in Xerxes gefunden und wir haben sieben Probleme gefunden in Libsas und da gucken wir gerade noch, ob die ausnutzbar sind. Und es hat sich herausgestellt, dass praktisch alle Software mit mehreren Reports erzeugen. Und einige dieser Reports stammen daher, dass wir prinzipielle Probleme mit C++ haben, weil es keine explizite Typinformationen gibt und Entwickler missbrauchen das Typsystem in vielen verschiedenen seltsamen Wegen, zum Teil zusätzliche Nachrichten bringen und Reports bringen und dann nachzugucken, ob das echte Bugs sind oder nicht ist noch zusätzlicher Overhead, zusätzlicher Zeit, die wir reinstecken müssen, zum Beispiel in Libsas. Wir konzentrieren uns auf kleine Software, vor allem, um es rauszufinden, ob das skaliert und Bugs zu finden, aber wir haben auch zum Beispiel Firefox angeguckt um den Performance Overhead rauszufinden. Und das sind die Ergebnisse für Firefox, die wir haben und sind ziemlich eindrucksvoll. Das heißt, mit einer spezifischen Benchmark haben wir gefunden, dass, sagen wir mal, einige Type Confusion Reports gefunden haben. Und wir wissen noch nicht, wie wir diese große Anzahl handhaben sollen. Viele von denen werden Duplikate sein, viele werden auch falsche Positivberichte sein und wir versuchen, das zu reduzieren auf eine kleinere Menge von echten Fehlern, die wir dann an Firefox berichten können. Das große Problem, das wir mit Firefox haben und auch bei Chrome, aber noch mehr bei Firefox, ist, dass der Code wirklich messig ist. Und Firefox hat viele Alucera, viele Allocators. Es gibt mehrere Stellen im Code, die verschiedene Teile vom Heaphand haben und wird Daten hin und her geschoben und Daten geshared. Und es gibt seltsame Allocators, die verschiedene Sachen man in verschiedenen Stellen und es sind nicht wirklich 7 Milliarden Type Confusion Bugs in Firefox, hoffen wir jedenfalls nicht. Die echte Anzahl ist wahrscheinlich viel niedriger, aber es ist ein erster Schritt und wir arbeiten daran, diese Zahlen zu reduzieren. Also Firefox ist Arbeit, an der wir gerade im Moment sind und wir sehen zu, wie wir das besser benutzbar machen können. Und wenn man am Schluss nach 5 oder 6 Tage Fuzzing, wenn man dann 7 Milliarden Reports hat, dann ist das auf jeden Fall viel zu viel. Das heißt, wir müssen gucken, wie wir das reduzieren können und welche davon wirklich interessant sind. Ja, kommen wir zur Zusammenfassung. Was haben wir gemacht? Vielleicht sind die nächsten Schritte. Wie können wir von hier weitergehen? Auf der einen Seite, wir wollen alle Dinge fassen. Wir wollen tiefer gehen. Wir wollen mehr Software benutzen. Wir wollen bessere Testfälle, bessere Fass-Inputs, bessere Code-Abladeckung. Besonders, wenn wir auf Firefox gucken, eine Sache, die wir machen wollen, ist selektives Fuzzing. Ein großes Software zu fassen, kann in einem sehr großen Anzahl falscher, positiver führen, auf der Art, wie die Software aufgebaut ist, die Architektur der Software ist. Es kann sein, dass man ein Objekt anuziert, dass man es mehrfach benutzt, ohne dass es durchschnittlich freigegeben wird oder dass wir einen Schritt zurückgehen. Eines der Probleme, die wir bei Firefox gesehen haben, die zu vielen Berichten führt, ist, dass man häufig ein Objekt allokiziert. Man gibt das Objekt an ein Pool zurück, der Anwender weiß, dass es keine Referenz auf dieses Objekt gibt. Aber der Benutzer weiß, dass es keine Referenz mehr gibt und es wird dann später als ein anderer Typ zurückgegeben. Das ist keine Wunderbarität. Das ist einfach eine Merkwürdigkeit des Typsystems. Wir wollen selektiver fassen können. Wir sind nur in dieser Teilmenge der Typ-Hierarchie interessiert. Wir wollen nur Checks für diese Teilmenge der Typ-Hierarchie interessiert. Wir wollen aber nicht diesen ganzen Rest fassen. Also nur den Dom oder nur die JavaScript-Objekte oder sowas. Außerdem gucken wir in einen Always-On-Check, also einen immer laufenden Check für Polymorpho-Objekte. Eine Option ist, dass man den Typ des Objekts checkt, jedes Mal, wenn man einen virtuellen Aufruf macht. Das wird von der Typverwirrung, die wir vorhin gesehen haben, schützen. Und als Beispiel, wenn man auf den Code hier anguckt, wenn ich den Kompilierer hat, es hat zwei Versionen gebaut. Die zweite Version ist die mit Typ-Sicherheit, mit dem Typ-Sicherheitsmechanismus. Und wenn ich mit diesem Typ-Sicherheitsmechanismus-Schutz laufen lassen, statt die dann gibt es einem tatsächlich einen Report zurück, dass eine Typ-Sicherheit, also eine Typ-Verwirrung vorliegt, anstatt den Rechner aufzumachen. Und das wollen wir erweitern, damit wir größere und größere Systeme laufen lassen können und das wir auf Firefox selektiv machen können. Aber auch ihr könnt es für eure Software benutzen, um spezifisch gegen diese Art Dispatch-Wunderabilitäten zu schützen. Wie gesagt, wir sagen, say hi, er möchte Dispatchen und wenn es tatsächlich die Exit-Methode ist, stoppen wir das Programm. Also Schussfolgerung, Typ-Konfusion, also Typ-Verwirrung ist in heutigen Exploits für Software fundamental. Vorhandene Lösungen sind unvollständig. Schützen nur teilweise und sind sehr langsam. Um große Systeme wie Firefox schützen zu können, müssen wir neue Ansätze entwickeln, um für Typ-Konfusion zu schützen. Hextype erlaubt es auf Typ-Verwirrung festzustellen, aber nicht erlaubt es den wahren Typ jedes Objekts zu nachzuvollziehen und beim Cast oder Dispatch erlaubt es, einen tatsächlichen Typcheck durchzuführen. Also wir können den Typ-Verwirrung abfangen, aber keine Speichersicherheitsverletzung. Und mit der Fuzzing habe ich gezeigt, man kann eine nette Menge an Bugs finden, die jetzt gerade gefixt werden oder gefixt wurden. Das hat vertretlichen Overhead, also bei Firefox haben wir eine Verlangsamung zwischen 0 und 50%. Und man kann das mit AFL integrieren, um auf so großen Bereichern Bugs zu finden. Und wie immer bei unserer Forschung ist es Open Source. Es braucht ungefähr 15 Minuten, um es auf der Maschine zu bauen. Und dann kann man dieses eure Software mit LFM und vollen Typchecks kompilieren. Ich danke euch für eure Aufmerksamkeit und verantworte gerne weitere Fragen. Awesome, awesome. So, wir haben vier Mikrofone hier, eins, zwei, drei, vier, wo ihr Fragen stellen könnt. Und um klar zu sein, eine Frage ist vielleicht ein oder zwei Sätze mit einem Fragezeichen am Ende. Und damit frage ich mit Mikrofon 2 ein. Danke für die Präsentation. Wäre es auch möglich, einen Compiler-Placken zu haben, das verhindert, dass man statische Casts missbraucht, kann man etwas bauen, das nicht erlaubt, statische Casts mit Dynamic Dispatch zu benutzen. Also, da muss ich gerade mal drüber nachdenken. Würdest du die Type für Verwirrung schon statisch erkennen oder willst du nur verbieten, dass der Programmierer statischen Cast für alle Objekte benutzt, die eine virtuelle Funktion haben? Nein, ich möchte nur Bucks finden. Wie üblich bei C++ versuchen wir wirklich viele Checks zur Compile zu machen. Das heißt, ja, Ubisan hat einen ähnlichen Ansatz versucht, alle Casts, sucht Casts in dynamischen Casts zu verwandeln und sie zu ersetzen. Aber wie das Greater Example gezeigt hat, ist die Basisklasse nicht notwendigerweise Polymorph. Also, man läuft in seltsames Laufzeitverhalten, also, wenn die Basisklasse nicht, ist nicht polymorphisch, und wenn man einen statischen Cast in einen dynamischen Cast umwandelt, dann scheitert es. C++ Code ist sehr, sehr messig, sehr unsauber. Man kann es als eine Warnung berichten, beim Compilieren, aber dann muss man etwas nicht Polymorphe, Basisklassen unter... ...unterstützen. Die sind sehr überraschend häufig, insbesondere für Browser. Es gibt sehr viele Basisklassen, die nicht polymorph sind. Danke für den guten Tag. Du erwähntest, dass man in Firefox manche Objekte freigegeben werden und dann wieder wieder benutzt werden. Kann man darauf, dass irgendwie eine Speichersicherheitsanalyse bauen? Ja, ja, bestimmt. Temporal Memories, Safety haben zwei bis drei Fachen Overhead. Das heißt, es ist viel teurer, als das, was wir machen. Das wäre kein Hindernis für Fuzzing, oder? Wir machen es nur für Fuzzing benutzt und nicht in Produktion? Ja, bestimmt. Aber idealerweise würde man das mit anderen Sanitisern kombinieren, zum Beispiel mit einem Type Sanitizer, noch kombiniert mit... Spatial and Temporal Speichersanitizer. Du hast gefragt, ob zusätzliche Daten, die man von Type-Safety-Systemen von Speichersicherheitssystemen hat, ob das nützlich wäre für unsere Analyse. Und ich will sagen, wir haben bei Speichersanitizer das gleiche Problem haben. Und das ist C oder C++. Das heißt, wir haben ganz viel ungetübten Speicher und Firefox benutzt den Speicher noch mal neu, obwohl es noch Referenzen darauf gibt und ändert dann den Typ. Und C++-Semantik erlaubt das. Es ist nicht illegal. Es ist nur, dass es einfach ganz schmutzig und ganz messig ist. Und dann hat man eben diese Probleme. Frank, I'm a bit puzzled by your... Ich bin ein bisschen von deiner Thermologie verwirrt, denn nur weil das Type-System nicht dynamisch checked heißt nicht, dass es nicht existiert, wäre es nicht besser, eine statische Lösung zu haben, die verhindert oder verbietet, die Downcasts verbietet, die statisch sind. Und Entwickler zu zwingen, nur dynamische Downcasts zu benutzen, das wäre sehr viel schneller, als ein Fuzzer zu haben, der die ganze Applikation fasst, weil das Problem zur Kompilierzeit gefunden wird und nicht irgendwann später reiben. Fuzzing Step. Ja, das wäre eine tolle Lösung, aber das skaliert leider nicht. Dann versucht das mal für 57 Code-Zeilen zu machen, wo man mehrere Tausend Violations hat und die Software neu zu schreiben, ist immer eine gute Idee, aber manchmal geht es zeitlich einfach nicht. Das ist einfach der Sourcecode, den wir haben. Und es ist eine tolle Lösung und es würde super funktionieren, um solche Downcasts oder confused Downcasts zu verhindern. Aber in Wirklichkeit hat man natürlich ein Problem, die ganze Software neu zu schreiben. Und es gibt nicht Polymorph-Objekte, wo man dynamische Cards nicht forcieren kann. Das heißt, man kann nur für Polymorph-Objekte dynamische Cards machen. Und es gibt vielleicht illegale Downcasts, die kann man auch in Polymorphen Objekten haben. Danke für den Vortrag. Vor dem ganz am Anfang hast du die Behauptung aufgestellt, dass statische Cards keinen Code einfügen. Das ist nicht ganz klar, die Imponation unterscheiden sich. Insbesondere wenn man von einem, wenn man es schippt Sachen hin und her, insbesondere wenn man ein Typ mit mehreren Basisklassen hat. Es gibt ja ein Typ von, wenn man zu einem Vault-Pointer castet und dann von dem Vault-Pointer zu einer anderen Klasse castet, dann machte diese Shift-Operation um verschiedene Basisklassen zu unterscheiden, nicht, wie versucht ihr diese Fälle zu entdecken? Für unser System, ja, wenn man Speicher anfordert mit einem spezifischen Typ und wenn man einen neuen Foo-Typ anlegt, dann speichert man, dass dieser Speicherbereich vom Type Foo ist. Und dann kann man mit deinem Pointer alles machen, was man will. Und der Speicherbereich hat immer noch den Tag Foo. Und wenn man es in einem anderen Typ castet, dann gucken wir trotzdem nach, was ist der Basistyp von einem Objekt. Und wenn es auch ein Type Foo gecastet wird, dann ist es okay und sonst nicht. Ich meine, auch wenn man von einem Vault-Pointer castet, von allem, von irgendwas castet. Und das bringt mich zu einem Thema, worüber ich nicht gesprochen habe. Und C-Style Casts gehören zu den ekligsten Sachen Features, die C++ hat. Und ich wollte nur mal sagen, darauf hinweisen auf dieses hässliche Feature. Und ein Cast wie in C mit nur mit Klammern und dem Target-Typ, den Ziel-Typ. Und das ist ein Hammer, der einfach dieses Objekt in einen anderen Typ mit Gewalt reinhemmert. Und das ist aus diesem Speicherbereich diesen anderen Typ. Und das ist das hässlichste, was man überhaupt machen kann. Und wenn man in C++ programmiert, macht niemals, nie, nie, und da gar keinen Umständen diesen C-Typ, diesen C-Cast, weil es alles kaputt macht. Ich wundere mich immer, wie jemand mit der Safari versucht hat und was passiert ist. Nein, haben wir nicht gemacht. Wir haben es mit Firefox gemacht und ein bisschen mit Chrome. Aber nochmal, unsere zukünftige Arbeit ist das auf mehr Software auszudehnen und größere Software. Aber für dieses haben wir uns oft kleinere Bibliotheken konzentriert. Aber wenn jemand noch mehr Ressourcen hat und uns zur Verfügung stellt, gerne. Wenn du es auf Safari machen willst, klar, es ist Open Store, Open Source, macht es mit Safari. Ich dachte, dass du mehr von dieser Firefox-artigen Wachen habt, so wie sie casten. Also die Unterschiede zwischen Firefox und Chrome sind, ich weiß nicht, Safari, aber der Unterschied zwischen Firefox und Chrome ist, Firefox hat eine sehr alte Codebasis. Es ist ganz viel hässliches Zeug da drin. Und wir haben Sachen gefunden, die direkte Dispatches machen oder indirekte Dispatches machen in Assembler. Und die machen seltsame Sachen mit V-Tablezeigern und in Line Assembler Code. Und das liegt an der alten, all den Altlasten. Und Chrome ist etwas neuer und benutzt neure C++-Standards und ist viel schöner. Wir werden wahrscheinlich nicht so viele Fehler finden. Und das Alter des Codes ist einfach schlecht bei Firefox oder führt zu viel mehr potenziellen Verwundbarkeiten. Meine Frage ist, das Konzept des Downcasts ist nicht richtig. Wenn euer Joel es, erlaubt, weiterhin Codesmails zu schreiben und riechenden Code zu schreiben, wäre es nicht besser, dass das Leuten helfen würde, nicht riechenden Code zu schreiben. Ja, ja, ich bin unbedingt dafür, auf jeden Fall. Und es gibt nur diese 100 Mio. Codezeilen, die rumliegen. Und die können wir nicht mal eben schnell loswerden. Und was wir versuchen, dass so sicher wie möglich wir versuchen, Verwundbarkeiten in existierendem Code zu finden. Aber wenn man unbegrenzte Ressourcen hat, dann schreiben wir mal gerade eben alles neu in einer sicheren Programmiersprache. Ich finde ich super, ich bin dafür. Aber der Fakt, dass wir diesen riesenmenge alten Code rumlegen haben und benutzen, müssen wir einfach das Beste daraus machen und versuchen den Schutz von diesem Code etwas zu erhöhen. Danke für die gute Arbeit. Hast du irgendwelche ähnlichen Ideen für C-Code? Ja. Ist da schon was getan? Kann ich darüber was lesen? Das ist noch Arbeit, doch in Arbeit, aber du kannst mit mir sprechen darüber. Danke für den Vortrag. Ich würde fragen, warum die Typen bei der Alluzierung euch merkt und nicht im Konstruktor. Das würde für mich viel natürlicher werden. Nicht alle Typen haben meinen Konstruktor. Das heißt, was wir machen ist, wenn man einen neuen Typ anfordert, dann benutzen wir nicht den Allocator, sondern durch Klangen wissen wir, wir können den dann kennzeichnen. Was wir mit dem Klang machen, wir indicieren alles und taggen alles, wo die Allocator sind und machen die Instrumentierung in einem späteren Schritt. Deswegen erwischen wir dann auch alle. Das heißt, wir haben eine größere Abdeckung dadurch, nicht nur Klassen- Konstruktoren, sondern auch mit Anforderungen. Man sich vorstellt, das haben wir in einiger älterer Software gefunden, wenn man Meloc aufruft mit einem Struct und benutzt es dann als Klasse. Das heißt, dann könnte man nie herausfinden, wenn man als ein instrumentierten Konstruktor und in Firefox passieren solche Sachen und anderen älteren Code. Man ruft Meloc statt New auf und benutzt einen Struct statt einer Klasse. Der Code ist wirklich hässlich und hier sieht man die Gemeinsamkeiten von C und C++ und eine Klasse ist nur ein Struct und wenn man Objekte als Struct alluziert, dann verliert man ganz viele Objekte. Sollte dein statischer Cast nicht sehr kompilierzeit schreitern, wenn man so etwas macht? Eine Klasse kann ein Struct sein, die sind equivalent und wenn das der selbe Typ ist, dann kann man Struct als Basetype benutzen und dann kann man eine Klasse haben, die ein Descendant ist davon. Jetzt verstehe ich jetzt, danke. C++ ist hässlich und jetzt sind wir am Ende der Fragen. Ich möchte unserem Sprecher Matthias Danke für diesen großartigen Vortrag und dass ihr uns dieses C++ Haufen hässlichen Sachen gezeigt habt. Vielen Dank.