Viragt - Killer Process
Bom, esse post será a "continuação" do meu post anterior HEVD - HackSys. Então agora eu vou mostrar como é possível utilizar um driver para conseguir matar processos do Windows.
O driver em questão é o viragt64.sys, que não é novo e já existem diversos POCs de exploração dele no GitHub. Ainda assim, vou utilizá-lo aqui principalmente por ser extremamente fácil de explorar.
Caso queira baixar o driver você pode encontrar ele no: Living Off The Land Drivers.
As informações que você encontrar neste post, técnicas, códigos, provas de conceito ou qualquer outra coisa são estritamente para fins educacionais.
Análise do Driver
Bom, vou partir para mostrar as partes principais do driver. Não vejo por que enrolar aqui. Olhando as importações do driver, vamos ver ZwTerminateProcess, que é justamente a API do kernel responsável por terminar processos.
Então, indo até onde ZwTerminateProcess é chamado, vamos cair na função sub_12EF4, onde será possível ver o seguinte:

O que está acontecendo aqui é que, após alguma comparação anterior (representada pelo v21), se o resultado for zero, o código prepara uma estrutura OBJECT_ATTRIBUTES e uma estrutura CLIENT_ID.
A linha v22 = (void *)*((_QWORD *)i + 10) está obtendo o PID do processo a partir de alguma estrutura sendo iterada. Esse PID é então colocado em ClientId.UniqueProcess, enquanto ClientId.UniqueThread é zerado, indicando que queremos abrir o processo inteiro, não apenas uma thread específica.
Em seguida, o código chama ZwOpenProcess passando permissões completas (0x1F0FFF, que equivale a PROCESS_ALL_ACCESS). Se conseguir obter um handle válido para o processo, a função simplesmente chama ZwTerminateProcess(ProcessHandle, 99), terminando o processo com código de saída 99.
O interessante aqui é que não existe nenhuma validação sobre quem está pedindo para terminar o processo ou se essa operação deveria ser permitida. O driver simplesmente abre e mata o processo sem questionar.
Então, indo até sub_12EF4 e olhando onde essa função é chamada, vamos cair na função sub_13624, onde será possível ver o seguinte:

O valor -2106392528 em decimal é na verdade 0x82730030 em hexadecimal, que é o código IOCTL que aciona essa funcionalidade.
Quando o driver recebe um IOCTL com esse valor específico, ele chama diretamente sub_12EF4 passando o ponteiro MasterIrp, que contém os dados enviados pelo processo em modo usuário. Não existe nenhuma verificação de privilégios, nenhuma validação de quem está chamando, absolutamente nada.
Qualquer processo pode simplesmente abrir o dispositivo do driver, enviar esse IOCTL com o nome de um processo, e o driver vai terminá-lo sem fazer nenhuma pergunta.
Desenvolvendo o Exploit
Agora que entendemos como o driver "funciona internamente", podemos começar a desenvolver nosso exploit. O primeiro passo é definir o código IOCTL que identificamos na análise e criar a estrutura que o driver espera receber:
O IOCTL 0x82730030 é exatamente o valor que vimos na função sub_13624 (representado como -2106392528 em decimal). A estrutura TERMINATE_PROCESS_REQUEST é bem simples: apenas um array de caracteres para armazenar o nome do processo que queremos terminar.
Em seguida, criamos uma classe chamada MemoryTester para gerenciar a comunicação com o driver:
O método Initialize usa CreateFileW para abrir o dispositivo \\.\Viragtlt, que é o nome do dispositivo exposto pelo driver viragt64.sys. Passamos GENERIC_READ | GENERIC_WRITE para ter permissões completas de leitura e escrita, e OPEN_EXISTING porque o dispositivo já deve existir (ou seja, o driver já deve estar carregado).
Método que realmente envia o IOCTL para terminar processos:
Esse método começa verificando se o dispositivo foi inicializado corretamente.
Depois, criamos a estrutura TERMINATE_PROCESS_REQUEST e zeramos ela completamente.
Em seguida, copiamos o nome do processo fornecido para o campo ProcessName da estrutura usando strcpy.
Na chamada DeviceIoControl, que é a função do Windows responsável por enviar comandos (IOCTLs) para drivers. Os parâmetros importantes são:
hDevice→ o handle do dispositivo que obtivemos noInitializeIOCTL_TERMINATE_PROCESS→ o código IOCTL0x82730030que identificamos&request→ ponteiro para nossa estrutura contendo o nome do processosizeof(TERMINATE_PROCESS_REQUEST)→ tamanho da estrutura que estamos enviando
Passamos NULL para o buffer de saída e 0 para o tamanho. O driver simplesmente processa a requisição e termina o processo.
Testando o Exploit
Com a classe pronta, podemos criar a função main para testar tudo:
O resultado final é esse:

Last updated