
empty_list - استغلال لثغرة p0 issue 1564 (CVE-2018-4243) iOS 11.0 - 11.3.1 kernel r/w
empty_list - استغلال للثغرة p0 issue 1564 (CVE-2018-4243) iOS 11.0 - 11.3.1 kernel r/w @i41nbeer
الخطأ: تأخذ الدالة getvolattrlist وسيطة bufferSize يتحكم بها المستخدم عبر استدعاء النظام fgetattrlist.
عند تخصيص مخزن مؤقت للنواة لتسلسل قائمة السمات، يوجد التعليق التالي:
/*
المشكلة هي أن الكود لا يتعامل بعد ذلك بشكل صحيح مع الحالة التي يكون فيها حجم المخزن المؤقت الذي يوفره المستخدم أصغر من حجم الرأس المطلوب. إذا مررنا ATTR_CMN_RETURNED_ATTRS فسنصل إلى الكود التالي:
/* Return attribute set output if requested. / if (return_valid) { ab.actual.commonattr |= ATTR_CMN_RETURNED_ATTRS; if (pack_invalid) { / Only report the attributes that are valid */ ab.actual.commonattr &= ab.valid.commonattr; ab.actual.volattr &= ab.valid.volattr; } bcopy(&ab.actual, ab.base + sizeof(uint32_t), sizeof (ab.actual)); }
لا يوجد فحص للتأكد من أن المخزن المؤقت المخصص كبير بما يكفي لاحتواء ذلك على الأقل.
الاستغلال: آمل أن أنشر شرحًا أطول لهذه الثغرة، وهذه بعض الملاحظات التقريبية حول كيفية عمل الاستغلال:
يمنحك الخلل القدرة على كتابة 8 بايت صفرية خارج نهاية تخصيص kalloc.16. بينما يبدو أنك قد تتمكن من التحكم في بعض البتات في هذه البايتات، لست متأكدًا من أنه يمكنك ذلك حقًا، لذا ركزت على الاستغلال كما لو كان يكتب مؤشر NULL خارج النهاية.
هذا أساس محدود إلى حد ما، لذا فإن الخطوة الأولى هي محاولة تعداد الأشياء الممكنة التي يمكنك فعلها:
في النهاية اخترت الخيار الأول. ثم هناك متطلبان إضافيان:
اخترت استهداف struct ipc_port، الذي يحتوي على حقل عداد مرجعي ككلمته الثانية، وبذلك يفي بالشرط الأول. لكنه لا يتم تخصيصه في kalloc.16؛ بدلاً من ذلك يعيش في نطاقه الخاص (ipc_ports).
هذا يعني أنه يجب علينا محاذاة كتلة منطقة kalloc.16 قبل كتلة ipc_ports مباشرة، ثم الفائض خارج آخر تخصيص kalloc.16 في كتلة kalloc.16 إلى أول تخصيص في ipc_ports.
هناك خدعتان يمكننا استخدامهما لتسهيل ذلك:
عكس القائمة الحرة: تأتي تخصيصات النطاق أولاً من الصفحات الوسيطة (الممتلئة جزئيًا). هذا يعني أنه إذا بدأنا في تحرير وتخصيص كائنات k.16 في مكان ما في منتصف التمهيد، فلن يتم إعادة استخدامها حتى تمتلئ الصفحة الوسيطة الحالية أو تصبح فارغة.
يشكل هذا تحديًا لأن القوائم الحرة للصفحات الجديدة تُملأ بشكل شبه عشوائي بحيث تذهب تخصيصاتها من الداخل إلى الخارج:
| 9 8 6 5 2 1 3 4 7 10 | <-- مثال على ترتيب تخصيص "عشوائي" من صفحة جديدة خالية تمامًا
هذا يعني أن صفحاتنا الوسيطة النهائية لـ k.16 و ports ستبدو هكذا تقريبًا:
| - - - 5 2 1 3 4 - - | - - - 4 1 2 3 5 - - | kalloc.16 ipc_ports
إذا استخدمنا الفائض لإفساد إدخال في القائمة الحرة، فسنصاب بالذعر إذا تم تخصيصه، لذا نحتاج إلى تجنب ذلك.
الخدعة هي أنه من خلال التحكم في ترتيب التخصيص والتحرير يمكننا عكس القوائم الحرة بحيث تبدو الصفحات الوسيطة النهائية هكذا: | 1 4 - - - - - 5 3 2 | 2 5 - - - - - 4 3 1 | kalloc.16 ipc_ports
عند هذه النقطة، من المحتمل جدًا أن نتمكن من تحرير kalloc.16 وإعادة تخصيصه للفائض بحيث يمكننا ضرب أول qword من ipc_port.
تخصيصات آمنة من الفائض: نظرًا لوجود العديد من التخصيصات المحتملة التي سنضطر إلى الفائض منها قبل أن نصل إلى الهدف (الموجود في النهاية، قبل ipc_port مباشرة)، نحتاج إلى التأكد من أن الكائنات المخصصة على صفحة kalloc.16 آمنة من الإفساد بمؤشر NULL.
أستخدم واصفات mach message ool_port لهذا، حيث أن NULL قيمة صالحة.
تدفق الاستغلال: نقوم بالتمهيد لعكس القوائم الحرة لـ kalloc.16 ونبدأ في محاولة الفائض إلى ipc_port.
نحن نعرف النطاق التقريبي لأسماء منافذ mach التي تحتوي على المنفذ الذي سيتم إفساده؛ بعد كل محاولة فائض نتحقق من كل من هذه المنافذ لمعرفة ما إذا كان المنفذ قد تم إفساده. أحد الآثار الجانبية للإفساد الناجح هو أن علامة io_active للمنفذ ستصبح صفرًا. يمكننا اكتشاف ذلك دون التسبب في آثار جانبية باستخدام طريقة MIG mach_port_kobject.
بمجرد أن نجد المنفذ المفسد، نحتاج إلى التسبب في أخذ مرجع وإفلاته عليه؛ والأهم أن مسار الكود الذي يفعل ذلك لا يتحقق من علامة io_active. mach_port_set_attributes ستفعل ذلك لنا.
الآن قمنا بتحويل كتابة المؤشر NULL خارج نهاية kalloc.16 إلى منفذ mach معلق :)
نتسبب في جمع القمامة للنطاق، بهدف إعادة استخدام ذاكرة المنفذ كصفحة kalloc.4096. أولاً نجعلها تُعاد استخدامها كواصف ool_ports حيث يتداخل حقل ip_context مع حق إرسال نرسله لأنفسنا إلى منفذ حارس. هذا يتيح لنا معرفة العنوان التقريبي لكائناتنا في النواة. ثم نستبدل ool_desc بمخزن أنبوب pipe، وبقليل من التعديل نتمكن من تحديد مكان منفذ mach المعلق في الذاكرة.
نصنع منفذ مهمة نواة مزيف هناك ثم نقوم بالتنظيف.
الموثوقية: الاستغلال يعمل، وهذا كان هدفي :) الموثوقية حوالي 30% ربما، كل شيء يعتمد على مدى سرعة قيامك بالفائض الأولي وحلقة الاختبار. إذا جاء شيء آخر وقام بتخصيص أو تحرير في kalloc.16 تزيد احتمالية إفساد إدخال في القائمة الحرة أو شيء آخر وستصاب بالذعر.
أنا متأكد من أنه يمكن جعل الاستغلال أكثر موثوقية؛ لقد وصلت به فقط إلى نقطة أثبت فيها أن هذه الثغرة قابلة للاستغلال. إذا كنت تريد أخذ هذه نقطة بداية وإظهار كيفية تحسين الموثوقية، سأحب قراءة تدوينة! أتخيل أن هذا سيتضمن مراقبة تخصيصات kalloc.16 وفهم حالات الفشل وكيفية منعها.
يبدو أن نسب النجاح تكون أعلى عندما يتم إعادة تشغيل الجهاز وتركه خاملاً لفترة.
التنظيف: إذا نجح الاستغلال، يجب أن ينظف خلف نفسه ولا يصيب الجهاز بالذعر. سيبقى منفذ مهمة النواة المزيف حيًا.
استخدم الدوال في kmem.h لقراءة وكتابة ذاكرة النواة. أبقِ حق إرسال لـ tfp0 هناك إذا أردت الاحتفاظ بالوصول إلى ذاكرة النواة بعد خروج هذه العملية.
اختبرت على: iPod Touch 6G, iPhone 6S, iPhone SE, iPhone 7, iPhone 8 يجب أن يعمل على iOS 11 إلى iOS 11.3.1