
Whitepaper
TL;DR: This PoC demonstrates a 3‑stage Structured Exception Handling (SEH) cascade caused by deliberate misaligned execution into raw byte blobs whose first valid instruction is a faulting
div regwith a zero divisor.
Each exception stage installs the next SEH handler, triggers a new misaligned divide‑by‑zero, and executes a benign observable payload before restoring the original SEH chain and terminating cleanly.
MOEW (Misaligned Opcode Exception Waterfall) is a defensive research sample that showcases controlled exception‑driven multi‑stage execution on x86/Wow64 Windows. It demonstrates:
fs:[0]blob1, blob2, blob3)KiUserExceptionDispatcherRtlDispatchExceptionAll payloads are benign:
%TEMP%The PoC is intentionally defanged. No data is encrypted, modified, or destroyed.
div at controlled offsets (ECX/EDX/EBX = 0)ORIGINAL_SEH)The PoC uses nightly‑only features:
#![feature(asm_experimental_arch)]
#![feature(naked_functions)]
Install the necessary components:
rustup toolchain install nightly
rustup target add i686-pc-windows-msvc --toolchain nightly
Since the PoC installs custom SEH handlers that are not present in the SAFESEH table, the linker must be instructed to disable SAFESEH validation.
Create .cargo/config.toml:
[target.i686-pc-windows-msvc]
rustflags = [
"-C", "link-arg=/SAFESEH:NO",
]
Build the binary:
cargo +nightly build --target i686-pc-windows-msvc --release
Output is located at:
target\i686-pc-windows-msvc\release\seh_waterfall.exe
Execute:
seh_waterfall.exe
Expected control‑flow:
Stage 0 → misaligned blob1 → Stage 1 handler
Stage 1 → misaligned blob2 → Stage 2 handler
Stage 2 → misaligned blob3 → Final handler
Final → restore SEH → exit
Visible artifacts:
%TEMP%\moew_stage2.txt is created (Stage 2)On 32‑bit Windows, SEH records form a linked list stored at fs:[0]:
#[repr(C)]
struct SehRec {
next: *mut SehRec,
handler: usize,
}
Each handler uses the standard SEH signature:
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();
Used to:
fs:[0] as ORIGINAL_SEH.fs:[0] with this new record.blob1 + 5, which decodes as div ecx (after setting ECX = 0).notepad.exe.blob2 + 3 → div edx (with EDX = 0).%TEMP%\moew_stage2.txt.blob3 + 3 → div ebx (with EBX = 0).calc.exe.ORIGINAL_SEH into 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 (with 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 (with 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 (with EBX = 0 → #DE)Each faulted stage re‑enters the Windows user‑mode exception pipeline:
KiUserExceptionDispatcher
→ RtlDispatchException
→ SEH chain walk (fs:[0])
→ MOEW handler
Typical debugger view:
seh_waterfall!blobX+offset
ntdll!KiUserExceptionDispatcher
ntdll!RtlDispatchException
seh_waterfall!stageN_handler
The waterfall is entirely driven by genuine hardware faults and SEH dispatch; no synthetic or fake exceptions are used.
The file written in Stage 2 looks like:
MOEW Stage 2 Marker
-------------------
This file was written by the Stage 2 SEH handler
as a benign demonstration payload.
Its presence in %TEMP% serves as a simple, observable proof that Stage 2 executed via the SEH chain.
Potential extensions include:
The complete PoC implementation is available in this repository (see src/main.rs).
This project is intended for defensive research and education. Copyright <2025>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
The real‑world MOEW sample that inspired this PoC did not restore fs:[0] at the end of execution.
Instead, its final stage:
fs:[0]) with either NULL or a pointer to invalid memory.RtlDispatchException to encounter an invalid handler pointer.unknown.unknown.Application Error events with meaningless fault offsets.KiUserExceptionDispatcher calls,This destructive SEH corruption step serves the sample’s primary anti‑forensic purpose:
erasing the causal chain and producing an un‑attributable terminal crash.
Unlike the real sample, this PoC:
Captures the original SEH head during Stage 0:
asm!("mov {old}, fs:[0]", old = out(reg) old_head);
ORIGINAL_SEH = old_head;
Restores the original SEH head in the final handler:
asm!("mov fs:[0], {p}", p = in(reg) ORIGINAL_SEH);
Terminates cleanly via process::exit(0) instead of triggering an unhandled fault.
As a result, the PoC:
Application Error 1000 entries,However—critically—the PoC does not eliminate MOEW’s exception‑driven telemetry degradation behavior.
The PoC absolutely does degrade telemetry in the same fundamental way:
It deliberately triggers multiple misaligned hardware faults.
It produces multiple first‑chance exceptions in rapid succession.
It forces Windows to repeatedly execute:
KiUserExceptionDispatcher
RtlDispatchException
→ custom handler
→ misaligned blob
→ hardware fault
It generates non‑linear, exception‑dominant call stacks.
It distorts control‑flow reconstruction in debuggers and EDR by routing execution through:
Thus the PoC faithfully reproduces:
…while avoiding the destructive final crash.
This makes the PoC ideal for instrumentation and research without invoking the full anti‑forensic payload.
Because the PoC preserves the overall behavior of MOEW minus the SEH corruption crash, it is:
The PoC models the exception waterfall (the essence of MOEW) while removing the final destructive signature.
It demonstrates that telemetry degradation arises not only from SEH corruption, but from the exception‑driven control‑flow model itself.
This section formalizes the behavioral differences while keeping the PoC’s purpose clear:
demonstrate the MOEW waterfall in a safe, research‑friendly form without the anti‑forensic final crash.
| Behavior | Real MOEW Sample | PoC Implementation |
|---|
| Misaligned opcode waterfall | ✔ | ✔ |
| Recursive SEH‑driven state machine | ✔ | ✔ |
| Dominant exception‑dispatch call stacks | ✔ | ✔ |
| Telemetry / stack‑trace degradation | ✔ | ✔ |
| Intentional SEH corruption | ✔ | ❌ |
| Dangling or invalid SEH pointers | ✔ | ❌ |
| Final unhandled exception | ✔ | ❌ |
| WER “unknown module” crash | ✔ | ❌ |
Clean restoration of fs:[0] | ❌ | ✔ |
| Clean termination | ❌ | ✔ |