
Prova de Conceito CVE-2025-24990 (driver da Agere Systems)
ltmdm64.sys). Este driver é muito antigo e não é carregado por padrão na minha máquina de teste, então vou explorá-lo em um cenário BYOVD. Curiosamente, de acordo com minha pesquisa, este driver existe desde o Windows 7 e tem pelo menos um bug. aqui
Naquela época, a MSRC não tomou nenhuma ação 🤡
Alguns IOCTLs dentro deste driver usam METHOD_NEITHER, mas não verificam se o buffer de endereço fornecido pelo chamador é do modo usuário ou do modo kernel. Aqui está um exemplo de código IOCTL que decodifiquei com OSR:
Isso significa que você pode fornecer um endereço de kernel para a API DeviceIoControl e o driver o manipulará normalmente.
Observe que você precisa contornar o kASLR primeiro para vazar o endereço do kernel. Usarei EnumDeviceDrivers (No Windows 24h2, você precisa de SeDebugPriv para fazer isso).
O problema está no IOCTL 0x802b200f (ud_response). Novamente, este despacho de IOCTL não valida o endereço que forneço do modo usuário, mas vou aproveitar isso mais tarde.
O ud_response chama ll_load_diagnostics, e chegarei ao seguinte código:
No início, a variável global eeprom não é inicializada, então conterá NULL. Aqui está um código simples que acionará isso.
Vou aproveitar isso mais tarde.
Este IOCTL simplesmente converte a string de versão do driver "8.36" para o número 0x836 (um DWORD) e o escreve no endereço fornecido pelo chamador (graças ao METHOD_NEITHER). Tecnicamente, posso escrever esses quatro bytes (36 08 00 00) em um endereço arbitrário do kernel. Vou aproveitar isso para sobrescrever as variáveis globais do driver e alterar o fluxo de execução.
Chamarei este 0x802b2003 de IOCTL_GET_VERSION
Null arbitrário de 1 byte:
Voltando ao caso de desreferência nula, uso a API VirtualAlloc para alocar um endereço fixo (0x083600000000). Em seguida, uso o IOCTL_GET_VERSION para escrever em *(eeprom + 4) os quatro bytes descritos acima. Quando o driver desreferenciar eeprom mais tarde, ele lerá do endereço que aloquei.
Após corrigir a desreferência nula, o IOCTL escreve uma string no endereço que forneço do modo usuário, com base no tamanho do buffer.
Este código simplesmente demonstra o que descrevi acima: alocar um buffer e preenchê-lo com 0xAA, corrigir a desreferência nula e então chamar o driver. Observe que aloco 11 bytes, mas forneço apenas um tamanho de buffer de 10 ao driver para ver como ele se comporta.
Ele escreve uma sequência fixa de bytes no meu buffer e então zera o byte final (o 11º), mesmo que eu forneça apenas um tamanho de 10. Ele substitui o último 0xAA no meu buffer por 0x00. Isso indica que, se eu fornecer um tamanho de 0, o driver ainda escreve um único byte 0x00 no endereço de destino.
Decremento arbitrário
Agora que tenho o null e o arbitrário com 4 bytes fixos, vamos criar outra primitiva.
Este IOCTL definirá o global LtMsgEvent para o meu buffer de usuário e então verificará se WDM é nulo e o definirá como zero novamente.
Então, em 0x802b2207, ele chamará a API ObfReferenceObject.
No estado inicial, o WDM é nulo, mas com a ajuda do IOCTL_GET_VERSION, posso definir WDM para 0x36 (seu tamanho é apenas 1 byte) e o LtMsgEvent ainda é meu buffer. Então, anularei WDM e chamarei 0x802b2207. Finalmente, alcançarei o ObfReferenceObject. Chamarei esses dois ioctls de IOCTL_SET_LtMsgEvent e IOCTL_DEREF_LtMsgEvent.
A técnica de exploit usando ObfReferenceObject altera o PreviousMode do nosso KTHREAD de UserMode para KernelMode; você pode ler sobre isso aqui. No entanto, o Windows corrigiu esse exploit, então não podemos usá-lo.
Mas a primitiva em ObfReferenceObject ainda existe. A API subtrai 0x30 do endereço que fornecemos, converte o resultado em um inteiro de 8 bytes e então subtrai 1.
*(signed long long)(LtMsgEvent-0x30) -= 1
Mas o problema é que ela verifica se o próximo valor é 0 ou se o valor atual é < 1 (interpretado como um inteiro assinado de 8 bytes). Se qualquer uma das condições for verdadeira, ela salta para KeBugCheckEx e trava o sistema.
Escrita arbitrária
Com o decremento arbitrário em mãos, preciso encontrar outro lugar para escrever o byte 0xFF e então decrementá-lo para o byte que desejo, e encontrei este ioctl 0x802b2243:
Vamos nos concentrar no ramo flip. pbVar5 é o endereço que forneço do modo usuário e pode ser qualquer endereço de destino que eu escolher. Escrevo o byte 0x0C em DAT_TARGET_EX (com a ajuda do IOCTL_GET_VERSION e da primitiva de decremento arbitrário), e também anulo 1 byte no endereço de destino. A primeira chamada a este IOCTL define 0xC0 no endereço de destino, que é então decrementado para 0xBF. Uma segunda chamada define 0xFF no endereço de destino (0xBF | 0xC0 = 0xFF). Uma vez que o destino contém 0xFF, apenas o decremento para o valor desejado.
Escreverei um byte por vez e tomarei cuidado com o KeBugCheckEx no ObfReferenceObject.
Leitura arbitrária
Para a primitiva de leitura, uso a técnica descrita aqui (@carrot_c4k3). Simplesmente sobrescrevo o objeto UNICODE_STRING no kernel (ExpManufacturingInformation) e então chamo NtQuerySystemInformation. Por causa do ObfReferenceObject chamar KeBugCheckEx, anularei 8 bytes adjacentes a ExpManufacturingInformation.
É isso, agora temos R/W arbitrário; podemos usar essas primitivas para fazer muitas coisas. O driver não é carregado por padrão, então vou explorá-lo em um cenário BYOVD e definir o PPL de um processo.
O exploit que descrevi acima funciona em todas as versões do Windows, mas é instável devido ao KeBugCheckEx. Mas no Windows 11 22h2+ existe uma técnica chamada ioring. Esta técnica simplesmente sobrescreve ioring->Buffer com um endereço controlável. Concretamente, podemos sobrescrever ioring->Buffer e seu tamanho com 0x083600000000 e 0x836, respectivamente (usando IOCTL_GET_VERSION). Usando esta técnica, realizo apenas 2 escritas e então uso a primitiva R/W de forma muito estável. Observe que esta abordagem requer vazar o endereço do kernel.
O Windows não carrega o driver no estado padrão. Então você precisa carregá-lo manualmente. O arquivo ltmdm64.sys está localizado em C:\Windows\System32\DriverStore\...\ltmdm64.sys. Execute este comando como administrador e execute o exploit:
sc create ltmdm64_srv binPath="C:\Windows\System32\DriverStore...\ltmdm64.sys" type=kernel && sc start ltmdm64_srv
O exploit usará a técnica ioring para desativar o PPL do lsass.exe e usará minha técnica somente de dados para definir o PPL para notepad.exe (no win 11 24h2, SeDebugPriv precisa estar habilitado)
https://github.com/user-attachments/assets/05a35b38-d26c-484f-9fb7-137f8fe8c079
Relatei este bug à ZDI. Mas parece que foi duplicado com a submissão de Fabian Mosch e Jordan Jay à MSRC, então este PoC apenas mostra o bug e agradece o trabalho deles. Quase meu primeiro CVE 😍