
CVE-2018-4280: iOS 11.2.6 पर launchd में Mach port प्रतिस्थापन भेद्यता जो सैंडबॉक्स एस्केप, विशेषाधिकार वृद्धि और कोडसाइनिंग बाईपास की ओर ले जाती है।
Blanket एक सैंडबॉक्स एस्केप है जो iOS 11.2.6 को लक्षित करता है, हालांकि मुख्य कमजोरी को केवल iOS 11.4.1 में पैच किया गया था। यह launchd में एक Mach पोर्ट रिप्लेसमेंट कमजोरी (CVE-2018-4280) का शोषण करता है, साथ ही अन्य सेवाओं में कई छोटी कमजोरियों का, ReportCrash प्रक्रिया के अंदर कोड निष्पादित करने के लिए, जो बिना सैंडबॉक्स के है, रूट के रूप में चलती है, और इसमें task_for_pid-allow एंटाइटलमेंट है। यह blanket को फोन पर चलने वाली हर प्रक्रिया पर नियंत्रण प्रदान करता है, जिसमें amfid जैसी सुरक्षा-महत्वपूर्ण प्रक्रियाएं भी शामिल हैं।
एक्सप्लॉइट में कई चरण शामिल हैं। यह README मुख्य कमजोरी और सैंडबॉक्स एस्केप के चरणों को चरण-दर-चरण समझाएगा।
iOS पर क्रैश रिपोर्टिंग पर शोध करते समय, मैंने launchd में एक Mach पोर्ट रिप्लेसमेंट कमजोरी की खोज की। एक विशेष तरीके से क्रैश होने पर, एक प्रक्रिया कर्नेल को launchd को एक Mach संदेश भेजने के लिए प्रेरित कर सकती है, जिससे launchd अपने IPC नेमस्पेस में एक Mach पोर्ट के लिए एक भेजने के अधिकार को अधिक-डीलोकेट कर देता है। यह एक हमलावर को किसी भी launchd सेवा का प्रतिरूपण करने की अनुमति देता है जिसे वह सिस्टम के बाकी हिस्सों में देख सकता है, जो विशेषाधिकार वृद्धि के लिए कई रास्ते खोलता है।
यह कमजोरी macOS पर भी मौजूद है, लेकिन iOS पर कमजोरी को ट्रिगर करना अधिक कठिन है क्योंकि launchd में जांच होती है जो सुनिश्चित करती है कि Mach अपवाद संदेश कर्नेल से आता है।
Launchd अपने मुख्य पोर्ट पर कई अलग-अलग Mach संदेश हैंडलरों को मल्टीप्लेक्स करता है, जिसमें अपवाद संदेशों के लिए एक MIG हैंडलर भी शामिल है। यदि कोई प्रक्रिया अपने स्वयं के बूटस्ट्रैप पोर्ट पर mach_exception_raise या mach_exception_raise_state_identity संदेश भेजती है, तो launchd उस संदेश को होस्ट-स्तरीय अपवाद के रूप में प्राप्त और संसाधित करेगा।
दुर्भाग्य से, इन संदेशों को संभालने में launchd का व्यवहार बगी है। यदि अपवाद प्रकार EXC_CRASH है, तो launchd संदेश में भेजे गए थ्रेड और टास्क पोर्ट को डीलोकेट करेगा और फिर सेवा रूटीन से KERN_FAILURE लौटाएगा, जिससे MIG सिस्टम थ्रेड और टास्क पोर्ट को फिर से डीलोकेट करेगा। (धारणा यह है कि यदि कोई सेवा रूटीन सफलता लौटाती है, तो उसने Mach संदेश में सभी संसाधनों का स्वामित्व ले लिया है, जबकि यदि सेवा रूटीन त्रुटि लौटाती है, तो उसने कोई भी संसाधन नहीं लिया है।)
यहाँ launchd की mach_exception_raise संदेशों के लिए सेवा रूटीन का कोड है, जिसे IDA/Hex-Rays का उपयोग करके डीकंपाइल किया गया है और पठनीयता के लिए हल्के से संपादित किया गया है:```C
kern_return_t __fastcall
catch_mach_exception_raise( // (a) The service routine is
mach_port_t exception_port, // called with values directly
mach_port_t thread, // from the Mach message
mach_port_t task, // sent by the client. The
exception_type_t exception, // thread and task ports could
mach_exception_data_t code, // be arbitrary send rights.
mach_msg_type_number_t codeCnt)
{
__int64 __stack_guard; // ST28_8@1
kern_return_t kr; // w0@1 MAPDST
kern_return_t result; // w0@4
__int64 codes_left; // x25@6
mach_exception_data_type_t code_value; // t1@7
int pid; // [xsp+34h] [xbp-44Ch]@1
char codes_str[1024]; // [xsp+38h] [xbp-448h]@7
__stack_guard = *__stack_chk_guard_ptr;
pid = -1;
kr = pid_for_task(task, &pid);
if ( kr )
{
_os_assumes_log(kr);
_os_avoid_tail_call();
}
if ( current_audit_token.val[5] ) // (b) If the message was sent by
{ // a process with a nonzero PID
result = KERN_FAILURE; // (any non-kernel process),
} // the message is rejected.
else
{
if ( codeCnt )
{
codes_left = codeCnt;
do
{
code_value = *code;
++code;
__snprintf_chk(codes_str, 0x400uLL, 0, 0x400uLL, "0x%llx", code_value);
--codes_left;
}
while ( codes_left );
}
launchd_log_2(
0LL,
3LL,
"Host-level exception raised: pid = %d, thread = 0x%x, "
"exception type = 0x%x, codes = { %s }",
pid,
thread,
exception,
codes_str);
kr = deallocate_port(thread); // (c) The "thread" port sent in
if ( kr ) // the message is deallocated.
{
_os_assumes_log(kr);
_os_avoid_tail_call();
}
kr = deallocate_port(task); // (d) The "task" port sent in the
if ( kr ) // message is deallocated.
{
_os_assumes_log(kr);
_os_avoid_tail_call();
}
if ( exception == EXC_CRASH ) // (e) If the exception type is
result = KERN_FAILURE; // EXC_CRASH, then KERN_FAILURE
else // is returned. MIG will
result = 0; // deallocate the ports again.
}
*__stack_chk_guard_ptr;
return result;
}
यह कोड वास्तव में क्या करता है:
1. यह फ़ंक्शन `mach_exception_raise` अपवाद संदेशों के लिए Mach सेवा दिनचर्या है: यह सीधे Mach सिस्टम द्वारा तब लागू किया जाता है जब launchd `mach_exception_raise` Mach अपवाद संदेश को संसाधित करता है। सेवा दिनचर्या के तर्क Mach संदेश से पार्स किए जाते हैं, और इसलिए संदेश भेजने वाले द्वारा नियंत्रित होते हैं।
2. (b) पर, launchd जाँचता है कि Mach अपवाद संदेश केर्नल द्वारा भेजा गया था या नहीं। प्रेषक के ऑडिट टोकन में फ़ील्ड 5 में भेजने वाली प्रक्रिया का PID होता है, जो केवल केर्नल के लिए शून्य होगा। यदि संदेश केर्नल द्वारा नहीं भेजा गया, तो इसे अस्वीकार कर दिया जाता है।
3. संदेश से थ्रेड और टास्क पोर्ट को (c) और (d) पर स्पष्ट रूप से डीलोकेट किया जाता है।
4. (e) पर, launchd जाँचता है कि अपवाद प्रकार `EXC_CRASH` है या नहीं, और यदि ऐसा है तो `KERN_FAILURE` लौटाता है। इसका उद्देश्य यह सुनिश्चित करना है कि `EXC_CRASH` संदेशों को न संभाला जाए, संभवतः ताकि ReportCrash को कॉर्प्स हैंडलर के रूप में लागू किया जा सके। हालांकि, इस बिंदु पर `KERN_FAILURE` लौटाने से बाद में अपवाद संदेश को साफ करते समय टास्क और थ्रेड पोर्ट फिर से डीलोकेट हो जाएंगे। इसका मतलब है कि ये दो पोर्ट ओवर-डीलोकेट हो जाएंगे।
इस भेद्यता के उपयोगी होने के लिए, हम launchd के एक Mach सेवा के लिए भेजने के अधिकार को मुक्त करना चाहेंगे, ताकि हम फिर उस सेवा का रूप धारण कर सकें और सिस्टम के बाकी हिस्सों से संवाद कर सकें। इसका मतलब है कि हमें अपवाद संदेश में टास्क और थ्रेड पोर्ट को वास्तव में उस Mach सेवा पोर्ट के लिए भेजने के अधिकार के रूप में चाहिए जिसे हम launchd में मुक्त करना चाहते हैं। फिर, एक बार जब हम launchd को दुर्भावनापूर्ण अपवाद संदेश भेजकर सेवा पोर्ट को मुक्त कर देते हैं, तो हम उसी पोर्ट नाम को पुनः उपयोग करने का प्रयास करेंगे, लेकिन इस बार एक ऐसे Mach पोर्ट के लिए जिसके लिए हमें प्राप्त करने का अधिकार है। इस तरह, जब कोई क्लाइंट launchd से सेवा के लिए Mach पोर्ट पर भेजने का अधिकार मांगता है, तो launchd उसके बजाय हमारे पोर्ट पर भेजने का अधिकार देगा, जिससे हम क्लाइंट के सामने उस सेवा का रूप धारण कर सकते हैं। उसके बाद, सिस्टम विशेषाधिकार प्राप्त करने के कई अलग-अलग तरीके हैं।
### भेद्यता को ट्रिगर करना
भेद्यता को वास्तव में ट्रिगर करने के लिए, हमें उस जाँच को बायपास करना होगा जो सुनिश्चित करती है कि संदेश केर्नल द्वारा भेजा गया था। ऐसा इसलिए है क्योंकि यदि हम अपवाद संदेश सीधे launchd को भेजते हैं तो वह खारिज कर दिया जाएगा। किसी तरह, हमें केर्नल से एक "दुर्भावनापूर्ण" अपवाद संदेश भिजवाना होगा जिसमें वास्तविक थ्रेड और टास्क पोर्ट के बजाय एक सिस्टम सेवा के लिए Mach भेजने का अधिकार हो।
जैसा कि पता चला है, एक Mach ट्रैप है, `task_set_special_port`, जिसका उपयोग कुछ स्थितियों में वास्तविक टास्क पोर्ट के स्थान पर उपयोग करने के लिए एक कस्टम भेजने का अधिकार सेट करने के लिए किया जा सकता है। ऐसी ही एक स्थिति तब होती है जब केर्नल किसी टास्क की ओर से एक अपवाद संदेश उत्पन्न करता है: वास्तविक टास्क भेजने के अधिकार को अपवाद संदेश में रखने के बजाय, केर्नल `task_set_special_port` द्वारा प्रदान किए गए भेजने के अधिकार का उपयोग करेगा। अधिक विशेष रूप से, यदि कोई टास्क अपने `TASK_KERNEL_PORT` विशेष पोर्ट के लिए एक कस्टम मान सेट करने के लिए `task_set_special_port` को कॉल करता है और फिर टास्क क्रैश हो जाता है, तो केर्नल द्वारा उत्पन्न अपवाद संदेश में "टास्क" फ़ील्ड में वास्तविक टास्क पोर्ट के बजाय कस्टम पोर्ट पर भेजने का अधिकार होगा। एक समतुल्य API, `thread_set_special_port`, का उपयोग उत्पन्न अपवाद संदेश के "थ्रेड" फ़ील्ड में एक कस्टम पोर्ट सेट करने के लिए किया जा सकता है।
इस व्यवहार के कारण, केर्नल को एक "दुर्भावनापूर्ण" अपवाद संदेश उत्पन्न कराना वास्तव में कठिन नहीं है जिसमें टास्क और थ्रेड पोर्ट के बजाय एक Mach सेवा पोर्ट हो। हालांकि, हमें अभी भी यह सुनिश्चित करने की आवश्यकता है कि हमारे द्वारा उत्पन्न अपवाद संदेश launchd तक पहुंचाया जाए।
एक बार फिर, यह सुनिश्चित करना कि केर्नल "दुर्भावनापूर्ण" अपवाद संदेश को launchd तक पहुंचाए, मुश्किल नहीं है यदि आप सही API जानते हैं। फ़ंक्शन `thread_set_exception_ports` किसी भी Mach भेजने के अधिकार को उस पोर्ट के रूप में सेट कर देगा जहां इस थ्रेड पर अपवाद संदेश भेजे जाते हैं। इस प्रकार, हमें बस `thread_set_exception_ports` को बूटस्ट्रैप पोर्ट के साथ लागू करने की आवश्यकता है, और फिर हम जो भी अपवाद उत्पन्न करेंगे, वह केर्नल को launchd को एक अपवाद संदेश भेजने का कारण बनेगा।
पहेली का अंतिम टुकड़ा सही अपवाद प्रकार प्राप्त करना है। भेद्यता केवल `EXC_CRASH` अपवादों के लिए ट्रिगर होगी। थोड़े परीक्षण और त्रुटि से पता चलता है कि हम मानक `abort` फ़ंक्शन को कॉल करके आसानी से `EXC_CRASH` अपवाद उत्पन्न कर सकते हैं।
इस प्रकार, संक्षेप में, हम मौजूदा और अच्छी तरह से प्रलेखित API का उपयोग कर सकते हैं ताकि केर्नल हमारी ओर से एक दुर्भावनापूर्ण `EXC_CRASH` अपवाद संदेश उत्पन्न करे और उसे launchd तक पहुंचाए, जिससे भेद्यता ट्रिगर हो और Mach सेवा पोर्ट मुक्त हो:
1. इस थ्रेड के लिए अपवाद हैंडलर के रूप में launchd को सेट करने के लिए `thread_set_exception_ports` का उपयोग करें।
2. launchd से उस सेवा के लिए सेवा पोर्ट प्राप्त करने के लिए `bootstrap_look_up` को कॉल करें जिसका हम रूप धारण करना चाहते हैं।
3. अपवाद संदेशों में वास्तविक टास्क और थ्रेड पोर्ट के बजाय उस सेवा पोर्ट का उपयोग करने के लिए `task_set_special_port`/`thread_set_special_port` को कॉल करें।
4. `abort` को कॉल करें। केर्नल launchd को एक `EXC_CRASH` अपवाद संदेश भेजेगा, लेकिन संदेश में टास्क और थ्रेड पोर्ट लक्ष्य सेवा पोर्ट होंगे।
5. Launchd अपवाद संदेश को संसाधित करेगा और सेवा पोर्ट को मुक्त कर देगा।
### क्रैश के बाद कोड चलाना
उपरोक्त रणनीति में एक समस्या है: `abort` को कॉल करने से हमारी प्रक्रिया समाप्त हो जाएगी। यदि हम भेद्यता को ट्रिगर करने के बाद कोई कोड चलाने में सक्षम होना चाहते हैं, तो हमें क्रैश को किसी अन्य प्रक्रिया में करने का एक तरीका चाहिए।
(अन्य अपवाद प्रकारों के साथ एक प्रक्रिया वास्तव में अपवाद से उबर सकती है। एक प्रक्रिया जिस तरह से उबरेगी वह यह है कि वह अपने थ्रेड अपवाद हैंडलर को launchd और अपने टास्क अपवाद हैंडलर को स्वयं सेट करेगी। Launchd द्वारा अपवाद को संसाधित करने और उसे संभालने में विफल होने के बाद, केर्नल अपवाद को टास्क हैंडलर को भेजेगा, जो थ्रेड स्थिति को रीसेट करेगा और केर्नल को सूचित करेगा कि अपवाद को संभाल लिया गया है। हालांकि, एक प्रक्रिया अपने स्वयं के `EXC_CRASH` अपवादों को नहीं पकड़ सकती है, इसलिए हमें दो प्रक्रियाओं की आवश्यकता है।)
एक रणनीति iOS पर किसी अन्य प्रक्रिया में एक भेद्यता का शोषण करना और उस प्रक्रिया को अपने केर्नल पोर्ट सेट करने और क्रैश करने के लिए मजबूर करना है। हालांकि, एक प्रूफ-ऑफ-कॉन्सेप्ट के लिए, एक ऐप एक्सटेंशन बनाना आसान है।
iOS 8 में पेश किए गए ऐप एक्सटेंशन, एक एप्लिकेशन की कुछ कार्यक्षमता को पैकेज करने का एक तरीका प्रदान करते हैं ताकि वह एप्लिकेशन के बाहर उपलब्ध हो। एक ऐप एक्सटेंशन का कोड एक अलग, सैंडबॉक्स प्रक्रिया में चलता है। यह एक ऐसी प्रक्रिया लॉन्च करना बहुत आसान बनाता है जो अपने विशेष पोर्ट सेट करेगी, launchd को अपने `EXC_CRASH` के लिए अपवाद हैंडलर के रूप में पंजीकृत करेगी, और फिर `abort` को कॉल करेगी।
एक ऐप के लिए अपने स्वयं के ऐप एक्सटेंशन को प्रोग्रामेटिक रूप से लॉन्च करने और उससे बात करने का कोई समर्थित तरीका नहीं है। हालांकि, इयान मैकडॉवेल ने एक [बेहतरीन लेख][Multi-Process iOS App Using NSExtension] लिखा है जिसमें बताया गया है कि एक ऐप एक्सटेंशन प्रक्रिया को लॉन्च करने और उससे संवाद करने के लिए निजी `NSExtension` API का उपयोग कैसे करें। मैंने यहां लगभग समान रणनीति का उपयोग किया है। एकमात्र अंतर यह है कि हमें ऐप एक्सटेंशन प्रक्रिया को एक Mach पोर्ट संचारित करने की आवश्यकता है, जिसमें launchd के साथ एक डमी सेवा पंजीकृत करना शामिल है जिससे ऐप एक्सटेंशन जुड़ता है।
[Multi-Process iOS App Using NSExtension]: https://ianmcdowell.net/blog/nsextension/
### launchd में पोर्ट पुन: उपयोग को रोकना
एक चुनौती जो आपको तब दिखाई देगी यदि आप वर्णित अनुसार शोषण चलाते हैं, वह यह है कि कभी-कभी आप मुक्त किए गए पोर्ट को पुनः प्राप्त नहीं कर पाएंगे। इसका कारण यह है कि केर्नल एक प्रक्रिया की मुफ्त IPC प्रविष्टियों को एक फ्रीलिस्ट में ट्रैक करता है, और इसलिए एक मुक्त किए गए पोर्ट नाम का पुन: उपयोग (एक अलग जनरेशन नंबर के साथ) किया जाएगा जब IPC तालिका में एक नया पोर्ट आवंटित किया जाता है। इस प्रकार, हम केवल उस पोर्ट नाम को पुनः आवंटित करेंगे यदि launchd पहले किसी अन्य पोर्ट के लिए उस IPC प्रविष्टि स्लॉट का पुन: उपयोग नहीं करता है।
इससे निपटने का तरीका मुफ्त IPC प्रविष्टि स्लॉट को फ्रीलिस्ट में नीचे दबा देना है, ताकि यदि launchd नए पोर्ट आवंटित करता है तो पहले उन अन्य स्लॉट का उपयोग किया जाएगा। हम यह कैसे करते हैं? हम launchd में बहुत सी डमी Mach सेवाओं को उन पोर्ट के साथ पंजीकृत कर सकते हैं जिनके लिए हमारे पास प्राप्त करने का अधिकार है। जब हम `abort` को कॉल करते हैं, तो अपवाद हैंडलर पहले फायर करेगा, और फिर प्रक्रिया की स्थिति, जिसमें Mach पोर्ट शामिल हैं, साफ कर दी जाएगी। जब launchd `EXC_CRASH` अपवाद प्राप्त करता है तो वह अनजाने में लक्ष्य सेवा पोर्ट को मुक्त कर देगा, जिससे उस पोर्ट नाम के अनुरूप IPC प्रविष्टि स्लॉट फ्रीलिस्ट के शीर्ष पर आ जाएगा। फिर, जब हमारे ऐप एक्सटेंशन के शेष Mach पोर्ट नष्ट हो जाते हैं, तो launchd को सूचनाएं प्राप्त होंगी और वह डमी सेवा पोर्ट को मुक्त कर देगा, जिससे लक्ष्य IPC प्रविष्टि स्लॉट अभी-अभी मुक्त किए गए पोर्ट के स्लॉट के पीछे दब जाएगा। इस प्रकार, जब तक launchd हमारे द्वारा पंजीकृत डमी सेवाओं की संख्या से कम पोर्ट आवंटित करता है, तब तक लक्ष्य स्लॉट अभी भी फ्रीलिस्ट पर होगा, जिसका अर्थ है कि हम अभी भी launchd को मूल सेवा के समान पोर्ट नाम के साथ स्लॉट को पुनः आवंटित करने का कारण बना सकते हैं।
इस रणनीति की सीमा यह है कि launchd के साथ सेवाओं को पंजीकृत करने के लिए हमें `com.apple.security.application-groups` एंटाइटलमेंट की आवश्यकता है। launchd में Mach पोर्ट छुपाने के अन्य तरीके हैं, लेकिन एप्लिकेशन समूहों का उपयोग करना निश्चित रूप से सबसे आसान है, और इस प्रूफ-ऑफ-कॉन्सेप्ट के लिए पर्याप्त है।
### मुक्त सेवा का रूप धारण करना
एक बार जब हमने क्रैशर ऐप एक्सटेंशन को प्रकट किया और launchd में एक Mach भेजने के अधिकार को मुक्त कर दिया, तो हमें उस Mach पोर्ट नाम को एक भेजने के अधिकार के साथ पुनः आवंटित करने की आवश्यकता है जिसके लिए हमारे पास प्राप्त करने का अधिकार है। इस तरह, launchd द्वारा उस पोर्ट नाम पर भेजे गए कोई भी संदेश हमें प्राप्त होंगे, और जब भी launchd उस पोर्ट नाम को किसी क्लाइंट के साथ साझा करेगा, तो क्लाइंट को हमारे पोर्ट पर भेजने का अधिकार प्राप्त होगा। विशेष रूप से, यदि हम launchd के किसी Mach सेवा के लिए भेजने के अधिकार को मुक्त कर सकते हैं, तो कोई भी प्रक्रिया जो launchd से उस सेवा का अनुरोध करेगी, उसे वास्तविक सेवा पोर्ट के बजाय हमारे अपने पोर्ट पर भेजने का अधिकार प्राप्त होगा। यह हमें सेवा का रूप धारण करने या मैन-इन-द-मिडल हमला करने की अनुमति देता है, क्लाइंट द्वारा सेवा को भेजे गए सभी संदेशों का निरीक्षण करता है।
मुक्त किए गए पोर्ट नाम का पुन: उपयोग कराना ताकि वह हमारे स्वामित्व वाले पोर्ट को संदर्भित करे, यह भी काफी सरल है, यह देखते हुए कि हमने पहले ही एप्लिकेशन-ग्रुप एंटाइटलमेंट का उपयोग करने का निर्णय लिया है: बस launchd के साथ डमी Mach सेवाओं को तब तक पंजीकृत करें जब तक कि उनमें से एक मूल पोर्ट नाम का पुन: उपयोग न कर ले। हमें इसे बैचों में करने की आवश्यकता होगी, बड़ी संख्या में डमी सेवाओं को एक साथ पंजीकृत करना, यह जांचना कि क्या किसी ने सफलतापूर्वक मुक्त पोर्ट नाम का पुन: उपयोग किया है, और फिर उन्हें अपंजीकृत करना। इसका कारण यह है कि हमें यह सुनिश्चित करने की आवश्यकता है कि हमारे पंजीकरण IPC पोर्ट फ्रीलिस्ट में वापस जाकर उस दबे हुए पोर्ट नाम को पुनः प्राप्त करें जिसे हम चाहते हैं।
हम यह जांच सकते हैं कि क्या हमने `bootstrap_look_up` के साथ मूल सेवा को देखकर मुक्त पोर्ट नाम का सफलतापूर्वक पुन: उपयोग किया है: यदि यह हमारे पंजीकृत सेवा पोर्टों में से एक लौटाता है, तो हम कर चुके हैं।
एक बार जब हम एक नई सेवा पंजीकृत करने में सफल हो जाते हैं जो मूल के समान पोर्ट नाम प्राप्त करती है, तो जो भी क्लाइंट launchd में मूल सेवा को देखेंगे, उन्हें वास्तविक सेवा पोर्ट नहीं, बल्कि हमारे पोर्ट पर भेजने का अधिकार दिया जाएगा। इस प्रकार, हम सिस्टम के बाकी हिस्सों के लिए प्रभावी रूप से मूल सेवा का रूप धारण कर रहे हैं (या कम से कम, उन प्रक्रियाओं के लिए जो हमारे हमले के बाद सेवा को देखते हैं)।
स्टेज 1: होस्ट-प्रिव पोर्ट प्राप्त करना
---------------------------------------------------------------------------------------------------
एक बार जब हमारे पास मनमानी सिस्टम सेवाओं का रूप धारण करने की क्षमता होती है, तो अगला कदम होस्ट-प्रिव पोर्ट प्राप्त करना है। यह कदम सीधा है, और iOS 11.3 में परिवर्तनों से प्रभावित नहीं होता है। इस हमले का उच्च-स्तरीय विचार SafetyNet का रूप धारण करना, ReportCrash को क्रैश करना, और फिर अपवाद संदेश में भेजे गए मरते हुए ReportCrash टास्क पोर्ट से होस्ट-प्रिव पोर्ट प्राप्त करना है।
### ReportCrash और SafetyNet के बारे में
ReportCrash iOS पर क्रैश रिपोर्ट उत्पन्न करने के लिए जिम्मेदार है। यह एकल बाइनरी वास्तव में 4 अलग-अलग सेवाओं (प्रत्येक एक अलग प्रक्रिया में, हालांकि सभी किसी भी समय चल रही नहीं हो सकती हैं) को बेचती है:
1. `com.apple.ReportCrash` क्रैश होने वाली प्रक्रियाओं के लिए क्रैश रिपोर्ट उत्पन्न करने के लिए जिम्मेदार है। यह `EXC_CRASH`, `EXC_GUARD`, और `EXC_RESOURCE` अपवादों के लिए होस्ट-स्तरीय अपवाद हैंडलर है।
2. `com.apple.ReportCrash.Jetsam` Jetsam रिपोर्टों को संभालता है।
3. `com.apple.ReportCrash.SimulateCrash` सिम्युलेटेड क्रैश के लिए रिपोर्ट बनाता है।
4. `com.apple.ReportCrash.SafetyNet` `com.apple.ReportCrash` सेवा के लिए पंजीकृत अपवाद हैंडलर है।
हमारे लिए रुचिकर सेवाएं `com.apple.ReportCrash` और `com.apple.ReportCrash.SafetyNet` हैं, जिन्हें आगे से केवल ReportCrash और SafetyNet कहा जाएगा। ये दोनों MIG-आधारित सेवाएं हैं, और ये प्रभावी रूप से समान कोड चलाती हैं।
जब ReportCrash शुरू होता है, तो वह launchd में SafetyNet सेवा को देखता है और लौटाए गए पोर्ट को टास्क-स्तरीय अपवाद हैंडलर के रूप में सेट करता है। इसका उद्देश्य यह प्रतीत होता है कि यदि ReportCrash स्वयं क्रैश हो जाए, तो एक अलग प्रक्रिया इसके लिए क्रैश रिपोर्ट उत्पन्न करेगी। हालांकि, यह कोड पथ निष्क्रिय दिखता है: ReportCrash SafetyNet को `mach_exception_raise` संदेशों के लिए पंजीकृत करता है, जबकि दोनों ReportCrash और SafetyNet केवल `mach_exception_raise_state_identity` संदेशों को संभालते हैं। फिर भी, दोनों सेवाएं अभी भी मौजूद हैं और iOS कंटेनर सैंडबॉक्स से पहुंच योग्य हैं।
### ReportCrash हेरफेर आदिम
निम्नलिखित हमले को अंजाम देने के लिए, हमें ReportCrash (या SafetyNet) को उस तरीके से व्यवहार करने में सक्षम होना चाहिए जैसा हम चाहते हैं। विशेष रूप से, हमें निम्नलिखित क्षमताओं की आवश्यकता है: मांग पर ReportCrash शुरू करना, ReportCrash को बाहर निकलने के लिए मजबूर करना, ReportCrash को क्रैश करना, और यह सुनिश्चित करना कि जब हम इसका उपयोग कर रहे हों तो ReportCrash बाहर न निकले। यहां मैं बताऊंगा कि हम प्रत्येक उद्देश्य को कैसे प्राप्त करते हैं।
ReportCrash शुरू करने के लिए, हमें बस इसे एक Mach संदेश भेजने की आवश्यकता है: launchd इसे मांग पर शुरू करेगा। हालांकि, इसके विशिष्ट डिजाइन के कारण, `mach_exception_raise_state_identity` को छोड़कर कोई भी संदेश प्रकार ReportCrash को नए संदेशों का जवाब देना बंद करने और अंततः बाहर निकलने का कारण बनेगा। इस प्रकार, यदि हम चाहते हैं कि यह बाद में जीवित रहे, तो हमें `mach_exception_raise_state_identity` संदेश भेजना होगा।
ReportCrash से बाहर निकलने के लिए, हम इसे किसी अन्य प्रकार का Mach संदेश भेज सकते हैं।
ReportCrash को क्रैश करने के कई तरीके हैं। संभवतः सबसे आसान है थ्रेड पोर्ट को `MACH_PORT_NULL` पर सेट करके `mach_exception_raise_state_identity` संदेश भेजना।
अंत में, हमें यह सुनिश्चित करने की आवश्यकता है कि जब हम इसका उपयोग कर रहे हों तो ReportCrash बाहर न निकले। यह जो प्रत्येक `mach_exception_raise_state_identity` संदेश संसाधित करता है, वह अगले संदेश को सुनने के लिए एक और थ्रेड शुरू करने का कारण बनता है जबकि मूल थ्रेड क्रैश रिपोर्ट उत्पन्न करता है। ReportCrash तब बाहर निकलेगा जब क्रैश रिपोर्ट उत्पन्न करने वाले सभी लंबित थ्रेड समाप्त हो जाएंगे। इस प्रकार, यदि हम उन थ्रेड्स में से एक को तब रोक सकते हैं जब वह क्रैश रिपोर्ट उत्पन्न करने की प्रक्रिया में है, तो हम इसे कभी भी बाहर निकलने से रोक सकते हैं।
मुझे ऐसा करने का सबसे आसान तरीका टास्क और थ्रेड फ़ील्ड में एक कस्टम पोर्ट के साथ एक `mach_exception_raise_state_identity` संदेश भेजना था। एक बार जब ReportCrash एक क्रैश रिपोर्ट उत्पन्न करने का प्रयास करता है, तो वह "टास्क" पोर्ट पर `task_policy_get` को कॉल करेगा, जो उस पोर्ट पर एक Mach संदेश भेजने का कारण बनेगा जिसे हमने भेजा था और एक उत्तर की प्रतीक्षा करेगा। लेकिन चूंकि "टास्क" पोर्ट सिर्फ एक साधारण पुराना Mach पोर्ट है, हम बस Mach संदेश का उत्तर नहीं दे सकते हैं, और ReportCrash `task_policy_get` के लिए अनिश्चित काल तक प्रतीक्षा करेगा।
### ReportCrash से होस्ट-प्रिव निकालना
शोषण के पहले चरण के लिए, हमला योजना अपेक्षाकृत सीधी है:
1. SafetyNet सेवा शुरू करें और इसे हमारे हमले की अवधि के लिए जीवित रहने के लिए मजबूर करें।
2. SafetyNet का रूप धारण करने के लिए लॉन्चड सेवा प्रतिरूपण आदिम का उपयोग करें। यह हमें एक नया पोर्ट देता है जिस पर हम वास्तविक SafetyNet सेवा के लिए इच्छित संदेश प्राप्त कर सकते हैं।
3. ReportCrash के किसी भी मौजूदा उदाहरण को बाहर निकलने दें। इस तरह, हम सुनिश्चित कर सकते हैं कि ReportCrash अगले चरण में हमारे SafetyNet पोर्ट को देखे।
4. ReportCrash शुरू करें। ReportCrash launchd में SafetyNet को देखेगा और परिणामी पोर्ट, जो कि नकली SafetyNet पोर्ट है जिसके लिए हमारे पास प्राप्त करने का अधिकार है, को `EXC_CRASH` संदेशों के गंतव्य के रूप में सेट करेगा।
5. ReportCrash में एक क्रैश ट्रिगर करें। मूल अपवाद प्रकार के लिए कोई पंजीकृत हैंडलर नहीं देखने के बाद, ReportCrash प्रक्रिया मृत्यु चरण में प्रवेश करेगा। इस बिंदु पर XNU देखेगा कि ReportCrash ने `EXC_CRASH` अपवाद प्राप्त करने के लिए नकली SafetyNet पोर्ट पंजीकृत किया है, इसलिए यह एक अपवाद संदेश उत्पन्न करेगा और उसे उस पोर्ट पर भेजेगा।
6. फिर हम `EXC_CRASH` संदेश के लिए नकली SafetyNet पोर्ट पर सुनते हैं। यह `mach_exception_raise` प्रकार का होगा, जिसका अर्थ है कि इसमें ReportCrash का टास्क पोर्ट होगा।
7. अंत में, हम ReportCrash का होस्ट पोर्ट प्राप्त करने के लिए ReportCrash टास्क पोर्ट पर `task_get_special_port` का उपयोग करते हैं। चूंकि ReportCrash असैंडबॉक्सित है और रूट के रूप में चलता है, यह होस्ट-प्रिव पोर्ट है।
सैंडबॉक्स से बचने के इस चरण के अंत में, हमारे पास एक उपयोग योग्य होस्ट-प्रिव पोर्ट होता है। यह अकेला दर्शाता है कि यह एक गंभीर सुरक्षा मुद्दा है।
स्टेज 2: सैंडबॉक्स से बचना
---------------------------------------------------------------------------------------------------
भले ही हमारे पास होस्ट-प्रिव पोर्ट हो, हमारा लक्ष्य पूरी तरह से सैंडबॉक्स से बचना और रूट के रूप में कोड चलाना है, जिसमें `task_for_pid-allow` एंटाइटलमेंट हो। इसे प्राप्त करने में पहला कदम केवल सैंडबॉक्स से बचना है।
तकनीकी रूप से बोलते हुए, सैंडबॉक्स से बचने से पहले होस्ट-प्रिव पोर्ट प्राप्त करने का कोई कारण नहीं है: ये दो चरण स्वतंत्र हैं और किसी भी क्रम में हो सकते हैं। हालांकि, यह चरण सिस्टम को अस्थिर छोड़ देगा यदि यह या बाद के चरण विफल हो जाते हैं, इसलिए इसे बाद में रखना उचित है।
उच्च-स्तरीय हमला एक सिस्टम सेवा का रूप धारण करने के लिए उसी लॉन्चड भेद्यता का उपयोग करना है। हालांकि, इस बार हमारा लक्ष्य एक ऐसी सेवा का रूप धारण करना है जिसके लिए कोई क्लाइंट Mach संदेश में अपना टास्क पोर्ट भेजेगा। iOS 11.2.6 पर प्रयोग द्वारा यह पता लगाना आसान है कि यदि हम backboardd द्वारा होस्ट की गई `com.apple.CARenderServer` (इसके बाद CARenderServer) का रूप धारण करते हैं और फिर `com.apple.DragUI.druid.source` के साथ संवाद करते हैं, तो असैंडबॉक्सित druid डेमॉन नकली सेवा पोर्ट पर एक Mach संदेश में अपना टास्क पोर्ट भेजेगा।
शोषण का यह चरण iOS 11.3 पर टूटा हुआ है क्योंकि druid अब CARenderServer को Mach संदेश में अपना टास्क पोर्ट नहीं भेजता है। इसके बावजूद, मुझे विश्वास है कि इस भेद्यता का उपयोग अभी भी सैंडबॉक्स से बचने के लिए किया जा सकता है। ऐसा करने का एक तरीका उन असैंडबॉक्सित सेवाओं की तलाश करना है जो अन्य सेवाओं से इनपुट पर भरोसा करती हैं। इस प्रकार की "भेद्यताएं" सिस्टम सेवाओं को बदलने की क्षमता के बिना कभी भी शोषण योग्य नहीं होंगी, जिसका अर्थ है कि वे शायद Apple के अंदर और बाहर दोनों जगह कम प्राथमिकता वाली हमले की सतह हैं।
### druid को क्रैश करना
ReportCrash की तरह ही, हमें druid को पुनरारंभ करने में सक्षम होने की आवश्यकता है, यदि वह पहले से चल रहा है, ताकि वह launchd में हमारे नकली CARenderServer पोर्ट को देखे। मैंने इस उद्देश्य के लिए libxpc में एक बग का उपयोग करने का निर्णय लिया जो पहले से ही ठीक किए जाने वाला था।
libxpc में देखते हुए, मुझे एक आउट-ऑफ-बाउंड रीड मिला जिसका उपयोग किसी भी XPC सेवा को क्रैश करने के लिए किया जा सकता है:```C
void _xpc_dictionary_apply_wire_f
(
OS_xpc_dictionary *xdict,
OS_xpc_serializer *xserializer,
const void *context,
bool (*applier_fn)(const char *, OS_xpc_serializer *, const void *)
)
{
...
uint64_t count = (unsigned int)*serialized_dict_count;
if ( count )
{
uint64_t depth = xserializer->depth;
uint64_t index = 0;
do
{
const char *key = _xpc_serializer_read(xserializer, 0, 0, 0);
size_t keylen = strlen(key);
_xpc_serializer_advance(xserializer, keylen + 1);
if ( !applier_fn(key, xserializer, context) )
break;
xserializer->depth = depth;
++index;
}
while ( index < count );
}
...
}
समस्या यह है कि हमलावर-नियंत्रित डेटा पर एक अनियंत्रित strlen का उपयोग सीरियलाइज़्ड डिक्शनरी एंट्री की कुंजी को डेटा बफर के अंत से आगे बढ़ने की अनुमति देता है। इसका मतलब है कि डिक्शनरी को डीसीरियलाइज़ करने वाली XPC सेवा क्रैश हो जाएगी, या तो जब strlen सीमा-से-बाहर मेमोरी को डीरेफ़रेंस करती है या जब _xpc_serializer_advance सीरियलाइज़र को आपूर्ति किए गए डेटा के अंत से आगे बढ़ाने का प्रयास करता है।
जब मैंने इस बग की खोज की, तब तक यह iOS 11.3 बीटा में पहले से ही ठीक कर दिया गया था, इसलिए मैंने इसे Apple को रिपोर्ट नहीं किया। शोषण मेरे xpc-crash रिपॉजिटरी में एक स्वतंत्र प्रोजेक्ट के रूप में उपलब्ध है।
druid को क्रैश करने के लिए इस बग का उपयोग करने के लिए, हमें बस druid सेवा को एक विकृत XPC संदेश भेजने की आवश्यकता है, जैसे कि डिक्शनरी की कुंजी असमाप्त हो और संदेश के अंतिम बाइट तक विस्तारित हो।
हमारी सेवा प्रतिरूपण आदिम का उपयोग करके iOS 11.2.6 पर druid का टास्क पोर्ट प्राप्त करना आसान है:
एक बार जब हमारे पास druid का टास्क पोर्ट हो जाता है, तब भी हमें यह पता लगाने की आवश्यकता है कि druid प्रक्रिया के अंदर कोड कैसे निष्पादित करें।
समस्या यह है कि XNU प्लेटफ़ॉर्म बाइनरी के टास्क पोर्ट को गैर-प्लेटफ़ॉर्म बाइनरी द्वारा संशोधित होने से बचाता है। यह रक्षा task_conversion_eval फ़ंक्शन में लागू की गई है, जिसे convert_port_to_locked_task और convert_port_to_task_with_exec_token द्वारा कॉल किया जाता है:```C
kern_return_t
task_conversion_eval(task_t caller, task_t victim)
{
/*
* Tasks are allowed to resolve their own task ports, and the kernel is
* allowed to resolve anyone's task port.
*/
if (caller == kernel_task) {
return KERN_SUCCESS;
}
if (caller == victim) {
return KERN_SUCCESS;
}
/*
* Only the kernel can can resolve the kernel's task port. We've established
* by this point that the caller is not kernel_task.
*/
if (victim == kernel_task) {
return KERN_INVALID_SECURITY;
}
#if CONFIG_EMBEDDED /* * On embedded platforms, only a platform binary can resolve the task port * of another platform binary. / if ((victim->t_flags & TF_PLATFORM) && !(caller->t_flags & TF_PLATFORM)) { #if SECURE_KERNEL return KERN_INVALID_SECURITY; #else if (cs_relax_platform_task_ports) { return KERN_SUCCESS; } else { return KERN_INVALID_SECURITY; } #endif / SECURE_KERNEL / } #endif / CONFIG_EMBEDDED */
return KERN_SUCCESS;
}
MIG रूपांतरण रूटीन जो इन फ़ंक्शनों पर निर्भर करते हैं, जिनमें `convert_port_to_task` और
`convert_port_to_map`, शामिल हैं, इस प्रकार विफल होंगे जब हम उन्हें druid के टास्क पर कॉल करेंगे। उदाहरण के लिए,
`mach_vm_write` हमें druid की मेमोरी में हेरफेर करने की अनुमति नहीं देगा।
हालांकि, XNU में MIG फ़ाइल `osfmk/mach/task.defs` को देखते हुए, मैंने कुछ दिलचस्प देखा
:```C
/*
* Returns the set of threads belonging to the target task.
*/
routine task_threads(
target_task : task_inspect_t;
out act_list : thread_act_array_t);
task_threads फ़ंक्शन, जो किसी कार्य में थ्रेड्स की गणना करता है, वास्तव में task_t के बजाय task_inspect_t लेता है, जिसका अर्थ है कि MIG इसे convert_port_to_task के बजाय convert_port_to_task_inspect का उपयोग करके रूपांतरित करता है। convert_port_to_task_inspect पर एक त्वरित नज़र डालने से पता चलता है कि यह फ़ंक्शन task_conversion_eval जाँच नहीं करता है, जिसका अर्थ है कि हम इसे प्लेटफ़ॉर्म बाइनरी पर सफलतापूर्वक कॉल कर सकते हैं। यह दिलचस्प है क्योंकि लौटाए गए थ्रेड thread_inspect_t अधिकार नहीं हैं, बल्कि पूर्ण thread_act_t अधिकार हैं। दूसरे शब्दों में, task_threads एक गैर-संशोधनीय कार्य अधिकार को संशोधनीय थ्रेड अधिकारों में बढ़ावा देता है। और चूंकि कोई समकक्ष thread_conversion_eval नहीं है, इसका मतलब है कि हम मैक थ्रेड API का उपयोग करके किसी कार्य के थ्रेड को संशोधित कर सकते हैं, भले ही वह कार्य एक प्लेटफ़ॉर्म बाइनरी हो।
इसका लाभ उठाने के लिए, मैंने threadexec नामक एक लाइब्रेरी लिखी, जो मैक थ्रेड्स API के ऊपर एक पूर्ण-सुविधा युक्त फंक्शन कॉल क्षमता का निर्माण करती है। threadexec परियोजना अपने आप में एक महत्वपूर्ण प्रयास था, लेकिन चूंकि यह इस शोषण से केवल अप्रत्यक्ष रूप से संबंधित है, इसलिए मैं इसके आंतरिक कार्यप्रणाली की विस्तृत व्याख्या से बचूंगा।
एक बार जब हमारे पास druid के अंदर होस्ट-प्रिव पोर्ट और अनसैंडबॉक्स्ड कोड निष्पादन हो जाता है, तो पूर्ण सैंडबॉक्स एस्केप का अगला चरण एक नया होस्ट-स्तरीय अपवाद हैंडलर स्थापित करना है। यह प्रक्रिया हमारी वर्तमान क्षमताओं को देखते हुए सीधी है:
host_get_exception_ports को कॉल करके EXC_BAD_ACCESS के लिए वर्तमान होस्ट-स्तरीय अपवाद हैंडलर प्राप्त करें।EXC_BAD_ACCESS के लिए नया होस्ट-स्तरीय अपवाद हैंडलर होगा।host_set_exception_ports कॉल करने दें ताकि हमारे Mach पोर्ट को EXC_BAD_ACCESS के लिए होस्ट-स्तरीय अपवाद हैंडलर के रूप में पंजीकृत किया जा सके।इस चरण के बाद, जब भी कोई प्रक्रिया एक अमान्य मेमोरी पते तक पहुँचती है (और उसके पास कोई पंजीकृत अपवाद हैंडलर भी नहीं है), तो हमारे नए अपवाद हैंडलर पोर्ट पर एक EXC_BAD_ACCESS अपवाद संदेश भेजा जाएगा। यह हमें किसी भी क्रैश होने वाली प्रक्रिया का कार्य पोर्ट देगा, और चूंकि EXC_BAD_ACCESS एक पुनर्प्राप्त करने योग्य अपवाद है, इस बार हम कोड निष्पादित करने के लिए कार्य पोर्ट का उपयोग कर सकते हैं।
अगला चरण ReportCrash में एक EXC_BAD_ACCESS अपवाद ट्रिगर करना है ताकि इसका कार्य पोर्ट हमारे नए अपवाद हैंडलर पोर्ट पर एक अपवाद संदेश में भेजा जा सके:
EXC_BAD_ACCESS अपवाद उत्पन्न करेगा। चूंकि ReportCrash के पास EXC_BAD_ACCESS के लिए कोई अपवाद हैंडलर पंजीकृत नहीं है (याद रखें SafetyNet EXC_CRASH के लिए पंजीकृत है), अपवाद होस्ट-स्तरीय अपवाद हैंडलर को वितरित किया जाएगा।KERN_SUCCESS लौटाएँ ताकि कर्नेल को संकेत मिले कि अपवाद को संभाल लिया गया है और ReportCrash को फिर से शुरू किया जा सकता है।इस बिंदु पर, हमारे पास एक अनसैंडबॉक्स्ड, रूट, task_for_pid-allow प्रक्रिया के अंदर कोड निष्पादन है।
अगले दो चरण सख्ती से आवश्यक नहीं हैं लेकिन फिर भी किए जाने चाहिए।
एक बार जब हमारे पास ReportCrash के अंदर कोड निष्पादन हो जाता है, तो हमें druid का उपयोग करके EXC_BAD_ACCESS के लिए होस्ट-स्तरीय अपवाद हैंडलर को रीसेट करना चाहिए:
EXC_BAD_ACCESS के लिए पुराने होस्ट-स्तरीय अपवाद हैंडलर को फिर से पंजीकृत करने के लिए druid में host_set_exception_ports कॉल करें।यह हमारे अपवाद हैंडलर पोर्ट को अन्य क्रैश होने वाली प्रक्रियाओं के लिए अपवाद संदेश प्राप्त करने से रोकेगा।
अंतिम चरण launchd को हुए नुकसान को पुनर्स्थापित करना है जब हमने सेवा पोर्ट को उसके IPC नेमस्पेस में मुक्त किया था ताकि उनका प्रतिरूपण किया जा सके:
task_for_pid कॉल करके launchd का कार्य पोर्ट प्राप्त करें।mach_port_insert_right कॉल करके वास्तविक सेवा पोर्ट को launchd के IPC स्पेस में मूल नाम के तहत डालें।इस चरण के बाद, सिस्टम को फिर से पूरी तरह कार्यात्मक होना चाहिए। सफल शोषण के बाद, डिवाइस को फोर्स रीसेट करने की आवश्यकता नहीं होनी चाहिए, क्योंकि शोषण स्वयं सभी क्षतियों की मरम्मत करता है।
Blanket एक पोस्ट-एक्सप्लॉइटेशन पेलोड भी पैकेज करता है जो amfid को बायपास करता है और एक बाइंड शेल स्पॉन करता है। यह खंड वर्णन करेगा कि यह कैसे प्राप्त किया जाता है।
ReportCrash में कोड निष्पादन प्राप्त करने के बाद भी, उस क्षमता का उपयोग करना आसान नहीं है: हम प्रक्रिया के अंदर से अलग-अलग फ़ंक्शन कॉल करने तक सीमित हैं, जो जटिल कार्य करना कठिन बनाता है। आदर्श रूप से, हमें एक ऐसा तरीका चाहिए जो या तो ReportCrash में कोड इंजेक्ट करके या समान (या उच्चतर) विशेषाधिकारों वाली एक नई प्रक्रिया स्पॉन करके ReportCrash के विशेषाधिकारों के साथ मूल रूप से कोड चलाए।
Blanket प्रक्रिया स्पॉनिंग मार्ग चुनता है। हम ReportCrash में task_for_pid और अपनी प्लेटफ़ॉर्म बाइनरी स्थिति का उपयोग करके launchd का कार्य पोर्ट प्राप्त करते हैं और launchd के अंदर एक नया थ्रेड बनाते हैं जिसे हम नियंत्रित कर सकते हैं। फिर हम अपने पेलोड बाइनरी को लॉन्च करने के लिए posix_spawn कॉल करने के लिए उस थ्रेड का उपयोग करते हैं। पेलोड बाइनरी को प्रतिबंधित एंटाइटलमेंट के साथ हस्ताक्षरित किया जा सकता है, जिसमें task_for_pid-allow शामिल है, ताकि अतिरिक्त क्षमताएँ प्रदान की जा सकें।
iOS को हमारी नई स्पॉन की गई बाइनरी को स्वीकार करने के लिए, हमें कोडसाइनिंग को बायपास करना होगा। वर्षों में विभिन्न रणनीतियों पर चर्चा की गई है, लेकिन वर्तमान सबसे सामान्य रणनीति amfid के लिए एक अपवाद हैंडलर पंजीकृत करना है और फिर एक डेटा पैच करना है ताकि amfid MISValidateSignatureAndCopyInfo कॉल करने का प्रयास करते समय क्रैश हो जाए। यह हमें उस फ़ंक्शन के कार्यान्वयन को नकली बनाने की अनुमति देता है ताकि यह दिखाया जा सके कि कोड हस्ताक्षर मान्य है।
हालाँकि, एक और दृष्टिकोण है जो मेरे विचार में अधिक मजबूत और लचीला है: amfid को पैच करने के बजाय, हम केवल कर्नेल में एक नया amfid पोर्ट पंजीकृत कर सकते हैं।
कर्नेल HOST_AMFID_PORT नामक एक होस्ट विशेष पोर्ट का उपयोग करके amfid को संदेश भेजने के लिए किस पोर्ट पर नज़र रखता है। यदि हमारे पास अनसैंडबॉक्स्ड रूट कोड निष्पादन है, तो हम इस पोर्ट को एक नए मान पर सेट कर सकते हैं। Apple ने यह जाँच करके इस हमले से बचाव किया है कि क्या सत्यापन अनुरोध का उत्तर वास्तव में amfid से आया है: भेजने वाले के cdhash की तुलना amfid के cdhash से की जाती है। हालाँकि, यह वास्तव में संदेश को amfid के अलावा किसी अन्य प्रक्रिया को भेजे जाने से नहीं रोकता है; यह केवल उत्तर को गैर-amfid प्रक्रिया से आने से रोकता है। यदि हम एक त्रिभुज स्थापित करते हैं जहाँ कर्नेल हमें संदेश भेजता है, हम उत्तर उत्पन्न करते हैं और इसे amfid को पास करते हैं, और फिर amfid उत्तर को कर्नेल को भेजता है, तो हम भेजने वाले की जाँच को बायपास करने में सक्षम होंगे।
इस दृष्टिकोण के कई फायदे हैं, जिनमें से सबसे बड़ा शायद verify_code_directory सेवा रूटीन में अतिरिक्त फ्लैग तक पहुँच है। भले ही amfid उन सभी का उपयोग नहीं करता है, लेकिन कई अन्य आउटपुट फ्लैग हैं जिन्हें amfid कोडसाइनिंग के व्यवहार को नियंत्रित करने के लिए सेट कर सकता है। यहाँ verify_code_directory का एक आंशिक प्रोटोटाइप है:```C
kern_return_t
verify_code_directory(
mach_port_t amfid_port,
amfid_path_t path,
uint64_t file_offset,
int32_t a4,
int32_t a5,
int32_t a6,
int32_t * entitlements_valid,
int32_t * signature_valid,
int32_t * unrestrict,
int32_t * signer_type,
int32_t * is_apple,
int32_t * is_developer_code,
amfid_a13_t a13,
amfid_cdhash_t cdhash,
audit_token_t audit);
जेलब्रेक डेवलपर्स के लिए विशेष रुचि का `is_apple` पैरामीटर है। यह पैरामीटर amfid द्वारा उपयोग किया हुआ प्रतीत नहीं होता है, लेकिन यदि सेट किया जाता है, तो यह कर्नेल को `CS_PLATFORM_BINARY` कोडसाइनिंग फ़्लैग सेट करने का कारण बनेगा, जो एप्लिकेशन को प्लेटफ़ॉर्म बाइनरी विशेषाधिकार प्रदान करता है। विशेष रूप से, इसका मतलब है कि एप्लिकेशन अब प्लेटफ़ॉर्म बाइनरी को सीधे संशोधित करने के लिए टास्क पोर्ट का उपयोग कर सकता है।
इस हमले में उपयोग की गई खामियाँ
---------------------------------------------------------------------------------------------------
यह हमला कई ऐसी खामियों का लाभ उठाता है जो स्वयं सुरक्षा कमज़ोरियाँ नहीं हैं, लेकिन विभिन्न एक्सप्लॉइट शमन की प्रभावशीलता को न्यूनतम कर देती हैं। इन सभी को एक साथ बंद करने की आवश्यकता नहीं है, क्योंकि कुछ आंशिक रूप से अनावश्यक हैं, लेकिन फिर भी उन सभी को सूचीबद्ध करना उचित है।
कर्नेल में:
1. `task_threads` केवल-निरीक्षण वाले `task_inspect_t` को संशोधन-सक्षम `thread_act_t` में प्रोमोट कर सकता है।
2. थ्रेड्स के लिए `task_conversion_eval` की भूमिका निभाने के लिए कोई `thread_conversion_eval` नहीं है।
3. एक गैर-प्लेटफ़ॉर्म बाइनरी प्लेटफ़ॉर्म बाइनरी के लिए `task_inspect_t` अधिकार का उपयोग कर सकती है।
4. अनसैंडबॉक्स्ड प्रक्रियाओं के लिए अपवाद संदेश सैंडबॉक्स्ड प्रक्रियाओं को दिए जा सकते हैं, भले ही यह सैंडबॉक्स से बचने का एक तरीका प्रदान करता हो। यह स्पष्ट नहीं है कि इस खामी के लिए कोई साफ समाधान है या नहीं।
5. अनसैंडबॉक्स्ड कोड निष्पादन, होस्ट-प्रिव पोर्ट, और `task_for_pid-allow` प्रक्रिया को क्रैश करने की क्षमता को मिलाकर `task_for_pid` का एक कार्य-विकल्प बनाया जा सकता है। (कार्य-विकल्प यह है: एक नया होस्ट-स्तरीय अपवाद हैंडलर सेट करने के लिए `host_set_exception_ports` को कॉल करें, फिर `task_for_pid-allow` प्रक्रिया को क्रैश करके इसका टास्क पोर्ट प्राप्त करें और अधिकार के साथ कोड निष्पादित करें।)
ऐप एक्सटेंशन में:
1. ऐप एक्सटेंशन जो एक एप्लिकेशन ग्रुप साझा करते हैं, Mach संदेशों का उपयोग करके संचार कर सकते हैं, इस तथ्य के बावजूद कि दस्तावेज़ीकरण बताता है कि होस्ट ऐप और ऐप एक्सटेंशन के बीच संचार असंभव होना चाहिए।
अनुशंसित सुधार और शमन
---------------------------------------------------------------------------------------------------
मैं निम्नलिखित सुधारों की सिफारिश करता हूं, मोटे तौर पर महत्व के क्रम में:
1. Mach पोर्ट्स को केवल launchd सेवा रूटीन में `KERN_SUCCESS` लौटाने पर डीलोकेट करें। इससे Mach पोर्ट प्रतिस्थापन कमज़ोरी ठीक हो जाएगी।
2. `task_threads` खामी को बंद करें जो एक गैर-प्लेटफ़ॉर्म बाइनरी को प्लेटफ़ॉर्म बाइनरी के टास्क पोर्ट का उपयोग करके कोड निष्पादन प्राप्त करने की अनुमति देती है।
3. ReportCrash में क्रैशिंग समस्याओं को ठीक करें।
4. कंटेनर सैंडबॉक्स से पहुँचने योग्य Mach सेवाओं का सेट कम से कम किया जाना चाहिए। मुझे अधिकांश iOS ऐप्स के लिए ReportCrash या SafetyNet से संचार करने का कोई वैध कारण नहीं दिखता।
5. जितनी संभव हो उतनी प्रक्रियाओं को सैंडबॉक्स किया जाना चाहिए। मुझे यकीन नहीं है कि druid को ठीक से काम करने के लिए अनसैंडबॉक्स्ड होने की आवश्यकता है या नहीं, लेकिन यदि नहीं, तो इसे उपयुक्त सैंडबॉक्स में रखा जाना चाहिए।
6. मृत कोड को समाप्त किया जाना चाहिए। SafetyNet अपने इच्छित कार्यक्षमता को निष्पादित नहीं कर रहा है। यदि इसकी अब आवश्यकता नहीं है, तो इसे शायद हटा दिया जाना चाहिए।
7. `host_set_exception_ports`-आधारित `task_for_pid` कार्य-विकल्प को बंद करें। उदाहरण के लिए, इस पर विचार करें कि क्या `host_set_exception_ports` को रूट तक सीमित करना या कुछ कॉन्फ़िगरेशन के तहत होस्ट-प्रिव पोर्ट की उपयोगिता को प्रतिबंधित करना उचित है। यह Mach के सुरुचिपूर्ण क्षमता-आधारित डिज़ाइन का उल्लंघन करता है, लेकिन `host_set_exception_ports` दुरुपयोग के लिए एक आशाजनक लक्ष्य हो सकता है।
8. इस पर विचार करें कि क्या `task_inspect_t` में `task_conversion_eval` जोड़ना उचित है।
ब्लैंकेट चलाना
---------------------------------------------------------------------------------------------------
Blanket को iOS 11.2.6 चलाने वाले किसी भी डिवाइस पर काम करना चाहिए।
1. प्रोजेक्ट डाउनलोड करें: ```
git clone https://github.com/bazad/blanket
cd blanket
headers/config.h को संपादित करें और APP_GROUP को उस एप्लिकेशन ग्रुप आइडेंटिफ़ायर में बदलें जो आपने पहले निर्दिष्ट किया था।उसके बाद, आप डिवाइस पर प्रोजेक्ट को बिल्ड और रन करने में सक्षम होना चाहिए।
यदि blanket सफल होता है, तो यह पेलोड बाइनरी (स्रोत blanket_payload/blanket_payload.c में) चलाएगा, जो डिफ़ॉल्ट रूप से पोर्ट 4242 पर एक बाइंड शेल स्पॉन करता है। आप netcat के साथ उस पोर्ट से कनेक्ट कर सकते हैं और मनमाने शेल कमांड चला सकते हैं।
Ian Beer और Jonathan Levin को उनके उत्कृष्ट iOS सुरक्षा और आंतरिक अनुसंधान के लिए बहुत-बहुत धन्यवाद।
मैंने यह कमजोरी जनवरी 2018 में खोजी, और फरवरी के अंत में एक्सप्लॉइट विकसित करना शुरू किया। मैंने 13 अप्रैल को Apple को इस समस्या की सूचना दी। Apple ने launchd में Mach पोर्ट रिप्लेसमेंट कमजोरी को CVE-2018-4280 निर्दिष्ट किया, और इसे 9 जुलाई को iOS 11.4.1 और macOS 10.13.6 में पैच किया गया।
Blanket MIT लाइसेंस के तहत जारी किया गया है।
Brandon Azad