
CVE-2018-4280: ثغرة استبدال منفذ Mach في launchd على macOS 10.13.5 تؤدي إلى تصعيد الامتيازات المحلية وتجاوز SIP.
launchd-portrep هو استغلال لثغرة استبدال المنفذ (port replacement) في launchd، وهي العملية الأولية في فضاء المستخدم وداemon إدارة الخدمات في macOS. من خلال إرسال رسالة Mach مصمّمة إلى منفذ bootstrap، يمكن إجبار launchd على تحرير حقه في الإرسال (send right) لأي منفذ Mach يمتلكه المهاجم أيضًا حق الإرسال فيه. وهذا يسمح للمهاجم بانتحال هوية أي خدمة launchd يمكنه البحث عنها لباقي النظام.
يقوم launchd بمعالجة متعددة لمعالجات رسائل Mach المختلفة عبر منفذه الرئيسي، بما في ذلك معالج MIG للرسائل الاستثنائية. إذا أرسلت عملية رسالة mach_exception_raise أو mach_exception_raise_state_identity إلى منفذ bootstrap الخاص بها، فسيستقبل launchd تلك الرسالة ويعالجها كاستثناء على مستوى المضيف.
لسوء الحظ، فإن معالجة launchd لهذه الرسائل معيبة. إذا كان نوع الاستثناء هو EXC_CRASH، فسيقوم launchd بتحرير منافذ thread و task المرسلة في الرسالة ثم يعيد KERN_FAILURE من دالة الخدمة، مما يؤدي إلى قيام نظام MIG بتحرير منافذ thread و task مرة أخرى. (الافتراض هو أنه إذا أعادت دالة الخدمة نجاحًا، فقد استولت على جميع الموارد في رسالة Mach، بينما إذا أعادت خطأً، فلم تستولِ على أي من الموارد.)
هذا هو الكود من دالة خدمة launchd لرسائل mach_exception_raise، تم تفكيكه باستخدام IDA/Hex-Rays وتحريره قليلاً للقراءة:
kern_return_t __fastcall
catch_mach_exception_raise( // (a) يتم استدعاء دالة الخدمة
mach_port_t exception_port, // بقيم مباشرة من رسالة Mach
mach_port_t thread, // التي أرسلها العميل. يمكن أن
mach_port_t task, // تكون منافذ thread و task
unsigned int exception, // حقوق إرسال عشوائية.
mach_exception_data_t code, //
unsigned int codeCnt)
{
kern_return_t kr; // eax@1 MAPDST
kern_return_t result; // eax@10
int pid; // [rsp+14h] [rbp-43Ch]@1
char codes_str[1024]; // [rsp+20h] [rbp-430h]@5
__int64 __stack_guard; // [rsp+420h] [rbp-30h]@1
__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 ( codeCnt )
{
do
{
__snprintf_chk(codes_str, 0x400uLL, 0, 0x400uLL, "0x%llx", *code);
++code;
--codeCnt;
}
while ( codeCnt );
}
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_mach_port(thread); // (b) يتم تحرير منفذ "thread" المرسل
if ( kr ) // في الرسالة.
{
_os_assumes_log(kr);
_os_avoid_tail_call();
}
kr = deallocate_mach_port(task); // (c) يتم تحرير منفذ "task" المرسل
if ( kr ) // في الرسالة.
{
_os_assumes_log(kr);
_os_avoid_tail_call();
}
result = 0;
if ( *__stack_chk_guard_ptr == __stack_guard )
{
LOBYTE(result) = exception == 10; // (d) إذا كان نوع الاستثناء هو 10
result *= 5; // (EXC_CRASH)، فسيتم إرجاع خطأ
} // KERN_FAILURE.
return result; // سيقوم MIG بتحرير المنافذ
} // مرة أخرى.
هذا التحريف المزدوج لأسماء المنافذ مشكلة لأنه يمكن لعملية تعيين أي منافذ تريدها كمنافذ task و thread في رسالة الاستثناء. لا يقوم launchd بأي تحقق من أن حقوق الإرسال المستلمة تتوافق فعليًا مع thread و task؛ قد تكون المنافذ، على سبيل المثال، حقوق إرسال لمنافذ موجودة بالفعل في فضاء IPC الخاص بـ launchd. ثم سيؤدي التحريف المزدوج إلى قيام launchd بإسقاط مرجع مستخدم على أحد منافذه الخاصة.
يمكن استغلال هذا الخلل لتحرير حق إرسال launchd لأي منفذ Mach يمتلكه أيضًا حق إرسال في العملية المهاجمة. على وجه الخصوص، إذا كانت العملية المهاجمة يمكنها البحث عن خدمة نظام باستخدام launchd، فيمكنها تحرير حق إرسال launchd لتلك الخدمة ثم انتحال شخصية الخدمة لباقي النظام. بعد ذلك، هناك العديد من الطرق المختلفة للحصول على امتيازات النظام.
هذا الخلل هو نسخة أقل عمومية من CVE-2016-7637، وهي مشكلة في التعامل مع مراجع المستخدم لمنافذ Mach في XNU اكتشفها Ian Beer وسمحت للعمليات بتحرير منافذ Mach في عمليات أخرى. استغل Ian Beer تلك الثغرة على macOS عن طريق استبدال حق إرسال launchd إلى نقطة نهاية خدمة com.apple.CoreServices.coreservicesd وانتحال شخصية coreservicesd لباقي النظام. coreservicesd هو هدف جذاب لأنه واحد من الخدمات القليلة التي سيرسل إليها العملاء منفذ المهمة (task port) الخاص بهم في رسالة Mach. من خلال استبدال حق إرسال launchd إلى coreservicesd بمنفذ خاص به ثم تشغيل عملاء مميزين للبحث عن coreservicesd والتواصل معه، تمكن من الحصول على منفذ المهمة لعملية مميزة ثم تنفيذ كود داخلها.
بما أن السلوك على macOS لم يتغير، فقد قمت بنسخ استراتيجية Ian Beer لهذه الثغرة. نرسل رسائل استثناء إلى launchd تحتوي على منفذ خدمة coreservicesd حتى نحرر حق إرسال launchd لذلك المنفذ. يمكننا اكتشاف متى حررنا الحق عن طريق استدعاء bootstrap_look_up() مرة أخرى على الخدمة: إذا أعاد launchd اسم منفذ غير صالح، فقد نجحنا في تحرير حق إرسال launchd للمنفذ. بعد ذلك، نقوم بتسجيل وإلغاء تسجيل عدد كبير من الخدمات مع launchd بشكل متكرر حتى يتم تعيين نفس اسم منفذ Mach في فضاء IPC الخاص بـ launchd مثل منفذ coreservicesd الأصلي. في هذه المرحلة، أي عملية تبحث عن com.apple.CoreServices.coreservicesd في launchd ستحصل على حق إرسال لخدمتنا المزيفة بدلاً من coreservicesd الحقيقي. ثم نقوم بتشغيل خادم MITM على منفذ الخدمة المزيف، لفحص جميع منافذ Mach في الرسائل المستلمة من العملاء قبل إرسالها إلى coreservicesd الحقيقي. في هذه المرحلة، نرسل رسالة إلى sysdiagnose مما يجعله يشغل tailspin، مما يؤدي إلى اتصال sysdiagnose بمنفذ coreservicesd المزيف وإرسال منفذ مهمته إلينا. نظرًا لأن sysdiagnose لديه صلاحية task_for_pid-allow، يمكننا الآن الحصول على منفذ المهمة لأي عملية.
لإعادة الوظائف الطبيعية للنظام (إلى حد كبير)، نستخدم sysdiagnose للحصول على منفذ مهمة launchd، ثم نستخدم منفذ مهمة launchd لاستبدال حق إرسال launchd إلى منفذ خدمتنا المزيفة بحق إرسال إلى coreservicesd الحقيقي. بهذه الطريقة، يمكن للعملاء المستقبليين الوصول فعليًا إلى coreservicesd.
مشكلة واحدة لاحظتها مع هذا النهج هي أن النظام يبدو أنه يتعطل عند الإغلاق لفترة قصيرة. أفترض أن هذا بسبب أن العبث بمنافذ launchd يخل ببعض محاسبة launchd أو إشعارات المنفذ. لم أتحقق من هذه المشكلة أكثر، ولكن إعادة تشغيل coreservicesd باستخدام launchctl يبدو أنه يحلها:
$ sudo launchctl kickstart -k -p system/com.apple.coreservicesd
بمجرد أن يكون لدينا تنفيذ كود داخل عملية task_for_pid-allow، يمكننا التحكم في أي مهمة على النظام. هذا رائع لأنه ليس فقط يمكننا القيام برفع الامتيازات القياسي، ولكن يمكننا أيضًا تجاوز SIP عن طريق حقن كود في العمليات المخولة بـ SIP.
يوضح هذا الاستغلال استخدامين محتملين: تنفيذ أوامر النظام كجذر وحقن المكتبات الديناميكية. لتنفيذ أمر نظام، نقوم ببساطة باستدعاء دالة system() القياسية من داخل sysdiagnose، مررين سلسلة الأمر التي قدمها المستخدم. لحقن مكتبة ديناميكية في عملية، نستدعي task_for_pid() من داخل sysdiagnose للحصول على منفذ مهمة الهدف، ثم نستخدم منفذ المهمة لاستدعاء dlopen() على المكتبة المقدمة.