Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2024-6769 — アクティベーションキャッシュポイズニングにより、整合性レベルを中から高に昇格させる (CVE-2024-6769) | Kitploit
ツール/GitHubGitHub/fortra/cve-2024-6769
特権昇格脆弱性分析エクスプロイト論文と研究学習と教育バイナリエクスプロイト
GitHubfortra/cve-2024-6769

CVE-2024-6769

アクティベーションキャッシュポイズニングにより、整合性レベルを中から高に昇格させる (CVE-2024-6769)

リポジトリを見る
78111年前Kitploit レビュー済み

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

Blogpost: CVE-2024-6769 アクティベーションキャッシュの汚染による中整合性から高整合性への昇格

このブログは、2つの連鎖したバグについてのものです。ステージ1はROOTドライブの再マッピングによって引き起こされるDLLハイジャックバグであり、ステージ2はCSRSSサーバーによって管理されるアクティベーションキャッシュポイズニングバグです。

最初のステージは、Ekoparty 2023において、BlueFrost SecurityのNicolás Economou氏による「I'm High」というプレゼンテーションで詳細に発表されました。彼は、当時まだMicrosoftによってパッチが適用されていなかった脆弱性をどのように悪用するかを説明しました。これにより、MEDIUM INTEGRITYのユーザーが昇格して限定的なHIGH PRIVILEGESを取得できるようになりましたが、完全なAdministratorになるための完全なアクセス権はありませんでした。

2番目のステージはそのカンファレンスでは発表されませんでしたが、研究を始めるためのいくつかの手順が示唆されました。

まず、導入の文脈として最初のステージを復習します。そこから、2番目のステージに関する私の研究に進み、限定的なHIGH INTEGRITYから完全なAdministratorへの完全な昇格を達成する詳細を掘り下げます。これには、すべてのWindowsバージョン向けの両ステージの完全な動作PoCが含まれており、Windows 10、Windows 11、Windows Server 2022、Windows Server 2019で、すべての更新プログラムが適用された状態で正常にテストされています。

目次:

  • 最初のステージの復習
  • 2番目のステージを悪用するための手順
  • アクティベーションキャッシュとは何か?
  • ALPC攻撃ベクトルを使用したアクティベーションキャッシュの汚染
  • システムがアクティベーションコンテキストを受け入れる方法
  • アクティベーションキャッシュを汚染する方法
  • 埋め込まれたXMLマニフェストがどのように読み取られるか
  • 埋め込まれたXMLマニフェストがどのように解析されるか
  • 偽のimm32.dllがどのようにロードされるか
  • ビデオデモ
  • 機能的な概念実証
  • TL;DR 悪用手順の簡単な説明

最初のステージの復習

A red square with white text and a number on it Description automatically generated

このステージの唯一の要件は、初期プロセスがMEDIUM INTEGRITY LEVELで開始され、ユーザーがAdministratorグループに属していることです。

最初のステージの悪用は、以下の手順に要約できます。

  1. NtCreateSymbolicLinkObject関数を使用したROOTドライブの再マッピング

例: ディスクを

"C:\" から "C:\users\public" に再マッピング

これにより、"system32"フォルダも

"C:\windows\system32" から "C:\users\public\windows\system32" に再マッピングされます

  1. 再マッピング後、一部のサービスが影響を受け、新しい偽のユーザー制御のsystem32からライブラリをロードしようとします。

影響を受けるプログラムの1つはCTFMONで、これはHIGH INTEGRITY LEVELで実行されますが、Administrator権限はありません。

通常、実際のsystem32フォルダからMsCtfMonitor.dllというモジュールをロードしようとしますが、ROOTドライブが再マッピングされたため、私たちの制御する偽のsystem32内で同じ名前のDLLを作成して配置できる場所でMsCtfMonitor.dllを探します。

  1. MsCtfMonitor.dllの作成

