
Windows x64용 리플렉티브 PE 매퍼 및 회피 강화 모듈 스톰핑 엔진
Windows x64용 최소한의 리플렉티브 PE 매퍼 및 모듈 스톰핑 엔진으로, 깔끔한 IPlugin ABI를 기반으로 구축되었습니다. 메모리 내 바이트 버퍼에서 DLL을 매핑하고, 익스포트를 해석한 뒤 플러그인 함수를 호출합니다 — 익명 메모리(고전적 리플렉티브 로드)에서든, 정상적으로 로드된 희생 DLL 내부(모듈 스톰핑)에서든 말입니다.
모듈 스톰핑 경로는 회피 강화되어 있습니다: 섹션별 보호(no 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
sacrificial DLL을 LoadLibrary를 통해 정상적으로 로드한 다음, 그 .text를 플러그인으로 연속적인 이미지로서 덮어씁니다. 실행은 sacrificial DLL의 주소 공간에서 발생합니다 — 호출 스택과 모듈 목록은 해당 코드를 sacrificial 모듈에 귀속시킵니다.
하네스는 정리 후 일시 중지되므로, 프로세스에 대해 메모리 스캐너를 실행할 수 있습니다.
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)을 내보냅니다. CRT 안전 할당을 위해 HeapAlloc + placement new를 사용하십시오.
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를 0으로 초기화mov eax,1; ret가 FreeLibrary 중 DllMain 충돌을 방지VirtualFree (부유 PE 제거)ReflectiveLoaderEngine.h) — 입력 버퍼에서 직접 매핑, 중간 VirtualAlloc 복사 없음IPlugin.h) — 사용하지 않는 헬퍼(BeaconCout, CStrLenA, PluginAlloc, PluginFree) 제거main.cpp) — NDEBUG를 통한 조건부 컴파일; Release 빌드는 명령 출력과 스캔 명령만 표시-nostartfiles (반사 로드 코드에 CRT 없음)