
Resumo: Esta PoC demonstra uma cascata de Tratamento de Exceção Estruturada (SEH) de 3 estágios causada por execução desalinhada deliberada em blocos de bytes brutos cuja primeira instrução válida é um
div regcom divisor zero.
Cada estágio de exceção instala o próximo manipulador SEH, dispara uma nova divisão por zero desalinhada e executa uma carga benigna observável antes de restaurar a cadeia SEH original e terminar de forma limpa.
MOEW (Cachoeira de Exceção por Opcode Desalinhado) é uma amostra de pesquisa defensiva que demonstra execução de múltiplos estágios controlada por exceções no Windows x86/Wow64. Ela demonstra:
fs:[0]blob1, blob2, blob3)KiUserExceptionDispatcherRtlDispatchExceptionTodas as cargas são benignas:
%TEMP%A PoC é intencionalmente inofensiva. Nenhum dado é criptografado, modificado ou destruído.
div desalinhado em offsets controlados (ECX/EDX/EBX = 0)ORIGINAL_SEH)A PoC usa recursos exclusivos do nightly:
#![feature(asm_experimental_arch)]
#![feature(naked_functions)]
Instale os componentes necessários:
rustup toolchain install nightly
rustup target add i686-pc-windows-msvc --toolchain nightly
Como a PoC instala manipuladores SEH personalizados que não estão presentes na tabela SAFESEH, o vinculador deve ser instruído a desabilitar a validação SAFESEH.
Crie .cargo/config.toml:
[target.i686-pc-windows-msvc]
rustflags = [
"-C", "link-arg=/SAFESEH:NO",
]
Compile o binário:
cargo +nightly build --target i686-pc-windows-msvc --release
A saída está localizada em:
target\i686-pc-windows-msvc\release\seh_waterfall.exe
Execute:
seh_waterfall.exe
Fluxo de controle esperado:
Estágio 0 → blob1 desalinhado → Manipulador Estágio 1
Estágio 1 → blob2 desalinhado → Manipulador Estágio 2
Estágio 2 → blob3 desalinhado → Manipulador Final
Final → restaurar SEH → sair
Artefatos visíveis:
%TEMP%\moew_stage2.txt é criado (Estágio 2)No Windows 32 bits, os registros SEH formam uma lista encadeada armazenada em fs:[0]:
#[repr(C)]
struct SehRec {
next: *mut SehRec,
handler: usize,
}
Cada manipulador usa a assinatura padrão SEH:
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();
Usado para:
fs:[0] como ORIGINAL_SEH.fs:[0] com este novo registro.blob1 + 5, que decodifica como div ecx (após definir ECX = 0).notepad.exe.blob2 + 3 → div edx (com EDX = 0).%TEMP%\moew_stage2.txt.blob3 + 3 → div ebx (com EBX = 0).calc.exe.ORIGINAL_SEH em 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 (com 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 (com 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 (com EBX = 0 → #DE)Cada estágio com falha reentra no pipeline de exceção do modo usuário do Windows:
KiUserExceptionDispatcher
→ RtlDispatchException
→ Caminhada da cadeia SEH (fs:[0])
→ Manipulador MOEW
Visão típica no depurador:
seh_waterfall!blobX+offset
ntdll!KiUserExceptionDispatcher
ntdll!RtlDispatchException
seh_waterfall!stageN_handler
A cachoeira é inteiramente conduzida por falhas de hardware genuínas e despacho SEH; nenhuma exceção sintética ou falsa é usada.
O arquivo escrito no Estágio 2 se parece com:
MOEW Stage 2 Marker
-------------------
Este arquivo foi escrito pelo manipulador SEH do Estágio 2
como uma carga de demonstração benigna.
Sua presença em %TEMP% serve como uma prova simples e observável de que o Estágio 2 foi executado através da cadeia SEH.
Extensões potenciais incluem:
A implementação completa da PoC está disponível neste repositório (veja src/main.rs).
Este projeto destina-se à pesquisa defensiva e educação. Copyright <2025>
A permissão é concedida, gratuitamente, a qualquer pessoa que obtenha uma cópia deste software e arquivos de documentação associados (o “Software”), para lidar com o Software sem restrição, incluindo, sem limitação, os direitos de usar, copiar, modificar, mesclar, publicar, distribuir, sublicenciar e/ou vender cópias do Software, e permitir que pessoas a quem o Software seja fornecido o façam, sujeito às seguintes condições:
O aviso de copyright acima e este aviso de permissão devem ser incluídos em todas as cópias ou partes substanciais do Software.
O SOFTWARE É FORNECIDO “COMO ESTÁ”, SEM GARANTIA DE QUALQUER TIPO, EXPRESSA OU IMPLÍCITA, INCLUINDO, MAS NÃO SE LIMITANDO ÀS GARANTIAS DE COMERCIALIZAÇÃO, ADEQUAÇÃO A UM DETERMINADO FIM E NÃO VIOLAÇÃO. EM NENHUM CASO OS AUTORES OU DETENTORES DE DIREITOS AUTORAIS SERÃO RESPONSÁVEIS POR QUALQUER REIVINDICAÇÃO, DANOS OU OUTRA RESPONSABILIDADE, SEJA EM UMA AÇÃO DE CONTRATO, ATO ILÍCITO OU DE OUTRA FORMA, DECORRENTE DE, OU EM CONEXÃO COM O SOFTWARE OU O USO OU OUTRAS NEGOCIAÇÕES NO SOFTWARE.
A amostra real do MOEW que inspirou esta PoC não restaurou fs:[0] ao final da execução.
Em vez disso, seu estágio final:
fs:[0]) com NULL ou um ponteiro para memória inválida.RtlDispatchException encontrasse um ponteiro de manipulador inválido.unknown.unknown.Application Error no Visualizador de Eventos com offsets de falha sem significado.KiUserExceptionDispatcher,Esta etapa destrutiva de corrupção SEH serve ao propósito anti-forense principal da amostra:
apagar a cadeia causal e produzir uma falha terminal não atribuível.
Ao contrário da amostra real, esta PoC:
Captura a cabeça SEH original durante o Estágio 0:
asm!("mov {old}, fs:[0]", old = out(reg) old_head);
ORIGINAL_SEH = old_head;
Restaura a cabeça SEH original no manipulador final:
asm!("mov fs:[0], {p}", p = in(reg) ORIGINAL_SEH);
Termina de forma limpa via process::exit(0) em vez de disparar uma falha não tratada.
Como resultado, a PoC:
Application Error 1000 no Visualizador de Eventos,No entanto—criticamente—a PoC não elimina o comportamento de degradação de telemetria dirigida por exceção do MOEW.
A PoC certamente degrada a telemetria da mesma forma fundamental:
Ela deliberadamente dispara múltiplas falhas de hardware desalinhadas.
Ela produz múltiplas exceções de primeira chance em rápida sucessão.
Ela força o Windows a executar repetidamente:
KiUserExceptionDispatcher
RtlDispatchException
→ manipulador personalizado
→ blob desalinhado
→ falha de hardware
Ela gera pilhas de chamadas não lineares e dominadas por exceções.
Ela distorce a reconstrução de fluxo de controle em depuradores e EDR ao rotear a execução através de:
Assim, a PoC reproduz fielmente:
…enquanto evita a falha final destrutiva.
Isso torna a PoC ideal para instrumentação e pesquisa sem invocar a carga anti-forense completa.
Como a PoC preserva o comportamento geral do MOEW menos a falha de corrupção SEH, ela é:
A PoC modela a cachoeira de exceções (a essência do MOEW) enquanto remove a assinatura destrutiva final.
Ela demonstra que a degradação de telemetria surge não apenas da corrupção SEH, mas do próprio modelo de fluxo de controle dirigido por exceção.
Esta seção formaliza as diferenças comportamentais, mantendo claro o propósito da PoC:
demonstrar a cachoeira MOEW de forma segura e adequada à pesquisa, sem a falha final anti-forense.
| Comportamento | Amostra Real MOEW | Implementação PoC |
|---|
| Cachoeira de opcode desalinhado | ✔ | ✔ |
| Máquina de estados recursiva dirigida por SEH | ✔ | ✔ |
| Pilhas de chamadas dominadas por despacho de exceção | ✔ | ✔ |
| Degradação de telemetria / rastreamento de pilha | ✔ | ✔ |
| Corrupção intencional de SEH | ✔ | ❌ |
| Ponteiros SEH pendentes ou inválidos | ✔ | ❌ |
| Exceção final não tratada | ✔ | ❌ |
| Falha WER “módulo desconhecido” | ✔ | ❌ |
Restauração limpa de fs:[0] | ❌ | ✔ |
| Terminação limpa | ❌ | ✔ |