この時点で、偽のsystem32フォルダに私たちのバージョンのMsCtfMonitor.dllを配置することで、そのDoMsCtfMonitor関数が呼び出され、HIGH INTEGRITY LEVELで私たちのコードが実行されます。

  1. DoMsCtfMonitor関数にMessageBoxAを配置します。MsCtfMonitor.dllがロードされると、"TRIGGER"というMessageBoxAが表示されます。

  1. DLLがHIGH INTEGRITY LEVELで実行されるCTFMONプロセスにロードされたことを確認します。

同時に、プロセスがHIGH INTEGRITY LEVELであるにもかかわらず、Administrator権限を持っていないことを確認できます。

2番目のステージの悪用手順 A red square with white text and a number on it Description automatically generated

NicolasはEkopartyのプレゼンテーションで、悪用を完了するための以下の手順を提案しました。

これは単純に見えますが、リバーシングとデバッグに多くの時間を要します。

この攻撃ベクトルの歴史を少し掘り下げると、アクティベーションコンテキストキャッシュの汚染がいくつかのエクスプロイトで使用されてきたことが明らかになりました。したがって、以前にどのように悪用が行われてきたかを学ぶことは、追加の文脈と洞察を提供するために価値があります。この悪用の詳細は、Zero Day Initiativeの記事『Activation Context Cache Poisoning: Exploiting CSRSS for Privilege Escalation』で入手できます。

アクティベーションキャッシュとは何か?

アクティベーションキャッシュの使用は、プログラムが特定のバージョンを必要とするライブラリをロードするときに発生します。

例えば、アプリケーションがC:\Windows\System32\comctl32.dllをロードしようとする場合、その場所にあるcomctl32.dllがアプリケーションが必要とするバージョンである保証はありません。これがアクティベーションコンテキストキャッシュの基本的な使用例です。プログラムはCSRSSサーバーにリクエストを送信して、新しいアクティベーションコンテキストエントリをキャッシュに追加し、そのプログラムが必要とする特定のライブラリバージョンをロードできるようにします。

この目的のために、いわゆるマニフェストが使用され、XML形式で記述されます。通常はEXEまたはDLLファイルにリソースとして埋め込まれます。あるいは、Windowsはプログラムの実行可能ファイルと同じフォルダ内のマニフェストファイルを検索します。

上記のURLには、古いエクスプロイトで使用されたマニフェストファイルの例がいくつかあります。例えば、PATH TRAVERSAL手法によって攻撃者が制御するディレクトリからライブラリadvapi32.dllをロードするようにシステムを騙すものなどです。

もちろん、一部の使用された攻撃ベクトルはパッチが適用され、新しい手法が発見されました。さらに、Windows 11 22H2向けの2022年10月のパッチで、新しいチェックが追加されました。

このパッチが実装された後、Activation Context (ACTX) が登録される際のチェックは、キャッシュに新しいエントリを追加するプロセスが、それを使用するプロセスと同じかそれ以上のRIDを持つ場合にのみバイパスできます。

winnt.hでRID値を確認できます。

このチェックをバイパスする提案は、細工されたDLLが実行されるCTFMONプロセスからActivation Contextを含むリクエストを作成することです。この細工されたDLLはRID=0x3000を持ち、エントリがキャッシュに追加された後、RID=0x3000のTCMSETUPがtapi32.dllをロードします。

手順に従おうとした際、CreateActCtx を使用してACTXを登録するすべての可能な組み合わせを試しました。これを避けるチェックが常に存在するため、不可能であることが判明しました。

この関数はユーザーランドにあり、kernel32.dllによってエクスポートされていることに注意することが重要です。チェックはメモリ内のDLLにパッチを適用することで回避できます。これは非常にエレガントではありませんが、可能であり、機能するはずです。

NicolasのプレゼンテーションスライドはLOW LEVELを使用することを示唆しています。しかし、ウインクの顔に注目すると、パッチなしでこのバグを悪用する場合、CreateActCtxを使用することは最善の選択肢ではないことが明らかです。

