
White paper
TL;DR: Questo PoC dimostra una cascata di Structured Exception Handling (SEH) a 3 stadi causata da una deliberata esecuzione disallineata in blocchi di byte grezzi la cui prima istruzione valida è una
div regche genera un fault con divisore zero. Ogni stadio di eccezione installa il successivo handler SEH, innesca una nuova divisione per zero disallineata ed esegue un payload osservabile benigno prima di ripristinare la catena SEH originale e terminare in modo pulito.
MOEW (Misaligned Opcode Exception Waterfall) è un campione di ricerca difensiva che mostra un'esecuzione controllata multi-stadio guidata da eccezioni su Windows x86/Wow64. Dimostra:
fs:[0]blob1, blob2, blob3)KiUserExceptionDispatcherRtlDispatchExceptionTutti i payload sono benigni:
%TEMP%Il PoC è volutamente reso innocuo. Nessun dato viene crittografato, modificato o distrutto.
div disallineato a offset controllati (ECX/EDX/EBX = 0)ORIGINAL_SEH)Il PoC usa funzionalità solo-nightly:
#![feature(asm_experimental_arch)]
#![feature(naked_functions)]
Installa i componenti necessari:
rustup toolchain install nightly
rustup target add i686-pc-windows-msvc --toolchain nightly
Poiché il PoC installa handler SEH personalizzati che non sono presenti nella tabella SAFESEH, è necessario istruire il linker per disabilitare la validazione SAFESEH.
Crea .cargo/config.toml:
[target.i686-pc-windows-msvc]
rustflags = [
"-C", "link-arg=/SAFESEH:NO",
]
Compila il binario:
cargo +nightly build --target i686-pc-windows-msvc --release
L'output si trova in:
target\i686-pc-windows-msvc\release\seh_waterfall.exe
Esegui:
seh_waterfall.exe
Flusso di controllo atteso:
Stage 0 → misaligned blob1 → Stage 1 handler
Stage 1 → misaligned blob2 → Stage 2 handler
Stage 2 → misaligned blob3 → Final handler
Final → restore SEH → exit
Artefatti visibili:
%TEMP%\moew_stage2.txt (Stadio 2)Su Windows a 32 bit, i record SEH formano una lista concatenata memorizzata in fs:[0]:
#[repr(C)]
struct SehRec {
next: *mut SehRec,
handler: usize,
}
Ogni handler usa la firma SEH standard:
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();
Usato per:
fs:[0] come ORIGINAL_SEH.fs:[0] con questo nuovo record.blob1 + 5, che decodifica come div ecx (dopo aver impostato 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 in 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)Ogni stadio in fault rientra nella pipeline delle eccezioni in modalità utente di Windows:
KiUserExceptionDispatcher
→ RtlDispatchException
→ SEH chain walk (fs:[0])
→ MOEW handler
Tipica visualizzazione nel debugger:
seh_waterfall!blobX+offset
ntdll!KiUserExceptionDispatcher
ntdll!RtlDispatchException
seh_waterfall!stageN_handler
La cascata è interamente guidata da veri fault hardware e dispatch SEH; non vengono usate eccezioni sintetiche o finte.
Il file scritto nello Stadio 2 appare così:
MOEW Stage 2 Marker
-------------------
This file was written by the Stage 2 SEH handler
as a benign demonstration payload.
La sua presenza in %TEMP% serve come semplice prova osservabile che lo Stadio 2 è stato eseguito tramite la catena SEH.
Le estensioni potenziali includono: