
استغلال إثبات المفهوم لثغرة CVE-2023-41993، وهي ارتباك نوع JIT في WebKit في Safari. يوفر بدائيات addrof/fakeobj عبر التلاعب بالكومة وارتباك GetterSetter، مما يتيح القراءة/الكتابة العشوائية في عملية WebContent.
استغلال PoC لـ CVE-2023-41993. مكتوب فقط حتى addrof/fakeobj. الموثوقية ليست كبيرة. إذا أردت تحسينه، جرّب توزيع معرفات البنية (structure IDs).
https://po6ix.github.io/POC-for-CVE-2023-41993/pwn.html
قررت الاستضافة عبر GitHub Pages بسبب طلبات متعددة.
تمنوا لي الحظ ألا يحظرني GitHub...
ذلك لأن قيمة العامل (factor) المعرّفة في دالة pwn غير صحيحة لجهازك.
في هذه الحالة، جعلتها تستخدم قيمة عشوائية بين 87 و1088.
لذا يمكنك العثور على قيمة العامل الصحيحة بمجرد التحديث عدة مرات.
يجب أن تعمل خلال 100 محاولة احتماليًا.
سيكون من الجيد أيضًا أن ترسل لي المعلومات التي تظهر في حالة النجاح.
يمنحك هذا بدائية قراءة/كتابة (r/w primitive) على عملية محتوى الويب في Safari.
لكن لجعله مفيدًا فعليًا، ستحتاج إلى ربطه بمكونات أخرى.
ربما ترغب في شرح مفصّل لهذا الأمر، لكن للأسف ليس لدي الوقت لكتابته.
لذا أكتب بعض الملاحظات هنا لتتمكن من فهم كيفية عمل هذا.
إذا نظرت إلى التعديل (commit)، فهو يتعلق بالتغيير على HeapLocation.
تمت إضافة عامل جديد لمعرفة ما إذا كانت العُقد متطابقة أم لا.
يخبرنا أن العُقد مثل GetByOffset وMultiGetByOffset يمكن الخلط بينها.
لكن في الواقع الأمر يتعلق فقط بالإزاحة (offset).
لنقل إن كان لديه عقدتان من نوع GetByOffset بإزاحتين مختلفتين.
سيتم تطبيق CSE على إحداهما وسيتم استخدام المتبقية بدلاً منها.
إذن هو في الأساس خلط في الإزاحات (offset confusion) لكنه لا يمنحك الوصول إلى إزاحة عشوائية.
لأنه لتطبيق CSE على هذه الأنواع من العُقد، يجب رفعها (hoisted) بواسطة LICMPhase.
أما أنواع العُقد التي تنفذ عمليات كتابة، فلا يُسمح برفعها في هذه المرحلة.
لذا لا يحدث نفس الخلط لعُقد PutByOffset وMultiPutByOffset.
أيضًا عند رفع GetByOffset، تُستدعى دالة safeToExecute للتحقق من أن العقدة قانونية للتنفيذ، وتسمح فقط بالوصول إلى إزاحة أقل من سعة التخزين (inline/ool).
لذا كانت فكرة الاستغلال هي GetterSetter.
إذا استدعيت Object.__defineGetter__ لتعريف خاصية، يتم إنشاء كائن GetterSetter لكنه يُخزَّن في تخزين الخصائص (property storage) ولا يمكن الوصول إليه بشكل طبيعي.
لكن يمكنك ذلك باستخدام التلاعب بالإزاحات الذي تملكه.
ثم تستدعي دالة Object لإحداث خلط في الأنواع (type confusion).
JSObject* JSCell::toObjectSlow(JSGlobalObject* globalObject) const
{
Integrity::auditStructureID(structureID());
ASSERT(!isObject());
if (isString())
return static_cast<const JSString*>(this)->toObject(globalObject);
if (isHeapBigInt())
return static_cast<const JSBigInt*>(this)->toObject(globalObject);
ASSERT(isSymbol());
return static_cast<const Symbol*>(this)->toObject(globalObject);
}
سيقوم بإنشاء كائن SymbolObject يكون فيه GetterSetter كقيمة داخلية. وهذا غير صحيح، لأن القيمة الداخلية لـ SymbolObject من المفترض أن تكون نسخة من Symbol وليس GetterSetter.
let getterSetter = jitme(1);
let symbolObject = Object(getterSetter);
symbolObject.description; // call the getter
String Symbol::description() const
{
auto& uid = m_privateName.uid();
return uid.isNullSymbol() ? String() : uid;
}
ثم عند استدعاء getter الخاص بـ description، سيعيد نسخة من String. هذا خلط أنواع بين Symbol.m_privateName و GetterSetter.m_getter. في كل مرة تستدعي هذا الـ getter، سيزيد حقل عداد المرجع الخاص بـ m_privateName.m_uid الموجود عند الإزاحة 0x0. هذا مفيد جدًا، لأن هذه الإزاحة هي مكان معرف البنية (structure ID) لدالة الـ getter. من خلال استدعاء هذه الدالة عدة مرات، يمكنك تغيير معرف البنية لنسخة JSFunction. لقد أعددت نوعًا آخر يملك العديد من الخصائص. ثم إذا طابقت معرف البنية معه، يمكنك القيام بكتابة خارج الحدود (oob write) إلى تخزين الخصائص. هذا يمنحك مباشرة بدائية addrof/fakeobj.