Sobre a OFFCEPT
Construída por operadores que passaram anos a invadir redes antes de criarem uma empresa em torno disso.
Saber Mais
← Voltar
Investigação Técnica24 de maio de 202614 min de leitura

Matar a Visibilidade do EDR no Kernel: BYOVD

A maioria dos produtos EDR partilha um pressuposto arquitetural fundamental: dependem de kernel callbacks para observar o que acontece no endpoint. Criação de processos, injeção de threads, carregamento de imagens, modificação do registry. São os olhos e ouvidos de todos os principais EDR do mercado. O pressuposto é que se o driver do EDR está carregado e os seus callbacks registados, o SOC tem visibilidade. Mas o que acontece quando alguém simplesmente remove esses callbacks do kernel?

Este artigo percorre a técnica que apresentámos no rootedPT 2026. Não é teórica. A cadeia de ataque funciona contra produtos EDR atualmente implementados, as ferramentas estão disponíveis publicamente e o problema central é conhecido há anos sem uma correção abrangente. Publicamos isto porque os defensores precisam de perceber exatamente quão frágil é a sua visibilidade e o que podem realmente fazer quanto a isso.

Como os EDR realmente veem os seus endpoints

Quando um agente EDR instala o seu driver numa máquina Windows, não monitoriza diretamente todas as system calls. Isso seria demasiado caro. Em vez disso, regista rotinas de callback no kernel do Windows. Estes callbacks são funções que o kernel promete chamar sempre que ocorrem eventos específicos. Os mais importantes:

  • PspCreateProcessNotifyRoutine dispara sempre que um processo é criado ou termina. É assim que o seu EDR sabe quando o notepad.exe cria o cmd.exe.
  • PspCreateThreadNotifyRoutine dispara na criação de threads. É assim que o seu EDR deteta remote thread injection, em que um processo cria uma thread dentro de outro.
  • PspLoadImageNotifyRoutine dispara quando uma DLL ou EXE é carregado em memória. É assim que o seu EDR vê quando uma DLL suspeita é injetada num processo legítimo.
  • CmCallbackList dispara em operações de registry. É assim que o seu EDR acompanha mecanismos de persistência a serem instalados no registry.

O kernel armazena estes callbacks em arrays fixos. PspCreateProcessNotifyRoutine, por exemplo, é um array que pode conter até 64 entradas. Quando um processo é criado, o kernel percorre este array e chama cada função registada. O driver do EDR recebe o evento, inspeciona-o e decide se o regista, se alerta ou se o bloqueia.

Esta arquitetura é eficiente e bem desenhada. Também tem uma fraqueza crítica: os arrays vivem em memória de kernel e são escrevíveis.

BYOVD: a cadeia de ataque

Bring Your Own Vulnerable Driver (BYOVD) é exatamente o que o nome sugere. O atacante carrega um driver de kernel legitimamente assinado que contém uma vulnerabilidade conhecida e depois explora essa vulnerabilidade para obter capacidades arbitrárias de leitura e escrita no kernel. Sem qualquer zero-day. O driver é real, assinado por um vendor real e, em muitos casos, ainda distribuído através do Windows Update ou de pacotes de software do fabricante.

A cadeia corre em cinco passos, todos a partir de user mode, sem privilégios especiais para além de administrador local:

  • Largar o driver vulnerável. O atacante coloca em disco um ficheiro .sys conhecido como vulnerável. Dell, ASRock, Gigabyte e outros já lançaram drivers mais tarde revelados como contendo vulnerabilidades exploráveis. O ficheiro passa a verificação de assinatura porque a assinatura é válida. Era suposto estar ali.
  • Carregar o driver. APIs standard de criação de serviços Windows. O Service Control Manager aceita-o porque a assinatura é válida. O EDR vê um evento de carregamento de driver, verifica a assinatura, vê o nome de um vendor legítimo e segue em frente.
  • Explorar a vulnerabilidade. Abrir um handle para o device object do driver e enviar um IOCTL manipulado. A vulnerabilidade despoleta operações arbitrárias de leitura ou escrita de memória em kernel space. O atacante tem agora uma primitiva de leitura/escrita para todo o espaço de endereçamento do kernel, a partir de user mode.
  • Localizar os arrays de callbacks. Com a primitiva de leitura de kernel, fazer scan da memória do kernel à procura dos arrays de callbacks. Ficam em offsets fixos a partir de símbolos exportados do kernel. Os offsets variam por build do Windows mas são trivialmente descobertos através de análise reversa do ntoskrnl.exe. Ferramentas como o CallbackDb mantêm bases de dados de offsets atualizadas para todas as builds. Ler o conteúdo dos arrays e sabe-se exatamente o que o EDR está a vigiar.
  • Zerar os callbacks. Com a primitiva de escrita de kernel, sobrescrever cada ponteiro de função de callback com null (0x0000000000000000). O kernel continua a percorrer o array quando ocorrem eventos, mas todas as entradas são null. Os callbacks nunca disparam. O driver do EDR continua carregado, o seu serviço continua a correr, o dashboard continua verde. Mas o kernel já não lhe está a dizer nada.

