
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 */ }
};
5つのエントリポイント(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をゼロ化mov eax,1; retによりFreeLibrary中のDllMainクラッシュを防止VirtualFree(浮遊PEを排除)ReflectiveLoaderEngine.h) — 入力バッファからの直接マッピング、中間のVirtualAllocコピーなしIPlugin.h) — 不要なヘルパー(BeaconCout、CStrLenA、PluginAlloc、PluginFree)を削除main.cpp) — NDEBUGによる条件付きコンパイル。Releaseビルドではコマンド出力とスキャンコマンドのみを表示-nostartfiles(リフレクティブロードコードにCRTなし)