
Mapeador PE reflexivo y motor de stomping de módulos reforzado contra evasión para Windows x64
Un mapeador PE reflexivo mínimo y un motor de module stomping para Windows x64, construido en torno a una ABI IPlugin limpia. Mapea una DLL desde un búfer de bytes en memoria, resuelve las exportaciones y llama a funciones del plugin — ya sea desde memoria anónima (carga reflexiva clásica) o desde el interior de una DLL sacrificial cargada legítimamente (module stomping).
La ruta de module stomping está endurecida contra evasión: protecciones por sección (sin RWX), limpieza de mapeo privado, restauración de bytes originales y limpieza del heap. Los escaneos posteriores a la limpieza con pe-sieve, Moneta, hollows_hunter, BeaconEye y YARA devuelven cero hallazgos en el módulo sometido a stomping.
Requiere CMake 3.15+ y MSVC, Clang o MinGW en 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)
Para una compilación de depuración con salida de diagnóstico completa:
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build build
.\harness.exe cmdplugin.dll whoami
Asigna el plugin a memoria VirtualAlloc'd. Sin entrada en el PEB, sin notificación del cargador.
.\harness.exe cmdplugin.dll whoami --stomp xpsservices.dll
.\harness.exe cmdplugin.dll "dir C:\" --stomp mscms.dll
Carga la DLL sacrificial de forma legítima mediante LoadLibrary, luego sobrescribe su .text con el plugin como una imagen contigua. La ejecución ocurre desde el espacio de direcciones de la DLL sacrificial — las pilas de llamadas y las listas de módulos atribuyen el código al módulo sacrificial.
El arnés se pausa después de la limpieza para que puedas ejecutar escáneres de memoria contra el proceso.
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
Tras la limpieza, la DLL señuelo es indistinguible de un módulo cargado normalmente. Las páginas están respaldadas por archivo (no privadas), el contenido coincide con el archivo en disco y no quedan artefactos PE en el heap.
| IOC | Mitigación |
|---|---|
| Regiones de memoria RWX | Protecciones por sección: .text RX, .rdata R, .data RW |
| Memoria ejecutable privada (PE flotante) | VirtualFree sobre el mapeo privado tras la resolución de exportaciones |
| Código de módulo modificado | Restaurar los bytes originales de .text, luego FreeLibrary + LoadLibrary para obtener páginas limpias respaldadas por archivo |
| Cabeceras PE en el heap | memset(0) sobre el búfer DLL sin procesar y los bytes guardados antes de liberar |
| Fallo de DllMain al descargar | Punto de entrada parcheado a mov eax,1; ret antes de sobrescribir |
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 incluye 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 */ }
};
Exporta los cinco puntos de entrada (create_plugin, destroy_plugin, plugin_init, plugin_exec, plugin_cleanup). Usa HeapAlloc + placement new para una asignación segura para el CRT.
Añade un CMakeLists.txt y regístralo en la raíz con add_subdirectory().
La sección .text de la DLL de sacrificio debe ser más grande que el tamaño de la imagen virtual del plugin. No debería estar siendo usada activamente por el proceso ni verificada por integridad en tiempo de ejecución.
Buenos candidatos: xpsservices.dll (1.8 MB .text), mscms.dll, colorui.dll, npmproxy.dll
Malos candidatos: ntdll.dll, kernel32.dll (usadas activamente), clr.dll (verificada por integridad)
.text de sacrificioEsta versión es una reescritura desde cero del proof-of-concept original:
StompedLoaderEngine.h) — stomping de imagen contigua con limpieza de evasión completa.text RX, .rdata R, .data RW; ya no hay regiones RWXFreeLibrary + LoadLibrary reemplaza las páginas privatizadas por páginas limpias respaldadas por archivo.text original guardado se ponen a cero antes de la ventana de escaneomov eax,1; ret evita el crash de DllMain durante FreeLibraryVirtualFree del mapeo reflexivo tras la resolución de exportaciones (elimina el PE flotante)ReflectiveLoaderEngine.h) — mapeo directo desde el búfer de entrada, sin copia intermedia con VirtualAllocIPlugin.h) — eliminados helpers muertos (BeaconCout, CStrLenA, PluginAlloc, PluginFree)main.cpp) — compilación condicional mediante NDEBUG; las compilaciones Release muestran solo la salida de comandos y los comandos de escaneo-nostartfiles para las DLLs del plugin (sin CRT en el código cargado reflexivamente)