Neste ponto, o atacante tem total liberdade para executar técnicas no endpoint. Injeção de processos, dumping de credenciais, movimento lateral. O EDR não vai ver nada disto porque os kernel callbacks que reportariam estes eventos foram silenciados.

Prova: WinDbg antes e depois

Dump de memória de kernel no WinDbg mostrando o array de callbacks PspCreateProcessNotifyRoutine antes do zeroing - populado com ponteiros de funções do EDR

Demonstrámos isto no rootedPT com um WinDbg ligado a um kernel Windows ao vivo. Antes do ataque, fazer dump de PspCreateProcessNotifyRoutine mostra um array populado. Vários ponteiros de funções são visíveis, cada um correspondente a um callback registado por um EDR ou produto de segurança. O array está ativo e funcional.

Depois do ataque, a mesma região de memória mostra apenas zeros. Cada ponteiro de callback foi sobrescrito. O driver do EDR não tem consciência de que o seu callback foi removido porque o próprio driver não tem mecanismo para verificar se o seu callback ainda está no array. Registou o callback no arranque e assume que será chamado para sempre.

Dump de memória de kernel no WinDbg mostrando os arrays de callbacks PspCreateProcessNotifyRoutine e PspLoadImageRoutine depois do zeroing - todos os ponteiros a null

De seguida executámos uma série de técnicas de ataque: injeção de processos, dumping de credenciais via acesso ao LSASS e movimento lateral via PsExec. O EDR detetou zero destas. A consola não mostrou alertas. A árvore de processos não mostrou atividade suspeita. O endpoint parecia completamente normal enquanto tínhamos acesso total.

A Lacuna 1 está resolvida. E a Lacuna 2?

Então os kernel callbacks estão zerados. O mecanismo de deteção primário do EDR está cego. Mas os produtos EDR modernos também têm modelos de ML, motores de análise comportamental e sistemas de reputação na cloud. Mesmo sem kernel callbacks, certamente que estes apanhariam atividade suspeita?

Esta é a segunda lacuna, e é independente da primeira.

A deteção por ML e comportamental depende da recolha de telemetria sobre processos, escritas em ficheiros, ligações de rede e alterações ao registry. Mas a qualidade desta telemetria importa enormemente. Se o atacante conceber o payload com cuidado, os sinais comportamentais parecem benignos:

  • PE headers e metadata válidos. O payload tem timestamps de compilação corretos, recursos de versão com aspeto legítimo e assinaturas digitais próprias (certificados self-signed com nomes de organização credíveis). Modelos de ML que dependem de análise estática veem um executável normal.
  • Tabela de imports legítima. As funções importadas são APIs Windows standard sem padrões suspeitos. Sem VirtualAllocEx, sem WriteProcessMemory, sem CreateRemoteThread. As chamadas perigosas são resolvidas dinamicamente em runtime.
  • Alinhamento com a baseline comportamental. O perfil de comportamento do payload corresponde a ferramentas administrativas legítimas. Corre a partir de diretórios esperados, acede a recursos esperados e segue padrões de execução esperados. Modelos comportamentais que sinalizam anomalias estatísticas não veem nada de invulgar.
  • Manipulação de providers ETW. Muitos EDR complementam os kernel callbacks com providers de Event Tracing for Windows (ETW). Mas os providers ETW também são estruturas de kernel. Um atacante com uma primitiva de escrita no kernel pode desativar providers ETW específicos alterando o registo dos seus GUIDs da mesma forma que zera os arrays de callbacks. O modelo de ML não recebe telemetria para analisar porque o pipeline que o alimenta foi cortado na origem.
Bypass de deteção comportamental por ML mostrando como payloads concebidos a propósito se alinham com baselines comportamentais legítimas para evadir a análise do EDR

O insight-chave é que a Lacuna 1 (cegueira dos kernel callbacks) e a Lacuna 2 (bypass de ML/comportamental) são independentes. Corrigir uma não corrige a outra. Pode fazer hardening dos kernel callbacks (falamos do HVCI já a seguir) e ainda assim ter um atacante a passar direto pela sua deteção de ML. Ou pode ter deteção comportamental perfeita e ainda assim perder tudo porque os seus kernel callbacks foram zerados.

