 Ja, willkommen bei meinem Talk Dirty Cow, Holy Cow, wie man mit schmutzigen Kühen Android routet. Erstmal vielleicht kurz. Zu Dirty Cow, Dirty Cow ist eine Privilege Escalation im Linux-Körnel, die ist ungefähr im Oktober 2016 bekannt geworden und einfach weitermachen, genau. Das Ganze wurde veröffentlicht von Phil Oester im Oktober 2016, ich glaube im Mai oder so hat er das schon entdeckt. Dirty Cow ist etwa zehn Jahre alt, wurde bereits 2005 versucht von Linus Torvalds zu patchen. Der Patch wurde allerdings dann wieder zurückgezogen, weil es dann Probleme mit der IBM ESA 390 Architektur gab und dazu diesen Zeitpunkt, die Lücke sich nicht so ohne weiteres ausnutzen ließ, ist das Ganze dann irgendwie ein bisschen in Vergessenheit geraten. Ja, betroffen sind Linux-Körnel von Version 2.6.22 bis 4.8.3, in 4.8.3 ist dann der Patch eingeflossen, der upstream Patch ist vom 18.10. auch wieder von Linus Torvalds committed worden. Ja, mein Thema ist ja Android im Zusammenhang mit Dirty Cow. Das erste Android Release verwendet den Kernel Version 2.6.25, also Android ist von Anfang an betroffen mit Dirty Cow. Ja, genau, hier nochmal die, also die CVE-Bezeichnung für die Lücke, die das Problem tritt durch eine Race-Condition auf und ermöglicht eben, das schreiben in einem Speicherbereich, der eigentlich nur lesbar sein sollte. Das Ganze passiert eben, weil ein Fleck gesetzt wird, was aber in dem Moment, wo geschrieben wird, dann inkonsistent ist und das sehen wir gleich nochmal, wie sich das dann ausnutzen lässt. Und zwar wird dafür mittels M-App einfach die zuüberschreibende Datei geladen bzw. in den virtuellen Speicherbereich geladen. Und dann kann man in zwei Threads, in dem einen Thread sage ich dem Kernel dann mit M-Advisor und NEED, dass ich eben den Speicherbereich gar nicht mehr brauche, schreibe aber trotzdem parallel im anderen Thread immer wieder rein. Das Ganze passiert in der Schleife und wenn man Glück hat, erwischt man den richtigen Moment und überschreibt dann eben den Speicherbereich, der eigentlich nicht zu überschreiben sein sollte. Genau, wie das Ganze aussieht, also das wäre dann praktisch der eine Thread, der dann in der Schleife die Loop hier, das ist variabel, also das hängt immer vom System an, wie oft man es versuchen muss. Ich glaube in meinen Tests hat das dann meistens mit 10.000 ausgereicht, umso mehr, umso länger dauert es natürlich, aber das ist ein bisschen trial and error, was man da, wie oft man die Schleife durchgeht. Genau, also in einem Thread schreiben wir halt immer, sagen wir dem Kernel mit M-Advisor immer, dass wir den Speicherbereich gar nicht mehr brauchen. Und in einem anderen Thread, der parallel läuft, versuchen wir eben auf den Speicher zu schreiben und dafür öffnen wir den Proxy of Mem über Open und sehen dann wieder in der Schleife, dass wir immer wieder in den gleichen Speicherbereich schreiben. Und am Ende, wenn wir erfolgreich waren, haben wir eben die Daten überschrieben. Das ist so kurz mal zur Erklärung. So, als erstes dann direkt mal das Ganze auf Android angewendet. Da gab es ein schönes Proof of Concept auf GitHub von TimWR. Da gibt es auf Android ein standardmäßiges Binary, das ist Run-As. Das hat das Capability-Set-UID gesetzt. Das heißt, es ist in der Lage, die UserID auf 0 zu setzen. Normalerweise macht das natürlich bestimmte Checks und erlaubt eben nicht jedem da einfach die UserID auf 0 zu setzen. Normalerweise wird das dazu, also zum Debugging verwendet, um Android-Apps dann als den bestimmten App-User zu starten und entsprechend auch auf die Daten der Apps zuzugreifen. Lässt sich normalerweise nur als Route oder eben als Shell-User ausführen. Shell-User ist der User, den man verwendet, wenn man über ADB zum Beispiel mit Android verbunden ist über die Entwickleroptionen. Ja, genau, also hier sehen wir schon, das ist ein gepatchedes Run-As, was im Prinzip diese ganzen Checks auf die ganzen Checks verzichtet und am Ende sehen wir schon UID 0, also wir sind eigentlich schon Route. An der Stelle sind wir aber noch nicht ganz fertig. Das Problem ist jetzt SE Linux. SE Linux verhindert nämlich dann an der Stelle, dass wir dann zum Beispiel ein Dateisystem mounten können oder einfach irgendwas überschreiben können. Also wir sind zwar Route, aber sobald wir dann versuchen, mount system rw, bekommen wir permission denied, weil eben einfach SE Linux ab Android 5.0, glaube ich, als Enforcing eingestellt und eben das einfach verhindert. Also habe ich mich mal ein bisschen umgesehen in den SE Policies. Erstmal hat es für diesen Fall jetzt sogar ausgereicht, einfach in die Open Source Policies zu schauen. Es gibt aber auch Möglichkeiten, sich die Binary Policies, die auf dem Device sind, auseinanderzunehmen und zu gucken, was da drin steht. In dem Fall haben die Open Source, das ist natürlich ein bisschen einfacher, weil wir da auch ein paar Kommentare haben. Das ist ein Auszug jetzt aus der Saigoti SE Policie. Saigoti ist der Prozess, der dafür verantwortlich ist, die ganzen Apps zu starten und also er macht einen Fork für jede Anwendung, die gestartet wird und genau, das ist dieser Saigoti Prozess und der hat relativ viele Berechtigungen. Hier sehen wir schon, allow Saigoti Self Capability, DHC Override, ZGID, ZUID, F-Own, also genauso wie das Run As Binary, können wir hier auch wieder die UID setzen und uns praktisch damit zum Route machen. Genau. Außerdem erlaubt der Saigoti Prozess die Transition in den System Server Kontext. Der System Server Kontext ist auch ganz interessant, da gucken wir auch mal rein. Da sehen wir, dass der System Server die Berechtigung hat, ein Kernelmodul zu laden. Ist natürlich schön, weil wenn wir im Kernel Mode sind, heißt das ja de facto, dass wir eigentlich alles machen können und dann ist unser SE Linux auch egal. Also sobald wir in den Kernel reinkommen, können wir natürlich SE Linux deaktivieren. Und genau das machen wir jetzt. Dafür habe ich ein bisschen Shale Code hier geschrieben, das ist ARM64 Assembler, weil das Gerät, also auf dem ich das Ganze getestet habe, ist ein HTC M9. Mit Android 6.0 gewesen. Und genau, das ist ein 64-Bit-System. Also hier ARM64 Assembler, diesen Shale Code, den müssen wir dann irgendwie in diesen AppProcess, also der, der diesen Saigoti und System Server bereits stellt, reinpatschen. Idealerweise wollen wir das eben so machen, dass das System davon nicht abstürzt. Also wir können natürlich einfach jetzt unser eigen AppProcessor draufpacken, aber sobald wir das tun, schmiert er natürlich ab. Das Schöne ist an dem, an der ganzen Geschichte mit M-App, dass wir, wenn wir die Datei überschreiben, verändern wir auch gleichzeitig den laufenden Prozess. Das heißt, wenn wir den jetzt so patchen, dass wir das Binary nicht zu sehr verändern, eben einfach nur ein Shale Code an der geeignete CodeCave schicken und dann einen Jump dahinsetzen, und zwar bevor der System Server den eigentlichen Fork in die Anwendung macht, können wir das Ganze machen, ohne dass das System irgendwie abstürzt. Also theoretisch könnte man dann auch eine App, die dann vielleicht irgendwo eine böswillige App, die dann einfach sich installiert und der Benutzer merkt gar nicht, dass die im Hintergrund dann Dirty Cow ausnützt und das ganze System routet, weil eben nichts abstürzt und keine Fehlermeldungen und so weiter. Also genau, das ist hier der, der, der Shale Code. Was der macht, ist eigentlich einfach der, also macht ein Fork, in dem Fall ist das jetzt Clone, weil es auf der ARM64 Architektur gibt es keinen Fork, aber Clone, also wir sehen Clone mit nur 0,5 Parametern, als Parametern ist letztendlich genau das gleiche, wie ein Fork. Also wir haben dann eine Kopie des aktuellen Prozesses und da wir das vor dem eigentlichen Fork und vor dem Privileged Drop machen, sind wir an dieser Stelle noch root und wir sind dann noch im Saigoti Process, können dann aber auch wieder wechseln in den System Server Kontext, womit wir dann wieder können modulaten können. Genau, also machen einfach ein Fork und machen dann einen exec.ve auf system.bin.tc. System.bin.tc ist auch ein Binary, was existiert. In dem Fall ist es aber auch wieder ein Binary, was vorher mittels Dirty Cow einfach ausgetauscht wurde, da läuft jetzt ein eigenes Binary, was dann eben das Kernelmodulät und gewisse andere Dinge noch macht. Also im Endeffekt können wir da irgendwas nehmen, es muss nur eine existierende Datei sein, auf dem wir Lesungszugriff haben, weil Dirty Cow uns ja eben erlaubt, eigentlich nur lesbare Dateien zu überschreiben. Wir können aber keine neuen Dateien anlegen, weil das Dateisystem ja standardmäßig read only ist. Das heißt aber, wenn wir jetzt einfach System.bin.tc. nehmen und die einfach mit was eigenem überschreiben, dann können wir die aufrufen und die kann dann machen, was sie will. Genau, um das Ganze hinzukriegen, wie kriegen wir jetzt SE Linux deaktiviert? SE Linux, dafür gibt es die Variable SE Linux Enforce, beziehungsweise SE Linux Enforcing. Wenn die auf 1 steht, heißt es, dass das SE Linux Enforce ist und dass die ganzen Regeln angewendet werden. Wenn die auf 0 steht, ist SE Linux de facto deaktiviert. Also es steht dann auf permissive. Es ist zwar noch aktiviert, aber es gibt eben nur noch die Audit-Meldungen. Wir sehen dann, was SE Linux im aktivierten Zustand verhindern würde, aber es wird nicht verhindert. Das heißt, sobald wir die auf 0 stellen, sind wir praktisch komplett im Besitz des Systems. Das ist natürlich eine kernel-internal Variable und die ist auch nicht exportiert. Das ist auch kein Symbol zu. Das heißt, es müssen ein bisschen tricksen, um überhaupt an die Variable ranzukommen. Um das Ganze zu machen, habe ich mal ein bisschen in den Source Code des Kernels reingeschaut und da gibt es diese Funktion cellreadenforce, die auf diese Variable zugreift. Das heißt, wenn wir die Funktion cellreadenforce im Kernel finden, dann können wir darüber dann auch wieder an diese Variable beziehungsweise an der Adresse kommen, an der der Wert steht, den wir überschreiben wollen. Das heißt, also so sieht die Funktion aus. Die Funktion cellreadenforce ist eine SCN Print F, um eben die SE Linux Enforcing zu lesen. So, um zu sehen, wie das Ganze dann im eigentlichen Kernel aussieht, ein Object Dump auf den Kernel, auf den entsprechenden Bereich. Also hier sehen wir auch wieder, dass der S ganz unten, also es fehlt noch ein bisschen was von der Funktion, aber wichtig ist uns eigentlich nur bis zum SCN Print F, weil das ist genau, was uns interessiert. Und diesen Teil wollen wir lesen, weil wir wissen ja, dass SCN Print F, als vierten Parameter eben diese Variable übergeben gekriegt. Das heißt, im Sammler müsste dann an der vierten Stelle die Adresse der Variable stehen. Genau, also wissen wir jetzt, wie die Funktion aussieht und dann können wir uns so ein Kernelmodul basteln. Genau, also um das Ganze zu erreichen, patchen wir eben nicht den App Process direkt, sondern die LibAndroid Runtime, die wird von dem App Process verwendet und die macht diesen eigentlichen Fork und so weiter. Das heißt, wir suchen CodeCave in dieser LibAndroid Runtime. Im Prinzip müssen wir eigentlich nur eine Funktion finden, die möglichst selten aufgerufen wird, damit wir eben keinen Fehler erzeugen. Und wir können, sobald wir root sind, können wir ja eigentlich wieder die original LibAndroid Runtime drüber schieben, um eben zu verhindern, dass vielleicht doch irgendwann mal diese Funktion, wo wir unseren Code reingeschmissen haben, ausgeführt wird und es zu einem Fehler führt. Das heißt, wir tun unseren Shell Code einfach an die entsprechende Stelle und dann genau bevor der Fork passiert, wo die Handwende gestartet wird, machen wir einen Jump in unseren Shell Code und der wiederum startet dann das SystemBendTC, wo dann einfach ein C-Programm drin liegt, was unser Kernelmodul lädt. So, hier nochmal, wie das Ganze, wie das Kernelmodul aussieht. Also es ist ein bisschen tricky. Wir suchen im Prinzip mit CallSams die Funktion CellReadInForce und lesen, gucken dann, schauen dann da rein, suchen, bestimmt haben wir uns einen Magic Marker gesetzt, in dem Fall hier diese ASM-Sub. Und suchen einfach danach, und wenn wir das gefunden haben, dann müssen wir ein bisschen bitjonglieren, weil hier genau mit ADRP gearbeitet wird, ADRP auf ARM64 setzt eben die Adresse auf den aktuellen Offset der Instruktion, also Instruction Pointer plus das, was als Parameter an ADRP übergeben wird. Das heißt, wir müssen da auch nochmal wieder ein bisschen Tricks um letztendlich die komplette Adresse dann rauszukriegen. Genau, und ja, das Ganze sieht dann so aus. Das ist dann das Kernelmodul. Wir können das laden. Das geht natürlich nur, wenn jetzt kein Module Signature aktiviert ist und so was in dem Fall glücklicherweise nicht aktiviert war. Das heißt, wir konnten das Kernelmodul einfach laden, weil der Systemserver eben diese Berechtigung hat, Kernelmodule zu laden. Und was es dann einfach macht ist, also der Teil fehlt jetzt hier, das ist jetzt hier nur die Funktion, die praktisch die Variable raussucht und die gibt mir am Ende die Adresse der Variable zurück. Und dann kann ich einfach an diese Stelle eine Null reinschreiben. Und in dem Moment, wo ich da Null reinschreibe, natürlich um das Ganze ein bisschen schöner zu haben, wollen wir irgendwie so was, normalerweise, wenn man das Thema gut hat, hat man ja dann irgendwie so was wie Supersu, was dann eben verhindert, dass einfach eine App sich Routrechte holt, ohne dass der Benutzer da irgendwie einen Zugriff drauf hat. Natürlich könnte man auch biswillig sein und einfach auf diese Supersu-Geschichte verzichten, weil ich bin ja jetzt Rout und ich kann jetzt alles machen, was ich will, ohne dass der Benutzer das werkt. Aber wenn ich jetzt mein eigenes Gerät ruhte und ich habe vorher keinen Routzugriff gehabt, für dieses Gerät gab, dann kann ich das so machen und kann Supersu im Systemless Mode installieren. Was so viel heißt, ich habe ein X4-Image einfach, wo dann die Subinaries drin liegen und das wird dann einfach mit MountBind über das aktuelle System geladen. Das heißt, ich mache eigentlich gar keine Änderung am richtigen Fallsystem, sondern ich lad einfach das X4-Image drüber. Dafür muss ich vorher noch mit Z-Namespace den Namespace wechseln, weil ich sonst eben in einem anderen Namespace bin und das funktioniert zwar. Ich habe da mein Zoo aus dem Kontext heraus. Wenn ich dann aber aus einem anderen Kontext heraus versuche, das Zoo auszuführen, wird das nicht gehen, weil das ein anderer MountNamespace ist. Also setzen wir in dem Fall das einfach auf dem gleichen Namespace wie init. Proc1, 1 ist init, NSMNT und in dem Moment sind wir beim gleichen Namespace und dann haben wir auch, weil sich die Namespaces eben vererben haben, dann alle Anwendungen eben Zugriff drauf. So sieht das Ganze aus. Wir haben dann die Super-User-Anfrage, genau. Und ja, das ist so das Ding. Dann ist noch die Sache, also das Dirty Cow ist bei Android gepatched ab dem Sicherheits-Patch-Level 6. November 2016. Also da, wo wir hier unten das Spiel sehen, das ist ein nicht gepatchedes System. Aber wenn jemand mal bei seinem eigenen System gucken will, ob das gepatched ist oder nicht, dann ist es wichtig, dass das Mindestins der 6.11.2016 mit dem den Anwendungen auf den Sicherheits-Patch-Level ist. Und vermutlich wird es sehr viele Geräte geben, die eben noch nicht diesen Patch-Level. Das ist ja das allgemein bekannte Problem bei Android, dass Google sich zwar immer bemüht, dann eben inzwischen mit diesen Security-Voluntines jeden Monat dann irgendwelche Patches rauszubringen. Aber die Hersteller leider dann oft sehr lange brauchen, bis sie tatsächlich dann ein Update rausbringen, was dann diese Patches drin hat. Geschweige denn bei ganz alten Geräten, wo es dann einfach gar keine Updates mehr gibt, wo der Benutzer dann einfach gar keine Möglichkeit hat, dieses Problem in Ordnung zu bringen. weil der Back ist im Kernel und der Benutzer kann ohne ein Update das Hersteller ist da nicht einfach den Kernel tauschen. Alternative sind natürlich so Custom ROMs wie Cyanogen, Mod oder Linux, OS jetzt, genau. So, ja. Und damit bin ich eigentlich auch schon fast durch, wenn noch Fragen sind. Also es gibt, ich habe dazu auch in der CT-Nartikel, wo es ein bisschen ausführlicher beschrieben ist, aber ansonsten stehe ich euch gerne jetzt nochmal ein paar Fragen zu Danke. Keine Fragen? Ist in neueren Android-Versionen dieses Kernel Module Signing aktiv? Das ist herstellarabhängig. Also ich glaube, das ist keine Standard, also es ist nicht von Google standardmäßig vorgesehen. Es gibt einige Systeme, wo das auch schon auf Android 6 aktiv ist. In dem Fall bei HTT war das bei Android 6 nicht aktiv, aber das ist herstellarabhängig. Könnte man dann trotzdem irgendwie da drumherum kommen? Also das ist ja jetzt nur eine Möglichkeit, wie man praktisch ... Also das war der erste Weg, den ich gefunden habe. Ich habe gesehen, ich kann Kernel modulaten und dann war mir direkt klar, okay, ich kann Kernel modulaten, wenn ich im Kernel mode bin, dann kann ich alles machen, dann kann ich SELinux deaktivieren. Aber es gibt auch andere Möglichkeiten. Also wenn man ein bisschen online schaut, dann haben auch manche versucht, dann einfach den Init-Prozess zu, also Code in the Init-Prozess zu injecten, der dann natürlich auch wieder Route-Berechtigung hat, wo ich dann gar nicht, also der einfach die Berechtigung hat, dann SELinux zu deaktivieren, wo ich dann gar kein Kernelmodul für laden muss. Also in dem Fall war jetzt der Weg über das Kernelmodul einfach der Weg des geringsten Widerstands. Ich frage mich, wie das mit Safety Net aussieht, dieser Google ... Ich frage mich, wie es mit Safety Net aussieht, dieser Google-Technik, um zu überprüfen, ob ein Telefon dem aktuellen Stand entspricht, das ja für Google Pay und auch sowas wichtig ist. Checkt Google Pay, quatscht dieses Safety Net auch irgendwie dem Patch-Level und weigert sich dann, weißt du was davon? Kann ich nicht verantworten. Aber ich vermute mal wahrscheinlich, also der Safety-Patch-Level, der kommt ja von Google, also die bringen jeden Monat diese Security-Bulletins raus und da ist dann genau beschrieben, welche Lücken in diesem Patch-Level geschlossen sein müssen, damit das Mobilität, also damit Android das diesen Patch-Level anzeigen darf. Aber wie das überprüft wird, das weiß ich nicht. Alles klar. Ja, wenn wir keine weiteren Fragen mehr haben, dann vielen Dank noch mal und noch eine schöne Guestzeit hier auf dem Easter-Hack. Danke.