Vectored Exception Handling² en Rust: cuando las excepciones se convierten en flujo de control

En Windows, las excepciones suelen entenderse como un mecanismo defensivo: algo salió mal, el sistema interrumpe la ejecución y, con suerte, el programa sobrevive. Sin embargo, esa interpretación es incompleta. En realidad, las excepciones son uno de los puntos de control más poderosos que ofrece el sistema operativo, y cuando se observan desde una perspectiva ofensiva, dejan de ser errores para convertirse en oportunidades.

Vectored Exception Handling (VEH) es una de esas APIs que existen a plena vista, documentadas por Microsoft, pero raramente explotadas fuera de contextos muy específicos como debugging o profiling. Sin embargo, investigaciones recientes —particularmente las publicadas por CrowdStrike y exploradas en profundidad por FluxSec en su artículo Vectored Exception Handling Squared in Rust— han demostrado que VEH puede utilizarse como algo radicalmente distinto: un motor alternativo de control de flujo, capaz de reemplazar por completo técnicas clásicas de hooking y parcheo de memoria.

Y lo han bautizado como Vectored Exception Handling² (VEH²). No como una API nueva, sino como un patrón ofensivo moderno. Veremos cómo las excepciones pueden utilizarse deliberadamente para controlar la ejecución, por qué este enfoque resulta tan atractivo frente a EDRs actuales, y cómo Rust, a través de unsafe y la API de Windows, permite implementar este tipo de técnicas con un nivel de control sorprendente.

Excepciones en Windows: una pausa con privilegios

Cuando ocurre una excepción en Windows —una violación de acceso, una instrucción ilegal, un breakpoint— el sistema operativo detiene inmediatamente el hilo afectado. En ese instante, Windows construye una estructura EXCEPTION_POINTERS que encapsula dos elementos fundamentales: un EXCEPTION_RECORD, que describe qué ocurrió, y un CONTEXT, que captura el estado completo de la CPU en ese momento exacto.

Este detalle suele pasarse por alto, pero es crítico: el CONTEXT contiene todos los registros del procesador, incluyendo el puntero de instrucción (RIP), el stack pointer y los registros generales. En otras palabras, una excepción no solo informa de un error; expone el cerebro del programa en pleno funcionamiento.

Lo más interesante es que Windows permite que el propio proceso inspeccione y modifique ese estado antes de decidir si la ejecución continúa, se redirige o termina. Ahí es donde entra VEH.

Vectored Exception Handling permite registrar handlers globales que se ejecutan antes que cualquier manejador tradicional de Structured Exception Handling (SEH). Estos handlers se invocan automáticamente cada vez que ocurre una excepción en el proceso, independientemente de dónde se haya originado.

Desde un handler VEH, el código tiene acceso directo al EXCEPTION_RECORD y al CONTEXT. Puede examinar el tipo de excepción, inspeccionar los registros y, lo más importante, modificar el estado de ejecución antes de devolver el control al sistema operativo.

En un uso legítimo, esto sirve para debugging avanzado o instrumentación. En un uso ofensivo, se convierte en algo mucho más potente: la capacidad de reescribir el flujo de ejecución sin modificar el código en memoria.

El salto conceptual: provocar excepciones a propósito

Aquí es donde aparece el cambio de paradigma que da origen a VEH². En lugar de tratar las excepciones como eventos accidentales, la técnica consiste en provocarlas deliberadamente. Un int3, un acceso controlado a memoria inválida o un breakpoint de hardware son suficientes para transferir la ejecución al handler VEH.

A partir de ese momento, el código visible deja de ser la fuente de verdad. La lógica real vive en los handlers, que actúan como un dispatcher invisible. El programa avanza no porque se ejecuten llamadas o saltos explícitos, sino porque cada excepción desencadena una decisión.

Esta idea —usar excepciones como primitiva de control— es el núcleo de lo que CrowdStrike describió en sus investigaciones sobre bypasses “patchless”, y lo que FluxSec materializa en su implementación en Rust bajo el nombre de Vectored Exception Handling Squared o VEH al cuadrado (VEH²).

VEH² no es una técnica puntual, sino un patrón. Consiste en utilizar VEH como mecanismo principal de control del flujo de ejecución, desplazando la lógica fuera del código tradicional y hacia handlers que solo existen durante la gestión de una excepción.

La consecuencia directa es poderosa: el código en memoria puede permanecer completamente intacto, incluso cuando su comportamiento efectivo ha sido alterado de forma radical. No hay trampolines, no hay bytes sobrescritos, no hay páginas RX modificadas. Para muchos mecanismos defensivos, simplemente no hay nada que escanear.

