
Inline-Execute-PE は、Beacon Object File(BOF)と、CobaltStrike 用の Aggressor スクリプトのスイートです。これにより、オペレーターはアンマネージド Windows 実行可能ファイルを Beacon メモリにロードして実行し、出力を取得して Beacon コンソールに表示できます。
これにより、オペレーターは多くのサードパーティ製ツール(Mimikatz、Dsquery、Sysinternals ツールなど)を、ディスクに書き込んだり、Donut のようなツールを使って位置独立コードに再フォーマットしたり、新しいプロセスを作成して実行したりする必要がなくなります。
これらの実行可能ファイルは Beacon メモリにマッピングされるため、ネットワーク経由で送信したり、新しいメモリを割り当てたり、毎回新しい conhost.exe プロセスを作成したりすることなく、繰り返し実行できます。
Beacon にロードされた実行可能ファイルは、CobaltStrike Team Server に接続されたすべての CobaltStrike クライアントがアクセスおよび実行できます。
Inline-Execute-PE は、x64 Beacon と、Mingw または Visual Studio でコンパイルされた x64 Windows C または C++ 実行可能ファイル用に設計されています。このプロジェクトは、x86 実行可能ファイルや、別の言語で記述された、または別のコンパイラでコンパイルされた x64 実行可能ファイルはサポートしていません。

リポジトリをクローンし、必要に応じて make を実行して BOF を再コンパイルします。
Inline-Execute-PE.cna を CobaltStrike クライアントにロードします。CobaltStrike が実行されているディレクトリがユーザーによって書き込み可能であることを確認してください。Inline-Execute-PE は、機能に必要なデータの可用性を確保するために、そこにテキストファイル(petable.txt)を作成します。
Inline-Execute-PE は、BOF を実行する 3 つのターゲット向けコマンドと、プロジェクトのデータ構造を操作する 3 つの内部コマンドで構成されています。
ターゲット向け:
内部データ構造:
peload は Inline-Execute-PE の開始点です。このコマンドは、PE を Beacon メモリにロードするために使用されます。以下の主要なアクションを実行します:
perun は Inline-Execute-PE の 2 番目のステップです。以下の主要なアクションを実行します:
peunload は、オペレーターが PE を使い終えたとき、または別の PE をロードしたいときに、Beacon メモリから PE を削除するために呼び出されます。以下の主要なアクションを実行します:
petable は、現在 Beacon にロードされているすべての PE に関する情報を表示するために使用されます。
各 CobaltStrike クライアントは独自の petable を持っています。Inline-Execute-PE は、接続されているすべての CobaltStrike クライアント間でデータの同期を確保するために多大な努力を払っており、すべてのオペレーターが PE を使用できるようにしています。詳細については、「設計上の考慮事項と解説」を参照してください。

peconfig は、Inline-Execute-PE の機能に関するオプションを構成するために使用されます。現在変更可能な 2 つのオプションは次のとおりです:
pebroadcast は、クライアントの petable の内容を、接続されている他のすべての CobaltStrike クライアントに手動でブロードキャストするために使用できます。
その他のすべての CobaltStrike クライアントは、ブロードキャストされたデータで petable を更新します。これが必要になることはほとんどありませんが、念のため機能として存在します。
peload を使用して PE を Beacon メモリにロードします。

または、ターゲットマシン上に新しいプロセスを作成せずに使用したい PE がある場合は、パスと --local スイッチを指定します。

perun を呼び出し、ロードされた PE に任意の引数を渡します。

引数内の二重引用符は、バックスラッシュでエスケープする必要があります。

アンロード時に DLL の解放に問題が発生する PE を特定した場合は、peconfig を使用して unloadlibraries を false に設定します。

PE の使用が完了したら、peunload を呼び出して Beacon からクリーンアップします。

これで、別の PE を Beacon にロードできるようになります。

