 Als wir reden über Windows Driver Angriffsfläche, eine genäue Information. Ich arbeite für IOACTIV. Ich bin der Leiter von Penetration Testing. Also im Prinzip ist meine Aufgabe, Zeug kaputt zu machen. Ich werde jetzt über Windows Driver Angriffsoberfläche sprechen. Also wir haben jetzt zwei Teile. Das eine ist ein bisschen Hintergrund, also wo. Und der zweite Teil ist der Hauptteil des Talks. Da geht es um was. Es gibt viel anzuschauen heute. Also der erste Teil macht mir eher schnell. Also im Prinzip rede ich über die Angriffsoberfläche von Windows WDM. Das Windows Driver Modell. Also im Prinzip geht es um die Implementation der Sicherheitsmechanismen. Also wenn ihr nach Bugs sucht in Windows Driver, das ist relativ interessant. Oder wenn ihr Driver entwickelt. Oder einfach wenn ihr neugierig seid, ist dieser Talk interessant für euch. Also zum Wissen, das erforderlich ist. Also gut wäre, wenn ihr im Prinzip ein bisschen Hintergrund wissen habt über Windows Driver. Also oder noch besser über den Windows Kernel. Aber es geht auch ohne. Also ganz kurz, bevor ich mit meinem Teil anfange, ich stehe hier auf den Schultern von Riesen. Also es gibt viele Leute, die Interessantes geleistet haben in diesem Gebiet. Also das sind die Leute hier. Also falls ihr das Gefühl habt, ich habe jemanden verpasst, dann schreibt mir auch nicht, wer euren Namen auch noch auf die Liste tun. Also dieser Talk ist ein bisschen anders als die anderen. Der größte Teil der Bishänge Forschungsarbeit im Bereich Windows Driver ging um das Ausnutzen von Sicherheitslücken. Und es ging spezifisch um das Ausnutzen von Körnerlücken nicht wirklich in den Treibern. Und oft ging es da nur um ein bestimmtes Problem. Meine Präsensionierung ist anders. Ein Schwerpunkt liegt nicht wirklich auf der Ausennutzung, sondern eher das Finden und Identifizieren von Fehlern und das dann Richtlinien gibt über, wie man damit umgeht. Und es geht nicht so sehr um Windows Code selbst, sondern um Code von bestimmten Treibern. Also natürlich geht das eine nicht ohne das andere, aber es ist ein bisschen anders als andere Talks. Und ja, ich werde hier nicht über ein bestimmtes Problem sprechen, sondern also ich habe mich hingesetzt und habe viele der üblichen Bugs angeschaut. Das haben wir mit Windows Treibern und es ging mir so ein bisschen darum, so ein gutes Thema zu finden, dass vieles abdeckt. Also es ist eine Einführung und man kann natürlich tagelang sprechen über diese Dinge und die Details davon. Also es ging mir halt darum, das eine Stunde zu kondensieren, das Wichtigste. Es gibt sehr viele obskure Dinge, über die fast niemand weiß. Also wenn man nicht Bescheid weiß, über solche kleinen Dinge dann, oder es finden, ob etwas dokumentiert ist oder nicht, also vieles davon findet man auf dem Microsoft Developer Network, aber man muss so zwischen den Zeilen lesen. Also insbesondere was problematisch ist und was nicht. Und gewisse Dinge sind aber natürlich schon explizit erwähnt. Also ja, viele Treiber-Entwickler. Also legen wir los mit Teil 1. Also diese Auflistung hier ist nicht abschließend, es ist nur so eine kurze Auffrischung als Gedächtnis. Also kurz ein bisschen was zur Architektur. Also von hohen Ebenen ist Windows in gewisse Teile aufgebaut, die Managers heißen. Also wenn man als Benutzer zu diesem Managern-Comvol führt man ein Systemcall auf und am Ende wird das von einem Manager bearbeitet. Also es sieht 50.000 Fußansicht, es sieht etwas so aus. Also aus dem User-Land ruft man ein Systemcall auf und dann wird es da durch die Systemcall-Table und ja dann man vielleicht im IO-Manager zum Beispiel. Also da drin gibt es eine große Anzahl Frameworks, das ist eine Liste hier. Also alle von diesen haben eigentlich ihre eigene Präsentation verdient. Aber ja, wir sind halt zeitlich eingeschränkt hier. Also ein ganz, ganz großen Teil wird jetzt über WDM gehen und kleiner Teil über KDF. Also dieses Treiber-Modell ist seit Windows 2000 in Windows. Es ist im Prinzip ein Update zur Windows NT Treiber-Modell. Es geht im Moment in Richtung KDF, aber es gibt immer noch ganz, ganz viel WDM. Also ja, das hat der Fokus auf WDM. Also dann schauen wir uns auch noch den IO-Manager genau an. Das ist im Prinzip ein Proxy, der zwischen dem Benutzer und dem Treiber sitzt. Also es kommt darauf an, was man für ein Systemcall aufruft, je nachdem macht er was oder nicht der IO-Manager. Es geht darum, die Anfragen zu pakettieren in IO-Request-Packets und findet dann die Dispatch-Routine des Treibers und übergibt das an diese Schnittstelle. Also ich werde euch das mit ein bisschen Code zeigen hier. Also das ist jetzt zum Beispiel ein ganz, ganz einfacher Treiber. Also da gibt es den Haupt-Einstiegspunkt, Driver-Entry. Und jeder Treiber, der mit dem User-Land interagiert, hat diesen Befehl, hey, erstelle dieses Gerät, das IO-Device-Crate. Also und damit man damit kommunizieren kann, ist, muss man einen symbolischen Link erstellen, damit man mit dem User-Land kommunizieren kann. Also man kann dann Dispatch-Calls dem Treiber schicken über den IO-Manager. Also man kann zum Beispiel IO-CTL oder FSC-CTLs machen. Also das ist jetzt ein Codebeispiel hier. Also man erstellt das Gerät, dann stellt man den Link und dann hat man diese Dispatch-Funktion hier. Also da gibt es ganz viele davon. Im Prinzip für alle diese Funktionen sieht das dann in etwa so aus. Man hat zwei Argumente, das Device-Objekt und das tatsächliche IRP. Also IRP ist interessant, weil es die ganzen Daten enthält, die da paketiert wurden aus dem User-Land, die dann im Treiber zur Verfügung gestellt werden. Also das sind interessante Dinge wie zum Beispiel der IO-Status, was für ein Request das ist. Also das sind im Prinzip die Pointer, die aus dem User-Land kommen und das ist der IRP-Stack. Also wenn wir mal da drüber für die Anfrage, die reinkommt, gibt es einen Stack dazu, eben diesen IRP-Stack. Da gibt es sehr spezifische Informationen, die der Treiber benötigt für diese Operation. Also wollen wir zum Beispiel aufgerufen mit einer Major- und Minor-Funktion-Number und es geht über dieses Device-Objekt und dieses File-Objekt. Also es gibt eine spezifische Liste von Parametern und dann hat man da drin Elemente und je nach Demo bis dann in die IOC-TL oder Lesen oder Schreiben ist. Also ja, dann alle wichtigen Dinge holt man dann aus dem IRP-Stack. Also wie kommt man aus dem User-Land dahin? Man ruft diesen System-Call auf und also die letzten fünf Argumente hier sind jetzt möglicherweise Angriffsfläche. Also sagt man den Control-Code oben, dann ein- und Ausgabe-Puffer. Also man geht durch den IOManager, also durch den Treiber und durch das Device-Control-Dispatch. Also je nach IOC-Code und wie das die Anfang packt, werden die Anfang verschieden paketiert. Also hören wir uns mal diesen IOC-Code an. Tatsächlich ist das aufgeteilt in verschiedene Stücke. Also wir schauen jetzt da nur zwei Dinge an. Wir schauen uns eigentlich nur den Transfer-Type und den Access-Type an. Es gibt Require-Axis, ein von drei, File-Any-Access, File-Read-Data oder File-Write-Data hinter einem Händel aufmachst und ich mache es als Read-Only auf und ich mache den IOC-Code, der write data, der blockt das. Der IOManager wirft dich raus dafür. Das ist der restriction set. Der andere ist der Transfer-Type. Da gibt es vier Transfer-Arten, Buffered, Neither, Indirect und Outdirect. Das erste ist Method Buffered. Das macht man normalerweise fast immer. Das ist so verstandert Anspruch eben für Driver. Der Buffer schaut sich den Input an, schaut den an, er macht einen eigenen Subbuffer dazu und gibt die Information weiter an den Treiber. Auf die Methode muss sich der Treiber selber nicht drum kümmern, dass die Adressen stimmen und dass es keine Overflows gibt. Das brauchst du alles nicht, wenn du Method Buffers verwendest. Und der Kabel handelt die Memorie bei jeder Feier und wenn das überläuft, dann gibt es einen Buffer zurück. Method Neither ist genau das Gegenteil davon. Der IOManager macht gar nichts. Er nimmt die Daten und reicht sie einfach so, wie sie sind durch. Die Input Buffer und Input Buffer Länge ist ein rohes Datum. Da gibt es keine Value dazu. Wenn einfach so weitergegeben. Das ist die Endresource von Windows Driver Bugs. Meiner Meinung nach, das ist etwas, was man unbedingt vermeiden sollte. Wird aber oft genug angewandt. Da gibt es einen zusätzlichen Nutzen davon. Aber es ist, manuell alles checken selber und das ist securitymäßig einfach sehr gefährlich. Jetzt muss ich etwas über MDL sagen, bevor wir damit gehen. Das ist eine Memory Descriptalist. Die Datenstruktur innerhalb dem Kernel, dass seinen Buffer bei seiner physikalischen Adressierung anspricht. Der Kernel hat fast eine Menge Geld, wie man sich abw Martinez mit den über die MDL spricht. Und habe ich einen MDL in meinen physischen Buffer beschreibt und Kernel gibt mir einen Exes und gibt mir eine MAP in die innovatuelle Adresse hinein und Memory Manager sollte das handeln, als die zurückgeben. Warum ist es wichtig? In diesem bestimmten Fall Was bekommt ihr das doppelte Mapping und der User geht und gibt es einen und der IOManager gibt es einen MDL-Wetter, versucht es, die physikalische Adressierung impägt es und gibt es dem Karel weiter und der passt das von Karel zum Driver und der Driver selbst tagt ihn wieder und dann sagt er zum IOManager, dass er die Adresse im Karel geben soll, das heißt du kriegst eine doppelte Adressierung im IOManager und das verwendet man vor allem dann, wenn man große Mengen an Daten verwendet, weil man hier nichts kopieren muss, sondern einfach linear weiterrechnen kann. Wenn man das denn so in einem Zeichnen will, dann sieht das so aus. Zero Cops, du musst kein Trakcept machen, du hast kein Kopieren, du hast das direkt im User Memory und es gibt auch einen Nachteil dazu, das Kommarmar hat dazu. Method Indirect und Method Outdirect sind ähnlich und da ist im Prinzip das MDL-Input gemacht, wird vom MDL eingereden und Outdirect ist kein Gegenteil davon, written to, also hinausgeschrieben. Das ist es. Also das letzte Slide für den ersten Teil, also ich hoffe das nicht zuverwehren war, ich habe es ziemlich schnell vorgelesen, aber also das nächste Teil ist dann der coole Abschnitt. Also die meisten Dinge, über die ich gesprochen habe, die ich jetzt reden werde, ist das WDF-Modell, das im Alt ist und das KMDF ist das Neue. Das Teil von WDF, das ist Dings-Tiber-Framework. Das wurde entwickelt, um Fehler zu korrigieren, die man aus der WDF-Mwelt kannte. Also es ging auch darum, in Bezug auf Security einige Dinge zu ändern, also die jetzt einfacher sind, richtig zu machen im KMDF. Also grundsätzlich ist es einfacher gewonnen, Treiber zu schreiben und es ist weniger wahrscheinlich, dass es dann Bugs gibt. Aber es ist natürlich im Rahmen der gleichen Infrastruktur, dass er auch MDFs braucht, dass man muss das alte Modell immer noch verstehen, sonst macht man auch mit KMDF-Bugs. Aber generell ist es schwieriger geworden, Bugs zu haben. Aber ein gutes Beispiel für ein Security-Feature ist, das einem dazu anhält, Pointer direkt zum Körner zu verwenden und man hat da Schnittstellen, um Daten zu extrahieren aus den Requests. Also ein Beispiel dafür ist dieses WDF-Request-Input-Buffer. Also wenn man einen Methodenpuffer hat, ein Out-Direct-Modell hat, dann wird einem dieses Ding gleich die Pointer holen und wenn man Method niederverwendet, dann wird das aber scheitern und dann wird da der unser hier insichere Puffer geholt und es wird am explizit gesagt, dass das unsicher ist. Also das seien es dieser Dinge, bei denen man die halt wussten, dass man nieder nicht verwenden sollte, weil es sonst ganz schlimm ist und ja, sie geben einem Anreiz, das nicht zu verwenden. Generell als Treiberentwickler solltest du KMDF über WDF verwenden. Ist eigentlich schon aktuell schon open sourced. Ich habe das auf GitHub, die Adresse steht hier, 90 Prozent und der Code ist der. Manche kleinen Teile fehlen aber so, KOSUM oder ist alles da. KMDF ist eigentlich voll verfügbar. Freestoff oder License. Und du gehst euch das anschauen. So jetzt kommen wir zu Teil zwei. Lass uns hineingehen, wie man die Service ist für Windows Attacks strukturiert. Wenn wir uns die Arten der Bugs anschauen in den Treiber, gibt Elevation a Privilege, der Nile of Service, ja, wo du keine Panik kriegst oder ein Deadlock oder ein Resource-Starvation-Type für den Tag. Und das dritte ist Information Leak. Das sind so die drei, wenn wir, wenn für Security in Drivers uns anschauen. Das würde ich jetzt sprechen. Was würde ich nicht sprechen, wer als Exploitational Bypassing of Mitigations für das Meister hier und da, wenn wir sie erwähnen, ich gehe von der Andere aus, dass die Bugs ausgenutzt werden. Also, dass es ein Exploit ist. Das ist kein Exploit Talk. So, das ist ein theoretischer Talk. Ich werde mich nicht auf Exploits verstreifen. Ich werde mich um die Definitionen kümmern, das ist das Sinn dieses Talks. Und wie man das reparieren kann vor allem. Ich nehme auch an, dass er ein grundsätzliches Verständnis abt vom Bugs. Ich werde nicht diskutieren, was ein Puffer-Overflow ist oder Integer-Overflow oder Anwalt der Deadlength-Field oder so Scherze, auch die C++-Type-Bugs, die Constructor, die Destructor-Bugs. Wenn ihr interessiert seid, ich werde doch was drüber sagen. Vor allem in Windows Kernel gibt es diese APIs, um die Overflows zu verhindern. Es gibt einen Satz von APIs und wenn du ein Developer bist, solltest du sie verwenden und keine anderen. Man kann sie immer noch schlecht verwenden, also wenn man sie verwendet, immer check den Return Value, sonst verpasst du ein Integer-Overflow, immer den exakten Typus weitergeben, sonst kannst du Konversionsfunktionen nicht verwenden. Aber ich muss jetzt leider abschließen, weil sonst geht es mit der Zeit nicht mehr aus. Was links steht, ist mehr und was rechts steht, ist das, was man machen sollte. Also die APIs, so wie es links geht, nicht so war oder recht so, wie es war. Jetzt kommen wir zu den meisten Sachen, die die alten WDM-Driver machen, wenn sie mit User-Land interagieren wollen. Was ist so die generelle Idee, so fünf Punkte, die die meisten Träber machen wollen, wenn sie mit User-Land sprechen und mit dem Kernel reden? Wenn du ein Device schaffst, wenn du Daten und Output handelst, wenn du eine Memory Manager hast und mit dem Object Manager handelst, Cross and Moder, diese fünf Areas, die Bereiche, die ein Träber verwendet, in dem man mit User-Land und dem Kernel verbindet, gibt andere Wege. Aber Cross and Moder, so wie ich das sehe, sind das die fünf Punkte. Ich habe mir den nächsten Set of Slides über diese fünf aufgesetzt und dann haben wir noch einen Set of Slides, wo wir diskutieren, die Probleme, die wir da treffen. Passt auf auf die Icons, da wir sehen, links oben ist ein Bug und rechts oben, das ist ein Fix. Okay, machen wir mit Creation weiter, Punkt eins. Wie ich vorhin schon gesagt habe, es gibt zwei Methoden, ACLs zu sitzen, entweder die IO Create Device Secure oder der Machsteil in Info. Dann lässt man den Träber, den Device Createn, also Schaffung, und du kannst die Access Controls als Parameter weitergeben, dann gibt es zwei Methoden, du Calls IO Create Device Secure, das ist eine Security Description, der genau definiert, wer kann und wer kann nicht in diesem Device. Wenn du den nicht aufrufst, machst du ein, du spezifierst es im Info mit deinem String und der mit deinem Träber kommt und der wird dann generell eingesetzt, wenn dieser Träber aufgerufen wird. Üblicherweise fehlt das gern. Viele Drivers verwenden Stil, verwenden immer noch IO Create, ohne spezifizieren, welche Parameter übergeben, keine Parameter wird weiter, entweder der Default oder eine Aussichtung, die zu unspezifisch ist. Und da gibt es ein Standard-Device, ein Design-Problem mit Drivern, die Idee grundsätzlich ist, dass man sich mal hinsetzt und sich überlegt, wer muss diesen Device eigentlich erzählen können. Und es hat sich aus sich hinzusetzt und wirklich einmal nachzudenken, wer mit diesem Device reden soll oder nicht, denn je mehr Info du ausschließen kannst, desto weniger Security Leaks wirst du von Anfang an kriegen. Setz dich in, mach eine Liste. Wir werden seinen Träber schreiben und wir werden einen Service oben drüber und der filtert und der Service-Traumat, der die EOS filtert, so dass er den Träber selber nur weitergegeben wird, wirklich braucht, Entschuldigung. Der Nachteil, da muss man Wer-Code schreiben und natürlich wird Beformers-Decoration kommen, aber dafür kriegt man Exasecurity und wahrscheinlich auch bessere Verlässlichkeit. Aber die Idee ist grundsätzlich, dass eine Designfrage und also ich bin, ich denke dafür, dass man immer erstes definiert, wer soll diesen Device überhaupt wenden können und wofür. Gehen wir zu Punkt zwei. Wenn du den Device erzeugst, dann rufst du diesen IOP off, File Device Secure Open, da gibt es ein Bit drinnen, das ist der Stakeholder, File Device Secure Open, also ein ganz bestimmtes, wenn man ein Device created, by default, wird der EOManager immer annehmen, dass das File System Device ist. Was das heißt, wird also der EOManager annehmen, dass es hier Direktors und Files gibt. Wenn ich aber aufmache und sage Device Slash File, dann sage ich das nicht machen, macht das File System, handelt das und das funktioniert wunderbar, wenn du ein File System Driver ist, was die meisten Drivers aber nicht sind, du kein File System Driver bist, hast du plötzlich das Problem, wo der EOManager glaubt, du bist einer und er checkt nicht und gibt das alles ans File System weiter, da ist aber keins dahinter. Während des Bydesign, also das Echo Bypass Problem. Eigentlich jedes Mal, wenn du einen Driver machst, der kein File System Driver ist, sollte man das jedes Mal genau anschauen. Es ist bizarr, weil es ist eigentlich dokumentiert und es gibt sehr viele Drivers, die es einfach nicht machen ist. Dieser Bug ist ganz einfach zu finden. Du musst dich da mal ein Code disassembly machen, da gibt es ein Cool da draußen, du kriegst daraus und das wirft daraus. Kannst du das sehen? Du siehst wo die Echors kommen und es sagt dir, sagt dir, wo die Flags gesetzt sind und das Flag ist nicht gesetzt und der Freisag, das ist only admin und du hast den Bug right there, one click away. Was ist der Fix für diesen one? Also wer sollte dem eine Flag verwenden? Entweder du brauchst es nicht, weil du einen File System Driver machst, was wir gesagt haben, meistens nicht der Fall ist, dann sollten wir immer dieses File verwenden. Ja, viele sagen ja, du kannst, aber du musst nicht, kannst deinen eigenen machen, mach deinen dispatch call. Ja, klar kann man das, aber man sollte es eigentlich nicht, weil du machst schon wieder irgendwas eigenem Code und das ist alles schon im Windows Kanal da. Setz das Bit ordentlich und du kriegst es umsonst, das ist der Rest ist schon gemacht, und wenn du es selber magst, musst du haterschwindendes Einsorgen an, wo du die so schlippig beachten musst und du musst den IO Manager nachbauen und wozu. Das gibt es Gebanzen, ja man kann, aber man sollte nicht. Also das war eins von fünf. Ja, weiter hatte ich dann noch mit dem IO Manager was, arbeiten mit dem IO Manager, also das ist ein übliches Beispiel, wie das funktioniert, dass man hat dann IO CTL aufruf, Callback und das heißt dann da, also gut, wir nehmen diese Adresse, MDL Adress, also holen wir die Adresse und dann hatten wir davon Kernelpoint, aber niemand testet, ob er null ist. Also es kann null sein, also so wie das funktioniert, es ist halt gebaut, dass es null sein kann. Also bevor man das da anfasst, muss man überprüfen, ob es nicht den null pointer ist. Und wenn es ein Puffer ist, der Größe 0, dann passiert das eben. Und wenn man das nicht überprüft, dann ist es unter Umständen ein Bug oder noch schlimmer, dadurch reden wir gleich noch. Also wenn man das in und out direkt benötigt, dann überprüft euer MDL pointer. Also das zweite Problem mit dem IO Manager mit der IO Manager Klasse ist, wenn man Method Buffered verwendet. Also habe ich vorhin schon erwähnt, es ist im Prinzip die sichere Option, aber es gibt trotzdem noch Dinge, die man wissen muss. Es ist ähnlich mit den MDL Adressen hier, also der Pointer, den man gibt oder bekommen kann. Ja, das ist wieder dieser Herb System Buffer, ist ähnlich wie bei der MDL Adresse, der kann null sein. Also wieder das erste, was man machen muss, überprüfen. Das weitere Problem ist, obwohl es die sicherste Methode ist von allen vier, also weil das so gesehen wird, hat man ein falsches Gefühl der Sicherheit. Also das Erfassen und Testen des Puffers passiert für dich, aber man natürlich muss man immer noch selber überprüfen, ob der Inhalt gültig ist. Also da gibt es ja Leute sagen, ah nee, das wird ja für einem geprüft, da muss ich nichts mehr tun, aber das ist nicht so. Also wird dann verwendet, um Zeugs hin und her zu kopieren und plötzlich gibt es dann Speicherfehler. Also das Problem, die von Puffer ist jetzt, dass man eingebetteten Inhalte immer noch überprüfen muss. Einer meiner wirklich Pet-Pievs ist, wie kriegst du den Akto, alles in Ordnung, du kommst zurück, gibt ein Feld in deinem Herb und da gibt es IO Status und da steht IO Status Success und die Information ist, in den case of Success, was das heißt ist, IO hat es successfully completed und das ist die Menge der Beiz, die ich das Output zurückgebe. Sehr oft, was Leute machen, ist IO Status und Information, das ist Puffer-Length, der User mir gegeben hat, dass die nehme ich als Maß und klar, alles funktioniert, noch nichts crashed, ah, da gibt es aber einen kleinen Hacken dabei, du hast bei sich dieses Information-Leak, wenn du einen Okkel machst und der IO-Manager schaut sich den Input-Länge und die Output-Länge an und der schaut, welcher ist der größere und die Leberwärme an die Output ist die größere und der fragt Meilach of Colonel Puffer und hat vergleicht das mit dem Input-Puffer und wir nehmen das und kopieren das und du hast einen Buffer, der Smaller ist ein Output-Buffer und das Delta dazwischen ist dein Bug. Was du in dein Output hineinkopiert hast, ist kleiner als der Total Allocated Space, der Verhältnis und das Delta am Ende ist eine Slight Memory und wenn dein IO Status ist, das Info, volle Puffer-Länge, dann fährst du dieses Delta mit und es geht kopiert in das User-Länge hinein und das kann man dann einfach auslesen. Es ist recht subtil und es nicht so einfach, nichts geht kaputt, nichts crashed, aber man merkt es nicht, aber wenn du suchst, findest es das und da gibst du ganz große Puffers und gibst riesen Riesenhaufen von Delta, die Puffer werden immer länger und man kann irgendwann die halbe IO Arbeiter aus ablesen. Sieht man recht oft, man kann testen so lange man will, es crashed nicht, du musst es wirklich suchen. Also testen alleine nutzt nichts, man muss sich bewusst sein, dass die Puffer-Länge nicht in Ordnung ist, weil sie falsch kopiert wird. Das ist die Output-Puffer-Länge. Ganz die Output-Länge, Cross-Check, und plötzlich, wie gesagt, es liegt aus. Das andere, was hier spannend ist, ist, obwohl das ein WDM-Ding ist und KMDF ja da drauf gebaut ist in KMDM, KMDF kann man dieses spezifische Problem immer noch haben, dass man muss immer noch manuell die Mitglieder des ERP ausfüllen und also darum im Prinzip einfach die Information im Rauf übergibt, dass es immer noch ein WDF-Request hat und man hat immer noch dieses IO Status Field. Und wenn es zu groß gibt, hat man das gleiche Problem wie vorher. Und weil es Open Source ist, kann man einfach dann nachschauen, was diese Funktion genau macht. Das ist das hier, also tatsächlich intern und wird einfach da ERP nachgeschaut und IO Status, Punkt Information, der Link, den man da übergeben hat. Also wenn man das repariert, ist ganz simple. Also natürlich zuerst schwer herauszufinden, was man genau machen muss, aber weil eben, weil die Fehler so subtil sind, nix stürzt ab, nix verhält sich komisch, aber wenn man weiß, was das Problem ist, ist es leicht zu reparieren. Also im Prinzip jedes mal, wenn man diese Informationen setzt, muss man genau sein. Also wenn man fünf Bytes kopieren will, dann schreibt dahin fünf Bytes. Also ja, und dann hat man diesem Bug nie mehr. Also das Wichtige ist, dass man konsistent ist und ja, es ist halt schwer. Also insbesondere, wenn man sich alten Code anschaut, ist es halt leicht mal den ein oder anderen zu verpassen da. Also wenn man Code hat, der vor 20 Jahren geschrieben wurde, ja, also gut. Der letzte Teil des Iron Managers, den wir anschauen, ist im Prinzip ein groben Zügen ERP Cancellation. Also der Grund, weshalb ich das nur so ganz grob anschaue, ist, weil es sehr kompliziert ist. Das ist natürlich, könnte mal allein mit diesem ERP Canceling eine Stunde füllen. Also grundsätzlich, was da passiert ist, wenn das Userland IOCTL macht, dann sagt der Manager, ja, das ist etwas, das ich nicht gleich jetzt fertigstellen kann, dann kommt das auf so eine Pendentenliste. Kommt später wieder, ich geb dir dann das Resultaten, du kannst jetzt was anders tun und sobald der Treiber fertig ist und sagt dann, ah, dieser ERP ist nicht vollständig und später bekommt er dann das Signal, dass es fertig ist. Also je nachdem, was jetzt sein Treiber genau machen soll, es kann sein, dass das sehr lang geht, also wenn es zum Beispiel Festplatten basiert ist irgendwie, also zu jedem Zeitpunkt nach diesem ERP auf die Pendentenliste gelegt wurde und zurück zum Kernel geht, dieser Treiber, der mein ERP hält, kann ich sagen, das ging zu lange, bricht jetzt diesen Auftrag ab, dann wird der Treiber wieder aufgeweckt, findet diesen ERP und ja, cancelt dann. Dann ist das Problem, wenn man halt mehrere Threads hat, einer zum Beispiel probiert das abzuarbeiten und ein zweiter probiert diese Abarbeitung abzubrechen und wenn die jetzt nicht richtig synchronisiert sind, dann gibt es diese komischen Race Conditions. Also dieses Zeug wird dann extrem kompliziert. Also gibt es Dutzende von Beispiele, wie das aussieht und ja, ich wünschte, ich könnte euch das zeigen, aber ja, geht jetzt nicht. Die ERP Cancellation Routinen gibt es dann oft, Deadlocks und Leaks und Race Condition, Double Freeze und Use After Freeze, genau das ganze Zeug, da ja, läuft dann was was im Mund zusammen als Security Forscher. So, ja, wie fixen wir das? Da kann man eigentlich nicht einfach rat geben, es ist extrem komplex. Wenn du ERP Cancellation machst, wirklich, wirklich extrem konservativ und vorsichtig sein. Implementieren mit wirklicher Rücksicht, Double Check, Normal Check, Green Check und dann vielleicht nochmal checken. Wenn du noch keine Cancer Safe Arp Cues verwendest, dann solltest du das jetzt hier ernsthaft überlegen und nochmal check, check und nochmal. Jetzt gehen wir nochmal zu Userland Data und Pointers, Drivers. Wir reden jetzt mal hier dem Elefanten im Zimmer, was machen wir, wenn der Treiber direkte Einnahmen aus dem Userland nimmt? Du solltest proben, das ist ein fancy Word für, ist das, ist diese Pointe jetzt so lange wir sein soll, darf das überhaupt sein und dann validierst du sie und das machen diese Funktionen, da ist noch immer der Elefant im Zimmer. So, basically, you know, if driver code Pointers von uses nimmst und sie nicht approbiert, dann könnten sie auch irgendwo hinzeigen oder wenn du es vergisst und wenn du dann trotzdem davon liest, ist ein potenzielles Info-Leak. Du könntest irgendwas lesen, du könntest crashen, du korrubierst das Kernelmemory, das ist ganz, ganz schlecht. Du machst etwas, das write anything, anywhere, anything, into den Kernel, das tun sie das nicht. Um das zu verhindern, sollte man immer Userland-Inputs proben und nicht einfach ungecheckt werden geben. Das ist ein Beispiel, wo ich grundsätzlich mal input buffer genommen, irgendwo geprobt, wird einfach geschrieben und wird davon gelesen. Die sind sehr, sehr oft diese Bugs. Ein anderes Nugget wäre, das ich schon seit einigen Jahren bekam, so wie API-Probes üblich arbeiten, wenn die Größe 0 gegeben ist und da haben wir diese Möglichkeiten und wir haben, wenn Leute proben machen, aber Pro-Length ist 0. Wenn das passiert, dann gibt es eine Short-Circuit-Logic, da gibt es successful zurück und passt. Das Problem ist, da ist ein Risiko, wenn man das so macht, wenn du probst mit einer Länge von 0 und später und dann habe ich aber später zur selben Adresse embed, dann habe ich einen Längenunterschied zwischen 0 und 1, das ist immer mein erstes Problem, weil das aber so das lässt sich wahnsinnig ausnutzen, das ist weil der API so geschrieben ist, da vergisst der Code, Length-Checks zu machen und da gibt es Integer-Overflows und das auch ein Risikopunkt ist. Der Common Case ist der Code. Input-Buffer-Länge nehmen und werden einfach annehmen und nehmen an, dass es so stimmt, aber Sie checken es nie und geben es aber weiter und dann kommt genau das raus. Das andere ist, wenn du eine Länge-Checks machst, wenn du Overflow-Length zu 0 stellst, dann passiert das genauso. Also der Fix ist richtig, richtig proben, konsistent proben, immer, immer, immer, immer. Es ist einfach, also es ausschaut, Konsistenz ist der Schlüssel. Checken, nachher mal checken und den Code immer durchchecken, wo die Gegend des Treiber, die Funktion ruft und ruft und ruft und irgendwann weißt du nicht mehr, wohin die Rufe setzt und dann verlierst du eine Probe. Also das zweite Ding ist, wenn man jetzt ein Kernelpoint in den Treiber wegkommt und man den nicht probt und man jetzt den zu verwenden beginnt, dann kann man es natürlich immer noch falsch machen. Also jedes Mal, wenn man ein User-Landpoint im Kernelspace verwendet, dann muss man es immer in tri-accept block tun, weil wenn man das nicht tut, kann es sein, dass nach dem probing ein anderer User-Land-Thread kommt und der Speicherschutz auf diese Page ändert. Also natürlich im Kernel denkt man ja, man kann jetzt die einfach verwenden, aber plötzlich schreibt man in Pages, die nicht gemappt sind und dann wird ein Fehler zurückgegeben. Also muss man da immer tri-accept verwenden. Natürlich ja, kann man das vergessen und das ist dann ein Problem. Das weniger offensichtliche Problem ist, wenn man das hat, dass das mit dem Accept-Fall passiert halt nicht so oft, weil ja, das ist halt eben die Ausnahme und dann kann es natürlich Fehler geben in dieser Ausnahmeverarbeitungsroutine, weil das nicht oft passiert im Testen. Selbst wenn man tri-accept verwendet, dann kann es sein, dass man da nicht so wirklich Memory-Leaks sieht oder Reference-Counting-Leaks. Also ja, wie repariert man das? Erstens mal ja, immer tri-accept verwenden, wenn es um User-Land-Pointer geht und B ja so schaut, dass die Fehlerbearbeitungsroutinen sinnvoll sind. Ein weiterer Bug mit User-Land-Poitern heißt Double Fetches. Die Idee ist, dass ich im User-Land bin und ich sage jetzt im Treiber, hey, hier ist mein Pointer von mir. Der Treiber macht jetzt Probing, macht tri-accept richtig und nimmt also diesen Pointer und sagen wir, es enthält jetzt eingebettet eine Länge. Also dann zeigen wir, es wird überprüft, dass die Länge stimmt, z.B. in die Größe als 10 und kleiner als 100 und also ja, das kann nicht sein, dass das stimmt und wenn jetzt das dann genommen wird, dann nehmen Sie vielleicht das Längenfeld und probieren was anderes damit und da gibt es eine Race-Condition. Also im Prinzip zwischen der ersten Überprüfung und der zweiten Überprüfung kann sein, dass ein anderer User-Land-Thread das überschreibt. Also es kann sein, dass man das überprüft und dann stimmt es und wenn man es dann verwendet, ist es ein ganz anderer Wert. Also hier ist ein einfaches Beispiel, wie das aussehen kann. Also das ist jetzt z.B. eine Funktion im Kernel, bei der man irgendein User-Land-Pointer übergibt und der enthält eben eingebettet ein Längenfeld, das muss nämlich kleiner als 100 sein hier und später wird anders verwendet, um in einem lokalen Puffer zu kompieren, der kleiner als 100 Beiz ist und eben zwischen diesem ersten Check, der in rot ist und dem zweiten roten was verwendet wird, kann es sein, dass das überschrieben wird. Und im Prinzip gibt es dann Speicherfehler. Also das hier ist ein Beispiel eines Bugs. Ja, da gibt es double oder sogar dreifach Fetch. Also ich habe da ein Link zum Security Advisory. Wie repariert man das? Ja, Daten kopieren, überprüfen und dann verwenden, wie einfach die Daten behalten, die man validiert hat. Ein weiteres Beispiel bei User-Landzeug, Null-Pointer der Referenzierung. Das ist im Prinzip die gleiche Kategorie von Fehler. Das ist nicht Windows-Treiber spezifisch. Über diesem Bug habe ich jetzt schon gesprochen im 2006. Da ging es um Linux. Aber ja, bei Windows ist natürlich genauso ein Problem. Der einzige Unterschied ist, wenn man die Null-Page zu mapen zwischen Windows und Linux ist, dass man irgendwie sagt, das ist fix und die Adresse ist Null und dann wird das da gemapped bei Linux. Und bei Windows, wenn man probiert, Null zu mapen, dann sagt Windows, ah, du willst wohl nix mapen, weil da Null ist. Und bei Windows muss man sagen, nein, nein, nein, die Adresse ist 1 und es wird dann abgerundet auf Null. Also das war mal viel größeres Problem vor Windows 8. Aber ja, seit Windows 8 kann man die Null-Page nicht mapen. Also das sagt es einem zumindest, aber da stimmt nicht ganz. Bei 32-bit Windows ist es in der Standard-Einstellung ab. Aber wenn man alte DOS-Programme laufen lassen will, dann muss man dieses Zeug anschalten. Also weil zum Beispiel einen alten DOS-Games, das verwendet wird. Aber selbst wenn in diesem speziellen Fall das nicht funktioniert, man, also diese Null-Mapings nicht erlaubt sind in Windows 8, gibt es trotzdem noch weitere Orte im Kernel, wo man Null-Pointer möglicherweise ausnutzen kann. Also wenn man irgendwo zum Beispiel einen Offset hat und der Pointer ist dann Teil dieser Offsetzahl, also wenn man zum Beispiel umgekehrt das M-Copy macht, dann hat der Pointer halt eine spezielle Bedeutung. Also wie repariert man das wieder? Ja, defensives Programmieren. Also ja, nix Spezielles. Das ist Nr. 4 aus den Fünften, die ich hatte. Grundsätzlich, wenn du, gibt es 2 Arten von APIs mit Memoriaren zu sprechen. Du legst Pooh und Lokal Pooh mit Quota. Grundsätzlich gibt es eine Diskibanz zwischen den beiden. Eine API, wenn sie fehlt, gibt Null zurück und die andere gibt Exception zurück. Wenn du die verwechselst, erwartest du dir eine Null und du kriegst eine Exception oder du hast einen Exception-Handler und du kriegst eine No-Pointer zurück. Die muss man deutlicher als anderen erhalten können. Ich glaube, da gibt es eine Option, entweder wir fünft weg, gibt mir eine Null zurück. Ich empfehle hier auf jeden Fall Konsistenz. Also wie gesagt, das ist noch eine Möglichkeit, wie man mit einer API falsch gehen kann. Jetzt zwei andere Möglichkeiten noch, wo es schiefgehen kann. Lokal Pooh mit Quota, jedes Mal wenn du an eine Allokation zugehst, durch einen Input durch USELAND getrieben, solltest du die Allocation immer darauf passieren. Wenn du es analog darauf setzt, solltest du das Quota immer analog machen. Wenn du das nicht tust, halte ich das schon für den Park, wenn du das Entleger bauen kannst. Und die unhandelten Exceptions? Ja, wenn du die Exceptions nicht handelst und dann gibt dir die Exception-Logik und dann hast du einen Memory-Fehler. Das ist alles relativ einfach, ja, Check-Return-Values, paar Verlängs abhangen, keine... Das zweite Art von Memory-related bugs ist Mitigation-Problemen. Also früher war das, wenn man für Memory-Accasion gemacht und da waren die Pages immer zurück, immer zurück exekutierbar. Abwendersacht stimmt das nicht, weil da gibt es einen Page-Pool, Non-Page-Pool. Für den Page-Pool, bei Default, sind die nicht exekutable. Für einen Non-Page-Pool müssen sie immer noch Non-Page-Pool, Non-Exekutable sind es selten. Die Idee dahinter ist, anytime, dass du einen Non-Page-Pool, dass du jedes Mal eigen spezifizieren musst, es sei denn, du brauchst wirklich exekutable Non-Page-Pool, aber es ist eigentlich fast unmöglich, aber wenn du es wirklich brauchst, go ahead, use it. Aber man sollte versuchen, es möglichst zu verweigern. Also es ist kein Fehler als solches, das Non-Exekutable-Non-Page-Pool ist wirklich Mitigation-Problem. Nicht das für Kai von Page-Pool geht es nur darum, Fehler zu lindern. Also wenn man es macht, dann ist die Fehler vermeidungsschwerer. Also grundsätzlich ist die Idee, das gehen wir zurück, die Idee ist, dass man den Page-Pool verwendet, wenn man nicht Page-Pool exekutable Zuordnungen machen will. Also das nächste Speicher-Problem ist diese Funktion mmget system address for MDL, also wenn man eine MDL bekommt, wie kriegt man das gemapped, diese alte API, wenn man die aufruft, wenn das Mapping nicht funktioniert, dann gibt es ein Kernel Panic und deshalb gibt es einen neuen API call mmget system address for MDL safe und der löst keinen Kernel Panic aus. Also ja, das ist die neue, die soll man verwenden, wenn man die alte verwendet ist ein Bug und es gibt noch Treiber-Frameworks, die die alte verwenden. Also man, es gibt zwei Gründe, wenn man die alte verwenden soll. Entweder alten Code oder wenn man aufgrund von Treiber-Entwicklungsbüchern Treiber programmiert, dann, naja, das Buch wurde halt vor der neuen API geschrieben und empfiehlt einem dann, diese zu verwenden und ja, viele Windows Treiberbücher sind halt alt und empfehlen die alte Variante, die halt deshalb gibt es diese Bugs noch. Ja, das habe ich vorhin schon erwähnt. MDLs, um verwenden Double Mapping zu machen und dann gibt es Double Fetches von der Userpage, da gibt es Double Fetch Bugs, gibt es Direct und Indirect Mps, z.B. nimmst du deiner Input aus MDL und dann mit bestimmter Länge Fälle verwenden, ohne, ohne darauf Blick sich zu nehmen, dass die Länge dieses Memories sich jederzeit verändern kann. Sie sind nicht, das Problem ist, sie sind nicht so offensichtlich, weil ich mit einem Kernel Pointer arbeite und nachdem ich vom Kernel Virtual Agile Space lese, könnte man annehmen, dass der Kernel hat drüber Kontrolle hat, hat aber nicht. Das ist ein ganz einfaches Beispiel hier, wieso man es ausschauen könnte. Ach, zu viel schneller. Also benutze mir dieses Stück Länge hier, aber wenn es vom MDL kommt, dann kann es sein, dass im User lang das wieder verändert wird, bevor man es verändert. Ich glaube ich habe noch zwei Minuten. Super. Also zum fünften Teil Objekte verwalten. Wenn der User zum Kernel spricht, hier hast du ein File Handel, mach was damit. Der Kernel sagt dann, okay, wie übersetzt jetzt dieser Handler in einen Kernel Pointer, den ich verwenden kann. Da gibt es diese Funktion, Reference Object by Handel, die das eine das andere umwandelt. Die API sieht so aus. Es gibt diese zwei Dinge hier, ObjectType und AccessMode. ObjectType sagt im Prinzip, der Handler von diesem spezifischen Typ, also Windows hat übrigens etwa 40 Typen von Objekten. Also wenn man jetzt ein Handel nimmt und das da verwendet und mit dieser API verwendet, wenn der Type 0 ist, dann hat man diese üblichen Type Verwechslungsbox. Also zweite Sorte vom Box ist, der Zugriffsmodus ist, muss man eigentlich User angeben, aber wenn man jetzt Kernel-Mode angebt, dann gibt der User Space plötzlich Handlers aus dem Kernel abgibt. Da gibt es viele, die eigentlich nicht geheim sind, die man einfach Brute forcen kann. Und wenn man jetzt diesen Access-Mode verwendet, kann der User einen Kernel-Handler übergeben. Ein weiteres Problem in diesem Objektzeug, wenn man fertig ist, muss man das de-referenzieren. Also dann hat man das Problem, das Konzept, wenn man einen Fehler hat, wenn ein Fehler zurückgeben wird und dann kommt man in die Fehlerbehandlungsroutine, dann kann es da Leakscam, das dann ReferenceCounting Fehler. Aber wenn das wiederholbar ist, also wenn der ReferenceCounter, das wiederholen kann, das kann es sein, dass der ReferenceCounter überläuft, wenn man das vier Milliarden mal hintereinander macht. Also wenn der überläuft und dann natürlich zu 0 geht und man das Objekt freed, aber man hat immer noch Referenzen, dann hat man dann UseAfterFree Probleme. Also seit Windows 8 wurde da ein bisschen Code hinzugefügt zu diesem Objektreferenzaufruf, der einem vor diesem Overflow schützt. Also da wird eine Kernel-Panik ausgelöst, wenn das überläuft. Also ja, setzt nicht mehr ausnutzbar. Aber ja, bis zu Windows 7 war das halt ein Problem. Dann gibt es noch, ja, DoubleReferenceCount, Abnahme, also, ja, das zu reparieren, immer ausgleichen bei den ReferenceCalls. Okay, also wir sind durch. Unfortunately, we do not have time for questions.