Este modelo de duas lacunas é o que torna os ataques BYOVD tão perigosos na prática. Defensores que se focam apenas em blocklists de drivers tratam a Lacuna 1 mas ignoram a Lacuna 2. Defensores que se focam apenas na deteção comportamental tratam a Lacuna 2 mas ignoram a Lacuna 1. As duas lacunas têm de ser fechadas em simultâneo.

HVCI: a mitigação que devia funcionar

O Hypervisor-Protected Code Integrity (HVCI) usa o hypervisor do Windows para criar regiões de memória isoladas. Páginas de código e dados do kernel que não devem ser modificadas são colocadas em memória apenas acessível a partir do host Virtual Trust Level 0 (VTL 0), não a partir do sistema operativo convidado. Em teoria, isto significa que mesmo que um atacante tenha uma primitiva de escrita no kernel, não consegue modificar estruturas protegidas do kernel.

A Microsoft tem posicionado o HVCI como a defesa primária contra ataques ao nível do kernel, incluindo BYOVD. E numa implementação HVCI totalmente hardened, funciona. Os arrays de callbacks ficam protegidos. O atacante não os consegue zerar.

Mas o HVCI tem três superfícies de bypass que importam operacionalmente:

Superfície de bypass 1: drivers assinados vulneráveis

O HVCI restringe que drivers podem carregar com base em requisitos de code integrity. Mas drivers assinados antes de os requisitos do HVCI serem apertados podem ainda carregar através de exceções de compatibilidade. Nos nossos testes, encontramos consistentemente drivers vulneráveis que carregam sem problemas em sistemas com HVCI ativado.

Superfície de bypass 2: ataques de detonação

Mesmo quando o HVCI protege diretamente os arrays de callbacks, um atacante com uma primitiva de escrita no kernel pode visar outras estruturas de kernel não protegidas para alcançar o mesmo resultado. O HVCI não protege tudo. Os próprios arrays de callbacks podem estar guardados, mas as estruturas dispatcher que os invocam, os registos de providers ETW que alimentam a telemetria e os callbacks do object manager que acompanham acessos a handles não estão todos cobertos. Um atacante que estude quais estruturas estão e não estão protegidas pode redirecionar o ataque por um caminho desprotegido.

Superfície de bypass 3: a realidade das implementações

O HVCI exige suporte de hardware (virtualização nested na CPU) e configurações de firmware específicas. Em ambientes empresariais, encontramos rotineiramente máquinas onde o HVCI está ativado na política mas não está de facto a correr. Aplicações legadas, drivers personalizados e hardware mais antigo impedem frequentemente o HVCI de ativar. Uma percentagem significativa dos endpoints que testamos em projetos de EDR tem o HVCI listado como ativado no dashboard de segurança mas não está efetivamente protegido ao nível do hypervisor.

Estudo de caso: DsArk64

Durante a nossa apresentação no rootedPT, percorremos um exemplo real que demonstra porque é que as blocklists de drivers por si só são insuficientes. O DsArk64 é um driver assinado com WHQL. A assinatura WHQL (Windows Hardware Quality Labs) é o nível de confiança mais alto para drivers Windows. Significa que a Microsoft testou e aprovou o driver. É distribuído com o 360 Total Security, um produto de segurança legítimo.

Repositório GitHub do DsArk64 mostrando driver assinado com WHQL com primitivas de leitura/escrita de kernel e capacidade de matar processos

O DsArk64 expõe IOCTLs que fornecem terminação de processos e capacidades arbitrárias de leitura e escrita de memória de kernel. Estas capacidades são intencionais. O driver foi construído para as fornecer. A assinatura é legítima porque o driver foi submetido através dos canais próprios e passou a validação da Microsoft.

Este é o problema das blocklists reativas. O DsArk64 nunca foi adicionado a blocklist nenhuma porque foi assinado através do processo próprio. Fornece primitivas completas de leitura e escrita no kernel. É distribuído com um produto de segurança que os utilizadores instalam intencionalmente. Um atacante que largue o DsArk64 numa máquina-alvo está a carregar um driver aprovado pela Microsoft, assinado com um certificado WHQL e distribuído por canais legítimos.

A conclusão não é que a assinatura WHQL esteja comprometida. É que qualquer sistema que dependa de enumerar drivers maus estará sempre atrasado. Novos drivers vulneráveis são descobertos continuamente. Drivers legítimos com capacidades perigosas existem em grande número.

