
Mapeador PE reflexivo e mecanismo de module stomping reforçado contra evasão para Windows x64
Um mapeador PE reflexivo minimalista e mecanismo de module stomping para Windows x64, construído em torno de uma ABI IPlugin limpa. Mapeia uma DLL a partir de um buffer de bytes na memória, resolve exports e chama funções do plugin — seja a partir de memória anônima (carregamento reflexivo clássico) ou de dentro de uma DLL sacrificial carregada legitimamente (module stomping).
O caminho de module stomping é reforçado contra evasão: proteções por secção (sem RWX), limpeza de mapeamento privado, restauração de bytes originais e limpeza de heap. Análises pós-limpeza com pe-sieve, Moneta, hollows_hunter, BeaconEye e YARA não retornam quaisquer deteções no módulo alvo de stomping.
Requer CMake 3.15+ e MSVC, Clang ou MinGW no 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 uma compilação Debug com saída de diagnóstico completa:
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build build
.\harness.exe cmdplugin.dll whoami
Mapeia o plugin para memória alocada com VirtualAlloc. Sem entrada no PEB, sem notificação do loader.
.\harness.exe cmdplugin.dll whoami --stomp xpsservices.dll
.\harness.exe cmdplugin.dll "dir C:\" --stomp mscms.dll
Carrega a DLL sacrificial legitimamente via LoadLibrary, depois sobrescreve seu .text com o plugin como uma imagem contígua. A execução ocorre a partir do espaço de endereçamento da DLL sacrificial — call stacks e listas de módulos atribuem o código ao módulo sacrificial.
O harness pausa após a limpeza para que você possa executar scanners de memória contra o 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
Após a limpeza, a DLL sacrificial é indistinguível de um módulo carregado normalmente. As páginas são respaldadas por arquivo (não privadas), o conteúdo corresponde ao arquivo em disco e nenhum artefato PE permanece no heap.
| IOC | Mitigação |
|---|---|
| Regiões de memória RWX | Proteções por seção: .text RX, .rdata R, .data RW |
| Memória executável privada (PE flutuante) | VirtualFree no mapeamento privado após a resolução de exportações |
| Código de módulo modificado | Restaurar os bytes originais de .text, depois FreeLibrary + LoadLibrary para páginas limpas respaldadas por arquivo |
| Cabeçalhos PE no heap | memset(0) no buffer bruto da DLL e nos bytes salvos antes de liberar |
| Crash do DllMain ao descarregar | Ponto de entrada corrigido para mov eax,1; ret antes da sobrescrita |
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 inclua 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 */ }
};
Exporte os cinco pontos de entrada (create_plugin, destroy_plugin, plugin_init, plugin_exec, plugin_cleanup). Use HeapAlloc + placement new para alocação segura em relação ao CRT.
Adicione um CMakeLists.txt e registre-o na raiz com add_subdirectory().
A seção .text da DLL sacrificial deve ser maior que o tamanho da imagem virtual do plugin. Ela não deve estar sendo usada ativamente pelo processo nem ter verificação de integridade em tempo de execução.
Bons candidatos: xpsservices.dll (1,8 MB de .text), mscms.dll, colorui.dll, npmproxy.dll
Maus candidatos: ntdll.dll, kernel32.dll (usadas ativamente), clr.dll (verificada quanto à integridade)
.text sacrificialEsta versão é uma reescrita completa da prova de conceito original:
StompedLoaderEngine.h) — stomping de imagem contígua com limpeza completa de evasão.text RX, .rdata R, .data RW; sem mais regiões RWXFreeLibrary + LoadLibrary substitui páginas privatizadas por páginas limpas respaldadas por arquivo.text original salvo são zerados antes da janela de varreduramov eax,1; ret evita o crash do DllMain durante FreeLibraryVirtualFree no mapeamento reflexivo após a resolução de exportações (elimina o PE flutuante)ReflectiveLoaderEngine.h) — mapeamento direto a partir do buffer de entrada, sem cópia intermediária via VirtualAllocIPlugin.h) — removidos helpers mortos (BeaconCout, CStrLenA, PluginAlloc, PluginFree)main.cpp) — compilação condicional via NDEBUG; builds Release mostram apenas a saída de comandos e comandos de varredura-nostartfiles para DLLs de plugin (sem CRT no código carregado reflexivamente)