Este enfoque resulta especialmente atractivo frente a EDRs modernos, que han endurecido enormemente la detección de hooks clásicos basados en parcheo.

El verdadero potencial de VEH² emerge cuando se combina con breakpoints de hardware, configurados mediante los registros de depuración DR0–DR7. A diferencia de los breakpoints software, estos no requieren modificar instrucciones en memoria. El propio procesador genera una excepción cuando se cumple la condición configurada.

Cuando un breakpoint de hardware se dispara, Windows lanza una excepción EXCEPTION_SINGLE_STEP. Como cualquier otra excepción, esta pasa primero por los handlers VEH. En ese punto, el código tiene acceso total al contexto del hilo antes de que se ejecute una sola instrucción adicional.

Este patrón permite interceptar funciones críticas —como AMSI, validaciones criptográficas o rutinas de seguridad— sin tocarlas, algo que CrowdStrike identificó como una evolución clara en técnicas de evasión modernas.

VEH² paso a paso

Tradicionalmente, si quieres colocar un hardware breakpoint en Windows, el camino “oficial” es:

  1. Suspender el hilo
  2. Llamar a GetThreadContext
  3. Modificar DR0–DR7
  4. Llamar a SetThreadContext
  5. Reanudar el hilo

El problema es que este patrón es ruidoso. Los EDR modernos monitorizan llamadas a SetThreadContext porque es una API crítica usada por debuggers, injectores y malware. Si tu objetivo es AMSI o cualquier API sensible, pasar por ahí es casi una autodenuncia.

El insight clave del investigador de CrowdStrike fue darse cuenta de algo fundamental: Windows ya te da acceso al CONTEXT sin necesidad de llamar a SetThreadContext… si provocas una excepción.

Ahí nace VEH².

Paso 1: registrar un Vectored Exception Handler

Este paso es sencillo, pero conceptualmente importante. Cuando llamas a AddVectoredExceptionHandler, le estás diciendo a Windows: “Cada vez que ocurra cualquier excepción en este proceso, dame una oportunidad de verla antes que nadie.” 

Esto convierte al VEH en un hook global del motor de ejecución del proceso. Todavía no hemos hecho nada sospechoso. VEH tiene usos legítimos. Pero ya tenemos un punto de entrada privilegiado.

Paso 2: provocar una excepción controlada (int3)

Aquí ocurre el primer truco de la técnica. En lugar de esperar a que algo falle, forzamos una excepción deliberadamente usando int3. Esta instrucción:

  • Genera EXCEPTION_BREAKPOINT
  • Es extremadamente fiable
  • No depende de memoria inválida ni condiciones externas
  • Es una instrucción perfectamente válida

Cuando el int3 se ejecuta:

  1. El procesador detiene el hilo
  2. Windows construye la estructura CONTEXT
  3. Se llama al VEH

Y aquí está el punto crítico: Windows ya nos entregó el CONTEXT completo del hilo, incluidos los registros de debug. No hubo GetThreadContext. No hubo SetThreadContext. No hubo suspensión manual del hilo...

Paso 3: modificar el CONTEXT para registrar un hardware breakpoint

Este es el corazón de VEH². Dentro del VEH, cuando detectamos que la excepción proviene de nuestro propio int3, accedemos al CONTEXT y modificamos directamente:

  • DR0: dirección del breakpoint → AmsiScanBuffer
  • DR7: flags de habilitación y tipo de breakpoint

Esto es equivalente a colocar un hardware breakpoint clásico, pero con una diferencia brutal: El breakpoint se instala desde dentro del mecanismo de excepciones de Windows, no desde una API observable.

Desde el punto de vista del sistema:

  • No se modificó memoria
  • No se llamó a APIs sensibles
  • No se suspendió ningún hilo

El breakpoint simplemente “aparece” como parte del flujo normal de ejecución.

Paso 4: esperar a EXCEPTION_SINGLE_STEP

Una vez instalado el hardware breakpoint, el juego cambia. La próxima vez que cualquier código intente ejecutar AmsiScanBuffer, el procesador hará lo siguiente:

  1. Detecta acceso a la dirección monitorizada
  2. Genera EXCEPTION_SINGLE_STEP
  3. Windows vuelve a invocar el VEH

Este tipo de excepción es la señal de que el breakpoint se disparó.

Ahora el VEH sabe que:

  • AMSI está a punto de ejecutarse
  • Todavía no se ha ejecutado ninguna instrucción de la función
  • El stack y los registros están intactos

Este es el punto perfecto para intervenir.

Paso 5: falsificar el resultado de AMSI (RAX)

