コンポーネント・オブジェクト・モデル(COM)は、1990年代初頭以来、Microsoft Windows開発の基礎となっており、最新のWindowsオペレーティング・システムおよびアプリケーションでも引き続き広く普及しています。COMコンポーネントへの依存と長年にわたる機能開発により、広大な攻撃対象領域が生まれてきました。2025年2月、Google Project ZeroのJames Forshaw氏(@tiraniddo)は、分散型COM(DCOM)リモート・テクノロジーを悪用する新たな手法を詳述したブログ記事を公開しました。この手法では、トラップされたCOMオブジェクトを利用して、サーバーサイドのDCOMプロセスのコンテキスト内で.NETマネージド・コードを実行できます。Forshaw氏は、権限昇格とProtected Process Light(PPL)バイパスの複数のユースケースを取り上げています。
2025年3月初旬、Forshaw氏の研究を基にMohamed Fakroud氏(@T3nb3w)は、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は、NT AUTHORITY\SYSTEMのコンテキストで保護されたsvchost.exeプロセスとして実行される WaaSMedicSvcサービスに実装されています。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を利用します。StdFont オブジェクトの TreatAs レジストリキーを変更して COMハイジャック を行うことで、クラスは.NETフレームワーク内のSystem.Objectなど、私たちが選んだ別のCOMクラスをポイントするようになります。ただし、Forshaw氏は、このオブジェクトがプロセス外のインスタンス化のチェックを実行するため、StdPictureは実行不可能であると指摘しています。そのため、私たちはStdFontの使用に焦点を当てました。
.NETオブジェクトが私たちにとって興味深いのは、 System.ObjectのGet Typeメソッドのおかげです。GetTypeを通じて.NETリフレクションを実行し、最終的にAssembly.Loadにアクセスすることができます。System.Objectが選択されましたが、この型は.NETの型階層のルートになります。したがって、任意の.NET COMオブジェクトを使用できます。
初期段階が設定された場合、HKLM\Software\Microsoft\.NetFrameworkキーの下に、認識されたユースケースを実現するために必要な他の2つのDWORD値がありました。
最新バージョンのCLRと.NETを初期のテスト作業でロードできることを確認したとき、私たちは正しい道を進んでいることがわかりました。
リモート プログラムの側面に焦点を当てて、最初にリモート・レジストリーを使用して.NetFrameworkレジストリー・キーの値を操作し、ターゲット・マシン上のStdFontオブジェクトをハイジャックしました。次に、CoCreateInstanceとCoCreateInstanceExを入れ替えて、リモート・ターゲットのWaaSRemediationCOMオブジェクトをインスタンス化し、IDispatch インターフェースへのポインターを取得します。
IDispatchへのポインターを使用して、 GetTypeInfoメンバー・メソッドを呼び出して、サーバー内にトラップされているITypeInfoインターフェースへのポインターを取得します。その後呼び出されるメンバー・メソッドはサーバー側で発生します。対象となるタイプ・ライブラリー参照(stdole)を特定し、その後継となるクラス・オブジェクト参照(StdFont)を導出した後、最終的にITypeInfoインターフェースの「リモート可能な」CreateInstanceメソッドを使用して、StdFontオブジェクトのリンクフローを(前のTreatAs操作を介して)リダイレクトしてSystem.Objectをインスタンス化しました。
AllowDCOMReflectionが適切に設定されているため、DCOM上で.NETリフレクションを実行してAssembly.Loadにアクセスし、.NETアセンブリをCOMサーバーにロードすることができます。ここではAssembly.Load over DCOMを使用しているため、アセンブリのバイト転送は DCOM のリモート・マジックによって処理されるため、この横移動手法は完全にファイルレスです。オブジェクトのインスタンス化から反映までのこの技術的フローの詳細な説明については、次の図を参照してください。
最初の主な問題は、IDispatch->InvokeでAssembly.Load_3を呼び出すことでした。Invokeは引数のオブジェクト配列をターゲット関数に渡しますが、Load_3は1バイト配列を受け取るAssembly.Loadのオーバーロードです。したがって、バイトのSAFEARRAYを別のVARIANTのSAFEARRAYでラップする必要がありました。 – 最初は、単一のSAFEARRAYバイトを渡そうとし続けました。
もう1つの問題は、適切なAssembly.Loadオーバーロードを見つけることでした。Helper関数は、GetStaticMethod関数を含む、ForshawのCVE-2014-0257コードから取得されました。この関数は、NET reflection over DCOMを使用して、型ポインター、メソッド名、およびそのパラメーター数を指定して静的メソッドを検索します。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実行可能ファイルを検出します。
ルール Detect_Standard_ForsHops_PE_By_Hash
私たちの実装は、PPLバイパスのローカル実行ではなく、トラップされたCOMオブジェクトを横移動に利用することにより、Forshaw氏のブログで説明されているCOM悪用をわずかに拡張します。したがって、ローカル実行の実装と同じ検出の影響を受けます。
ForsHops.exeの概念実証の横移動コードはこちらでご覧いただけます。
この研究に対するフィードバックとブログ記事のレビューを提供してくださったDwight Hohnstein(@djhohnstein)とSanjiv Kawa(@sanjivkawa)に特別な感謝を申し上げます。