
SnatchBox (CVE-2020-27935) هي ثغرة أمنية واستغلال للهروب من الصندوق الرمل (sandbox) تؤثر على نظام macOS حتى الإصدار 10.15.x
SnatchBox (CVE-2020-27935) هي ثغرة تجاوز عزل (sandbox escape) تؤثر على macOS حتى الإصدار 10.15، بالإضافة إلى الإصدارات التجريبية المبكرة من macOS 11.0. الأثر الأكثر أهمية لـ SnatchBox هو أنها تسمح لناشر ضار بتجاوز عزل App Store الإلزامي في macOS والحصول على وصول كامل إلى جميع ملفات المستخدم، مما يكسر النموذج الأمني لـ App Store على macOS.
حقيقة أنه في macOS، على عكس iOS على سبيل المثال، فإن مهمة (task) في فضاء المستخدم تضع نفسها طوعًا في عزل (sandbox) هي نقطة ضعف بالتصميم. وبما أن هذه مهمة يملك مؤلفها الذي قد يكون ضارًا سيطرة شبه كاملة على تعيينات ذاكرتها ومحتوياتها، فإن الكود الذي يعمل قبل تهيئة العزل (مثل dyld نفسه، أو وقت تشغيل Objective-C) يحلل محتويات هذا الثنائي الذي قد يكون ضارًا، ويمكن استخدام بيانات مصممة بعناية لكسب تنفيذ كود قبل تهيئة العزل. إذا كانت العملية لن تنفذ أبدًا أي كود، بما في ذلك كود dyld، قبل فرض العزل عليها، لما كانت هذه مشكلة، لأن تنفيذ الكود المبكر لن يحقق أي شيء للهجوم في هذه الحالة. إنها مشابهة من الناحية المفاهيمية لـ تجاوز عزل Saagar Jha، باستثناء أنها تتجاوز التخفيفات المقدمة حديثًا وتحققات App Store.
قبل macOS 10.15، كان استغلال هذه الثغرة بسيطًا إلى حد ما. يمكن للمرء إنشاء ثنائي يحتوي على تصنيف (category) من Objective-C لفئة تُستخدم قبل تهيئة العزل (مثل OS_xpc_object) وتجاوز طريقة (يفضل أن تكون موروثة لتجنب تحذيرات وقت التشغيل) تُستخدم قبل تهيئة العزل (مثل +initialize، التي تُستدعى ضمنيًا عند أول وصول إلى فئة). نظرًا لأن التصنيفات تُحمَّل قبل تهيئة العزل، ويتم أول وصول إلى OS_xpc_object (أو فئات ضحية مناسبة أخرى) بعد تحميل التصنيفات وقبل تهيئة العزل، فإن طريقة التي يوفرها المهاجم (أو طريقة ضحية مناسبة أخرى) ستُستدعى قبل تهيئة العزل، مما يسمح للمرء بالوصول إلى بيانات خارج الحاوية، على سبيل المثال. بدلاً من ذلك، يمكن للمهاجم استبدال المرجع إلى (الذي يهيئ العزل) بدالة شبيهة بـ nop لتعطيل العزل (ربما بشكل مشروط) حتى بعد استئناف التنفيذ.
+initialize_libsecinit_initializerوقت تشغيل Objective-C المستخدم في macOS 10.15 ليس عرضة لتقنية الاستغلال المذكورة سابقًا، لأن التصنيفات لا تُحمَّل قبل تعيين didCallDyldNotifyRegister، مما يجعل طريقة +initialize الخاصة بنا تُستدعى فقط بعد حدوث تهيئة العزل.
ومع ذلك، لا يزال map_images يُستدعى على ثنائينا، مما يجعل من الممكن تغيير بيانات وقت التشغيل بطرق غير مقصودة تسمح لنا بتنفيذ كود قبل تهيئة العزل. الاستغلال الكامل والمعلَّق موجود في main.c، لكنني سأستعرض التفاصيل الأساسية هنا. نقوم بصياغة هيكل فئة Objective-C يشير مؤشر data الخاص به إلى موقع في libxbc.dylib. يجب اختيار ذلك الموقع بطريقة تجعل flags يحتوي على البت 31 (RW_REALIZED) مضبوطًا، حتى لا يحاول وقت التشغيل تحقيق (realize) هذه الفئة غير الصالحة وينهار، ويجب أن يتشارك firstSubclass عنوانه مع isa لفئة نريد تجاوزها. سترث فئة (meta) أخرى من هذه الفئة غير الصالحة، وتوفر طريقة +initialize الخاصة بها. نضيف الفئة الفرعية إلى __objc_nlclslist حتى يحققها وقت التشغيل.
عندما يحقق وقت التشغيل فئتنا الفرعية، وهو ما يحدث قبل تهيئة العزل، فإنه سيستدعي addSubclass على فئتنا الفائقة غير الصالحة وفئتنا الفرعية، مما سيستبدل isa الخاصة بالضحية بمؤشر إلى فئتنا الفرعية، مستبدلاً فعليًا جميع طرقها بطريقة +initialize الخاصة بنا. عندما تُستدعى طريقة +initialize الخاصة بنا، والتي ستكون قبل تهيئة العزل إذا اخترنا فئة ضحية مناسبة، يمكننا مرة أخرى استبدال مراجع _libsecinit_initializer بـ nops (بشكل مشروط أو لا)، وإصلاح تغييرات وقت التشغيل التي أجريناها لاستئناف التنفيذ دون انهيار لاحقًا.
يمكن بناء العرض التوضيحي المقدم عن طريق تشغيل make، وإنشاء ملف في ~/Documents/SecretDocument.txt، وتشغيل SnatchBox.app/Contents/MacOS/SnatchBox من الطرفية (يتم إنشاء حزمة (bundle) لأن com.apple.security.app-sandbox يتطلب ذلك، لكن هذا لا يزال برنامج سطر أوامر). الثنائي المبني موقَّع بـ com.apple.security.app-sandbox، وهو ما يمنع عادةً الوصول إلى ~/Documents/SecretDocument.txt (لأنه ليس في حاويتنا)، لكنه سيتمكن من قراءة بياناته على أي حال. بسبب تغييرات بنية وقت التشغيل، لن يعمل هذا العرض التوضيحي على macOS 10.14 والإصدارات الأقدم دون تعديل، لكنه سيعمل على 10.15 و 11.0 (مُختبر: 10.15.4 و 10.15.7 و 11.0 Beta (20A5354i)). يمكن دمج تقنيتي الاستغلال لاستهداف كلا إصداري وقت التشغيل، لكن مثل هذا العرض غير مقدم.
مثال على التشغيل:
CatalinaVM:SnatchBox lior$ make
mkdir -p SnatchBox.app/Contents/MacOS/
clang -O3 -Wall -framework Foundation main.m -o SnatchBox.app/Contents/MacOS/SnatchBox
cp Info.plist SnatchBox.app/Contents/
codesign --force --sign - SnatchBox.app --entitlements ent.xml
CatalinaVM:SnatchBox lior$ echo "Quack"> ~/Documents/SecretDocument.txt
CatalinaVM:SnatchBox lior$ codesign -d --entitlements :- SnatchBox.app/Contents/MacOS/SnatchBox
Executable=/Volumes/SharedFolders/Home/Projects/SnatchBox/SnatchBox.app/Contents/MacOS/SnatchBox
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>
CatalinaVM:SnatchBox lior$ SnatchBox.app/Contents/MacOS/SnatchBox
Found libsecinit_initializer at 0x7fff72309124
Found libSystem.B.dylib at 0x7fff6f0de000
Found __DATA at 0x7fff984eeca0
Replacing libsecinit_initializer reference at 0x7fff984eed48 with a nop
2020-12-18 16:31:48.196 SnatchBox[804:8043] Attempting to read protected file: /Users/lior/Documents/SecretDocument.txt
2020-12-18 16:31:48.197 SnatchBox[804:8043] Escaped sandbox! The contents are: <51756163 6b0a>
كما ذُكر سابقًا، يسمح هذا بإنشاء تطبيق macOS App Store لا يعمل داخل عزل على الرغم من أن سياسة App Store تتطلب ذلك. يمكن أيضًا استخدام الثغرة في إطار عمل (framework)، يمكن استخدامه بواسطة تطبيقات App Store المشروعة في حد ذاتها. أخيرًا، يمكن حتى دمجها مع شيء مشابه لـ "Xcode Ghost" لحقن كود ضار بشكل جماعي يعمل خارج العزل في تطبيقات App Store.
أصلحت Apple الثغرة أثناء مرحلة الاختبار التجريبي لـ macOS 11.0 بإضافة استدعاء إلى malloc_size في realizeClassWithoutSwift. وهذا يؤكد أنه إذا تم وضع علامة على فئة على أنها محققة (RW_REALIZED، مثل فئتنا المزيفة)، فإنها تمتلك بالفعل مؤشر بيانات صالحًا، malloc'ed، بالحجم الصحيح (0x20 بايت). إذا لم يكن الأمر كذلك، فسيُلغي وقت التشغيل التنفيذ مع رسالة مشابهة لـ realized class 0x100002078 has corrupt data pointer 0x7fff88c00948. تم تطبيق الإصلاح أيضًا على iOS وiPadOS وtvOS وwatchOS؛ حتى لو لم تكن متأثرة بشكل مباشر.