Kernel Data Protection: a correção que não está implementada

A Microsoft desenvolveu uma tecnologia chamada Kernel Data Protection (KDP) que abordaria diretamente o problema do zeroing de callbacks. O KDP usa o hypervisor para marcar estruturas específicas de dados do kernel como read-only, mesmo para código em kernel mode. Se o KDP fosse aplicado aos arrays de callbacks, a primitiva de escrita de um ataque BYOVD simplesmente falharia.

O problema é a implementação. O KDP existe no Windows, mas não está amplamente ativado para os arrays de callbacks na prática. As razões são complexas: preocupações de performance, requisitos de testes de compatibilidade e o facto de aplicar KDP a estruturas de kernel frequentemente modificadas exigir coordenação cuidadosa dentro da equipa do kernel Windows. Até aos nossos testes em 2026, os arrays de callbacks em sistemas Windows totalmente atualizados continuam escrevíveis a partir de kernel mode.

O que os defensores devem realmente fazer

A resposta standard dos vendors ao BYOVD é "ative o HVCI e atualize a blocklist de drivers". Está correta mas é insuficiente. Eis o que recomendamos com base no que vemos em projetos reais:

  • Ative o HVCI e verifique que está de facto a correr. Não confie no dashboard. Verifique que o hypervisor está ativamente a proteger a memória de kernel nos seus endpoints. Encontramos uma taxa de falha de 20-30% em implementações empresariais onde a política diz ativado mas o hypervisor não está ativo.
  • Implemente Windows Defender Application Control (WDAC) com uma política rigorosa. O WDAC permite definir que drivers têm permissão para carregar nas suas máquinas. Uma política WDAC bem configurada é mais eficaz do que qualquer blocklist porque bloqueia tudo exceto o que explicitamente permitir.
  • Vigie eventos de carregamento de drivers, não apenas drivers conhecidos como maus. Alerte sobre qualquer carregamento novo de driver fora da sua baseline. Se uma máquina de repente carrega um driver de um vendor que não usa, isso é suspeito independentemente de estar ou não numa blocklist.
  • Assuma que o seu EDR pode ficar cego e construa visibilidade secundária. Providers ETW (Event Tracing for Windows), Sysmon e Windows Event Logs fornecem fontes de dados independentes. Se o seu EDR ficar cego, estas fontes podem ainda capturar evidência. Mas atenção: os providers ETW também podem ser desativados a partir de kernel mode, por isso isto é uma camada, não uma garantia.
  • Teste o seu EDR contra técnicas BYOVD. Execute a cadeia de ataque que descrevemos contra os seus próprios endpoints num teste controlado. Veja o que o seu EDR apanha e o que deixa passar. Os resultados são normalmente um banho de água fria.
  • Mapeie a sua cobertura de deteção para o MITRE ATT&CK. Veja especificamente T1068 (Exploitation for Privilege Escalation), T1569 (System Services) e T1543 (Create or Modify System Process). Se as suas regras de deteção para estas técnicas dependem apenas de kernel callbacks, vão falhar durante um ataque BYOVD.

A verdade incómoda

O problema BYOVD não é novo. O abuso de drivers vulneráveis está documentado desde pelo menos 2018. A manipulação de kernel callbacks é discutida em círculos de segurança ofensiva há anos. As técnicas que apresentámos no rootedPT são construídas sobre vulnerabilidades publicamente conhecidas e internals de kernel publicamente documentados.

O que torna isto persistente é que a arquitetura fundamental não mudou. Os EDR ainda dependem de kernel callbacks armazenados em arrays escrevíveis. A mitigação (KDP) existe mas não está implementada à escala. A defesa (HVCI) funciona quando bem configurada, mas muitas vezes não está. O penso rápido (blocklists de drivers) é sempre reativo.

Para os defensores, a mensagem é direta: a visibilidade ao nível do kernel do seu EDR não é garantida. Depende de estruturas de dados que podem ser silenciosamente modificadas por qualquer pessoa que consiga carregar um driver na sua máquina. Teste esse pressuposto regularmente. Construa camadas de deteção que não dependam todas da mesma infraestrutura frágil de kernel. E quando avaliar produtos EDR, pergunte especificamente como lidam com a remoção de callbacks, não apenas como detetam malware conhecido.

Se quer descobrir se o seu EDR sobrevive a este ataque, nós testamo-lo por si. Executamos a cadeia BYOVD completa, zeroing de callbacks incluído, contra os seus endpoints reais e mostramos-lhe exatamente o que o seu EDR vê e o que deixa passar. Fale connosco.