
Motor de instrumentação de telemetria e hooking em modo de usuário sem patch, baseado em Hardware Breakpoint (DR0-DR7) (PoC de AMSI, WLDP e ETW).
Uma Prova-de-Conceito (POC) de pesquisa em segurança demonstrando hooking de funções baseado em breakpoints de hardware (registradores de depuração da CPU) como alternativa à tradicional correção de código em memória.
Propósito e Escopo
Este repositório é publicado estritamente para pesquisa defensiva em segurança, educação em red-team/purple-team, engenharia de detecção e estudo acadêmico de detalhes internos do Windows. Ele demonstra como um atacante poderia abusar dos registradores de depuração do processador para neutralizar telemetria de segurança em modo usuário — e, igualmente importante, o que defensores devem monitorar a fim de detectar tais técnicas. O autor não é responsável por qualquer uso indevido deste código. O uso desta técnica contra sistemas sem autorização explícita é ilegal e viola as leis relevantes de fraude e abuso de computadores na maioria das jurisdições. Não implante isto em qualquer ambiente que você não possua ou para o qual não tenha permissão escrita explícita para testar.
mora_hwbp.c implementa uma DLL que, uma vez carregada/injetada em um processo alvo (por exemplo, um host do PowerShell), intercepta quatro funções em modo usuário exclusivamente por meio de breakpoints de hardware da CPU armazenados nos registradores de depuração arquiteturais (DR0–DR7) de cada thread do processo:
Um Tratador de Exceções Vetorizado (VEH) por processo recebe as falhas EXCEPTION_SINGLE_STEP (0x80000004) geradas pelos registradores de depuração, simula o caminho de retorno bem-sucedido da função original reescrevendo o contexto da exceção e retoma a execução — tudo isso sem modificar um único byte da memória executável.
Isso torna a técnica particularmente interessante tanto do ponto de vista ofensivo quanto defensivo:
.text modificadas (hooking inline clássico, correção de EAT/IAT ou stubbing de Etwp*).GetThreadContext/SetThreadContext) que podem ser usados para detecção.Abordagens tradicionais de hooking em modo usuário — detours inline (sobrescritas de 5–14 bytes), hooking na tabela de endereços de importação (IAT) e hooking na tabela de endereços de exportação (EAT) — compartilham uma fraqueza comum: elas modificam memória que scanners de integridade e ETW podem observar.
Produtos modernos de AV/EDR implementam:
pageguard/guard-page, transições VirtualProtect para PAGE_EXECUTE_READWRITE e incompatibilidades de hash de seção.Breakpoints de hardware contornam tudo isso:
.text.SetThreadContext, que não dispara os sinais clássicos de "memória modificada" usados por scanners de integridade.Esta POC explora a eficácia e a detectabilidade dessa técnica contra AMSI (Interface de Varredura Antimalware), WLDP (Política de Bloqueio do Windows) e ETW (Rastreamento de Eventos para Windows) — os três primitivos de segurança em modo usuário mais amplamente utilizados na pilha de segurança moderna do Windows.
AMSI é o ponto de integração da plataforma Windows que permite que aplicativos (PowerShell, Office, VBScript, hosts .NET, etc.) solicitem varredura de conteúdo a provedores antimalware registrados. Dois pontos de entrada são de interesse primário:
AmsiScanBuffer(HANDLE hamsiContext, PVOID buffer, ULONG length, LPCWSTR contentName, HANDLE hamsiSession, AMSI_RESULT *pResult)AmsiScanString(HANDLE hamsiContext, LPCWSTR string, LPCWSTR contentName, HANDLE hamsiSession, AMSI_RESULT *pResult)Ao forçar o AMSI_RESULT retornado para AMSI_RESULT_CLEAN (0), o mecanismo de script acredita que o conteúdo foi inspecionado e considerado benigno, então a execução continua sem interrupção.
A WLDP implementa a avaliação de políticas para o Controle de Aplicativos do Windows Defender (WDAC / Device Guard). WldpIsClassInApprovedList responde se uma determinada classe COM (identificada por GUID) é permitida sob a política atual. Internamente, o AMSI consulta a WLDP para decidir se certas classes de script/conteúdo são "confiáveis" (na lista aprovada). Se a função relatar a classe como aprovada, o AMSI pode dispensar o escrutínio adicional para esse tipo de conteúdo.
A DLL define o parâmetro de saída isApproved (RDX) como TRUE e retorna S_OK, fazendo com que a classe avaliada pareça confiável.
EtwEventWrite em ntdll.dll é o sumidouro central em modo usuário para praticamente toda a emissão de eventos ETW no sistema. Suprimi-lo tem efeitos colaterais amplos relevantes para o monitoramento de segurança:
Microsoft-Windows-DotNETRuntime)A DLL simplesmente retorna ERROR_SUCCESS (0) sem executar a função real.
┌──────────────────────────────────────────────────────────────────────────┐ │ Target Process (e.g. powershell.exe) │ │ │ │ ┌──────────────────────────┐ ┌──────────────────────────────────┐│ │ │ mora_hwbp.dll │ │ CPU / Windows ││ │ │ │ │ ││ │ │ DllMain / InstallHook │ │ Thread A Thread B ││ │ │ │ │ │ ┌────────┐ ┌────────┐ ││ │ │ ▼ │ │ │ DR0..3 │ │ DR0..3 │ ││ │ │ Resolve exports │ │ └────────┘ └────────┘ ││ │ │ (amsi/wldp/ntdll) │ │ ││ │ │ │ │ │ #DB (single-step) ││ │ │ ▼ │ │ exception ──► Windows Dispatch ││ │ │ AddVectoredException │ │ │ ││ │ │ Handler(VEH) │ │ ▼ ││ │ │ │ │ │ ┌─────────────────────────────┐ ││ │ │ ▼ │ │ │ VectoredHandler (ours) │ ││ │ │ SetHwbpOnThread(ALL) │ │ │ • match #DB address │ ││ │ │ │ │ │ │ • rewrite context (RIP/RSP)│ ││ │ │ ▼ │ │ │ • spoof return value (RAX) │ ││ │ │ MonitorThread ◄──┐ │ │ │ • continue execution │ ││ │ │ (re-hook every │ │ │ └─────────────────────────────┘ ││ │ │ 500ms) └──────┘ │ ││ │ └──────────────────────────┘ └──────────────────────────────────┘│ └──────────────────────────────────────────────────────────────────────────┘
**Fluxo de alto nível:**
1. A DLL é carregada no processo de destino (por meio de qualquer técnica de injeção — consulte [Uso](#injection--usage-example)).
2. No `DLL_PROCESS_ATTACH` (ou por meio da função exportada `InstallHook`), as exportações de destino são resolvidas com `GetProcAddress` (opcionalmente forçando o carregamento de módulos via `LoadLibraryW`).
3. Um **Manipulador de Exceção Vetorizado** é registrado como o **primeiro** manipulador no processo (`AddVectoredExceptionHandler(1, ...)`).
4. A **thread atual** é interceptada imediatamente e, em seguida, **todas as threads existentes** no processo são enumeradas via `CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)` e interceptadas.
5. Uma **thread de monitoramento** acorda a cada 500 ms e reaplica os breakpoints em todas as threads — incluindo **threads recém-criadas** — garantindo a persistência do hook mesmo que uma thread seja criada depois do hook ou que os breakpoints sejam limpos externamente.
6. Quando qualquer função interceptada é chamada em qualquer thread, a CPU gera uma exceção de passo único `#DB`; o Windows a despacha para o VEH, que simula um retorno benigno e continua a execução.
### Exemplo de Saída de Depuração

*Figura 1 — Exemplo de saída de diagnósticos capturada com o Sysinternals DebugView. Cada linha informa o endereço alvo resolvido e o contador de acertos ao vivo para seu respectivo registrador de depuração.*
---
## Detalhamento Técnico
### 5.1 Breakpoints de Hardware em x64
Em x86/x64, cada CPU fornece **quatro registradores de endereço de depuração de hardware** (`DR0`–`DR3`) e um registrador de controle (`DR7`). Qualquer thread em execução com um breakpoint diferente de zero em `DR0`–`DR3` sofrerá uma falta sempre que o ponteiro de instrução atingir esse endereço (ou o acesso a dados corresponder às condições configuradas). O registrador de status `DR6` registra qual breakpoint foi acionado.
Breakpoints de hardware são **sensíveis ao contexto**: eles são armazenados na estrutura `CONTEXT` da thread e se aplicam apenas à thread na qual foram definidos. É por isso que uma implementação robusta deve definir breakpoints em **todas as threads** do processo (e reaplicá-los continuamente para novas threads).
### 5.2 Layout dos Registradores de Depuração (DR0–DR7)
`DR7` é um campo de bits que controla a habilitação e o comportamento dos breakpoints:
| Bits | Campo | Significado |
|--------|--------|-----------------------------------------------------|
| `0` | `L0` | Habilitação local para o breakpoint 0 (DR0) |
| `2` | `L1` | Habilitação local para o breakpoint 1 (DR1) |
| `4` | `L2` | Habilitação local para o breakpoint 2 (DR2) |
| `6` | `L3` | Habilitação local para o breakpoint 3 (DR3) |
| `8` | `LE` | Habilitação local legada (mantida para compatibilidade) |
| `9` | `GE` | Habilitação global legada (mantida para compatibilidade) |
| `16–17`| `R/W0` | Tipo de acesso para BP0 (`00` = execução de instrução) |
| `18–19`| `Len0` | Tamanho para BP0 (`00` = 1 byte) |
| `20–21`| `R/W1` | Tipo de acesso para BP1 (`00` = execução de instrução) |
| `22–23`| `Len1` | Tamanho para BP1 (`00` = 1 byte) |
| `24–25`| `R/W2` | Tipo de acesso para BP2 (`00` = execução de instrução) |
| `26–27`| `Len2` | Tamanho para BP2 (`00` = 1 byte) |
| `28–29`| `R/W3` | Tipo de acesso para BP3 (`00` = execução de instrução) |
| `30–31`| `Len3` | Tamanho para BP3 (`00` = 1 byte) |
Todos os quatro breakpoints são configurados para **execução (busca de instrução) em um único byte**, que é a condição apropriada para hooks de entrada de função.
### 5.3 O Manipulador de Exceção Vetorizado (VEH)
Quando um breakpoint é acionado, o processador gera uma exceção `#DB`. No Windows x64, a rotina de despacho do `ntdll` a encaminha pela **cadeia VEH** de todo o processo antes da cadeia do Manipulador Estruturado de Exceções (SEH) da thread. O manipulador neste projeto:
1. **Filtra** — só trata `EXCEPTION_SINGLE_STEP` (`0x80000004`); todo o resto cai em `EXCEPTION_CONTINUE_SEARCH`.
2. **Compara** — compara `ExceptionAddress` com os quatro endereços de função conhecidos.
3. **Reescreve o contexto**:
- `RIP = *(RSP)` → “retorna” ao chamador original desempilhando o endereço de retorno.
- `RSP += 8` → simula um `ret` (unwind de instrução única em x64).
- `RAX = 0` → falsifica `S_OK` / `ERROR_SUCCESS` (código de retorno bem-sucedido).
- `DR6 &= ~0xF` → limpa os bits de status do breakpoint para que a instrução possa ser executada novamente posteriormente sem estado espúrio.
4. **Modifica parâmetros de saída** (consulte [5.4](#54-per-component-interception-logic)).
5. **Retorna `EXCEPTION_CONTINUE_EXECUTION`**, o que diz ao Windows para reiniciar a thread com o contexto modificado — ou seja, a execução é retomada no *chamador*, e a função alvo real **nunca é executada**.
Cada local de interceptação é adicionalmente envolvido em uma proteção SEH `__try/__except` para que um layout de pilha malformado ou inesperado não possa derrubar o processo — uma consideração de robustez para alvos hostis/endurecidos.
### 5.4 Lógica de Interceptação por Componente
**DR0 — `AmsiScanBuffer`** (x64, primeiros 6 argumentos em `RCX, RDX, R8, R9, [RSP+0x28], [RSP+0x30]`):```
HRESULT AmsiScanBuffer(HANDLE, PVOID, ULONG, LPCWSTR, HANDLE, AMSI_RESULT* pResult);
[RSP+0x28] [RSP+0x30]
AMSI_RESULT_CLEAN (0) no 6º parâmetro (pResult, em [RSP+0x30]).S_OK (0) em RAX.DR1 — AmsiScanString (x64, primeiros 5 argumentos em RCX, RDX, R8, R9, [RSP+0x28]):```
HRESULT AmsiScanString(HANDLE, LPCWSTR, LPCWSTR, HANDLE, AMSI_RESULT* pResult);
[RSP+0x28]
- Escreve `AMSI_RESULT_CLEAN (0)` no 5º parâmetro (`pResult`, em `[RSP+0x28]`).
- Retorna `S_OK (0)` em `RAX`.
**DR2 — `WldpIsClassInApprovedList`** (primeiros 3 argumentos em `RCX, RDX, R8`):```
HRESULT WldpIsClassInApprovedList(const GUID* classId, PBOOL isApproved, DWORD evalCriteria);
RCX RDX R8
TRUE em *isApproved (via RDX).S_OK (0) em RAX.DR3 — EtwEventWrite (primeiros 4 argumentos em RCX, RDX, R8, R9):```
ULONG EtwEventWrite(HANDLE RegHandle, PCEVENT_DESCRIPTOR EventDescriptor,
ULONG UserDataCount, PEVENT_DATA_DESCRIPTOR UserData);
- Retorna `ERROR_SUCCESS (0)` em `RAX` sem tocar em nenhum parâmetro de saída.
- Consequência: os provedores de ETW **não** recebem eventos do processo com hook, suprimindo o registro de execução de scripts, carregamento de módulos, criação de processos e telemetria AMSI.
### 5.5 Gerenciamento de Threads e Persistência de Hook
Como os registradores de depuração são específicos por thread, o mecanismo deve manter continuamente os hooks:
1. **Hook imediato** — `DllMain` (ou `InstallHook`) aplica hook na thread chamadora com `SetHwbpOnThread(GetCurrentThread())`.
2. **Varredura de todas as threads** — `HookAllThreads()` enumera todas as threads do processo por meio de um snapshot `TH32CS_SNAPTHREAD`, suspende cada thread externa (`SuspendThread`), aplica os breakpoints (`SetHwbpOnThread`), a retoma e fecha o handle. A suspensão evita uma condição de corrida em que a thread falha no meio da troca de contexto entre `GetThreadContext` e `SetThreadContext`.
3. **Monitor de persistência** — `MonitorThreadProc` faz loop com `Sleep(500)` e chama `HookAllThreads()` a cada 500 ms. Isso **rearma quaisquer breakpoints que tenham sido removidos** (por exemplo, por uma chamada externa a `SetThreadContext`, uma ferramenta de depuração ou encerramento/criação de threads) e **cobre threads criadas após o hook inicial**.
4. **Sincronização** — `HookAllThreads` é executado sob uma `CRITICAL_SECTION` (`g_HookLock`) para que a thread do monitor e a rotina inicial de hook nunca intercalem trocas de contexto.
5. **Encerramento limpo** — `UninstallHook` interrompe o monitor, limpa `DR0–DR7` em todas as threads e remove o VEH.
> **Resposta explícita à pergunta sobre "persistência do hook":** sim — se os breakpoints forem removidos de qualquer thread (por outro agente, um depurador ou um EDR), a thread do monitor **os reaplica dentro de 500 ms**. Além disso, qualquer thread criada após o carregamento da DLL recebe hook dentro de um ciclo do monitor. A única maneira confiável de derrotar esse mecanismo específico é encerrar a thread do monitor *e* remover o VEH *e* limpar os registradores dentro da mesma janela — ou usar anti-debugging que negue `SetThreadContext` desde o início.
---
## API Exportada
| Exportação | Assinatura | Comportamento |
|-------------------|------------------------------------|-----------------------------------------------------------------------|
| `InstallHook` | `BOOL WINAPI InstallHook(void)` | Resolve os alvos, registra o VEH, aplica hook em todas as threads, inicia o monitor. |
| `UninstallHook` | `BOOL WINAPI UninstallHook(void)` | Interrompe o monitor, limpa os breakpoints em todas as threads, remove o VEH. |
| `GetStats` | `void WINAPI GetStats(void)` | Emite o status atual do hook (via `OutputDebugStringA`) — endereço, contadores de acertos. |
Os contadores de acertos (`g_HaveAmsiBuf`, `g_HaveAmsiStr`, `g_HaveWldp`, `g_HaveEtw`) são mantidos com `InterlockedIncrement` e são expostos na saída de depuração, o que é útil para validar que a interceptação está de fato ocorrendo em um ambiente de laboratório.
Observe que o próprio `DllMain` executa a sequência completa de hook em `DLL_PROCESS_ATTACH`, portanto as exportações são conveniências opcionais para cenários de carregamento/descarregamento em tempo de execução.
---
## Instruções de Compilação
**Requisitos:** Windows 10/11 x64, Visual Studio Build Tools (`icx.exe`), SDK.
Compile a DLL (x64):```bat
icx.exe /nologo /O3 /MT /EHsc "mora_hwbp.c" /link /DLL /out:"mora_hwbp.dll" /LIBPATH:"C:\Program Files (x86)\Intel\oneAPI\compiler\latest\lib"
Explicação das flags:
| Flag | Purpose |
|---|
O resultado é mora_hwbp.dll, que pode ser carregado em um processo alvo.
A DLL deve ser carregada em um processo que usa AMSI/WLDP/ETW — um host do PowerShell é o ambiente de teste canônico. O carregamento pode ser feito com qualquer técnica padrão de injeção de DLL. Uma demonstração mínima e autossuficiente usando injeção Refletiva/LoadLibrary pode ser realizada com um pequeno carregador em C:```bat
rem Run from an x64 developer prompt (example with a generic loader)
loader.exe mora_hwbp.dll powershell.exe
Ou, para uma verificação manual em laboratório, injete com a sua ferramenta preferida e depois valide a partir do PowerShell:```powershell
# 1. Inject mora_hwbp.dll into the PowerShell process (via external tool).
# 2. Verify that classic AMSI test vectors now return clean.
"AmsiTestSample:7e72c3ce-861b-4339-8740-0ac1484c1386"
Apenas validação em laboratório. Observe com um depurador ou
GetStats/OutputDebugStringque todos os quatro breakpoints reportam acertos à medida que o conteúdo do script é executado.
Este POC tem dupla finalidade: as mesmas características que o tornam eficaz ofensivamente são exatamente o que os defensores devem procurar.
GetThreadContext(CONTEXT_DEBUG_REGISTERS) em processos de alto valor e audite qualquer thread com DR0–DR3 diferente de zero fora dos perfis de depurador aprovados.Microsoft-Windows-Kernel-Process + Thread e alerte sobre NtGetContextThread/NtSetContextThread direcionados a processos relevantes para a segurança.EtwEventWrite, ETW do kernel, reverificação do consumidor de AMSI) em vez de apenas na integridade de .text.SetThreadContext por thread em outros processos como um sinal explícito de alta severidade.RCX/RDX/R8/R9 e depois [RSP+0x20…]). Uma variante x86 precisaria de reconstrução de parâmetros no estilo [EBP+…].Sleep(500); criação extremamente rápida de threads combinada com stripping agressivo poderia teoricamente ultrapassar o monitor por algumas centenas de milissegundos.OutputDebugStringA — o diagnóstico depende de um canal de saída de depuração; em um ambiente totalmente stripped/headless, você deve anexar um depurador ou redirecionar a saída para observação em laboratório.Este projeto é licenciado sob a Licença MIT - consulte o arquivo LICENSE para detalhes.
Este projeto é disponibilizado apenas para fins educacionais e de pesquisa defensiva. Se você é um fornecedor de segurança, blue team ou engenheiro de detecção, incentivamos você a usar o conteúdo deste repositório para melhorar sua cobertura de detecção para evasão baseada em breakpoints de hardware. Se você descobriu esta técnica sendo abusada na natureza, relate-a por meio do processo de divulgação responsável da sua organização e dos canais relevantes de fornecedor/autoridade.
Use por sua conta e risco. O uso não autorizado desta técnica pode violar leis aplicáveis.
| Registrador | Função Interceptada | Módulo | Propósito |
|---|
DR0 | AmsiScanBuffer | amsi.dll | Neutralizar a varredura de conteúdo do AMSI |
DR1 | AmsiScanString | amsi.dll | Neutralizar a varredura de strings do AMSI |
DR2 | WldpIsClassInApprovedList | wldp.dll | Forçar a aprovação de classe WLDP (Device Guard / WDAC) |
DR3 | EtwEventWrite | ntdll.dll | Suprimir o rastreamento de eventos ETW |
/O3 | Otimização máxima (código de função, não obrigatório) |
/MT | Ligação estática da CRT (sem dependência de DLL em tempo de execução) |
/EHsc | Tratamento de exceções C++/SEH (necessário para __try) |
/DLL | Produzir uma DLL com tabela de exportação |
| Artefato | Observável |
|---|
Chamadas GetThreadContext / SetThreadContext | Alternâncias de contexto de registradores de depuração em alta frequência em outros processos/threads (ETW do kernel: APIs Microsoft-Windows-Kernel-Process/Thread). |
DR0–DR3 diferentes de zero | Qualquer thread cujo CONTEXT_DEBUG_REGISTERS contenha um endereço em modo de usuário fora de fluxos de trabalho de depurador conhecidos. |
Bits de habilitação local (L0–L3) do DR7 com R/W = 00 | Breakpoints somente de execução em threads não gerenciadas por depurador — uma forte anomalia. |
Volume de EXCEPTION_SINGLE_STEP | Altas taxas de falhas #DB (0x80000004) originadas do VEH de um processo. |
| Registro de VEH de primeira chance | VEH recém-adicionado (AddVectoredExceptionHandler) pouco antes da tempestade de #DB. |
TH32CS_SNAPTHREAD + SuspendThread/ResumeThread | Padrões repetidos de enumeração de threads + suspensão (usados pelo monitor de 500 ms). |
Carga de wldp.dll/amsi.dll via LoadLibraryW quando não carregadas anteriormente | Cargas anômalas de módulos no processo alvo. |
EtwEventWrite nunca alcançado | Ausência de eventos ETW esperados (logs operacionais do PowerShell silenciosos enquanto os scripts são executados). |