
Cobalt Strike向けのBeacon Object Fileで、回避技術を用いてビーコン内で.NETアセンブリを実行します。
Cobalt Strike の Beacon Object File で、.NET アセンブリを回避技術とともに Beacon 内で実行します。
┌──────────────────────────────────────────────────────────────────────────────┐
│ Cobalt Strike Beacon │
│ (親プロセス) │
└──────────────────────────────────┬───────────────────────────────────────────┘
│
│ beacon_inline_execute()
│ - パックされた引数をパース
│ - go() を呼び出す
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ BOF Execute-Assembly エントリ (go) │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ 設定パース │ │
│ │ • ProxyMethod (None/Draugr/Timer/RegWait) │ │
│ │ • AmsiEvasion (None/Patch/HWBP) │ │
│ │ • EtwEvasion (None/Patch) │ │
│ │ • PipeName, AppDomainName, アセンブリバイト列, 引数 │ │
│ └────────────────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────▼───────────────────────────────────────┐ │
│ │ フレームワーク初期化 │ │
│ │ • InitVxTable() - システムコール番号を解決 │ │
│ │ └─> NtProtectVirtualMemory, NtContinue, NtCreateEvent, │ │
│ │ NtSetEvent, NtWaitForSingleObject, NtClose │ │
│ │ • DraugrInit() - 合成スタックフレームをセットアップ │ │
│ │ └─> RtlUserThreadStart, BaseThreadInitThunk の位置を特定 │ │
│ └────────────────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────▼───────────────────────────────────────┐ │
│ │ DLL 読み込み (ProxyLoadLibraryA) │ │
│ │ • amsi.dll, OleAut32.dll, mscoree.dll, User32.dll │ │
│ │ │ │
│ │ PROXY_NONE: LoadLibraryA() を直接呼び出し │ │
│ │ PROXY_DRAUGR: DRAUGR_API(LoadLibraryA) - スタックを偽装 │ │
│ │ PROXY_TIMER: CreateTimerQueue → タイマーコールバック │ │
│ │ PROXY_REGWAIT: RegisterWaitForSingleObject → イベントコールバック │ │
│ └────────────────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────▼───────────────────────────────────────┐ │
│ │ AMSI 回避設定 │ │
│ │ │ │
│ │ AMSI_PATCH: AMSI_HWBP: │ │
│ │ ┌─────────────────────────┐ ┌──────────────────────────────┐ │ │
│ │ │ 1. 4バイトをバックアップ │ │ 1. VEH ハンドラを追加 │ │ │
│ │ │ 2. NtProtectVirtualMem │ │ 2. RtlCaptureContext │ │ │
│ │ │ (RW) │ │ 3. DR0 = AmsiScanBuffer を設定│ │ │
│ │ │ 3. 書き込み: │ │ 4. DR7 ブレークポイントを有効化│ │ │
│ │ │ 48 31 C0 xor rax,rax│ │ 5. NtContinue (コンテキスト適用)│ │ │
│ │ │ C3 ret │ │ │ │ │
│ │ │ 4. NtProtectVirtualMem │ │ AmsiScanBuffer 呼び出し時: │ │ │
│ │ │ (復元) │ │ → #BP 例外 │ │ │
│ │ └─────────────────────────┘ │ → VEH が RET にリダイレクト│ │ │
│ │ │ → RAX = 0 │ │ │
│ │ └──────────────────────────────┘ │ │
│ └────────────────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────▼───────────────────────────────────────┐ │
│ │ ETW 回避 (有効時) │ │
│ │ • NtProtectVirtualMemory(NtTraceEvent, RW) │ │
│ │ • 4バイトをバックアップ │ │
│ │ • 書き込み: 48 31 C0 C3 (xor rax,rax; ret) │ │
│ │ • NtProtectVirtualMemory(保護を復元) │ │
│ └────────────────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────▼───────────────────────────────────────┐ │
│ │ 出力リダイレクト設定 │ │
│ │ ┌──────────────────────────────────────────────────────────────────┐ │ │
│ │ │ 1. CreateNamedPipeW(\\.\pipe\{CustomName}) → hPipe │ │ │
│ │ │ 2. CreateFileW(パイプパス) → hFile │ │ │
│ │ │ 3. AllocConsole() + ShowWindow(SW_HIDE) → 隠しコンソール │ │ │
│ │ │ │ │ │
│ │ │ 4. PEB 操作: │ │ │
│ │ │ • バックアップ: hCurrentStdOut = PEB->ProcessParameters->StdOut│ │ │
│ │ │ • バックアップ: hCurrentStdErr = PEB->ProcessParameters->StdErr│ │ │
│ │ │ • リダイレクト: PEB->StdOut = hFile │ │ │
│ │ │ • リダイレクト: PEB->StdErr = hFile │ │ │
│ │ └──────────────────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────▼───────────────────────────────────────┐ │
│ │ CLR ホスティング & アセンブリ実行 (ExecuteAssembly) │ │
│ │ ┌──────────────────────────────────────────────────────────────────┐ │ │
│ │ │ 1. CLR バージョン検出 │ │ │
│ │ │ • アセンブリバイト列から "v2.0.50727" または "v4.0.30319" をスキャン│ │ │
│ │ │ │ │ │
│ │ │ 2. CLR 初期化 │ │ │
│ │ │ • CLRCreateInstance → ICLRMetaHost │ │ │
│ │ │ • GetRuntime(v2/v4) → ICLRRuntimeInfo │ │ │
│ │ │ • GetInterface → ICorRuntimeHost │ │ │
│ │ │ • Start() │ │ │
│ │ │ │ │ │
│ │ │ 3. AppDomain 管理 │ │ │
│ │ │ • GetDefaultDomain() → デフォルト AppDomain │ │ │
│ │ │ • CreateDomain(CustomName) → 分離された AppDomain │ │ │
│ │ │ │ │ │
│ │ │ 4. アセンブリ読み込み │ │ │
│ │ │ • アセンブリバイト列で SAFEARRAY (VT_UI1) を作成 │ │ │
│ │ │ • SafeArrayAccessData → セーフアレイにアセンブリをコピー │ │ │
│ │ │ • CustomAppDomain->Load_3(safearray) → メモリにロード │ │ │
│ │ │ │ │ │
│ │ │ 5. 引数準備 │ │ │
│ │ │ • スペース区切りの引数をパース │ │ │
│ │ │ • 各引数に対して SAFEARRAY(VT_BSTR) を作成 │ │ │
│ │ │ • VARIANT 構造体にラップ │ │ │
│ │ │ │ │ │
│ │ │ 6. 実行 │ │ │
│ │ │ • Assembly->EntryPoint() → Main() の MethodInfo を取得 │ │ │
│ │ │ • MethodInfo->Invoke_3(arguments) → 実行 │ │ │
│ │ │ └─> アセンブリが Console に書き込み │ │ │
│ │ │ └─> hFile → 名前付きパイプにリダイレクト │ │ │
│ │ │ │ │ │
│ │ │ 7. クリーンアップ │ │ │
│ │ │ • COM インターフェースを解放 (MethodInfo, Assembly など) │ │ │
│ │ │ • UnloadDomain(CustomAppDomain) → 完全にアンロード │ │ │
│ │ │ • FreeLibrary(mscoree.dll) │ │ │
│ │ └──────────────────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────▼───────────────────────────────────────┐ │
│ │ 出力キャプチャ & 表示 │ │
│ │ • PEB を復元: StdOut/StdErr = 元のハンドル │ │
│ │ • バッファを割り当て (0x10000 バイト) │ │
│ │ • ReadFile(hPipe) → アセンブリの出力をキャプチャ │ │
│ │ • BeaconPrintf(CALLBACK_OUTPUT, output) → オペレーターに表示 │ │
│ └────────────────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────▼───────────────────────────────────────┐ │
│ │ クリーンアップ & 復元 │ │
│ │ • free(pAssemblyStdOut) │ │
│ │ • NtClose(hFile, hPipe) │ │
│ │ • FreeConsole() │ │
│ │ │ │
│ │ if (AMSI_PATCH): │ │
│ │ • RestoreAmsi() - 元の4バイトを書き戻し │ │
│ │ │ │
│ │ if (AMSI_HWBP): │ │
│ │ • RemoveHwbp() - デバッグレジスタをクリア │ │
│ │ • RemoveVectoredExceptionHandler(VehHandler) │ │
│ │ │ │
│ │ if (ETW_PATCH): │ │
│ │ • RestoreEtw() - 元の4バイトを書き戻し │ │
│ │ │ │
│ │ • PEB を復元: StdOut/StdErr = 元の値 │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────┘
│
│ Beacon に戻る
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ Beacon は実行を継続 │
│ (BOF メモリはクリーンアップされる) │
└──────────────────────────────────────────────────────────────────────────────┘
| 方式 | 説明 |
|---|---|
None | 直接API呼び出し |
Draugr | Draugr によるスタック偽装API呼び出し |
Regwait | RegisterWaitForSingleObject コールバック実行 |
| 方式 | 説明 |
|---|---|
None | AMSI バイパスなし |
Patch | AMSI!AmsiScanBuffer のメモリパッチ (xor rax,rax; ret) |
HWBP | VEH 経由のハードウェアブレークポイントフック on AMSI!AmsiScanBuffer |
| 方式 | 説明 |
|---|---|
None | ETW バイパスなし |
Patch | NTDLL!NtTraceEvent のメモリパッチ (xor rax,rax; ret) |
| パラメータ | 説明 | 例 |
|---|---|---|
| PipeName | アセンブリ出力をキャプチャする名前付きパイプ名 | P1p3N4m3 |
| AppDomain | アセンブリ分離用のカスタム .NET AppDomain 名 | Tot4lL3g1t |
LoadLibraryA("amsi.dll") → 直接呼び出し
DRAUGR_API(LoadLibraryA, "amsi.dll")
│
├─ 合成スタック構築
├─ 戻りアドレス偽装
└─ 間接実行
CreateTimerQueue() → CreateTimerQueueTimer(
callback = LoadLibraryA,
parameter = "amsi.dll",
dueTime = 100ms
) → Wait → DeleteTimerQueueEx()
CreateEvent() → RegisterWaitForSingleObject(
event,
callback = LoadLibraryA,
context = "amsi.dll"
) → SetEvent() → UnregisterWait()
パッチ前: パッチ後:
AmsiScanBuffer: AmsiScanBuffer:
4C 8B DC mov r11, rsp 48 31 C0 xor rax, rax
49 89 5B 08 mov [r11+8], rbx C3 ret
... ...
結果: すべてのスキャンが S_OK (クリーン) を返す
方法:
xor rax, rax; ret を書き込むセットアップ:
1. AddVectoredExceptionHandler
2. RtlCaptureContext
3. DR0 = AmsiScanBuffer アドレスを設定
4. DR7 ブレークポイントフラグを有効化
5. NtContinue (コンテキストを適用)
実行フロー:
AmsiScanBuffer が呼び出される
│
▼
#BP 例外 (EXCEPTION_SINGLE_STEP)
│
▼
VEH ハンドラがインターセプト
│
├─ RIP == AmsiScanBuffer を確認
├─ RIP = FindRetInstruction(AmsiScanBuffer) を設定
├─ RAX = 0 (S_OK) を設定
└─ TF (トラップフラグ) を設定
│
▼
RAX=0 で戻る
パッチ前: パッチ後:
NtTraceEvent: NtTraceEvent:
4C 8B D1 mov r10, rcx 48 31 C0 xor rax, rax
B8 XX XX mov eax, syscall C3 ret
通常のアセンブリ (BOF なし): BOF Execute-Assembly:
アセンブリ → Console.WriteLine 1. \\.\pipe\{name} を作成
│ │
▼ ▼
出力は失われる 2. パイプをファイルハンドルとして開く
│
▼
3. PEB ハンドルをリダイレクト:
• StdOut → パイプ
• StdErr → パイプ
│
▼
4. アセンブリを実行
│
▼
5. ReadFile(パイプ)
│
▼
6. BeaconPrintf → オペレーター
メモリ保護変更:
NtProtectVirtualMemory 呼び出しは EtwTiLogReadWriteVm を介してログ記録されるamsi.dll の .text セクションに RW→RX 遷移が発生ntdll.dll の .text セクションに RW→RX 遷移が発生検出: ロードされたモジュールのメモリ保護変更は強力な兆候。
名前付きパイプ作成:
NtCreateFile による \\.\pipe\* パスがミニフィルタードライバーに可視モジュール読み込み:
LdrLoadDll イベントは EDR カーネルドライバーによってログ記録されるスレッドコンテキスト操作 (HWBP 方式):
AllocConsole + ShowWindow(SW_HIDE))Cobalt Strike → Script Manager → Load → BOF_ExecuteAssembly.cna
メニュー: Additionals postex → Execute-Assembly Config

BOF_ExecuteAssembly --assembly /tmp/Ghostpack-CompiledBinaries/Rubeus.exe --args help

beacon> help BOF_ExecuteAssembl

Dockerfile を使用:
sudo docker build -t ubuntu-gcc-13 .
sudo docker run --rm -it -v "$PWD":/work -w /work ubuntu-gcc-13:latest make
または、システムに nasm, make, mingw-w64 (gcc-13 互換) がインストールされている場合:
make
出力: Bin/BOF_ExecuteAssembly.o
Timer | タイマーキューコールバック実行 |
| 技術 | バイパス対象 |
|---|
| 間接システムコール | ユーザーランド API フック (EDR/AV) |
| Draugr スタック偽装 | コールスタック検査ツール |
| AMSI パッチ/HWBP | .NET アセンブリスキャン |
| ETW パッチング | イベントベースの監視 |
| プロキシ DLL 読み込み | LoadLibrary スタックフレーム監視 |
| 名前付きパイプのマレアビリティ | パイプ監視 |
| カスタム AppDomain | デフォルト AppDomain 監視 |