コンポーネント・オブジェクト・モデル(COM)は、1990年代初頭以来、Microsoft Windows開発の基礎となっており、最新のWindowsオペレーティング・システムおよびアプリケーションでも引き続き広く普及しています。COMコンポーネントへの依存と長年にわたる主要な機能開発により、広大な攻撃対象領域が生まれてきました。2025年2月、Google Project ZeroのJames Forshaw氏(@tiraniddo)は、トラップされたCOMオブジェクトを用いて、サーバー側のDCOMプロセスのコンテキストで.NET管理コードを実行できる、分散COM(DCOM)リモート・テクノロジーを悪用する新しいアプローチを詳述したブログ記事を公開しました。Forshaw氏は、特権昇格とProtected Process Light(PPL)バイパスのためのいくつかのユースケースを強調しています。
Forshaw氏の研究に基づいて、Mohamed Fakroud氏(@T3nb3w)は2025年3月初旬にPPL保護を回避する手法の実装を公開しました。Jimmy Bayne氏(@bohops)と私は2025年2月に同様の研究を行い、トラップされたCOMオブジェクトを悪用した概念実証のファイルレス横移動技術を開発しました。
COMはバイナリー・インターフェース標準であり、基盤となるプログラミング言語に関係なく、個別のモジュール式コンポーネントを公開して相互に対話させ、アプリケーションとやり取りできるようにするミドルウェア・サービス層です。例えば、C++で開発されたCOMオブジェクトは、.NETアプリケーションと簡単に連携できるため、開発者はさまざまなソフトウェア・モジュールを効果的に統合できます。DCOMは、COMクライアントがプロセス間通信(IPC)またはリモート・プロシージャ・コール(RPC)を介してCOMサーバーと通信できるようにするリモート・テクノロジーです。多くのWindowsサービスは、ローカルまたはリモートでアクセスできるDCOMコンポーネントを実装しています。
COMクラスは通常、Windowsレジストリに登録され、格納されます。クライアント・プログラムは、COMオブジェクトと呼ばれるCOMクラスのインスタンスを作成してCOMサーバーと対話します。このオブジェクトは、標準化されたインターフェースへのポインターを提供します。クライアントはこのポインターを使用してオブジェクトのメソッドとプロパティにアクセスし、クライアントとサーバー間の通信と機能を促進します。
COMオブジェクトは、脆弱性の露出状況を評価し、悪用可能な機能を発見するための研究対象になることがよくあります。トラップされたCOMオブジェクトは、COMクライアントがプロセス外のDCOMサーバーでCOMクラスをインスタンス化するバグクラスであり、クライアントがマーシャリングバイリファレンス・オブジェクト・ポインターを介してCOMオブジェクトを制御します。状況によっては、この制御ベクトルにセキュリティー関連の論理的欠陥が生じる可能性があります。
Forshaw氏のブログでは、WaaSRemediation COMクラスで公開されるIDispatchインターフェースが、トラップされたCOMオブジェクトの悪用と.NETコードの実行のために操作される、PPLバイパスのユースケースについて説明しています。WaaSRemediationはWaaSMedicSvcサービスに実装されており、NT AUTHORITY\SYSTEMのコンテキストで、保護されたsvchost.exeプロセスとして実行されます。Forshaw氏の優れたウォークスルーは、当社の概念実証のファイルレス横移動技術の応用研究および開発の基礎となりました。
私たちの調査の旅は、IDispatchインターフェースをサポートするWaaSRemediation COMクラスを調査することから始まりました。このインターフェイスを使用すると、クライアントは遅延バインディングを実行できます。通常、COMクライアントには、使用しているオブジェクトのインターフェースと型定義がコンパイル時に定義されています。代わりに、遅延バインディングによりクライアントは実行時にオブジェクト上でメソッドを検出し呼び出すことができます。IDispatchには、ITypeInfoインターフェースを返すGettypeInfoメソッドが含まれています。ITypeInfoには、それを実装するオブジェクトの型情報を検出するために使用できるメソッドがあります。
COMクラスが型ライブラリーを使用する場合、クライアントはITypeLib(ITypeInfo-> GetContainingTypeLibから取得)を介してクエリーを実行し、型情報を取得できます。さらに、型ライブラリーは、追加の型情報を得るために他の型ライブラリーを参照する場合もあります。
Forshaw氏のブログ記事によると、WaaSRemediationは型ライブラリーWaaSRemediationLibを参照し、WaaSRemediationLibはstdole(OLE Automation)を参照しています。WaaSRemediationLibは、そのライブラリーの2つのCOMクラス、StdFontとStdPictureを利用します。TreatAsレジストリキーを変更してStdFontオブジェクトでCOMハイジャックを実行することで、クラスは.NET Framework内のSystem.Objectなど、選択した別のCOMクラスを指し示します。ただし、Forshaw氏は、このオブジェクトがプロセス外のインスタンス化のチェックを実行するため、StdPictureは実行不可能であると指摘しています。そのため、私たちはStdFontの使用に引き続き焦点を当てました。
.NETオブジェクトが私たちにとって興味深いのは、System.ObjectのGetTypeメソッドがあるからです。GetTypeを通じて.NETリフレクションを実行し、最終的にAssembly.Loadにアクセスできます。System.Objectが選択されましたが、この型は.NETの型階層のルートになります。したがって、任意の.NET COMオブジェクトを使用できます。
初期段階が設定されると、想定されるユースケースを現実のものにするために必要なHKLM\Software\Microsoft\.NetFrameworkキーの下に次の2つのDWORD値が追加されました。
最初のテスト作業でCLRと.NETの最新バージョンをロードできることを確認したところ、順調に進んでいることがわかりました。
リモート・プログラムの側面に焦点を当てて、最初にリモート レジストリを使用して.NetFrameworkレジストリ・キーの値を操作し、ターゲット・マシン上のStdFontオブジェクトをハイジャックしました。次に、CoCreateInstanceとCoCreateInstanceExを入れ替えて、リモート・ターゲットのWaaSRemediation COMオブジェクトをインスタンス化し、IDispatchインターフェイスへのポインターを取得します。
IDispatchへのポインターを使用して、GetTypeInfoメンバー・メソッドを呼び出し、サーバーにトラップされたITypeInfoインターフェースへのポインターを取得します。その後呼び出されるメンバー・メソッドはサーバー側で発生します。含まれる目的の型ライブラリー参照(stdole)を特定し、それに続く目的のクラス・オブジェクト参照(StdFont)を導出した後、最終的にITypeInfoインターフェースの「リモート可能な」CreateInstanceメソッドを使用して、StdFontオブジェクト・リンク・フローを(事前のTreatAs操作によって)リダイレクトし、System.Objectをインスタンス化しました。
AllowDCOMReflectionが適切に設定されているため、DCOM上で.NETリフレクションを実行してAssembly.Loadにアクセスし、.NETアセンブリをCOMサーバーにロードすることができます。DCOM上でAssembly.Loadを使用しているため、アセンブリのバイト転送はDCOMリモート・マジックによって処理されるため、この横移動技術は完全にファイルレスです。オブジェクトのインスタンス化からリフレクションまでのこの技術的フローの詳細な説明については、次の図を参照してください。
最初の主な問題は、IDDispatch->Invokeを介してAssembler.Load_3を呼び出すことでした。Invocateは、引数のオブジェクト配列をターゲット関数に渡しますが、Load_3は、1つのバイト配列を受け取るAssembler.Loadのオーバーロードです。したがって、バイトのSAFEARRAYを別のVARIANTのSAFEARRAYでラップする必要がありました。最初は、バイトの単一のSAFEARRAYを渡そうとし続けていました。
もう1つの問題は、適切なAssures.Loadのオーバーロードを見つけることでした。ヘルパー関数は、GetStaticMethod関数を含む、Forshaw氏のCVE-2014-0257コードから取得されました。この関数は、DCOM上の.NETリフレクションを使用して、型ポインター、メソッド名、およびそのパラメーター数が与えられた静的メソッドを検索します。Assembly.Loadには、1つの引数を取る2つの静的オーバーロードがあるため、最終的には八キーなソリューションを使用することになりました。単一の引数を持つLoadの3番目のインスタンスが正しい選択であることに気付きました。
この手法で観察された最大の欠点の1つは、生成されたビーコンの寿命がCOMクライアントに制限されることでした。この場合、武器化バイナリー「ForsHops.exe」のアプリケーションの寿命が制限されます(もちろん、的確な名称です)。そのため、ForsHops.exeがCOM参照をクリーンアップまたは終了すると、リモート・マシンのsvchost.exeの下で実行されていたビーコンも同様に処理されます。.NETアセンブリを無期限にメイン・スレッドにハンティングさせたり、別のスレッドでシェルコードを実行したり、ForsHops.exeにエクスプロイト・スレッドを保留したままにするなど、さまざまなソリューションを試しましたが、どれもうまくいきませんでした。
現在の状態では、ForsHops.exeはビーコンが終了するまで実行され、その時点でレジストリ・オペレーションが削除されます。改善の余地はありますが、それは読者の課題として残しておきます。
Mohamed Fakroud氏が実装を公開した後にSamir Bousseaden氏(@SBousseaden)が提案した検知ガイダンスも、この横移動技術に適用されます。
さらに、次の追加の制御を実装することをお勧めします。
さらに、次の概念実証のYARAルールを活用して、標準のForsHops.exe実行可能ファイルを検知します。
rule Detect_Standard_ForsHops_PE_By_Hash
今回の実装では、Forshaw氏のブログで説明されているCOMの悪用を少し拡張したもので、PPLバイパスのローカル実行ではなく、トラップされたCOMオブジェクトを横移動に利用しています。したがって、ローカル実行を行う実装と同じ検知の影響をやはり受けやすいです。
ForsHops.exe概念実証の横移動コードは、こちらでご覧いただけます。
この研究に対するフィードバックとブログ記事の内容レビューを提供してくださったDwight Hohnstein氏(@djhohnstein)とSanjiv Kawa氏(@sanjivkawa)に特別な感謝を申し上げます。