
Mapper PE riflessivo e motore di module stomping rafforzato contro l'evasione per Windows x64
Un mapper PE riflessivo minimale e un motore di module stomping per Windows x64, costruito attorno a un'ABI IPlugin pulita. Mappa una DLL da un buffer di byte in memoria, risolve le esportazioni e chiama le funzioni del plugin — sia dalla memoria anonima (caricamento riflessivo classico) sia dall'interno di una DLL sacrificale caricata legittimamente (module stomping).
Il percorso di module stomping è evasion-hardened: protezioni per sezione (niente RWX), pulizia della mappatura privata, ripristino dei byte originali e scrubbing dell'heap. Le scansioni post-pulizia con pe-sieve, Moneta, hollows_hunter, BeaconEye e YARA restituiscono zero rilevamenti sul modulo sottoposto a stomping.
Richiede CMake 3.15+ e MSVC, Clang o MinGW su Windows.
# Configure (defaults to Release)
cmake -B build -G Ninja
# Build
cmake --build build
# The build produces:
# build/Modules/CmdPlugin/cmdplugin.dll (example plugin)
# build/Testing/harness.exe (test harness)
Per una build di debug con output diagnostico completo:
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build build
.\harness.exe cmdplugin.dll whoami
Mappa il plugin nella memoria allocata con VirtualAlloc. Nessuna voce nel PEB, nessuna notifica al loader.
.\harness.exe cmdplugin.dll whoami --stomp xpsservices.dll
.\harness.exe cmdplugin.dll "dir C:\" --stomp mscms.dll
Carica la DLL sacrificale legittimamente tramite LoadLibrary, poi sovrascrive la sua .text con il plugin come immagine contigua. L'esecuzione avviene dallo spazio di indirizzi della DLL sacrificale — gli stack di chiamata e le liste dei moduli attribuiscono il codice al modulo sacrificale.
L'harness si mette in pausa dopo la pulizia così puoi eseguire scanner di memoria contro il processo.
1. MapImage(plugin) private reflective mapping
2. LoadLibrary(sacrificial) legitimate load, PEB entry
3. Save original .text bytes for later restoration
4. Copy plugin image -> .text contiguous image preserves RIP-relative offsets
5. Relocate stomped code fix addresses for new base
6. Patch entry point mov eax,1; ret (safe FreeLibrary)
7. Per-section protections RX / R / RW per section (never RWX)
8. Resolve exports from private mapping, return stomped addresses
9. Free private mapping eliminates floating PE artifact
10. Execute plugin code runs inside "sacrificial.dll"
11. Restore original .text bytes sacrificial code back in place
12. FreeLibrary(sacrificial) unloads the module
13. LoadLibrary(sacrificial) fresh load = clean file-backed pages
14. Scrub heap buffers zero saved bytes + raw DLL data
Dopo la pulizia, la DLL sacrificale è indistinguibile da un modulo caricato normalmente. Le pagine sono file-backed (non private), il contenuto corrisponde al file su disco e nessun artefatto PE rimane sull'heap.
| IOC | Mitigazione |
|---|---|
| Regioni di memoria RWX | Protezioni per sezione: .text RX, .rdata R, .data RW |
| Memoria eseguibile privata (PE fluttuante) | VirtualFree della mappatura privata dopo la risoluzione degli export |
| Codice del modulo modificato | Ripristino dei byte originali di .text, poi FreeLibrary + LoadLibrary per pagine file-backed pulite |
| Header PE sull'heap | memset(0) del buffer DLL grezzo e dei byte salvati prima di liberare |
| Crash di DllMain allo scaricamento | Entry point patchato a mov eax,1; ret prima dello stomping |
ReflectivePluginLoader/
├── CMakeLists.txt
├── Include/
│ ├── IPlugin.h Plugin ABI (TaskApi, IPlugin)
│ ├── ReflectiveLoaderEngine.h PE mapper + export resolver
│ └── StompedLoaderEngine.h Module stomping engine
├── Modules/
│ └── CmdPlugin/
│ ├── cmdplugin.cpp Command execution plugin
│ └── CMakeLists.txt
├── Testing/
│ ├── main.cpp Test harness
│ └── CMakeLists.txt
└── Tools/
└── file2hex.py DLL to C byte array converter
Modules/ e includi IPlugin.h.IPlugin:#include "IPlugin.h"
class MyPlugin : public IPlugin {
public:
void init() const override { /* setup */ }
void execute(TaskApi* task) const override { /* work */ }
void cleanup() const override { /* tear */ }
};
Esporta i cinque entry point (create_plugin, destroy_plugin, plugin_init, plugin_exec, plugin_cleanup). Usa HeapAlloc + placement new per un'allocazione sicura per il CRT.
Aggiungi un CMakeLists.txt e registralo nella root con add_subdirectory().
La sezione .text della DLL sacrificale deve essere più grande della dimensione dell'immagine virtuale del plugin. Non dovrebbe essere attivamente utilizzata dal processo né sottoposta a controlli di integrità a runtime.
Buoni candidati: xpsservices.dll (1.8 MB .text), mscms.dll, colorui.dll, npmproxy.dll
Cattivi candidati: ntdll.dll, kernel32.dll (attivamente utilizzate), clr.dll (sottoposta a controllo di integrità)
.text sacrificaleQuesta release è una riscrittura da zero del proof-of-concept originale:
StompedLoaderEngine.h) — stomping di immagini contigue con pulizia completa per l'evasione.text RX, .rdata R, .data RW; niente più regioni RWXFreeLibrary + LoadLibrary sostituisce le pagine privatizzate con pagine pulite backed da file.text originale salvata vengono azzerati prima della finestra di scansionemov eax,1; ret previene il crash di DllMain durante FreeLibraryVirtualFree del mapping riflessivo dopo la risoluzione degli export (elimina il PE fluttuante)ReflectiveLoaderEngine.h) — mapping diretto dal buffer di input, nessuna copia intermedia con VirtualAllocIPlugin.h) — rimossi helper morti (BeaconCout, CStrLenA, PluginAlloc, PluginFree)main.cpp) — compilazione condizionale tramite NDEBUG; le build Release mostrano solo l'output dei comandi e i comandi di scansione-nostartfiles per le DLL dei plugin (nessun CRT nel codice caricato in modo riflessivo)