
Рефлективный PE-маппер и устойчивый к обнаружению движок stomping модулей для Windows x64
Минимальный рефлективный PE-маппер и движок module stomping для Windows x64, построенный вокруг чистого ABI IPlugin. Отображает DLL из байтового буфера в памяти, разрешает экспорты и вызывает функции плагина — либо из анонимной памяти (классическая рефлективная загрузка), либо изнутри легитимно загруженной жертвенной DLL (module stomping).
Путь module stomping усилен против обнаружения: посекционная защита (без RWX), очистка приватных отображений, восстановление исходных байтов и затирание кучи. Пост-очистные сканирования с помощью pe-sieve, Moneta, hollows_hunter, BeaconEye и YARA возвращают нулевые обнаружения на stomped-модуле.
Требуется CMake 3.15+ и MSVC, Clang или MinGW на 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)
Для сборки Debug с полным диагностическим выводом:
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build build
.\harness.exe cmdplugin.dll whoami
Отображает плагин в память, выделенную через VirtualAlloc. Без записи в PEB, без уведомления загрузчика.
.\harness.exe cmdplugin.dll whoami --stomp xpsservices.dll
.\harness.exe cmdplugin.dll "dir C:\" --stomp mscms.dll
Загружает жертвенную DLL легитимно через LoadLibrary, затем перезаписывает её .text плагином как непрерывным образом. Выполнение происходит из адресного пространства жертвенной DLL — стеки вызовов и списки модулей приписывают код жертвенному модулю.
Харнесс приостанавливается после очистки, чтобы вы могли запустить сканеры памяти против процесса.
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
После очистки жертвенная DLL неотличима от нормально загруженного модуля. Страницы имеют файловую поддержку (не приватные), содержимое совпадает с файлом на диске, и никаких артефактов PE в куче не остаётся.
| IOC | Митигация |
|---|---|
| Области памяти RWX | Защита по секциям: .text RX, .rdata R, .data RW |
| Приватная исполняемая память (плавающий PE) | VirtualFree для приватного отображения после разрешения экспортов |
| Изменённый код модуля | Восстановить исходные байты .text, затем FreeLibrary + LoadLibrary для чистых страниц с файловой поддержкой |
| Заголовки PE в куче | memset(0) для сырого буфера DLL и сохранённых байтов перед освобождением |
| Падение DllMain при выгрузке | Точка входа пропатчена на mov eax,1; ret перед затиранием |
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/ и подключите 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 */ }
};
Экспортируйте пять точек входа (create_plugin, destroy_plugin, plugin_init, plugin_exec, plugin_cleanup). Используйте HeapAlloc + placement new для CRT-безопасного выделения памяти.
Добавьте CMakeLists.txt и зарегистрируйте его в корневом файле с помощью add_subdirectory().
Секция .text жертвенной DLL должна быть больше размера виртуального образа плагина. Она не должна активно использоваться процессом или проверяться на целостность во время выполнения.
Хорошие кандидаты: xpsservices.dll (1.8 МБ .text), mscms.dll, colorui.dll, npmproxy.dll
Плохие кандидаты: ntdll.dll, kernel32.dll (активно используются), clr.dll (проверяется на целостность)
.text жертвенной DLLЭтот релиз — полная переработка исходного proof-of-concept:
StompedLoaderEngine.h) — затирание непрерывного образа с полной очисткой для уклонения.text RX, .rdata R, .data RW; больше никаких RWX-регионовFreeLibrary + LoadLibrary заменяет приватизированные страницы на чистые, подкреплённые файлом.text обнуляются перед окном сканированияmov eax,1; ret предотвращает краш DllMain во время FreeLibraryVirtualFree рефлективного маппинга после разрешения экспортов (устраняет плавающий PE)ReflectiveLoaderEngine.h) — прямое отображение из входного буфера, без промежуточной копии через VirtualAllocIPlugin.h) — удалены мёртвые хелперы (BeaconCout, CStrLenA, PluginAlloc, PluginFree)main.cpp) — условная компиляция через NDEBUG; Release-сборки показывают только вывод команд и команды сканирования-nostartfiles для DLL плагинов (без CRT в рефлективно загружаемом коде)