Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
BYOVD-DriverKiller — Driver Reverse & Exploitation | Kitploit
Ferramentas/GitHubGitHub/alex3o/byovd-driverkiller
ExploitationReverse EngineeringPost-ExploitationBinary AnalysisLearning & EducationRed Teaming
GitHubalex3o/byovd-driverkiller

BYOVD-DriverKiller

Driver Reverse & Exploitation

Ver Repositório
8314há 11 mesesRevisado pelo Kitploit

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

BYOVD-DriverKiller

⚠️ Aviso: Este projeto é estritamente educacional e demonstrativo. Não se destina a uso em contexto malicioso. O objetivo é aprender a metodologia de engenharia reversa e as etapas de exploração de um driver do Windows.


Explico aqui a abordagem que segui para resolver o exercício proposto por d1rk(SaadAhla) https://github.com/SaadAhla, que consiste em realizar engenharia reversa e exploração em um driver legítimo, assinado e não presente nas blocklists (HVCI, LOLBIN...). Um programa em C que permite encerrar qualquer processo ativo no sistema por meio deste Kernel-mode Driver está disponível; detalho seu funcionamento um pouco mais abaixo.

POC-BYOD

📃 Uso: DriverKiller.exe <nome_processo.exe> [-d]

Opção -d: Permite remover o serviço e o Driver do sistema após a exploração.

O modo testsigning deve estar ativado na máquina alvo, pois o certificado do Driver expirou.


Parte 1 - Engenharia reversa:

O exercício fornece um arquivo .sys, nomeado com seu hash SHA-256. O primeiro passo é abrir esse arquivo com o IDA.
O IDA está disponível gratuitamente. Basta acessar o site da Hex-Rays para gerar uma licença e baixar o software.

Começamos listando a IAT (Import Address Table) do Driver e procurando a chamada para a API que nos interessa: ZwTerminateProcess.

screen1-git

Ao clicar duas vezes em ZwTerminateProcess, o IDA nos redireciona para o código compilado dessa função. Selecionando a entrada e exibindo as cross-references, obtemos a lista das funções do Driver que a chamam.

screen2-git

Observamos que é a função sub_12EF4, no offset 1CE, que usa ZwTerminateProcess. Após um duplo clique, o IDA exibe seu código compilado.

screen11-git

O código decompilado revela as chamadas a ZwOpenProcess (que abre um handle para o processo alvo) e a ZwTerminateProcess (que encerra o processo por meio desse handle).

