
Кратко: Данный PoC демонстрирует 3‑стадийный каскад обработки структурных исключений (SEH), вызванный намеренным невыровненным исполнением необработанных байтовых последовательностей, первая корректная инструкция которых — вызывающая сбой
div regс нулевым делителем.
Каждая стадия исключения устанавливает следующий SEH-обработчик, инициирует новое невыровненное деление на ноль и выполняет безвредную наблюдаемую нагрузку, после чего восстанавливает исходную SEH-цепочку и корректно завершает работу.
MOEW (Misaligned Opcode Exception Waterfall) — это образец оборонительного исследования, демонстрирующий управляемое многостадийное исполнение на основе исключений в среде x86/Wow64 Windows. Он показывает:
fs:[0]blob1, blob2, blob3)KiUserExceptionDispatcherRtlDispatchExceptionВся нагрузка безвредна:
%TEMP%PoC намеренно «обезврежен». Никакие данные не шифруются, не изменяются и не уничтожаются.
div по управляемым смещениям (ECX/EDX/EBX = 0)ORIGINAL_SEH)PoC использует возможности, доступные только в nightly:
#![feature(asm_experimental_arch)]
#![feature(naked_functions)]
Установите необходимые компоненты:
rustup toolchain install nightly
rustup target add i686-pc-windows-msvc --toolchain nightly
Поскольку PoC устанавливает пользовательские SEH-обработчики, отсутствующие в таблице SAFESEH, необходимо указать линковщику отключить проверку SAFESEH.
Создайте .cargo/config.toml:
[target.i686-pc-windows-msvc]
rustflags = [
"-C", "link-arg=/SAFESEH:NO",
]
Соберите бинарный файл:
cargo +nightly build --target i686-pc-windows-msvc --release
Результат находится по пути:
target\i686-pc-windows-msvc\release\seh_waterfall.exe
Выполните:
seh_waterfall.exe
Ожидаемый порядок управления:
Stage 0 → misaligned blob1 → Stage 1 handler
Stage 1 → misaligned blob2 → Stage 2 handler
Stage 2 → misaligned blob3 → Final handler
Final → restore SEH → exit
Видимые артефакты:
%TEMP%\moew_stage2.txt (Стадия 2)В 32‑битной Windows SEH-записи образуют связный список, хранящийся по адресу fs:[0]:
#[repr(C)]
struct SehRec {
next: *mut SehRec,
handler: usize,
}
Каждый обработчик использует стандартную сигнатуру 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();
Используется для:
fs:[0] как ORIGINAL_SEH.fs:[0] этой новой записью.blob1 + 5, который декодируется как div ecx (после установки ECX = 0).notepad.exe.blob2 + 3 → div edx (при EDX = 0).%TEMP%\moew_stage2.txt.blob3 + 3 → div ebx (при EBX = 0).calc.exe.ORIGINAL_SEH в 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 (при 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 (при 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 (при EBX = 0 → #DE)Каждая сбойная стадия повторно входит в пользовательский конвейер исключений Windows:
KiUserExceptionDispatcher
→ RtlDispatchException
→ обход SEH-цепочки (fs:[0])
→ обработчик MOEW
Типичный вид в отладчике:
seh_waterfall!blobX+offset
ntdll!KiUserExceptionDispatcher
ntdll!RtlDispatchException
seh_waterfall!stageN_handler
Каскад полностью основан на реальных аппаратных сбоях и диспетчеризации SEH; никакие синтетические или поддельные исключения не используются.
Файл, записываемый на Стадии 2, выглядит так:
MOEW Stage 2 Marker
-------------------
This file was written by the Stage 2 SEH handler
as a benign demonstration payload.
Его наличие в %TEMP% служит простым наблюдаемым доказательством того, что Стадия 2 была выполнена через SEH-цепочку.