ALPC攻撃ベクトルを使用したアクティベーションキャッシュの汚染

Advanced Local Procedure Call (ALPC) は、Windowsオペレーティングシステム内で高速にメッセージを送信するために使用されるプロセス間通信メカニズムです。標準のWindows APIとは異なり、ALPCはアプリケーションから直接利用することはできません。代わりに、Windowsオペレーティングシステムのコンポーネントのみがアクセスできる内部メカニズムです(そして私たちも)。

さらに調査を進めると、いくつかの古いキャッシュポイズニングエクスプロイトがALPCを使用してサーバーと直接通信していたことに気付きました。例として、Philip Tsukermanの記事『Activation Contexts—A Love Story』を参照できます。

CsrClientCallServer関数は、Win32プロセスとCSRSSプロセス間のALPCインターフェースを実装しています。

したがって、サーバーとして機能するCSRSSプロセスに対してCsrClientCallServerを使用して呼び出しを試みる必要があります。

古いエクスプロイトの例を探していると、Packet Stormで関連するヒープバッファオーバーフローの問題に関するページを見つけました。

CSRSSサーバーが正しいパッケージで呼び出されると、モジュールsxssrv.dllに属するBaseSrvSxsCreateActivationContextFromMessage関数で受信されます。

この関数は引数が1つだけあります。受信したパケットへのポインタです。これをリバースするために、カスタムのTotalMessage構造体を作成しました。

TotalMessage構造体パケットは、最初の0x40バイトがHEADERで、その後ろに埋め込まれたActivation Context Messageが続き、その構造体は**_BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG**です。

TotalMessage構造体は以下のとおりです。

そして、_BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG構造体は次のとおりです。

この構造体の中には、言語またはCultureFallbacks、AssemblyDirectory、TextualAssemblyIdentity、AssemblyNameに対応する6つのUNICODE_STRINGSと、それぞれ内部に1つのUNICODE_STRINGを含む2つの**_BASE_MSG_SXS_STREAM**構造体があります。

以下は**_BASE_MSG_SXS_STREAM**構造体です。

サーバーが受け入れる有効なパケットを作成するのが難しいため、その方法を詳しく説明する価値があります。

_BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG内のFlagsフィールドの値は非常に重要であり、多くの組み合わせがあります。正しいフラグ値がなければ、このバグを利用することはできません。

例えば、私のMsCtfMonitor.dllコードを考えてみてください。何度も試行した結果、このバグの悪用に正しいflags値は0x41であると結論付けました。

異なる値の組み合わせにより、誤ったパスフラグ値が生じる可能性があります。

同じTotalMessage構造体は、サイズ0x40バイトのヘッダーを持ちます。残りの0x1f8バイトは**_BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG**構造体用に予約されています。

struct TotalMessage

{

signed __int64 pad[8];

_BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG message;

};

割り当てサイズは0x40+0x1f8です。

次に、文字列をまとめて、tapi32.dllのアクティベーションキャッシュコンテキストを実行しました。これはTCMSETUPというプロセスによってロードされる非常にまれに使用されるDLLです。このプロセスはHIGH PRIVILEGES INTEGRITY LEVEL (RID=0x3000) を持ち、Administratorと同じ権限を持ちます。

私のDLLコードでは、CaptureUnicodestring関数が呼び出されます。これは最終的にCsrCaptureMessageStringを呼び出します。

NTSTATUS CaptureUnicodeString(LPVOID CaptureBuffer, PSTR OutputString,

PCWSTR String, ULONG Length = 0) {

if (Length == 0) {

Length = lstrlenW(String);

}

return CsrCaptureMessageString(CaptureBuffer, (PCSTR)String, Length * 2,

Length * 2 + 2, OutputString);

}