Consultando a documentação de ZwOpenProcess (https://learn.microsoft.com/fr-fr/windows-hardware/drivers/ddi/ntddk/nf-ntddk-zwopenprocess), verificamos que o parâmetro ClientID corresponde a um ponteiro que indica o PID do processo visado.

Na linha acima, ClientId.UniqueProcess é inicializado com a variável v22. Esta é definida logo acima:

root@kitploit:~
v22 = (void )((_QWORD *)i + 10);

Para entender essa atribuição, é preciso identificar a variável i e o campo +10.

screen3-git

Mais acima nessa função, observamos uma chamada a ZwQuerySystemInformation com o parâmetro SYSTEM_PROCESS_INFORMATION. Entendemos também que i é o iterador sobre as entradas dessa estrutura com a variável v6.

De acordo com a documentação de ZwQuerySystemInformation: (https://learn.microsoft.com/en-us/windows/win32/sysinfo/zwquerysysteminformation), essa função retorna uma matriz contendo uma entrada para cada processo ativo no sistema.

A estrutura SYSTEM_PROCESS_INFORMATION é descrita aqui: https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation

root@kitploit:~
typedef struct _SYSTEM_PROCESS_INFORMATION {
    ULONG NextEntryOffset;
    ULONG NumberOfThreads;
    BYTE Reserved1[48];
    UNICODE_STRING ImageName;
    KPRIORITY BasePriority;
    HANDLE UniqueProcessId;
    PVOID Reserved2;
    ULONG HandleCount;
    ULONG SessionId;
    PVOID Reserved3;
    SIZE_T PeakVirtualSize;
    SIZE_T VirtualSize;
    ULONG Reserved4;
    SIZE_T PeakWorkingSetSize;
    SIZE_T WorkingSetSize;
    PVOID Reserved5;
    SIZE_T QuotaPagedPoolUsage;
    PVOID Reserved6;
    SIZE_T QuotaNonPagedPoolUsage;
    SIZE_T PagefileUsage;
    SIZE_T PeakPagefileUsage;
    SIZE_T PrivatePageCount;
    LARGE_INTEGER Reserved7[6];
} SYSTEM_PROCESS_INFORMATION;

Lembrete: tamanhos de alguns tipos no Windows x64

  • ULONG = 4 octetos
  • USHORT = 2 octetos
  • HANDLE = 8 octetos
  • PWSTR = 8 octetos
  • KPRIORITY (typedef de um LONG) = 4 octetos
  • UNICODE_STRING = 16 octetos, pois esta é a sua estrutura:
root@kitploit:~
  typedef struct _UNICODE_STRING {
    USHORT Length;        -> 2      
    USHORT MaximumLength; -> + 2 = 4
    PWSTR  Buffer;        -> + 8 = 12 (12 não é múltiplo de 8, portanto padding de 4 adicionado antes de Buffer) = 16
} UNICODE_STRING;

Cálculo do offset de UniqueProcessId:

root@kitploit:~
    ULONG NextEntryOffset;        -> 4
    ULONG NumberOfThreads;        -> + 4 = 8
    BYTE Reserved1[48];           -> + 48 = 56
    UNICODE_STRING ImageName;     -> + 16 = 72
    KPRIORITY BasePriority;       -> + 4 = 76 (76 não é múltiplo de 8, portanto padding de 4 adicionado) = 80
    HANDLE UniqueProcessId;       -> + 8 = 88

O membro UniqueProcessId está, portanto, no offset 0x50 (80 decimal).

Observando a atribuição da nossa variável v22, vemos que i é convertido (cast) em ponteiro QWORD (8 octetos)

root@kitploit:~
v22 = (void )((_QWORD *)i + 10);
Portanto, v22 corresponde ao endereço de i + 10 * 8 = 80 octetos. Essa variável contém, de fato, o PID recuperado da estrutura SYSTEM_PROCESS_INFORMATION.

Para saber qual PID será passado para ZwTerminateProcess, é necessário analisar a condição que envolve essa atribuição.

screen4-git

Observamos que o nome da imagem do processo é primeiro recuperado:

root@kitploit:~
v9 = (wchar_t )((_QWORD *)i + 8);
Pois v9 = endereço de i + 8 × 8 = 64 octetos. Isso corresponde ao Buffer do membro ImageName, já que esse membro está no offset 56 + 2 (USHORT) + 2 (USHORT) + 4 (padding) = 64

Diante das manipulações e dos loops abaixo, podemos formular a hipótese de que é feita uma comparação entre o nome do processo passado como argumento (a2) e os processos ativos no sistema v9/String.

root@kitploit:~
sub_1C078(String, v9, (int)v13);
v17 = strupr(a2);
v18 = strupr(String);

É, portanto, o parâmetro a2 que deve conter o nome do processo a ser encerrado via ZwTerminateProcess. Notamos que a2 é um parâmetro da função sub_12EF4. Para ir mais longe, é preciso examinar as referências dessa função (renomeei-a para ZwTerminateProcessCaller para melhor legibilidade).

screen5-git

Observamos que ZwTerminateProcessCaller é chamada pela função sub_13624 no offset 61A.

screen6-git

Antes de analisar esse código decompilado, vou procurar as referências da função sub_13624 (renomeada ZwTerminateProcessCallerCaller) para garantir que esse código é realmente usado após uma chamada de API DeviceIoControl vinda do UserMode.

screen§-git

Observamos que ZwTerminateProcessCallerCaller é chamada pela função sub_14130 (renomeada ZwTerminateProcessCallerCallerCaller ...felizmente para nós, é a última antes do ponto de entrada 😅).

screen7-git

Observamos que ZwTerminateProcessCallerCallerCaller é chamada pela função sub_1A4A8 no offset 306.

screen8-git

Encontramos a atribuição da função ZwTerminateProcessCallerCallerCaller:

root@kitploit:~
memset64(DriverObject->MajorFunction, (unsigned __int64)ZwTerminateProcessCallerCallerCaller, 0x1Cu);
O que significa que essa função é atribuída a todas as entradas da tabela MajorFunction (0x1B = 27, e existem 28 IRPs principais).

screen9-git

Antes de voltar à função sub_13624 (alias ZwTerminateProcessCallerCaller), recuperamos o Symbolic Name e o Device Name (idênticos aqui): Viragtlt.

screen12-git

Voltando a ZwTerminateProcessCallerCaller, notamos que seu segundo parâmetro (portanto a2) corresponde a MasterIrp->AssociatedIrp.SystemBuffer.

screen13-git

Logo acima da chamada a ZwTerminateProcessCaller, encontramos o IOCTL code: -2106392528 (em hexadecimal: 0x82730030).

Graças a essas informações, podemos deduzir que, para explorar este Driver, é necessário enviar uma chamada de API DeviceIoControl ao Driver com o nome do processo a ser encerrado no SystemBuffer.


🔷 Informações recuperadas por meio da engenharia reversa:

  • IOCTLCode: 0x82730030
  • Device Name: Viragtlt
  • Symbolic Name: Viragtlt
  • O SystemBuffer deve conter o nome do processo alvo

Parte 2 - Exploração

Para explorar este Driver (se estiver instalado e ativo na máquina alvo), é necessário abrir um handle para ele e, em seguida, fazer uma chamada de API DeviceIoControl com um Buffer contendo o nome do processo que se deseja encerrar.
Para este exercício, desenvolvi um projeto em C que:

  • Verifica se o Driver está presente e ativo no sistema (com um nome de serviço específico):
    • Se sim, o programa explora o Driver com uma chamada de API DeviceIoControl.
    • Se não, o programa extrai o driver de seus recursos, implanta-o na área de trabalho do usuário, cria um serviço ativo e então explora o Driver com uma chamada de API DeviceIoControl. (Requer direitos de administrador, pois é feita a criação de um serviço.)
  • Se o Driver está presente no sistema, mas o serviço não foi iniciado, o programa tenta iniciar o serviço e então o explora com uma chamada de API DeviceIoControl.

Também adicionei uma opção -d que permite remover o serviço e o Driver do sistema após a exploração.

Este é o comportamento do programa C em seu ciclo de execução completo:

git

Evasão de AV/EDR

Neste caso, DriverKiller.exe não é detectado pelo Microsoft Defender, nem estaticamente nem dinamicamente. A evasão não faz muito sentido aqui, pois o Driver explorado possui um certificado expirado, tornando difícil considerar seu uso em condições reais. Mas, para maior furtividade, poderíamos ter implementado:

  • O mascaramento de algumas chamadas de API da tabela IAT por meio de implementações personalizadas de GetProcAddress e GetModuleHandle
  • Uma aproximação ao Kernel para a execução de chamadas de API (Direct/Indirect Syscalls)
  • Técnicas de Anti-VM / Anti-Debug

Detecção do driver em 29/08/2025 (resultado já existente; não enviei nada ao VirusTotal por razões óbvias):

image

⚠️ Este projeto foi realizado em um contexto de aprendizado. Pode conter imprecisões ou erros. Toda sugestão, correção ou discussão é bem-vinda! 😃 Obrigado a d1rk(SaadAhla): https://github.com/SaadAhla !

Baixar ferramenta