
Motor de instrumentación de telemetría y hooking en modo usuario sin parches basado en puntos de interrupción de hardware (DR0-DR7) (PoC de AMSI, WLDP y ETW).
Prueba de concepto (POC) de investigación en seguridad que demuestra el enganche de funciones basado en puntos de interrupción de hardware (registros de depuración de la CPU) como alternativa al parcheo tradicional de código en memoria.
Propósito y alcance
Este repositorio se publica estrictamente para investigación defensiva de seguridad, educación en red-team/purple-team, ingeniería de detección y estudio académico de los internals de Windows. Demuestra cómo un atacante podría abusar de los registros de depuración del procesador para neutralizar la telemetría de seguridad en modo usuario — y, lo que es igualmente importante, qué deben monitorear los defensores para detectar dichas técnicas. El autor no es responsable de ningún uso indebido de este código. El uso de esta técnica contra sistemas sin autorización explícita es ilegal y viola las leyes pertinentes de fraude y abuso informático en la mayoría de las jurisdicciones. No implemente esto en ningún entorno que no sea de su propiedad o para el que no tenga permiso explícito por escrito para probar.
mora_hwbp.c implementa una DLL que, una vez cargada/inyectada en un proceso objetivo (por ejemplo, un host de PowerShell), engancha cuatro funciones en modo usuario exclusivamente mediante puntos de interrupción de hardware de la CPU almacenados en los registros de depuración arquitectónicos (DR0–DR7) de cada hilo del proceso:
Un manejador de excepciones vectorizado (VEH) por proceso recibe las fallas EXCEPTION_SINGLE_STEP (0x80000004) generadas por los registros de depuración, simula la ruta de retorno exitosa de la función original reescribiendo el contexto de la excepción y reanuda la ejecución — todo sin modificar ni un solo byte de memoria ejecutable.
Esto hace que la técnica sea particularmente interesante tanto desde una perspectiva ofensiva como defensiva:
.text modificadas (enganche inline clásico, parcheo de EAT/IAT o stubbing de Etwp*).GetThreadContext/SetThreadContext) que pueden utilizarse para la detección.Los enfoques tradicionales de enganche en modo usuario — detours inline (sobrescrituras de 5–14 bytes), enganche de la tabla de direcciones de importación (IAT) y enganche de la tabla de direcciones de exportación (EAT) — comparten una debilidad común: modifican memoria que los escáneres de integridad y ETW pueden observar.
Los productos AV/EDR modernos implementan:
pageguard/guard-page, transiciones de VirtualProtect a PAGE_EXECUTE_READWRITE y discrepancias de hash de sección.Los puntos de interrupción de hardware evitan todo esto:
.text.SetThreadContext, que no dispara las señales clásicas de "memoria modificada" utilizadas por los escáneres de integridad.Este POC explora la eficacia y detectabilidad de esta técnica contra AMSI (Antimalware Scan Interface), WLDP (Windows Lockdown Policy) y ETW (Event Tracing for Windows) — los tres primitivos de seguridad en modo usuario más utilizados en la pila de seguridad moderna de Windows.
AMSI es el punto de integración de la plataforma Windows que permite a las aplicaciones (PowerShell, Office, VBScript, hosts de .NET, etc.) solicitar el escaneo de contenido a los proveedores antimalware registrados. Dos puntos de entrada son de interés principal:
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)Al forzar el AMSI_RESULT devuelto a AMSI_RESULT_CLEAN (0), el motor de scripts cree que el contenido fue inspeccionado y resultó benigno, por lo que la ejecución continúa sin interrupciones.
WLDP implementa la evaluación de políticas para Windows Defender Application Control (WDAC / Device Guard). WldpIsClassInApprovedList responde si una clase COM determinada (identificada por GUID) está permitida bajo la política actual. AMSI consulta internamente a WLDP para decidir si ciertas clases de script/contenido son "de confianza" (están en la lista aprobada). Si la función informa que la clase está aprobada, AMSI puede omitir un escrutinio adicional para ese tipo de contenido.
La DLL establece el parámetro de salida isApproved (RDX) en TRUE y devuelve S_OK, haciendo que la clase evaluada parezca confiable.
EtwEventWrite en ntdll.dll es el sumidero de modo usuario central para prácticamente toda la emisión de eventos ETW en el sistema. Suprimirlo tiene efectos secundarios amplios relevantes para el monitoreo de seguridad:
Microsoft-Windows-DotNETRuntime)La DLL simplemente devuelve ERROR_SUCCESS (0) sin ejecutar la función 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) └──────┘ │ ││ │ └──────────────────────────┘ └──────────────────────────────────┘│ └──────────────────────────────────────────────────────────────────────────┘
**Flujo de alto nivel:**
1. La DLL se carga en el proceso de destino (mediante cualquier técnica de inyección — consulte [Uso](#injection--usage-example)).
2. En `DLL_PROCESS_ATTACH` (o mediante la exportación `InstallHook`), las exportaciones de destino se resuelven con `GetProcAddress` (opcionalmente forzando la carga de módulos mediante `LoadLibraryW`).
3. Se registra un **controlador de excepciones vectorizado (VEH)** como el **primer** controlador del proceso (`AddVectoredExceptionHandler(1, ...)`).
4. El **subproceso actual** se engancha inmediatamente; luego, **todos los subprocesos existentes** del proceso se enumeran mediante `CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)` y se enganchan.
5. Un **subproceso de monitorización** se activa cada 500 ms y vuelve a aplicar los puntos de interrupción a todos los subprocesos — incluidos los **subprocesos recién creados** — lo que garantiza la persistencia del enganche incluso si un subproceso se crea después del enganche o si los puntos de interrupción se eliminan externamente.
6. Cuando se llama a cualquier función enganchada en cualquier subproceso, la CPU genera una excepción de paso único `#DB`; Windows la envía al VEH, que simula un retorno benigno y continúa la ejecución.
### Salida de depuración de ejemplo

*Figura 1 — Salida de diagnóstico de ejemplo capturada con Sysinternals DebugView. Cada línea informa de la dirección de destino resuelta y del contador de aciertos en vivo para su registro de depuración correspondiente.*
---
## Análisis técnico en profundidad
### 5.1 Puntos de interrupción de hardware en x64
En x86/x64, cada CPU proporciona **cuatro registros de direcciones de depuración de hardware** (`DR0`–`DR3`) y un registro de control (`DR7`). Cualquier subproceso que se ejecute con un punto de interrupción distinto de cero en `DR0`–`DR3` generará una falta cada vez que el puntero de instrucción alcance esa dirección (o cuando un acceso a datos coincida con las condiciones configuradas). El registro de estado `DR6` registra qué punto de interrupción se disparó.
Los puntos de interrupción de hardware son **sensibles al contexto**: se almacenan en la estructura `CONTEXT` del subproceso y se aplican solo al subproceso en el que se establecen. Por eso una implementación robusta debe establecer puntos de interrupción en **todos los subprocesos** del proceso (y volver a aplicarlos continuamente para los subprocesos nuevos).
### 5.2 Disposición de los registros de depuración (DR0–DR7)
`DR7` es un campo de bits que controla la habilitación y el comportamiento de los puntos de interrupción:
| Bits | Field | Meaning |
|--------|--------|-----------------------------------------------------|
| `0` | `L0` | Habilitación local para el punto de interrupción 0 (DR0) |
| `2` | `L1` | Habilitación local para el punto de interrupción 1 (DR1) |
| `4` | `L2` | Habilitación local para el punto de interrupción 2 (DR2) |
| `6` | `L3` | Habilitación local para el punto de interrupción 3 (DR3) |
| `8` | `LE` | Habilitación local heredada (mantenida por compatibilidad) |
| `9` | `GE` | Habilitación global heredada (mantenida por compatibilidad) |
| `16–17`| `R/W0` | Tipo de acceso para BP0 (`00` = ejecución de instrucción) |
| `18–19`| `Len0` | Longitud para BP0 (`00` = 1 byte) |
| `20–21`| `R/W1` | Tipo de acceso para BP1 (`00` = ejecución de instrucción) |
| `22–23`| `Len1` | Longitud para BP1 (`00` = 1 byte) |
| `24–25`| `R/W2` | Tipo de acceso para BP2 (`00` = ejecución de instrucción) |
| `26–27`| `Len2` | Longitud para BP2 (`00` = 1 byte) |
| `28–29`| `R/W3` | Tipo de acceso para BP3 (`00` = ejecución de instrucción) |
| `30–31`| `Len3` | Longitud para BP3 (`00` = 1 byte) |
Los cuatro puntos de interrupción están configurados para **ejecución (captura de instrucción) en un solo byte**, que es la condición adecuada para los enganches de entrada de función.
### 5.3 El controlador de excepciones vectorizado (VEH)
Cuando se dispara un punto de interrupción, el procesador genera una excepción `#DB`. En Windows x64, la rutina de despacho de `ntdll` la enruta a través de la **cadena VEH** de todo el proceso antes de la cadena del controlador de excepciones estructurado (SEH) del subproceso. El controlador de este proyecto:
1. **Filtra** — solo gestiona `EXCEPTION_SINGLE_STEP` (`0x80000004`); todo lo demás pasa a `EXCEPTION_CONTINUE_SEARCH`.
2. **Compara** — compara `ExceptionAddress` con las cuatro direcciones de función conocidas.
3. **Reescribe el contexto**:
- `RIP = *(RSP)` → «retornar» al llamador original extrayendo la dirección de retorno de la pila.
- `RSP += 8` → simular un `ret` (desenrollado de una sola instrucción en x64).
- `RAX = 0` → simular `S_OK` / `ERROR_SUCCESS` (código de retorno correcto).
- `DR6 &= ~0xF` → borrar los bits de estado del punto de interrupción para que la instrucción pueda volver a ejecutarse más tarde sin estado espurio.
4. **Modifica los parámetros de salida** (consulte [5.4](#54-per-component-interception-logic)).
5. **Devuelve `EXCEPTION_CONTINUE_EXECUTION`**, lo que indica a Windows que reinicie el subproceso con el contexto modificado — es decir, la ejecución se reanuda en el *llamador* y la función de destino real **nunca se ejecuta**.
Cada punto de interceptación se envuelve además en una protección SEH `__try/__except` para que un diseño de pila malformado o inesperado no pueda provocar un fallo del proceso — una consideración de robustez para objetivos hostiles/endurecidos.
### 5.4 Lógica de interceptación por componente
**DR0 — `AmsiScanBuffer`** (x64, primeros 6 argumentos en `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) en el sexto parámetro (pResult, en [RSP+0x30]).S_OK (0) en RAX.DR1 — AmsiScanString (x64, primeros 5 argumentos en RCX, RDX, R8, R9, [RSP+0x28]):```
HRESULT AmsiScanString(HANDLE, LPCWSTR, LPCWSTR, HANDLE, AMSI_RESULT* pResult);
[RSP+0x28]
- Escribe `AMSI_RESULT_CLEAN (0)` en el quinto parámetro (`pResult`, en `[RSP+0x28]`).
- Devuelve `S_OK (0)` en `RAX`.
**DR2 — `WldpIsClassInApprovedList`** (primeros 3 argumentos en `RCX, RDX, R8`):```
HRESULT WldpIsClassInApprovedList(const GUID* classId, PBOOL isApproved, DWORD evalCriteria);
RCX RDX R8
TRUE en *isApproved (a través de RDX).S_OK (0) en RAX.DR3 — EtwEventWrite (primeros 4 argumentos en RCX, RDX, R8, R9):```
ULONG EtwEventWrite(HANDLE RegHandle, PCEVENT_DESCRIPTOR EventDescriptor,
ULONG UserDataCount, PEVENT_DATA_DESCRIPTOR UserData);
- Devuelve `ERROR_SUCCESS (0)` en `RAX` sin tocar ningún parámetro de salida.
- Consecuencia: los proveedores de ETW **no** reciben **ningún** evento del proceso interceptado, suprimiendo el registro de ejecución de scripts, cargas de módulos, creación de procesos y telemetría de AMSI.
### 5.5 Gestión de hilos y persistencia de hooks
Debido a que los registros de depuración son por hilo, el motor debe mantener los hooks de forma continua:
1. **Hook inmediato** — `DllMain` (o `InstallHook`) engancha el hilo llamante con `SetHwbpOnThread(GetCurrentThread())`.
2. **Barrido de todos los hilos** — `HookAllThreads()` enumera cada hilo del proceso mediante una instantánea `TH32CS_SNAPTHREAD`, suspende cada hilo externo (`SuspendThread`), aplica los puntos de ruptura (`SetHwbpOnThread`), lo reanuda y cierra el identificador. La suspensión evita una condición de carrera en la que el hilo falla a mitad del cambio de contexto entre `GetThreadContext` y `SetThreadContext`.
3. **Monitor de persistencia** — `MonitorThreadProc` itera con `Sleep(500)` y llama a `HookAllThreads()` cada 500 ms. Esto **vuelve a armar cualquier punto de ruptura que haya sido eliminado** (por ejemplo, mediante una llamada externa a `SetThreadContext`, una herramienta de depuración o la creación/destrucción de hilos) y **cubre los hilos creados después del hook inicial**.
4. **Sincronización** — `HookAllThreads` se ejecuta bajo una `CRITICAL_SECTION` (`g_HookLock`) para que el hilo monitor y la rutina de hook inicial nunca intercalen cambios de contexto.
5. **Limpieza final** — `UninstallHook` detiene el monitor, limpia `DR0–DR7` en cada hilo y anula el registro del VEH.
> **Respuesta explícita a la pregunta sobre la "persistencia del hook":** sí — si los puntos de ruptura se eliminan de cualquier hilo (por otro agente, un depurador o un EDR), el hilo monitor **los vuelve a aplicar en un plazo de 500 ms**. Además, cualquier hilo creado después de la carga de la DLL queda enganchado dentro de un ciclo del monitor. La única forma fiable de derrotar a este motor específico es terminar el hilo monitor *y* limpiar el VEH *y* eliminar los registros dentro de la misma ventana — o usar anti-depuración que deniegue `SetThreadContext` desde el principio.
---
## API exportada
| Exportación | Firma | Comportamiento |
|------------------|------------------------------------|--------------------------------------------------------------------------|
| `InstallHook` | `BOOL WINAPI InstallHook(void)` | Resuelve objetivos, registra el VEH, engancha todos los hilos, inicia el monitor. |
| `UninstallHook` | `BOOL WINAPI UninstallHook(void)` | Detiene el monitor, limpia los puntos de ruptura en todos los hilos, elimina el VEH. |
| `GetStats` | `void WINAPI GetStats(void)` | Emite el estado actual del hook (vía `OutputDebugStringA`) — dirección, contadores de aciertos. |
Los contadores de aciertos (`g_HaveAmsiBuf`, `g_HaveAmsiStr`, `g_HaveWldp`, `g_HaveEtw`) se mantienen con `InterlockedIncrement` y se exponen en la salida de depuración, lo cual es útil para validar que la intercepción realmente está ocurriendo en un entorno de laboratorio.
Nótese que `DllMain` realiza la secuencia completa de hook en `DLL_PROCESS_ATTACH`, por lo que las exportaciones son conveniencias opcionales para escenarios de carga/descarga en tiempo de ejecución.
---
## Instrucciones de compilación
**Requisitos:** Windows 10/11 x64, Visual Studio Build Tools (`icx.exe`), SDK.
Compilar la 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"
Explicación de las opciones:
| Opción | Propósito |
|---|
El resultado es mora_hwbp.dll, que puede cargarse en un proceso de destino.
La DLL debe cargarse en un proceso que use AMSI/WLDP/ETW — un host de PowerShell es el banco de pruebas canónico. La carga se puede realizar con cualquier técnica estándar de inyección de DLL. Se puede realizar una demostración mínima y autocontenida usando inyección reflectiva/LoadLibrary con un pequeño cargador en C:```bat
rem Run from an x64 developer prompt (example with a generic loader)
loader.exe mora_hwbp.dll powershell.exe
O, para una comprobación manual de laboratorio, inyecta con tu herramienta preferida y luego valida desde 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"
Solo validación en laboratorio. Observa con un depurador o
GetStats/OutputDebugStringque los cuatro puntos de interrupción informan de aciertos mientras se ejecuta el contenido del script.
Esta POC tiene un doble propósito: las mismas características que la hacen eficaz ofensivamente son precisamente las que los defensores deberían buscar.
GetThreadContext(CONTEXT_DEBUG_REGISTERS) en procesos de alto valor y audita cualquier hilo con DR0–DR3 distinto de cero fuera de los perfiles de depurador aprobados.Microsoft-Windows-Kernel-Process + Thread y alerta sobre NtGetContextThread/NtSetContextThread dirigidos a procesos relevantes para la seguridad.EtwEventWrite, ETW del kernel, re-comprobación del consumidor AMSI) en lugar de solo la integridad de .text.SetThreadContext por hilo hacia otros procesos como una señal explícita de alta severidad.RCX/RDX/R8/R9 y luego [RSP+0x20…]). Una variante x86 necesitaría reconstrucción de parámetros al estilo [EBP+…].Sleep(500); la creación extremadamente rápida de hilos combinada con una eliminación agresiva podría teóricamente adelantarse al monitor durante unos cientos de milisegundos.OutputDebugStringA — los diagnósticos dependen de un canal de salida de depuración; en un entorno completamente despojado/sin interfaz gráfica (headless) deberías adjuntar un depurador o redirigir la salida para observación en laboratorio.Este proyecto está licenciado bajo la Licencia MIT; consulta el archivo LICENSE para más detalles.
Este proyecto se publica solo con fines educativos y de investigación defensiva. Si eres un proveedor de seguridad, equipo azul o ingeniero de detección, te animamos a usar el contenido de este repositorio para mejorar tu cobertura de detección de la evasión basada en puntos de interrupción por hardware. Si descubres que esta técnica se está utilizando de forma malintencionada en entornos reales, repórtalo a través del proceso de divulgación responsable de tu organización y los canales correspondientes del proveedor/autoridades.
Úsalo bajo tu propio riesgo. El uso no autorizado de esta técnica puede violar las leyes aplicables.
| Registro | Función enganchada | Módulo | Propósito |
|---|
DR0 | AmsiScanBuffer | amsi.dll | Neutralizar el escaneo de contenido de AMSI |
DR1 | AmsiScanString | amsi.dll | Neutralizar el escaneo de cadenas de AMSI |
DR2 | WldpIsClassInApprovedList | wldp.dll | Forzar la aprobación de clase WLDP (Device Guard / WDAC) |
DR3 | EtwEventWrite | ntdll.dll | Suprimir el seguimiento de eventos ETW |
/O3 | Optimización máxima (código de función, no requerido) |
/MT | Enlace CRT estático (sin dependencia de DLL en tiempo de ejecución) |
/EHsc | Manejo de excepciones C++/SEH (necesario para __try) |
/DLL | Produce una DLL con una tabla de exportaciones |
| Artefacto | Observable |
|---|
Llamadas a GetThreadContext / SetThreadContext | Cambios de contexto de registros de depuración de alta frecuencia en otros procesos/hilos (ETW del kernel: APIs Microsoft-Windows-Kernel-Process/Thread). |
DR0–DR3 distintos de cero | Cualquier hilo cuyo CONTEXT_DEBUG_REGISTERS contenga una dirección de modo usuario fuera de los flujos de trabajo de depuradores conocidos. |
Bits de habilitación locales de DR7 (L0–L3) con R/W = 00 | Puntos de interrupción de solo ejecución en hilos no gestionados por el depurador: una fuerte anomalía. |
Volumen de EXCEPTION_SINGLE_STEP | Altas tasas de fallos #DB (0x80000004) originados desde el VEH de un proceso. |
| Registro de VEH de primera oportunidad | VEH recién agregado (AddVectoredExceptionHandler) poco antes de la tormenta de #DB. |
TH32CS_SNAPTHREAD + SuspendThread/ResumeThread | Patrones repetidos de enumeración de hilos y suspensión (utilizados por el monitor de 500 ms). |
Carga de wldp.dll/amsi.dll mediante LoadLibraryW cuando no estaban cargadas previamente | Cargas anómalas de módulos en el proceso de destino. |
EtwEventWrite nunca alcanzado | Ausencia de eventos ETW esperados (registros operativos de PowerShell en silencio mientras se ejecutan los scripts). |