
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 لعملية System باستخدام PsInitialSystemProcess ويتحقق مما إذا كانت قيمته 4 (مُنفّذ في Main.c والدوال الخاصة بإيجاد EPROCESS لـ PID محدد مُنفّذة في EProcess.c). بالإضافة إلى ذلك، يسأل المستخدم عما إذا كان يريد إجبار نظامه على إعادة التشغيل. هذا ممكن عبر كتابة القيمة 0x0E إلى المنفذ 0xCF9.
تحذير: تم اختبار كل شيء على Windows 11 build 24H2. قد تختلف الإزاحات (offsets) في بنية EPROCESS على نظامك.
قم ببناء المشروع عبر
meson setup build
سيُنشئ دليل build. الآن قم بتجميع المشروع عبر
meson compile -C build
برنامج التشغيل متاح في دليل Drivers بالمستودع. يمكن استخراج برامج تشغيل ThrottleStop مباشرة من برنامج ThrottleStop.exe عبر Resource Hacker. ستجد برنامجي تشغيل - x64 و x86. يحمل DriverObject->MajorFunction[14] دالة الإرسال حيث تم تنفيذ جميع IOCTLs.
أنشئ وابدأ برنامج التشغيل باستخدام:
sc create ThrottleStop binPath="<Path>" type=kernel
sc start ThrottleStop
https://github.com/jonomango/superfetch
لم يُضف برنامج التشغيل هذا بعد إلى قائمة حظر برامج التشغيل من Microsoft ولا إلى loldrivers.