この手順は、パッケージを正しく準備し、システムが私のパケットの文字列をCSRSSプロセスにコピーできるようにするために必要です。これにより、文字列が有効に保たれ、私のポインタがそのコンテキスト内の有効なポインタに置き換えられます。

また、埋め込まれたXMLマニフェストを追加し、その中に"Tasks"言語を入れました。これは存在しない言語ですが、悪用の鍵となります(このアイデアはNicoに感謝します)。

私のコードのもう一つの重要な詳細は、CaptureBufferが作成される方法です。CsrAllocateCaptureBuffer関数には、管理してサーバーにコピーするUNICODE_STRINGSの数を定義する引数があります。

私の場合は"4"つの文字列を使用しました。

値"4"の引数は以下に示されています。

アクティベーションサーバーに到達するために、CsrClientCallServer関数は、私のMsCtfMonitor.dllから、前述の古いエクスプロイトと同じApiNumber0x1001001Eで私のパケットを送信します。

Geoff Chappellのブログでは、CsrClientCallServerの詳細が提供されています。

以下がCsrClientCallServerへの呼び出しです。

そして、これが私のDLLで構築された送信されるパッケージです。

Manifest.Offsetの値は、私の埋め込まれたXMLマニフェストを指しています。

アクティベーションプロセスをログに記録するための興味深いコマンドはsxstraceで、ターゲット内の管理者コンソールで使用します。

このコマンドはトレースを有効にし、ログ結果をsxstrace.etlに保存します。(ENTERキーを押してトレースを終了します。)

sxstrace trace -logfile:sxstrace.etl

生のsxstrace.etlファイルは、読み取り可能な形式に変換できます。

sxstrace parse -logfile:sxstrace.etl -outfile:sxstrace.txt

システムはアクティベーションコンテキストを受け入れるか?

パッケージが正しければ、csrssプロセスのsxssrvモジュール内のBaseSrvSxsCreateActivationContextFromMessage関数に到達するはずです。そのため、リモートカーネルをデバッグする際には、このプロセスにコンテキストを切り替える必要があります。次に、ユーザーモードシンボルをリロードしてブレークポイントを設定する必要があります。

私はIDA PROをWindbgプラグインと共にカーネルデバッグに使用しました。

BaseSrvSxsCreateActivationContextFromMessageで停止すると、RCXはTotalMessage構造体を指します。

最初の0x40バイトのHEADER(システムによってクライアントプロセスのPIDなどの値が入力されます)の後、_BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG構造体に属する私のアクティベーションメッセージが確認できます。

文字列へのポインタは、送信時と同じ値ではないことに注意してください。

ただし、正しく文字列を指しています。

パケットがクライアントからサーバーに送信されると、システムは私のプロセスから文字列をCSRSSプロセスにコピーし、私のパケット内のポインタをそのコンテキストで有効なものに変更しました。

その後、BaseSrvSxsCreateActivationContextFromMessage関数は文字列が有効かどうかをチェックします。

ループ内で6つの文字列をチェックしますが、チェックを問題なく通過します。私の場合は4つの文字列のみ渡し、残りの2つはゼロです。

その他の軽微なチェックの後、アクティベーションプロセスで最も重要な関数であるBaseSrvSxsCreateActivationContextFromStructExを呼び出します。

アクティベーションキャッシュを汚染する方法?

BaseSrvSxsCreateActivationContextFromStructExに到達すると、r8は**_BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG**、つまりアクティベーションメッセージを指します。

フラグの値を評価します。私の場合は値は0x41で、0xDに対して評価されます。

test関数は、プロセッサアーキテクチャを検証するためのフラグオプション(1)を使用してバイパスできます。

その後、呼び出し元プロセスのRIDを取得し、以降の比較のために保存します。この場合、CTFMONはHIGH INTEGRITY LEVELを持つため、RIDは0x3000です。

この関数で最も重要な部分は、BaseSrvActivationContextCacheLookupEntryの呼び出しです。

