
Livre blanc
TL;DR : Ce PoC démontre une cascade de gestion structurée des exceptions (SEH) en 3 étapes provoquée par une exécution volontairement désalignée dans des blobs d'octets bruts dont la première instruction valide est un
div regfautif avec un diviseur à zéro.
Chaque étape d'exception installe le gestionnaire SEH suivant, déclenche une nouvelle division par zéro désalignée et exécute une charge utile bénigne observable avant de restaurer la chaîne SEH d'origine et de se terminer proprement.
MOEW (Misaligned Opcode Exception Waterfall) est un échantillon de recherche défensive qui illustre une exécution multi-étapes contrôlée par exceptions sur Windows x86/WoW64. Il démontre :
fs:[0]blob1, blob2, blob3)KiUserExceptionDispatcherRtlDispatchExceptionToutes les charges utiles sont bénignes :
%TEMP%Le PoC est volontairement désamorcé. Aucune donnée n'est chiffrée, modifiée ou détruite.
div désaligné à des offsets contrôlés (ECX/EDX/EBX = 0)ORIGINAL_SEH)Le PoC utilise des fonctionnalités exclusives à nightly :
#![feature(asm_experimental_arch)]
#![feature(naked_functions)]
Installez les composants nécessaires :
rustup toolchain install nightly
rustup target add i686-pc-windows-msvc --toolchain nightly
Étant donné que le PoC installe des gestionnaires SEH personnalisés qui ne sont pas présents dans la table SAFESEH, l'éditeur de liens doit être configuré pour désactiver la validation SAFESEH.
Créez .cargo/config.toml :
[target.i686-pc-windows-msvc]
rustflags = [
"-C", "link-arg=/SAFESEH:NO",
]
Compilez le binaire :
cargo +nightly build --target i686-pc-windows-msvc --release
Le résultat se trouve à :
target\i686-pc-windows-msvc\release\seh_waterfall.exe
Exécutez :
seh_waterfall.exe
Flux de contrôle attendu :
Stage 0 → misaligned blob1 → Stage 1 handler
Stage 1 → misaligned blob2 → Stage 2 handler
Stage 2 → misaligned blob3 → Final handler
Final → restore SEH → exit
Artéfacts visibles :
%TEMP%\moew_stage2.txt est créé (étape 2)Sur Windows 32 bits, les enregistrements SEH forment une liste chaînée stockée à fs:[0] :
#[repr(C)]
struct SehRec {
next: *mut SehRec,
handler: usize,
}
Chaque gestionnaire utilise la signature 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();
Utilisé pour :
fs:[0] dans ORIGINAL_SEH.fs:[0] par ce nouvel enregistrement.blob1 + 5, qui se décode comme div ecx (après avoir défini ECX = 0).notepad.exe.blob2 + 3 → div edx (avec EDX = 0).%TEMP%\moew_stage2.txt.blob3 + 3 → div ebx (avec EBX = 0).calc.exe.ORIGINAL_SEH dans 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 (avec 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 (avec 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 (avec EBX = 0 → #DE)Chaque étape fautive rentre à nouveau dans le pipeline d'exceptions en mode utilisateur de Windows :
KiUserExceptionDispatcher
→ RtlDispatchException
→ SEH chain walk (fs:[0])
→ MOEW handler
Vue typique du débogueur :
seh_waterfall!blobX+offset
ntdll!KiUserExceptionDispatcher
ntdll!RtlDispatchException
seh_waterfall!stageN_handler
La cascade est entièrement pilotée par de véritables défauts matériels et la distribution SEH ; aucune exception synthétique ou factice n'est utilisée.
Le fichier écrit à l'étape 2 ressemble à :
MOEW Stage 2 Marker
-------------------
This file was written by the Stage 2 SEH handler
as a benign demonstration payload.
Sa présence dans %TEMP% sert de preuve simple et observable que l'étape 2 s'est exécutée via la chaîne SEH.
Les extensions potentielles incluent :
L'implémentation complète du PoC est disponible dans ce dépôt (voir src/main.rs).
Ce projet est destiné à la recherche défensive et à l'éducation. Copyright <2025>
Permission est accordée, gratuitement, à toute personne obtenant une copie de ce logiciel et des fichiers de documentation associés (le « Logiciel »), de traiter le Logiciel sans restriction, y compris sans limitation les droits d'utiliser, copier, modifier, fusionner, publier, distribuer, sous-licencier et/ou vendre des copies du Logiciel, et de permettre aux personnes auxquelles le Logiciel est fourni de le faire, sous réserve des conditions suivantes :
L'avis de copyright ci-dessus et cet avis de permission doivent être inclus dans toutes les copies ou parties substantielles du Logiciel.
LE LOGICIEL EST FOURNI « EN L'ÉTAT », SANS GARANTIE D'AUCUNE SORTE, EXPRESSE OU IMPLICITE, Y COMPRIS MAIS SANS S'Y LIMITER LES GARANTIES DE QUALITÉ MARCHANDE, D'ADÉQUATION À UN USAGE PARTICULIER ET D'ABSENCE DE CONTREFAÇON. EN AUCUN CAS LES AUTEURS OU TITULAIRES DU COPYRIGHT NE POURRONT ÊTRE TENUS RESPONSABLES DE TOUTE RÉCLAMATION, DOMMAGE OU AUTRE RESPONSABILITÉ, QUE CE SOIT DANS UNE ACTION DE CONTRAT, DÉLIT OU AUTRE, DÉCOULANT DE, OU EN RELATION AVEC LE LOGICIEL OU L'UTILISATION OU D'AUTRES OPÉRATIONS DU LOGICIEL.
L'échantillon MOEW réel qui a inspiré ce PoC ne restaurait pas fs:[0] à la fin de l'exécution.
Au lieu de cela, sa dernière étape :
fs:[0]) avec NULL ou un pointeur vers une mémoire invalide.RtlDispatchException à rencontrer un pointeur de gestionnaire invalide.unknown.unknown.Application Error de l'Observateur d'événements avec des offsets de faute sans signification.KiUserExceptionDispatcher,Cette étape destructrice de corruption SEH sert l'objectif anti-forensique principal de l'échantillon :
effacer la chaîne causale et produire un crash terminal non attribuable.
Contrairement à l'échantillon réel, ce PoC :
Capture la tête SEH d'origine à l'étape 0 :
asm!("mov {old}, fs:[0]", old = out(reg) old_head);
ORIGINAL_SEH = old_head;
Restaure la tête SEH d'origine dans le gestionnaire final :
asm!("mov fs:[0], {p}", p = in(reg) ORIGINAL_SEH);
Se termine proprement via process::exit(0) au lieu de déclencher une faute non gérée.
Par conséquent, le PoC :
Application Error 1000 dans l'Observateur d'événements,Cependant — point crucial — le PoC n'élimine pas le comportement de dégradation de la télémétrie pilotée par exceptions de MOEW.
Le PoC dégrade bel et bien la télémétrie de la même manière fondamentale :
Il déclenche délibérément plusieurs défauts matériels désalignés.
Il produit plusieurs exceptions de première chance en succession rapide.
Il force Windows à exécuter de manière répétée :
KiUserExceptionDispatcher
RtlDispatchException
→ custom handler
→ misaligned blob
→ hardware fault
Il génère des piles d'appels non linéaires, dominées par les exceptions.
Il fausse la reconstruction du flux de contrôle dans les débogueurs et l'EDR en routant l'exécution à travers :
Ainsi, le PoC reproduit fidèlement :
…tout en évitant le crash final destructeur.
Cela rend le PoC idéal pour l'instrumentation et la recherche sans invoquer la charge utile anti-forensique complète.
Parce que le PoC préserve le comportement global de MOEW moins le crash de corruption SEH, il est :
Le PoC modélise la cascade d'exceptions (l'essence de MOEW) tout en supprimant la signature destructrice finale.
Il démontre que la dégradation de la télémétrie provient non seulement de la corruption SEH, mais aussi du modèle de flux de contrôle piloté par exceptions lui-même.
Cette section formalise les différences comportementales tout en gardant l'objectif du PoC clair :
démontrer la cascade MOEW sous une forme sûre et adaptée à la recherche, sans le crash final anti-forensique.
| Comportement | Échantillon MOEW réel | Implémentation du PoC |
|---|
| Cascade d'opcodes désalignés | ✔ | ✔ |
| Machine à états pilotée par SEH récursive | ✔ | ✔ |
| Piles d'appels dominées par la distribution d'exceptions | ✔ | ✔ |
| Dégradation de la télémétrie / des traces de pile | ✔ | ✔ |
| Corruption volontaire de la SEH | ✔ | ❌ |
| Pointeurs SEH pendants ou invalides | ✔ | ❌ |
| Exception finale non gérée | ✔ | ❌ |
| Crash WER « module inconnu » | ✔ | ❌ |
Restauration propre de fs:[0] | ❌ | ✔ |
| Terminaison propre | ❌ | ✔ |