
CVE-2018-4280: ثغرة استبدال منفذ Mach في launchd على iOS 11.2.6 تؤدي إلى تجاوز الحماية (sandbox escape) ورفع الامتيازات وتجاوز توقيع الشيفرة.
Blanket هي أداة للهروب من صندوق الرمل تستهدف iOS 11.2.6، على الرغم من أن الثغرة الرئيسية لم تُصلَح إلا في iOS 11.4.1. تستغل ثغرة استبدال منفذ Mach في launchd (CVE-2018-4280)، بالإضافة إلى عدة ثغرات أصغر في خدمات أخرى، لتنفيذ كود داخل عملية ReportCrash، وهي عملية غير محصورة في صندوق الرمل، وتعمل بصلاحيات الجذر، وتمتلك امتياز task_for_pid-allow. وهذا يمنح Blanket تحكمًا كاملًا في كل عملية تعمل على الهاتف، بما في ذلك العمليات الحرجة أمنيًا مثل amfid.
يتكون الاستغلال من عدة مراحل. سيشرح هذا README الثغرة الرئيسية ومراحل الهروب من صندوق الرمل خطوة بخطوة.
أثناء بحثي في آلية الإبلاغ عن الأعطال على iOS، اكتشفت ثغرة استبدال منفذ Mach في launchd. من خلال التعطل بطريقة معينة، يمكن لعملية ما أن تجعل النواة ترسل رسالة Mach إلى launchd، مما يؤدي إلى إفراط launchd في إلغاء تخصيص حق إرسال لمنفذ Mach في فضاء أسماء IPC الخاص به. وهذا يسمح للمهاجم بانتحال صفة أي خدمة من خدمات launchd يمكنه البحث عنها أمام باقي النظام، مما يفتح العديد من السبل لرفع الامتيازات.
هذه الثغرة موجودة أيضًا على macOS، لكن استغلالها على iOS أصعب بسبب الفحوصات الموجودة في launchd والتي تضمن أن رسالة استثناء Mach تأتي من النواة.
يجمع launchd بين معالجات متعددة ومختلفة لرسائل Mach عبر منفذه الرئيسي، بما في ذلك معالج MIG لرسائل الاستثناء. إذا أرسلت عملية رسالة mach_exception_raise أو mach_exception_raise_state_identity إلى منفذ bootstrap الخاص بها، فسوف يستقبل 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 لرسائل استثناء `mach_exception_raise`: يتم استدعاؤها مباشرةً
بواسطة نظام Mach عندما يعالج launchd رسالة استثناء `mach_exception_raise` من نوع
Mach. الوسائط التي تُمرَّر إلى روتين الخدمة تُحلَّل من رسالة Mach، وبالتالي
يتحكم فيها مُرسِل الرسالة.
2. عند (b)، يتحقق launchd من أن رسالة استثناء Mach أُرسلت بواسطة النواة.
يحتوي رمز التدقيق (audit token) الخاص بالمُرسِل على معرّف العملية (PID) للعملية المُرسِلة في الحقل 5،
والذي يكون صفرًا فقط بالنسبة للنواة. إذا لم تكن الرسالة مُرسلة من النواة، فسيتم رفضها.
3. يتم تحرير منافذ المهمة والخيط (thread and task ports) من الرسالة بشكل صريح عند (c) و(d).
4. عند (e)، يتحقق launchd مما إذا كان نوع الاستثناء هو `EXC_CRASH`، ويعيد `KERN_FAILURE` إذا
كان كذلك. والهدف هو التأكد من عدم معالجة رسائل `EXC_CRASH`، على الأرجح حتى
يتم استدعاء ReportCrash كمعالج للجثة. ومع ذلك، فإن إعادة `KERN_FAILURE` في هذه النقطة
ستؤدي إلى تحرير منافذ المهمة والخيط مرة أخرى عند تنظيف رسالة الاستثناء لاحقًا.
هذا يعني أن هذين المنفذين سيتم تحريرهما بشكل زائد.
لكي تكون هذه الثغرة مفيدة، سنحتاج إلى تحرير حق الإرسال (send right) الخاص بـ launchd إلى خدمة
Mach التي يقدمها، حتى نتمكن بعد ذلك من انتحال شخصية تلك الخدمة أمام باقي النظام. هذا
يعني أننا سنحتاج إلى أن يكون منفذا المهمة والخيط في رسالة الاستثناء حقيقيًا بمثابة حقوق إرسال
إلى منفذ خدمة Mach الذي نريد تحريره في launchd. بعد ذلك، بمجرد أن نرسل إلى launchd رسالة
الاستثناء الضارة ونحرر منفذ الخدمة، سنحاول إعادة استخدام نفس اسم المنفذ، لكن هذه المرة
لمنفذ Mach نحن نملك حق الاستلام له. وبهذه الطريقة، عندما يطلب عميل من launchd
أن يمنحه حق إرسال إلى منفذ Mach الخاص بالخدمة، سيمنحه launchd بدلاً من ذلك حق
إرسال إلى منفذنا الخاص، مما يتيح لنا انتحال شخصية تلك الخدمة أمام العميل. بعد ذلك، هناك
العديد من الطرق المختلفة للحصول على امتيازات النظام.
### تفعيل الثغرة
من أجل تفعيل الثغرة فعليًا، سنحتاج إلى تجاوز الفحص الذي يتحقق من أن الرسالة قد أُرسلت بواسطة
النواة. وذلك لأننا إذا أرسلنا رسالة الاستثناء إلى launchd مباشرةً فسيتم تجاهلها فقط.
بطريقة ما، نحتاج إلى جعل النواة ترسل رسالة استثناء "ضارة" تحتوي على حق إرسال Mach
لخدمة نظام بدلاً من منافذ المهمة والخيط الحقيقية.