Windows x64 的反射式 PE 映射器与抗规避模块覆盖引擎
一个面向 Windows x64 的极简反射式 PE 映射器与模块踩踏引擎,围绕一个简洁的 IPlugin ABI 构建。从内存中的字节缓冲区映射 DLL,解析导出,并调用插件函数——既可以来自匿名内存(经典反射式加载),也可以来自一个合法加载的牺牲 DLL 内部(模块踩踏)。
模块踩踏路径经过规避强化:按节保护(无 RWX)、私有映射清理、原始字节恢复以及堆清理。使用 pe-sieve、Moneta、hollows_hunter、BeaconEye 和 YARA 对被踩踏模块进行清理后扫描,结果为零发现。
需要 CMake 3.15+ 以及 Windows 上的 MSVC、Clang 或 MinGW 之一。
# 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)
对于带有完整诊断输出的调试构建:
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
通过 LoadLibrary 合法加载牺牲 DLL,然后用插件作为连续映像覆盖其 .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 头 | 在释放前对原始 DLL 缓冲区和保存的字节执行 memset(0) |
| 卸载时 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() 注册它。
牺牲 DLL 的 .text 节必须大于插件的虚拟映像大小。它不应被进程主动使用或在运行时进行完整性检查。
好的候选:xpsservices.dll(1.8 MB .text)、mscms.dll、colorui.dll、npmproxy.dll
坏的候选:ntdll.dll、kernel32.dll(被主动使用)、clr.dll(进行完整性检查)
.text 节中此版本是对原始概念验证的彻底重写:
StompedLoaderEngine.h)— 连续映像覆盖,带完整规避清理.text RX、.rdata R、.data RW;不再有 RWX 区域FreeLibrary + LoadLibrary 循环用干净的文件支持页面替换私有化页面.text 清零mov eax,1; ret 防止 FreeLibrary 期间 DllMain 崩溃VirtualFree 反射映射(消除浮动 PE)ReflectiveLoaderEngine.h)— 直接从输入缓冲区映射,无中间 VirtualAlloc 副本IPlugin.h)— 移除无用辅助函数(BeaconCout、CStrLenA、PluginAlloc、PluginFree)main.cpp)— 通过 NDEBUG 进行条件编译;Release 构建仅显示命令输出和扫描命令-nostartfiles(反射加载代码中无 CRT)