PE に渡すコマンドライン引数には注意が必要です。一部の PE は間違った引数を与えるとすぐにクラッシュしますが、他の PE は無限に実行され、プロセスはまだ実行中であっても Beacon がコールバックしなくなることがあります。
これは、引数リストの最後に 'exit' を指定しない場合の Mimikatz.exe で確認できます。

...

Inline-Execute-PE は、指定されたタイムアウト値に達すると、実行中の PE のスレッドを終了します。これにより、Beacon が通常の通信を再開できるようになります(perun BOF の実行が完了するまで Beacon はコールバックしません)。この Beacon で通常の CobaltStrike コマンドや他の BOF を引き続き使用することはできますが、Inline-Execute-PE は無効になります。この方法で実行中の PE が終了すると、Beacon プロセスの stdout と stderr が壊れ、後続の PE が適切に機能しないようです。
PE は Beacon メモリからアンロードすることは可能です(また、そうすべきです)。ただし、petable を表示すると、この Beacon にそれ以上 PE をロードできなくなることが示されます。 
Inline-Execute-PE で実行する PE をテストし、perun にコマンドライン引数を渡す際には注意を払うことが必須です。許容度の高い PE もあれば、そうでないものもあります。
以下は、テストと開発中に行った、ユーザーが Beacon にロードしたいと思われる特定の PE に関する観察事項であり、順不同です。
Inline-Execute-PE に関連する IOC には、以下が含まれますが、これらに限定されません:
開発中に EDR に対する完全なバッテリーテストは行いませんでした。怠慢とテスト環境の不足が理由の一部です。ただし、最新パッチの Windows Defender(私の経験ではかなり優れた AV 製品です)に対してテストしました。
Mimikatz.exe はおそらく、Inline-Execute-PE で使用する候補として最も疑わしくよく知られた PE です。Inline-Execute-PE を使用した Mimikatz の実行を Windows Defender が検出できるかどうかは、Beacon が実行されているプロセスに依存することがわかりました。
スタンドアロン実行可能ファイルで実行されている Beacon(artifact kit を使用して Defender を通過して通常どおり実行および動作できる beacon.exe など)は、Inline-Execute-PE で Mimikatz.exe を使用すると検出されます。
Windows プロセスで実行されている Beacon(Explorer.exe、notepad.exe などにインジェクションされたもの、または DLL サイドローディングされた正規のプロセス内のもの)は、Inline-Execute-PE で Mimikatz.exe を使用しても検出されません。
ユーザーランドフックを行う EDR に関しては、テストしていませんが、以下の一般的な考えがあります:
PE は Beacon プロセス内で実行されているため、おそらくすでに NTDLL をアンフック/リフレッシュしているはずです。そのため、PE による API 呼び出しがフラグされることはあまりないと思います。ただし、PE が実際に行うこと(プロセスに触れる、レジストリキーを変更するなど)に関する同じ問題は依然として適用されます。
数ヶ月前、私は RunPE-In-Memory に出会い、CobaltStrike 用の BOF に変換してみようと考えました。その後の道のりは予想よりもはるかに複雑で、時間がかかりました。このプロジェクトは特に困難でした。なぜなら、それはそれ自体が独立したツールではなく、他のツールを実行するためのツールだからです。これには、幅広い PE と、それらの PE が同じタスク(引数の取得、終了など)を達成するためのさまざまな方法との互換性を確保するために、多大な柔軟性と努力が必要です。
当初、Inline-Execute-PE は、PE のロード、実行、解放をすべて担当するオールインワンの BOF として構想されていました。プロジェクト開始から約 3 週間後、POC が約 75% 完成した時点で、約 1.5 年前にリリースされ、私がやろうとしていたことのほとんどをすでに実行していた PEzor を発見しました。主な違いは、PEzor が手動で元の PE をメモリにマッピングするのではなく、内部で Donut を呼び出して PE をシェルコードに変換することでした。
この発見は、ある意味では歓迎すべきものであり、別の意味では残念なものでした。成熟したプロジェクトからインスピレーションを得て、コードのいくつかの難所を乗り越えるのに役立つのは素晴らしいことでしたが、知らず知らずのうちに車輪の再発明をしていたことに気づき、がっかりしました。PEzor について調べ、その設計、トレードクラフト関連の問題、および所属組織の運用上のニーズについて考えた後、Inline-Execute-PE の方向性を今日の形に変更しました。この決定は、以下で説明するいくつかの要因に基づいています。また、ここまで読んでいただいた方々が疑問に思うかもしれない、いくつかの興味深い設計上の選択についても説明します。
運用経験を検証した結果、ツールを繰り返し実行する必要がある複数のインスタンスとツールが見つかりました。PEzor では、オペレーターは毎回 PE をネットワーク経由で送信し、conhost.exe を作成し、Beacon に新しいメモリを割り当てるなどする必要があり、AV/EDR の観点からは望ましくない可能性があると考えられました。この考え方から、.PS1 を繰り返し使用するために Beacon にロードできるのと同様に、PE を Beacon に「ロード」するというアイデアが生まれました。conhost.exe は PE が最初にロードされたときに作成され、PE がメモリにロードされている間存続します。同様に、PE が最初にロードされたときに新しいメモリが一度割り当てられ、使用するたびに PE をネットワーク経由で送信する必要がなくなります。Inline-Execute-PE が採用したモデルには欠点がないわけではなく、さまざまな程度の成功を収めて対処しようとしました。
人目を引く設計上の選択肢は、Inline-Execute-PE が PE を Beacon に 2 回マッピングするという事実です。これは確かに望ましいことではなく、自ら進んで行った選択でもありませんが、必要性から生まれました。前述のように、Inline-Execute-PE は PE 内のコマンドライン引数に関連するいくつかの関数をフックする必要があります。マッピングされた PE は Beacon プロセス内で実行されるため、PE は PEB の PROCESS_PARAMETERS セクションで指定されたコマンドライン引数を使用しようとします。これを回避するために、PE がコマンドライン引数を取得するさまざまな関数のいずれかを呼び出すときに、PE を独自のカスタム定義関数に誘導し、perun を使用して CobaltStrike から渡された目的の引数を提供できるようにする必要があります。
これはうまく機能しますが、開発中にいくつかの異なる PE で奇妙なことに気付きました。PE が初めて実行されたときは、PE の IAT に提供したカスタム定義関数が適切に呼び出されましたが、その後 PE が異なる引数で実行されるたびに、PE はカスタム定義関数を呼び出さず、したがって CobaltStrike から渡された引数を受け取りませんでした。内部で実際に何が起こっているのかはわかりませんが、PE が一度実行されると、どこかのメモリにコマンドライン引数をコピーし、後続の実行では、初回のようにフックされた関数を呼び出す前に、そのメモリ位置を最初に参照するのではないかと考えています。この理論を、引数を含むポインタの配列へのポインタへのポインタが存在するメモリ位置を取得し、実行ごとにこのメモリ位置を適切なポインタを含むように手動で変更することで裏付けました。これは __getmainargs 関数と __wgetmainargs 関数では機能しましたが、__p___argv や __p___argc などの代替関数を呼び出す他の PE では、この方法は機能しませんでした。
PE が引数を取得するためにフックされた関数を実際に呼び出す状態に「リセット」できるようにするために、peload 中に PE の 2 番目のコピーをメモリに作成することにしました。このコピーも XOR 暗号化されており、Inline-Execute-PE のライフサイクル全体を通じて RX 保護で保持され、perun を使用して実際に実行される PE のコピーを上書きするために使用されます。前述のように、これは完全な解決策ではありませんが、さまざまな PE とそれらが使用するさまざまな API に対する解決策を模索する必要なく、すべての PE をカバーする包括的な解決策です。
Inline-Execute-PE の主要なセールスポイントの 1 つが、新しいプロセスを作成せずにツールを実行できることであるため、そのために新しいプロセス(conhost.exe)を作成しなければならないのは大きな痛手です。この要件は、コンソールが存在しない限り、Windows プログラムでは標準ストリーム(stdin/stdout/stderr)が初期化されないという事実に起因します。今回のケースではコンソールはまったく必要ありません。標準ストリームは匿名パイプにリダイレクトされ、そのようにキャプチャされますが、conhost がないとストリームは初期化されず、リダイレクトできません。
Inline-Execute-PE は、PEzor と同じ方法で conhost の問題にアプローチします。AllocConsole を呼び出し、その後すぐに ShowWindow を使用して非表示にします。8 GB RAM の Windows 11 VM では、コンソールウィンドウが一瞬表示されて消えることはありませんが、ターゲットシステムによって異なります。
最近、ネイティブの同等品(実際にははるかに高度なバージョン)をリリースした非常に高度な商用 C2 を開発している開発者と話しました。彼は、conhost.exe を生成せずに「Windows にコンソールがあると思い込ませる」ことで回避できたと教えてくれました。この情報を基に、約 1 週間かけて、Windows プログラムが conhost とどのように相互作用するかについてのドキュメントをインターネットで探し、WinDBG での書き込み関数とコンソールに関連する API 呼び出しをトレースしようとし、さらに驚くべきことに Github で入手可能な Windows Terminal のソースコードを調べました。PEB と標準ストリーム関連の事柄について多くのことを学びましたが、この取り組みからは空振りに終わりました。解決策の道筋は、kernel32 の特定のコンソール関連関数をパッチすることかもしれませんが、わかりません。正直なところ、解決策を見つけられなかったことにはかなり失望していますが、独学でキャリアも数年しかないので、おそらく予想通りでしょう。### PE タイムアウトとレスキュー BOF を書いたことのある人なら誰でも、BOF に伴う利点がある一方で、BOF のエラーやクラッシュが Beacon を強制終了させる可能性があるという大きな危険性をご存知でしょう。このプロジェクトでは、ユーザーが Inline-Execute-PE に渡すデータをどの程度制御できるか、また、開発者である私が簡単かつ確実に実装できる安全策がどれほど少ないかという性質上、その危険性はさらに増幅されます。例えば、ユーザーは x86 PE を x64 Beacon にロードしたり、先ほど触れたように、マッピングされた PE に不適切な引数を渡すことで Beacon をクラッシュさせる可能性があります。PE に誤った引数を渡して Beacon をクラッシュさせるのを防ぐことはできませんが、Mimikatz で 'exit' が指定されていない場合のように、無限に実行され続ける PE から Beacon を救出することは試みることができます。
理想的には、PE の実行を停止させ、Beacon が通常の機能を再開できるようにし、その後すぐにユーザーが (今回は正しい引数で) 再試行できるようにしたいところです。実際には、PE を終了させると stdout/stderr に関連付けられた FILE* が壊れてしまい、PE 全体をアンロードして新たにロードし直しても解決しないことがわかりました。これらはプロセス全体で壊れたままになります。
'timeout' オプションを超えて実行され続ける PE を終了させるには、CreateThread から返されたハンドルに対して TerminateThread が呼び出されます。これによりスレッドが正常に終了することはないため、何かが壊れるのも道理です。私は スレッドハイジャッキング を実装することでこの問題を緩和しようと試みました。目的は PE スレッドを一時停止し、その実行を ExitThread() API にリダイレクトすることです。ここでの期待は、スレッド自体が終了処理を開始した場合 (外部から強制的に終了させられた場合とは異なり)、stdout/stderr が引き続き機能するかもしれないというものでしたが、結局同じ問題が発生しました (さらに、Mimikatz の場合に PE スレッドを一時停止できないという問題も発生しました)。
この問題を緩和できなかったため、最終的には、影響を受けた Beacon で PE を実行し続けたり、追加の PE をロードしたりすることをユーザーに許可しない (そうすればクラッシュが発生する) という方法に落ち着きました。これも Inline-Execute-PE が本来あるべき姿に及ばない例ですが、少なくともオペレーターは Beacon を保持し、通常の機能に使用できるという事実で妥協しました。
このプロジェクトで困難だった点の 1 つは、チームサーバーに接続されているすべての CobaltStrike クライアントが、Beacon にロードされた PE を利用できることを保証することでした。Inline-Execute-PE のデータは、Inline-Execute-PE.cna によって作成された構造体に格納され、ツールを使用する各クライアントにロードされる必要があります。その結果、これらのデータ構造は各クライアント内に存在し、チームサーバー上には存在しません。このデータが単一の中央ロケーション (TS) に存在するのであれば、各クライアントからデータを取得するのは簡単で、この問題は発生しなかったでしょう。CobaltStrike チームが Inline-Execute-PE のような機能を正式に CobaltStrike に統合するなら、間違いなくこの方向を取るでしょう。しかし、これはコミュニティのアドオンであるため、現状でできることをやっています。
Beacon にロードされた PE に関する最新かつ正確なデータを各 CobaltStrike クライアントが確実に保持するためには、いくつかのシナリオを考慮する必要があります。
これらのシナリオに対処するために、多面的なアプローチが取られました。単一の CobaltStrike クライアントのみが TS に接続されている (したがって petable データを持つ唯一のエンティティである) 場合に対処するため、クライアントが petable を変更 (peload、peconfig、peunload など) するたびに、その petable の内容を CobaltStrike ディレクトリ内のローカルテキストファイルに書き出します。クライアントが終了/再起動した場合、または Inline-Execute-PE.cna が再読み込みされた場合、最初にローカルの petable.txt ファイルから読み取り、メモリ内の petable を設定しようとします。
複数のクライアントが TS に接続されており、新しいクライアントが参加した場合 (イベントログによる)、各クライアントは TS に接続されているすべてのユーザーのリストを取得し、アルファベット順に並べ替えます。そのリストの最初にあるクライアントが「ブロードキャスト」クライアントとして選択され、5 秒間待機した後 (新しいクライアントが初期化され、ローカルの petable.txt を読み取るため)、自身の petable の各エントリに対するメッセージ (アクション) をイベントログに送信します。すべてのクライアント (ブロードキャスト中のものを除く) はこれらのメッセージを読み取り、ブロードキャスト情報で自身の petable を更新します。これには、既存のエントリの更新と、自身の petable に含まれていない追加のエントリの追加が含まれます。
Inline-Execute-PE に関する通常の操作も、イベントログでのメッセージ送信に依存しています。クライアント A が peload を実行すると、関連するすべての petable 情報を含むメッセージがブロードキャストされます。すべてのクライアントは、"on Event_Action" フックを使用してこれらのブロードキャストされたイベントログメッセージを解析し、それぞれの petable を更新します。また、peload と peunload が BOF の実行を完了したときに、Inline-Execute-PE データへの変更が行われます。これらの変更は Beacon によって (例えば、peload の実行後、Beacon が pMemAddrs 構造体のメモリ位置をコールバックする) 通信され、そのため接続されているすべてのクライアントから認識可能であり、"on Beacon_Output" フックを使用してそれぞれの petable を更新します。
これらの個別の取り組みを組み合わせることで、Inline-Execute-PE は複数のクライアント間で重要なデータを効率的かつ確実に同期できます。
このプロジェクトは、以下のプロジェクトとリソースなしには実現しませんでした。これらは頻繁に参照され、このプロジェクトの中核部分の源泉となりました。著者の皆様のコードとビジョンに多大なる感謝を捧げます。