
CVE-2025-7771: Beliebiges Lesen/Schreiben von physischem Speicher und I/O-Ports über den ThrottleStop-Treiber
CVE-2025-7771: Beliebiges Lesen/Schreiben von physischem Speicher und I/O-Ports über den ThrottleStop-Treiber
ThrottleStop ist eine kleine legitime Anwendung zur Überwachung und Korrektur von CPU-Drosselung. Sie verfügt über einen Treiber, der diese Aufgaben ermöglicht und größtenteils keinerlei Eingabevalidierung besitzt.
| IOCTL | Schwachstelle |
|---|---|
| 0x80006498 | Beliebiges Speicherlesen |
| 0x8000649C | Beliebiges Speicherschreiben |
| 0x80006430 | Beliebiges Portlesen |
| 0x80006434 | Beliebiges Portschreiben |
Es scheint auch beliebiges MSR-Lesen und -Schreiben zu geben, aber ich konnte sie nicht zum Laufen bringen.
Fühlen Sie sich frei, selbst nachzusehen.
MSR-Lesen ist bei 0x80006448 und Schreiben bei 0x8000644C.
Wenn Sie es zum Laufen gebracht haben, reichen Sie bitte ein Issue ein, das das Verfahren beschreibt.
Alle unten erwähnten IOCTL-Aufrufe sind in der Datei Exploit.c implementiert.
Der IOCTL 0x80006498 mappt jede angegebene physische Adresse mit MmMapIoSpace in den Kernel-Speicher und liest 1, 2, 4 oder 8 Bytes daraus. Die Daten werden dann zurück in den User-Space kopiert.
Um diesen IOCTL auszulösen, DeviceIoControl mit der physischen Adresse als Eingabe und der Ausgabepuffergröße als Lesegröße (1, 2, 4 oder 8).
Vereinfachter Code für diesen IOCTL im Treiber:
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);
}
}
Ähnlich wie beim Lesen mappt der IOCTL 0x8000649C die angegebene physische Adresse mit MmMapIoSpace in den Kernel-Speicher und liest 1, 2, 4 oder 8 Bytes daraus.
Um diesen IOCTL auszulösen, DeviceIoControl mit dem folgenden Eingabepuffer-Layout:
struct {
ULONGLONG PhysicalAddress; // 8 bytes
union {
BYTE Value8;
WORD Value16;
DWORD Value32;
QWORD Value64;
};
};
Die Eingabepuffergröße beträgt 8 + (1, 2, 4 oder 8), abhängig von der Schreibgröße.
Ausgabepuffer und Ausgabepuffergröße werden nicht verwendet.
Vereinfachter Code für diesen IOCTL im Treiber:
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);
}
}
Der IOCTL 0x80006430 erlaubt es einem Benutzer, einen IO-Port anzugeben und direkt mit __inbyte, __inword oder __indword daraus zu lesen.
Der Eingabepuffer ist ein USHORT und die Eingabepuffergröße sollte sizeof(USHORT) betragen.
Der Ausgabepuffer enthält das Ergebnis (1, 2 oder 4 Bytes, abhängig von der angeforderten Größe) und die Ausgabegröße kann 1, 2 oder 4 sein.
Vereinfachter Code für diesen IOCTL im Treiber:
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);
}
Ähnlich wie beim Portlesen erlaubt der IOCTL 0x80006434 einem Benutzer, einen IO-Port anzugeben und beliebige Werte mit __outbyte, __outword oder __outdword dorthin zu schreiben.
Das Eingabepuffer-Layout ist:
struct {
USHORT PortNumber;
BYTE Padding[2]; // alignment
union {
BYTE Value8;
WORD Value16;
DWORD Value32;
};
};
Die Eingabepuffergröße sollte sizeof(USHORT) + sizeof(Value) (+ Padding) betragen. Ausgabepuffer und dessen Größe werden nicht verwendet.
Vereinfachter Code für diesen IOCTL im Treiber:
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);
}
Diese Übersetzung ist möglich, indem Superfetch missbraucht wird, ein legitimes Kernelmodul, das die Übersetzung von virtuellen zu physischen Adressen über undokumentierte APIs bereitstellt. Der Code dafür ist in vtop.c implementiert.
Dieses Projekt kann mit Meson kompiliert werden. Es liest das Feld UniqueProcessId, indem es die EPROCESS-Struktur des System-Prozesses mit PsInitialSystemProcess auflöst, und prüft, ob es 4 ist (implementiert in Main.c, und die Funktionen zum Finden der EPROCESS einer bestimmten PID sind in EProcess.c implementiert). Zusätzlich fragt es den Benutzer, ob er sein System zwangsweise neu starten möchte. Dies ist möglich, indem der Wert 0x0E an Port 0xCF9 geschrieben wird.
Warnung: Alles wurde auf Windows 11 Build 24H2 getestet. Die Offsets in der EPROCESS-Struktur können auf Ihrem System anders sein.
Erstellen Sie das Projekt mit
meson setup build
Es wird das Build-Verzeichnis erstellt. Kompilieren Sie nun das Projekt mit
meson compile -C build
Der Treiber ist im Verzeichnis Drivers des Repositorys verfügbar. ThrottleStop-Treiber können direkt aus dem Programm ThrottleStop.exe über Resource Hacker extrahiert werden. Sie finden 2 Treiber - x64 und x86. DriverObject->MajorFunction[14] enthält die Dispatch-Funktion, in der alle IOCTLs implementiert wurden.
Erstellen und starten Sie den Treiber mit:
sc create ThrottleStop binPath="<Path>" type=kernel
sc start ThrottleStop
https://github.com/jonomango/superfetch
Dieser Treiber wurde noch nicht zur Microsoft-Treibersperrliste oder zu loldrivers hinzugefügt.