En x64, las funciones devuelven su valor en RAXAmsiScanBuffer devuelve un HRESULT, y si queremos simular que el contenido es limpio, basta con escribir AMSI_RESULT_CLEAN (o S_OK, dependiendo del caso) en RAX.

No se emula la función, no se llama al motor antimalware y no se analiza ningún buffer.

Desde el punto de vista del llamador, la función ya devolvió éxito.

Paso 6: saltar fuera de la función sin ejecutarla

Aquí es donde la técnica se vuelve realmente elegante. En lugar de dejar que AmsiScanBuffer continúe, el VEH simula manualmente el retorno de la función.

¿Cómo?

  1. En x64, la dirección de retorno está en la parte superior del stack ([rsp])
  2. Se lee ese valor
  3. Se copia en RIP
  4. Se incrementa rsp en 8 bytes (equivalente a un ret)

En términos de CPU, acabamos de hacer esto: “Finge que la función se ejecutó por completo y acaba de retornar.” Pero en realidad no se ejecutó ni una sola instrucción de AMSI, no se tocó su código y no se alteró su memoria

El stack queda consistente, los registros están en un estado válido y la ejecución continúa normalmente.

Implementando VEH en Rust

Rust no fue diseñado para este tipo de interacción con el sistema operativo, pero tampoco la impide. A través de unsafe, el lenguaje permite acceder directamente a la API de Windows y manipular estructuras internas sin restricciones artificiales.

Un handler VEH en Rust debe declararse con la convención extern "system" y operar sobre punteros crudos:

unsafe extern "system" fn veh_handler(
    exception_info: *mut EXCEPTION_POINTERS,
) -> i32 {
    let record = (*exception_info).ExceptionRecord;
    let context = (*exception_info).ContextRecord;

    if (*record).ExceptionCode == EXCEPTION_BREAKPOINT {
        (*context).Rip += 1;
        return EXCEPTION_CONTINUE_EXECUTION;
    }

    EXCEPTION_CONTINUE_SEARCH
}

Aquí no hay red de seguridad. Un ajuste incorrecto del RIP basta para corromper el proceso. Rust deja claro, mediante unsafe, que este tipo de poder exige conocimiento profundo del sistema.

Registrar el handler es trivial:

unsafe {
    AddVectoredExceptionHandler(1, Some(veh_handler));
}

Desde ese momento, cualquier excepción en el proceso pasa por nuestro código.

La otra mitad del patrón consiste en provocar la excepción. Una simple instrucción int3 es suficiente:

unsafe {
    core::arch::asm!("int3");
}

Cada int3 es, en la práctica, una llamada indirecta al handler VEH. El flujo del programa avanza de excepción en excepción, mientras la lógica real se ejecuta fuera del camino visible. Para el análisis estático, el flujo parece fragmentado. Para la detección basada en integridad de memoria, no hay indicadores claros.

Tenéis un PoC completa en Rust en el repo: https://github.com/0xflux/Vectored-Exception-Handling-Squared. Si funciona no debería imprimir  println!("If this worked I should not print!!!! :("); tal que así:

Implicaciones ofensivas y defensivas

VEH es VEH² porque se está usando dos veces conceptualmente:

  1. Primero, como mecanismo para inyectar estado (instalar breakpoints)
  2. Segundo, como mecanismo para controlar ejecución (hook real)

VEH deja de ser un “handler” y se convierte en:

  • Instalador de hooks
  • Dispatcher
  • Motor de control de flujo

Todo basado en excepciones.

VEH² no es una vulnerabilidad ni un exploit. Funciona porque Windows fue diseñado para permitir que los procesos manejen sus propias excepciones. Precisamente por eso resulta incómodo desde el punto de vista defensivo.

Detectar este tipo de técnicas requiere observar comportamiento, no bytes: handlers VEH inusuales, uso anómalo de breakpoints de hardware, manipulación frecuente del contexto de ejecución. CrowdStrike y otros vendors han comenzado a instrumentar estas superficies, pero no todos los entornos están preparados.

Para el atacante, VEH² implica mayor complejidad y fragilidad. Para el defensor, es una señal clara de que el control de flujo es una superficie de ataque tan relevante como la memoria.

Como mostraron las investigaciones de CrowdStrike y el trabajo práctico de FluxSec, el futuro de la evasión no pasa por romper el sistema, sino por entenderlo mejor de lo que fue pensado para ser entendido.

En un sistema construido sobre excepciones, a veces la forma más efectiva de controlar la ejecución es provocar que todo falle… exactamente como estaba previsto.

Comentarios