Mappeur PE réflexif et moteur de stomping de modules renforcé contre l'évasion pour Windows x64
Un mappeur PE réflexif minimal et un moteur de module stomping pour Windows x64, construit autour d'une ABI IPlugin propre. Mappe une DLL depuis un tampon d'octets en mémoire, résout les exports et appelle les fonctions du plugin — soit depuis une mémoire anonyme (chargement réflexif classique), soit depuis l'intérieur d'une DLL sacrificielle légitimement chargée (module stomping).
Le chemin de module stomping est durci contre l'évasion : protections par section (pas de RWX), nettoyage du mapping privé, restauration des octets d'origine et effacement du tas. Les analyses post-nettoyage avec pe-sieve, Moneta, hollows_hunter, BeaconEye et YARA ne retournent aucun résultat sur le module stompé.
Nécessite CMake 3.15+ et soit MSVC, Clang, soit MinGW sous 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)
Pour une compilation Debug avec sortie de diagnostic complète :
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build build
.\harness.exe cmdplugin.dll whoami
Mappe le plugin dans une mémoire allouée par VirtualAlloc. Aucune entrée PEB, aucune notification du loader.
.\harness.exe cmdplugin.dll whoami --stomp xpsservices.dll
.\harness.exe cmdplugin.dll "dir C:\" --stomp mscms.dll
Charge la DLL sacrificielle légitimement via LoadLibrary, puis écrase son .text avec le plugin en tant qu'image contiguë. L'exécution se produit depuis l'espace d'adressage de la DLL sacrificielle — les piles d'appels et les listes de modules attribuent le code au module sacrificiel.
Le harnais met en pause après le nettoyage afin que vous puissiez exécuter des scanners de mémoire contre le processus.
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
Après le nettoyage, la DLL sacrificielle est indiscernable d'un module chargé normalement. Les pages sont adossées à un fichier (non privées), le contenu correspond au fichier sur disque, et aucun artefact PE ne subsiste sur le tas.
| IOC | Atténuation |
|---|---|
| Régions mémoire RWX | Protections par section : .text RX, .rdata R, .data RW |
| Mémoire exécutable privée (PE flottant) | VirtualFree du mappage privé après résolution des exports |
| Code de module modifié | Restaurer les octets originaux de .text, puis FreeLibrary + LoadLibrary pour des pages propres adossées à un fichier |
| En-têtes PE sur le tas | memset(0) du tampon brut de la DLL et des octets sauvegardés avant libération |
| Plantage de DllMain au déchargement | Point d'entrée patché en mov eax,1; ret avant l'écrasement |
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/ et incluez 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 */ }
};
Exporter les cinq points d'entrée (create_plugin, destroy_plugin, plugin_init, plugin_exec, plugin_cleanup). Utiliser HeapAlloc + placement new pour une allocation sûre vis-à-vis du CRT.
Ajouter un CMakeLists.txt et l'enregistrer à la racine avec add_subdirectory().
La section .text de la DLL sacrificielle doit être plus grande que la taille de l'image virtuelle du plugin. Elle ne doit pas être activement utilisée par le processus ni soumise à un contrôle d'intégrité à l'exécution.
Bons candidats : xpsservices.dll (1,8 Mo de .text), mscms.dll, colorui.dll, npmproxy.dll
Mauvais candidats : ntdll.dll, kernel32.dll (activement utilisés), clr.dll (contrôle d'intégrité)
.text sacrificielleCette version est une réécriture complète de la preuve de concept originale :
StompedLoaderEngine.h) — écrasement d'image contiguë avec nettoyage complet d'évasion.text RX, .rdata R, .data RW ; plus de régions RWXFreeLibrary + LoadLibrary remplace les pages privatisées par des pages propres adossées à un fichier.text originale sauvegardée sont mis à zéro avant la fenêtre de scanmov eax,1; ret empêche le crash de DllMain pendant FreeLibraryVirtualFree du mapping réflectif après résolution des exports (élimine le PE flottant)ReflectiveLoaderEngine.h) — mapping direct depuis le tampon d'entrée, sans copie intermédiaire via VirtualAllocIPlugin.h) — suppression des helpers morts (BeaconCout, CStrLenA, PluginAlloc, PluginFree)main.cpp) — compilation conditionnelle via NDEBUG ; les builds Release n'affichent que la sortie des commandes et les commandes de scan-nostartfiles pour les DLL de plugin (pas de CRT dans le code chargé de manière réflective)