Activation Context Cacheを検索して、tapi32.dllのエントリが存在するかどうかを判断します。

BaseSrvActivationContextCacheCompareEntriesという関数を呼び出し、Activation Messageエントリの特定の部分をキャッシュ内のすべての既存エントリと比較します。

私のパケットで送信されたLastWriteTimeの値を、すべてのエントリの同じ値と比較します。

私は以前にtapi32.dllでGetFileTimeを使用してこの値を計算し、アクティベーションパッケージ内に送信しました。

tapi32.dllのエントリは存在しないため、比較は一致しません。予想通り、エラー0xC0000225が返されます。その後、私のACTXがキャッシュに追加するのに適しているかどうかをチェックします。

サーバーは私のXML埋め込みマニフェストと、それを指すManifest.Offsetアドレスを読み取る必要があります。ただし、この新しいコンテキストでは、まだ有効なポインタではありません。この値にブレークポイントを設定して、私のXML埋め込みマニフェストがこの値を使っていつどのように読み取られるかを確認する価値があります。

埋め込まれたXMLマニフェストがどのように読み取られるか?

CSRSSが私のACTXリクエストで送信された埋め込みXMLマニフェストをどこで読み取るかを確認するには、Manifest.Offsetにブレークポイントを設定する必要があります。さらに、停止するたびに別のアドレスにコピーされる場合、ブレークポイントを追加する必要があります。

Manifest.Offset値のアドレスを読み取るときにブレークポイントで停止します。

このアドレスを使用して、CTFMONプロセスから埋め込みXMLマニフェストをNtReadVirtualMemoryを使って読み取ります。これは、Manifest.Offsetフィールドに配置されたアドレスがそのコンテキストに属しているためです。

私の埋め込みXMLマニフェストが読み取られ、宛先バッファにコピーされます。

CTFMONプロセスコンテキストに切り替え、以前送信したManifest.Offsetアドレスに私の埋め込みXMLマニフェストがあることを確認します。私の場合は0x7ff93a261470でした。

読み取られた埋め込みXMLマニフェストは、SxSGenerateActivationContextから呼び出されます。キャッシュ内の有効なエントリが見つからないため、埋め込みマニフェストを使用して「生成」を試みます。

そこから、私の埋め込みXMLマニフェストの解析が開始されます。

埋め込まれたXMLマニフェストがどのように解析されるか?

最後のコールスタックを見て、RtlReadOutOfProcessMemoryStreamの呼び出しにブレークポイントを設定し、バッファが完全に満たされたときに停止することにしました。

次に、文字列"Tasks"にアクセスブレークポイントを設定して、サーバーによって読み取られたか処理されたときに停止できるようにします。

埋め込まれたXMLマニフェスト内のtasks文字列はこちらです。

読み取りとコピーで数回停止します。

文字列"tasks"をワイド文字に変換するCharEncoder::wideCharFromUtf8で停止します。

次にXMLパーサーで停止します。

関数名parseAttributesが示すように、XML属性の解析を続行します。

次に、ValidateElementAttributesから呼び出されたmemcpyで停止します。

コピーされる場所に別のブレークポイントを設定できます。

関数名SxspValidateLanguageAttributeが示すように、言語属性を検証します。

再度memcpyで停止しますが、今回はSxspCreateAssemblyIdentityfromIdentityElementから呼び出されています。

もう一度、SxsInsertAssemblyIdentityAttribute+0xc48から呼び出されたmemcpyで停止します。

その後、SxsInsertAssemblyIdentityAttribute で停止します。

BufferedStream::prepairForInput から memcpy を最後に呼び出します。

次に、以下の場所で文字列 tasks を読み取ります。

続いて、ここから読み取ります。

ここから読み取りを続けます。

これらの関数の名前に注目しました。ProbingCandidate という名前には、SXS テキストログファイルで使用されている同じ単語 (probing manifests) が含まれています。

