CVE-2026-50416: تجاوز KASLR في Windows 11
على النظام المتأثر، أعادت هذه القراءة من تعيين المستخدم (user mapping) لـ Win32 desktop heap عنوانًا في النواة (kernel):
ULONG64 leaked = *(ULONG64 *)(desktop_heap + 0x100);
هذا هو الإفصاح بأكمله. في البداية كان مجرد قيمة من ثمانية بايتات ببتات عليا مقنعة. لقد قضيت وقتًا أطول في التحقق مما تتبعه القيمة أكثر من قراءتها.
نشرت Microsoft المشكلة باسم CVE-2026-50416، وهو إفصاح عن معلومات في Win32k مصنّف ضمن CWE-200.
يبدأ PoC من TEB الخاص بـ x64. يعطي GS:[0x30] الـ TEB الحالي، ويصل TEB + 0x800 إلى بيانات عميل Win32 في البناء الذي اختبرته، ويحتوي الفتحة 5 على عنوان المستخدم لكومة سطح المكتب:
PVOID teb = (PVOID)__readgsqword(0x30);
PVOID *client_info = (PVOID *)((BYTE *)teb + 0x800);
BYTE *desktop_heap = (BYTE *)client_info[5];
الخانة 5 هي 0x28 بايت أخرى داخل بيانات العميل تلك. وثّقت Trellix المسار نفسه TEB + 0x800 + 0x28 أثناء دراسة تعيينات tagWND على Windows 10 1909. هذه الإزاحات ليست ABI. لقد قمت بتضمينها بشكل ثابت وأستدعي VirtualQuery على النتيجة قبل استخدام المنطقة المعيَّنة.
يشمل فحص المؤشر امتداد الإشارة القانوني لـ x64، والمحاذاة على ثمانية بايتات، وأنماط القيم الحارسة المعروفة. بعد ذلك يجب أن تصمد القيمة أمام اختبارات السلوك أدناه.
يقرأ PoC الرئيسي الإزاحة 0x100، وينشئ نافذة STATIC واحدة ونافذة BUTTON واحدة ونافذة EDIT واحدة، ثم يقرأها مرة أخرى، ويدمّر النوافذ الثلاث، ثم يقرأها مرة أخيرة.
ULONG64 before = *(ULONG64 *)(desktop_heap + 0x100);
/* Create STATIC, BUTTON and EDIT windows. */
ULONG64 after_create = *(ULONG64 *)(desktop_heap + 0x100);
/* Destroy all three. */
ULONG64 after_destroy = *(ULONG64 *)(desktop_heap + 0x100);
تطابقت قيم QWORD الثلاث. ذهبت تخصيصات النافذة تلك وجاءت؛ أما القيمة عند 0x100 فلم تتغير.
يتولى kaslr_sandbox_proof.c مقارنة العمليات. يعيد الأب توجيه stdout لكل عملية فرعية عبر أنبوب (pipe)، ويحلل قيمة QWORD التي قرأتها العملية الفرعية، ويقارنها بالقيمة الأساسية.
أعادت العمليات الفرعية المرتبطة بسطح المكتب الافتراضي القيمة نفسها. أما العملية الفرعية المعيَّنة إلى سطح مكتب أُنشئ باستخدام CreateDesktop فقد أعادت قيمة مختلفة. يختبر برنامج الاختبار أيضًا عمليات فرعية بسلامة منخفضة (Low integrity)، وAppContainer، وتكوين LPAC الخاص به، وعمليات فرعية من نوع Low integrity AppContainer. جميعها تنفذ القراءة من TEB وتعيين الذاكرة الخاصين بها بدلًا من تلقي قيمة الأب.
ينفذ supporting_proof_no_window.c القراءة دون استدعاء CreateWindowEx. لا حاجة إلى نافذة USER جديدة.
يعامل PoC الرئيسي القيمة المسرَّبة ناقص 0x40 على أنها القاعدة الخاصة بجانب النواة (kernel-side) المقابلة لكومة سطح المكتب (desktop heap):
ULONG64 candidate_kernel_heap_base = leaked - 0x40;
ثم يبحث عن user32!gSharedInfo، ويستخدم البتات الـ 16 المنخفضة من HWND كفهرس لجدول مقابض USER، ويتقدّم عبر aheList بمقدار HeEntrySize. إذا كانت أول قيمة QWORD في ذلك المُدخل غير صفرية وأصغر من الكومة المُعيَّنة (mapped heap)، يستخدمها الـ PoC كإزاحة نسبية لـ desktop-heap:
ULONG index = (ULONG)(ULONG_PTR)hwnd & 0xffff;
BYTE *entry = (BYTE *)shared->aheList + index * shared->HeEntrySize;
ULONG64 heap_offset = *(ULONG64 *)entry;
if (heap_offset > 0 && heap_offset < heap_size) {
BYTE *user_candidate = desktop_heap + heap_offset;
ULONG64 kernel_candidate = candidate_kernel_heap_base + heap_offset;
}
هذه هي العلاقة العامة نفسها المستخدمة في تقنيات كومة سطح المكتب (desktop-heap) الأقدم: تضع واجهتا المستخدم والنواة كائنًا عند الإزاحة نفسها من قاعدتي الكومة الخاصتين بكل منهما. يقدّم Unit 42 خلفية مفيدة حول جدول المقابض USER وgSharedInfo وHMValidateHandle الأقدم لتسريبات العناوين.
العناوين الناتجة هي مرشّحات كائنات النواة في PoC. تنتمي علاقة 0x40 وتخطيط إدخال المقبض وإزاحات TEB إلى البنية (build) المستخدمة في الاختبار.
أترك مرشّحات الكائنات عند هذا الحد. إن قيمة QWORD عند desktop_heap + 0x100 هي بالفعل معلومات عنوان مستقرة في فضاء النواة ضمن تعيين قابل للقراءة من المستخدم. يمكن لخطأ منفصل في فساد ذاكرة Win32k أن يستخدمها بدلًا من تخمين عنوان.