
TL;DR: Este PoC demuestra una cascada de Manejo Estructurado de Excepciones (SEH) de 3 etapas causada por ejecución desalineada deliberada dentro de blobs de bytes crudos cuya primera instrucción válida es un
div regque provoca una falta con divisor cero. Cada etapa de excepción instala el siguiente manejador SEH, provoca una nueva división por cero desalineada y ejecuta una carga útil benigna observable antes de restaurar la cadena SEH original y terminar limpiamente.
MOEW (Cascada de Excepciones por Opcode Desalineado, por sus siglas en inglés Misaligned Opcode Exception Waterfall) es una muestra de investigación defensiva que demuestra ejecución multietapa controlada y dirigida por excepciones en Windows x86/Wow64. Demuestra:
fs:[0]blob1, blob2, blob3)KiUserExceptionDispatcherRtlDispatchExceptionTodas las cargas útiles son benignas:
%TEMP%El PoC está intencionalmente desactivado. No se cifra, modifica ni destruye ningún dato.
div desalineado en offsets controlados (ECX/EDX/EBX = 0)ORIGINAL_SEH)El PoC utiliza características exclusivas de nightly:
#![feature(asm_experimental_arch)]
#![feature(naked_functions)]
Instala los componentes necesarios:
rustup toolchain install nightly
rustup target add i686-pc-windows-msvc --toolchain nightly
Dado que el PoC instala manejadores SEH personalizados que no están presentes en la tabla SAFESEH, es necesario indicar al enlazador que deshabilite la validación SAFESEH.
Crea .cargo/config.toml:
[target.i686-pc-windows-msvc]
rustflags = [
"-C", "link-arg=/SAFESEH:NO",
]
Compila el binario:
cargo +nightly build --target i686-pc-windows-msvc --release
La salida se encuentra en:
target\i686-pc-windows-msvc\release\seh_waterfall.exe
Ejecuta:
seh_waterfall.exe
Flujo de control esperado:
Stage 0 → misaligned blob1 → Stage 1 handler
Stage 1 → misaligned blob2 → Stage 2 handler
Stage 2 → misaligned blob3 → Final handler
Final → restore SEH → exit
Artefactos visibles:
%TEMP%\moew_stage2.txt (Etapa 2)En Windows de 32 bits, los registros SEH forman una lista enlazada almacenada en fs:[0]:
#[repr(C)]
struct SehRec {
next: *mut SehRec,
handler: usize,
}
Cada manejador utiliza la firma SEH estándar:
extern "system" fn handler(
record: *mut u8,
frame: *mut u8,
context: *mut u8,
dispatcher: *mut u8,
) -> i32
static STAGE_COUNTER: AtomicU32 = AtomicU32::new(0);
static mut ORIGINAL_SEH: *mut SehRec = std::ptr::null_mut();
Se utiliza para:
fs:[0] como ORIGINAL_SEH.fs:[0] con este nuevo registro.blob1 + 5, que se decodifica como div ecx (después de establecer ECX = 0).notepad.exe.blob2 + 3 → div edx (con EDX = 0).%TEMP%\moew_stage2.txt.blob3 + 3 → div ebx (con EBX = 0).calc.exe.ORIGINAL_SEH en fs:[0].process::exit(0).blob1#[unsafe(naked)]
pub extern "C" fn blob1() {
naked_asm! {
".byte 0xB8, 0x10, 0x00, 0x00, 0x00", // mov eax, 0x10
".byte 0xF7, 0xF1", // div ecx
".byte 0xC3", // ret
".byte 0x90, 0x90, 0x90", // nop padding
}
}
mov eax, 0x10; div ecx; ret+5: div ecx (con ECX = 0 → #DE)blob2#[unsafe(naked)]
pub extern "C" fn blob2() {
naked_asm! {
".byte 0x55", // push ebp
".byte 0x8B, 0xEC", // mov ebp, esp
".byte 0xF7, 0xF2", // div edx
".byte 0xC3", // ret
".byte 0x90, 0x90, 0x90", // nop padding
}
}
push ebp; mov ebp, esp; div edx; ret+3: div edx (con EDX = 0 → #DE)blob3#[unsafe(naked)]
pub extern "C" fn blob3() {
naked_asm! {
".byte 0x53", // push ebx
".byte 0x8B, 0xD8", // mov ebx, eax
".byte 0xF7, 0xF3", // div ebx
".byte 0xC3", // ret
".byte 0x90, 0x90, 0x90", // nop padding
}
}
push ebx; mov ebx, eax; div ebx; ret+3: div ebx (con EBX = 0 → #DE)Cada etapa con fallo vuelve a entrar en la canalización de excepciones en modo usuario de Windows:
KiUserExceptionDispatcher
→ RtlDispatchException
→ SEH chain walk (fs:[0])
→ MOEW handler
Vista típica en el depurador:
seh_waterfall!blobX+offset
ntdll!KiUserExceptionDispatcher
ntdll!RtlDispatchException
seh_waterfall!stageN_handler
La cascada está impulsada por completo por fallos de hardware reales y despacho de SEH; no se utilizan excepciones sintéticas ni falsas.
El archivo escrito en la Etapa 2 tiene este aspecto:
MOEW Stage 2 Marker
-------------------
This file was written by the Stage 2 SEH handler
as a benign demonstration payload.
Su presencia en %TEMP% sirve como prueba simple y observable de que la Etapa 2 se ejecutó a través de la cadena SEH.
Las posibles extensiones incluyen:
La implementación completa del PoC está disponible en este repositorio (consulta src/main.rs).
Este proyecto está destinado a la investigación defensiva y la educación. Copyright <2025>
Por la presente se concede permiso, de forma gratuita, a cualquier persona que obtenga una copia de este software y de los archivos de documentación asociados (el «Software»), para tratar el Software sin restricción, incluidos, sin limitación, los derechos de usar, copiar, modificar, fusionar, publicar, distribuir, sublicenciar y/o vender copias del Software, y de permitir a las personas a quienes se les proporcione el Software hacer lo mismo, sujeto a las siguientes condiciones:
El aviso de copyright anterior y este aviso de permiso deberán incluirse en todas las copias o partes sustanciales del Software.
EL SOFTWARE SE PROPORCIONA «TAL CUAL», SIN GARANTÍA DE NINGÚN TIPO, EXPRESA O IMPLÍCITA, INCLUIDAS, ENTRE OTRAS, LAS GARANTÍAS DE COMERCIABILIDAD, IDONEIDAD PARA UN FIN PARTICULAR Y NO INFRACCIÓN. EN NINGÚN CASO LOS AUTORES O TITULARES DEL COPYRIGHT SERÁN RESPONSABLES DE CUALQUIER RECLAMO, DAÑO U OTRA RESPONSABILIDAD, YA SEA EN UNA ACCIÓN CONTRACTUAL, EXTRACONTRACTUAL O DE OTRO TIPO, QUE SURJA DE, O EN RELACIÓN CON, EL SOFTWARE O EL USO U OTROS TRATOS EN EL SOFTWARE.
La muestra real de MOEW que inspiró este PoC no restauraba fs:[0] al final de la ejecución.
En su lugar, su etapa final:
fs:[0]) con NULL o un puntero a memoria no válida.RtlDispatchException encontrara un puntero de manejador no válido.unknown.unknown.Application Error en el Visor de eventos con offsets de fallo sin significado.KiUserExceptionDispatcher,Este paso destructivo de corrupción del SEH cumple el propósito antiforense principal de la muestra: borrar la cadena causal y producir un bloqueo terminal no atribuible.
A diferencia de la muestra real, este PoC:
Captura la cabecera SEH original durante la Etapa 0:
asm!("mov {old}, fs:[0]", old = out(reg) old_head);
ORIGINAL_SEH = old_head;
Restaura la cabecera SEH original en el manejador final:
asm!("mov fs:[0], {p}", p = in(reg) ORIGINAL_SEH);
Termina limpiamente mediante process::exit(0) en lugar de provocar un fallo no manejado.
Como resultado, el PoC:
Application Error 1000 en el Visor de eventos,Sin embargo—y esto es crucial—el PoC no elimina el comportamiento de degradación de telemetría impulsada por excepciones de MOEW.
El PoC definitivamente sí degrada la telemetría de la misma manera fundamental:
Provoca deliberadamente múltiples fallos de hardware desalineados.
Produce múltiples excepciones de primera oportunidad en rápida sucesión.
Obliga a Windows a ejecutar repetidamente:
KiUserExceptionDispatcher
RtlDispatchException
→ custom handler
→ misaligned blob
→ hardware fault
Genera pilas de llamadas no lineales y dominadas por excepciones.
Distorsiona la reconstrucción del flujo de control en depuradores y EDR al enrutar la ejecución a través de:
Por lo tanto, el PoC reproduce fielmente:
…mientras se evita el bloqueo final destructivo.
Esto hace que el PoC sea ideal para instrumentación e investigación sin invocar la carga útil antiforense completa.
Debido a que el PoC conserva el comportamiento general de MOEW menos el bloqueo por corrupción del SEH, es:
El PoC modela la cascada de excepciones (la esencia de MOEW) a la vez que elimina la firma destructiva final. Demuestra que la degradación de la telemetría surge no solo de la corrupción del SEH, sino del propio modelo de flujo de control impulsado por excepciones.
| Comportamiento | Muestra Real de MOEW | Implementación del PoC |
|---|---|---|
| Cascada de opcodes desalineados | ✔ | ✔ |
| Máquina de estados recursiva impulsada por SEH | ✔ | ✔ |
| Pilas de llamadas dominadas por el despacho de excepciones | ✔ | ✔ |
| Degradación de telemetría / trazas de pila | ✔ | ✔ |
| Corrupción intencional del SEH | ✔ | ❌ |
| Punteros SEH colgantes o no válidos | ✔ | ❌ |
| Excepción final no manejada | ✔ | ❌ |
| Bloqueo WER de «módulo desconocido» | ✔ | ❌ |
Restauración limpia de fs:[0] | ❌ | ✔ |
| Terminación limpia | ❌ | ✔ |
Esta sección formaliza las diferencias de comportamiento manteniendo claro el propósito del PoC: demostrar la cascada de MOEW de forma segura y apta para la investigación, sin el bloqueo final antiforense.