再びここで停止します。

次に、GetFileAttributesExW を使用して、SXS テキストログに記載されている最初のファイルが存在するかどうかを確認します。存在しないため、ゼロを返します。

ファイル確認の順序はログファイルで確認できます。

2 番目のファイルは、tasks フォルダ内の tapi32.dll へのパスであるため存在しません。

そこから、tasks 内で tapi32.manifest を「プローブ (probing)」しているように見えます。

続いて CProbedAssemblyInformation::ProbeManifestExistence に到達します。

tasks フォルダに自分のマニフェストファイルが存在するかどうかを確認します。存在するため、エラーなしで戻ります。

さて、tasks フォルダ内の tapi32.manifest が見つかりました。

サーバは、内部に「tasks」という言語値を含む私の 埋め込み XML マニフェスト によって、system32 の tasks サブフォルダ内のマニフェストファイルを検索するように強制されました。

パスが使用される場所を確認するためにブレークポイントを設定し続けると、tapi32.manifest ファイルの内容が読み取られる EncodingStream::Read で停止します。

次に、TAPI32.manifest ファイルの内容を解析します。エラーがある場合は SXS TRACE ログに表示されるため、修正が容易になります。

私の TAPI32.manifest ファイルが正しく解析されると、エラーなしで BaseSrvSxsCreateActivationContextFromStructEx に戻ります。これにより、文字列 FAILED を含むメッセージが出力されるのを防ぎます。

私の場合、Activation Context の生成は成功し、私の TAPI32.manifest ファイルが使用されました。

次に、自分のエントリがキャッシュに挿入される呼び出しに到達しました。

問題なく通過し、ゼロを返します。これが正しい値であり、細工された TAPI32.manifest を含むエントリが正常に挿入されます。

私のエントリは Activation キャッシュに含まれ、サーバは CTFMON の DLL からの呼び出しに対して OK 応答を返します。

ログテキストファイルは完全なプロセスを示しています。

埋め込み XML マニフェスト を読み取ります。その言語が "Tasks" であるため、system32 の "Tasks" サブフォルダ内の新しいマニフェストファイルを検索します。これは、言語が "en-us" に設定されている場合に system32 のサブフォルダ "en-us" 内のマニフェストを検索するのと同じです。

SXS ログテキストファイルには、「Activation Context generation succeeded」というメッセージが表示されます。

偽の imm32.dll はどのようにしてロードされたのか?

自動生成されたボクシンググローブの説明

私の ACTX エントリがキャッシュに追加された後、tcmsetup.exe が実行されると、tapi32.dll がロードされ、私のマニフェストファイルを使用して imm32.dll がロードされるはずです。

しかし、それはそれほど単純ではありません。imm32.dll のロードを妨げる可能性のあるチェックがいくつかあるためです。

チェックは、同じ BaseSrvSxsCreateActivationContextFromStructEx 関数への後続の呼び出しで行われるため、すべてのブレークポイントを削除し、その関数にのみブレークポイントを残します。

そこから、コンソールから TCMSETUP.EXE を実行できます。ただし、私の PoC は、Activation Cache の改ざんが完了した後、MsCtfMonitor.dll から TCMSETUP を実行します。

ブレークポイントは何度も停止します。停止するたびに、r8 が指す構造体を調べて、tapi32.dll に関連するリクエストかどうかを確認します。

他のモジュールに対する多くの停止の後、TCMSETUP.exe のリクエストが表示されます。

コールスタックを見ると、プロセスの作成時に呼び出されていることがわかります。これは、TCMSETUP の Activation キャッシュにエントリがあるかどうかを確認するために呼び出されます。

TAPI32.dll に対する呼び出しが到着するまで実行を続けます。その前に、TCMSETUP に対するいくつかの呼び出しがあります。

最終的に、到着するパケットは、以前に DLL からキャッシュにエントリを挿入したときのパケットと非常に似ている必要があります。ただし、今度は TCMSETUP が TAPI32.dll をロードしようとしたときに停止します。

この時点で、このパッケージ内のいくつかの重要な値に気付きました。

_BASE_SXS_CREATE_ACTIVATION_CONTEXT_MSG 構造体の先頭から 0x40 バイト上に拡張して、TotalMessage 構造体を割り当てます。TAPI32.dll のリクエストを行うプロセスの PID は TCMSETUP です。これは、DLL をロードしようとしているためです。

コンテキストを TCMSETUP プロセスに変更すると、Manifest.Offset 値が何らかの 埋め込み XML マニフェスト を指していることがわかります。

tapi32.dll を NOTEPAD で開き、受信した XML 埋め込みマニフェストがファイルに含まれているものと同じであることを確認します。

TCMSETUP は、以前にファイルリソースを読み取り、マニフェストを読み取ってパケットに 埋め込み XML マニフェスト として格納します。

その後、再度 BaseSrvActivationContextCacheCompareEntries 関数によって比較が実行されます。この関数は BaseSrvSxsCreateActivationContextFromStructEx から呼び出されます。これで、tapi32.dll の私のエントリもキャッシュに存在することになります。

BaseSrvActivationContextCacheCompareEntries はサイクル内で呼び出され、実際のリクエストと Activation Context Cache のすべてのエントリ(私のものを含む)を比較します。

最初に両方の LastWriteTime 値を比較します。これらは等しいため、さらに多くの値を比較し続けます。

この LastWriteTime 値は重要です。異なる値がある場合、キャッシュされた私のエントリは破棄され、imm32.dll はロードされません。

次のチェックに進み、停止します。

次に、ResourceName 値をチェックします。両方とも 0x7c である必要があります。

次に、実際の ACTX パケットの言語("en-us")と、キャッシュされた私のエントリの言語を比較します。キャッシュされた私のエントリの言語も "en-us" です。

私のパッケージも同じ言語値を持っています。

次に、プロセッサアーキテクチャを比較します。この場合、両方とも 9 になります。

次に、両方の Manifest.path を比較します。

私はシステムディレクトリの値を使用して、ハードコーディングせずに同じパスを構築しました。

次に、AssemblyDirectory を比較します。これも同じです。

すべての比較が正しい場合、ゼロを返します。これは、activation cache に私のエントリが見つかり、それが使用されることを意味します。

私が最初にリクエストを送信してエントリを追加したとき、TAPI32.dll のキャッシュにエントリがなかったため、比較はエラーを返しました。私のエントリが以前に追加されていたため、今回の比較はゼロを返します。

その後、TCMSETUP の RID と CTFMON の RID を比較します。両方とも RID = 0x3000 であるため、プロセスは続行します。

RID パッチの完全な説明は、Zero Day Initiative のブログ で入手できます。

このパッチのコードは次のとおりです。

R15 には呼び出し元 TCMSETUP = 0x3000 の RID が含まれ、buffer には CTFMON プロセスの RID=0x3000 が格納されています。

前述のとおり、Microsoft は 2022 年 10 月にこの RID チェック パッチを追加しました。

そのパッチが実装された後、MEDIUM INTEGRITY LEVEL PROCESS (0x2000) から同じ MsCtfMonitor.dll を使用して tapi32.dll のエントリをキャッシュに追加しようとすると、エントリはキャッシュに追加されますが、失敗します。これは、呼び出し元プロセス 0x2000 の RID が保存され、RID=0x3000 の TCMSETUP を実行して imm32 をロードしようとすると、RID が比較され、エントリが削除されるためです。

その仮定のケースでは、R15 には tapi32.dll のロードを要求した TCMSETUP プロセスの RID=0x3000 が含まれ、変数 "buffer" には MEDIUM INTEGRITY LEVEL を持つキャッシュにエントリを追加したプロセスの RID=0x2000 が格納されます。

最新版の Windows では、エントリの追加を要求するプロセスが実行プロセスより低い場合、キャッシュポイズニングは機能せず、エントリは削除されます。このパッチより前にリリースされた以前のバージョンでは、どの RID でも問題なく機能します。

このケースに戻ると、RID チェックは通過し、両方のプロセスが同じ RID=0x3000 を持ちます。その結果、エントリは削除されず、エラーなく続行されます。

サーバは TCMSETUP に応答を返します。tapi32.dll をロードするとき、tapi32.manifest ファイルを含む私のエントリが使用され、tasks フォルダから imm32.dll がロードされます。

これが、LoadLibrary から TCMSETUP が tapi32.dll のロード時に Activation キャッシュにリクエストを行うまでの完全な流れです。

BasepCreateActCtx が CSRSS サーバにリクエストを行います。最終的に IMM32.dll モジュールがロードされる時点を確認する必要があります。

kernel32.dll を見ると、CsrBasepCreateActCtxCommon を呼び出しています。その内部には、私の DLL からキャッシュエントリを挿入するために行ったものと同様のサーバ呼び出しがあります。

私のものと同じ ApiNumber を使用しています。

TCMSETUP を実行するとき、サーバからの戻り時にブレークポイントを設定できます。これは、私の tapi32.manifest ファイルが受け入れられた後です。

これが、CsrBasepCreateActCtxCommon でのサーバ呼び出しが行われるまでの完全なコールスタックです。

コールスタックのいくつかの関数の戻りにブレークポイントを設定します。

停止したとき、imm32.dll が "tasks" フォルダからロードされていることがわかります。

PROCESS MONITOR を使用して、TCMSETUP が "tasks" フォルダから IMM32.dll をロードすることを検証できます。

実行されたばかりの CMD プロセスは HIGH 権限を持っています。

また、Administrator と同じ権限を持っています。

これらの権限を使用して、管理者への昇格が必要なプログラムをインストールし、任意のフォルダに書き込むことができます。たとえば、SYSTEM32 や任意のプログラムインストールフォルダに書き込むことができます。これは、以下のビデオデモで確認できます。

以下は、悪用前の権限です (整合性レベル Medium、管理者ではありません)。

そして、以下は悪用後の権限です (整合性レベル High、完全な Administrator)。

この時点で、細工した DLL をシステムフォルダにドロップすることで、SYSTEM に簡単に昇格する絶好の機会です。

ビデオデモと PoC テレビの漫画とボクシンググローブの自動生成された説明金メダルと赤いリボンの自動生成された説明

ビデオはこちら でご覧いただけます。また、機能する Proof of Concept はこちら にあります。

TL; DR: 悪用手順の簡単な説明

  • 細工された ACTX メッセージを CSRSS サーバに送信しました。

  • この ACTX メッセージには、それを指す オフセット を持つ 埋め込み XML マニフェスト が含まれていました。

  • サーバがそれを受信すると、そのオフセットを使用して CTFMON プロセスコンテキストから 埋め込み XML マニフェスト を読み取りました。

  • 埋め込み XML マニフェスト が解析されました。受け入れられた場合、外部フォルダから 2 番目の外部マニフェストをロードしようとしました。

  • 読み取り先のフォルダは、私が制御する 埋め込み XML マニフェスト の言語フィールドに依存していました。

  • 私の場合、埋め込み XML マニフェスト の言語は "tasks" でした。そのため、system32 の "tasks" サブディレクトリ内の外部マニフェストを検索し、それを見つけました。

  • 私が作成した tapi32.manifest ファイルを解析し、それを受け入れました。これにより、同じ "tasks" フォルダから外部の IMM32.dll をロードできるようになりました。

Nicolas Economou に感謝します。彼のプレゼンテーションは、私の研究とこのブログ記事の公開の出発点でした。

Ricardo Narvaja

ツールをダウンロード