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

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

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

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

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2023-28252 — CVE-2023-28252(Windows Common Log File System (CLFS) ドライバの特権昇格の脆弱性)の技術的分析と概念実証エクスプロイト。Nokoyawa ランサムウェア攻撃で使用されました。 | Kitploit
ツール/GitHubGitHub/fortra/cve-2023-28252
特権昇格メモリフォレンジック脆弱性分析エクスプロイトリバースエンジニアリングデバッガバイナリエクスプロイト
GitHubfortra/cve-2023-28252

CVE-2023-28252

CVE-2023-28252(Windows Common Log File System (CLFS) ドライバの特権昇格の脆弱性)の技術的分析と概念実証エクスプロイト。Nokoyawa ランサムウェア攻撃で使用されました。

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

人気

すべて見る →

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

すべてのツールを探索

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

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

2022年2月以降、Trend Microの調査によると、Windowsの0日脆弱性を悪用していると思われる新しいランサムウェアが報告されました。
このランサムウェアの詳細は、こちらのリンクでご覧いただけます。
Kasperskyの分析によると、Nokoyawaランサムウェアグループは2022年6月以降、Common Log File System (CLFS) ドライバーを標的とした他のエクスプロイトを使用しており、類似しているが異なる特徴を持ち、すべて単一のエクスプロイト開発者に関連しています。
2023年4月にMicrosoftがパッチをリリースした際に、CVE-2023-28252が割り当てられました。
以前、2022年には同じコンポーネントの類似のバグが私たちによって調査され、こちらのブログ記事に文書化されています。

Common Log File System (CLFS) ファイル形式:

解析に取り組むには、脆弱なCommon Log File System ドライバーであるCLFS.sysによって処理され、system32内のドライバーフォルダにある**.blf**ファイル形式を知る必要があります。

このファイルタイプの詳細については、以下のリンクを参照してください:

https://www.zscaler.com/blogs/security-research/technical-analysis-windows-clfs-zero-day-vulnerability-cve-2022-37969-part

https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/introduction-to-the-common-log-file-system

https://github.com/ionescu007/clfs-docs/blob/main/README.md

https://www.coresecurity.com/core-labs/articles/understanding-cve-2022-37969-windows-clfs-lpe

脆弱性:

この解析は、Windows 11 21H2、clfs.sys バージョン 10.0.22000.1574 を対象としていますが、Windows 10 21H2、Windows 10 22H2、Windows 11 22H2、Windows Server 2022 でも動作します。

以前のWindowsバージョンでは、いくつかの値を調整する必要があります。そうしないと、BSODが発生します。

2023年4月のMicrosoft Patch Tuesday.

中程度の信頼度で自動生成されたコンピューターのスクリーンショットドライバーバージョンは図のように確認できます

脆弱性が公開された2023年4月、私はEsteban Kazimirowと共にCLFS.sysドライバーのリバースエンジニアリングを開始しました。ただし、今回の場合、パッチを分析するだけではバグの場所やトリガー方法を推測するのは非常に困難でした。なぜなら、エクスプロイトは非常に複雑だからです。

その後、ブログ記事が公開され、その著者がマルウェアのサンプルからHexRaysによって逆コンパイルされたコードの一部と、エクスプロイトに取り組むための手がかりを提供しました。

明らかに提供された情報は完全ではありませんでしたが、この助けがなければPoCを構築し、後で機能するエクスプロイトを作成することは不可能だったでしょう。

理解を容易にするために、最初にPoCの構築方法を説明し、その後で脆弱性の分析を行います。

このブログ記事は2つのセクションで構成されています:

PoCの構築:

1-エクスプロイトに必要なカーネルアドレスを取得する

2-.blfファイルを作成するためのパスの準備

3-CreateLogFile()関数を使用して「トリガーblf」ファイルを作成する

4-「トリガーblf」ファイルを細工する

5-トリガーblfのBASE BLOCKのカーネルアドレスを取得する

6-トリガーblfのハンドルを使用してAddLogContainerを呼び出す

7-スプレーblfファイルを準備する

8-スプレーを実行するためのメモリを準備する

9-バグをトリガーする

デバッグ:

1-メモリスプレーを確認する

2-トリガーblfのRecordOffset[12]を見る

3-スプレーblfファイルのiFlushBlock値を見る

4-なぜBLOCK 0 CONTROLではなくBLOCK 1 SHADOWから読み取るのか?

5-なぜblfスプレーファイルのチェックサムがゼロなのか?

6-エクスプロイトを完了する。

7-実際のパッチ

PoCの構築:

1-エクスプロイトに必要なカーネルアドレスを取得する

InitEnvironmentという関数を作成し、必要なカーネルアドレスをいくつか取得します。

自分のプロセスのEPROCESSアドレスを取得し、g_EProcessAddress変数に格納します。次に、SYSTEMプロセスのEPROCESSアドレスを取得し、system_EPROCESSに格納します。次に、自分のプロセスのメインスレッドのEHTREADアドレスを取得し、g_EThreadAddressに格納します。最後に、このPoCのバージョンでは使用されないPREVIOUS MODEのアドレスを取得します。

低い信頼度で自動生成されたコンピューターコードのスクリーンショット

この方法はよく知られています。GetObjectKernelAddress関数は、最初の引数SystemExtendedHandleInformationでNtQuerySystemInformationを2回呼び出します。最初の呼び出しは誤ったサイズで渡され、エラーを返しますが、正しいサイズも返します。そのサイズが2回目の呼び出しで使用され、すべてのハンドルの情報を取得します。その後、各ハンドルの情報をループで調べ、正しいhandleinfoのObjectフィールドからカーネル内の目的のアドレスを取得します。

テキスト、スクリーンショット、フォント、行を含む画像(自動生成)

また、CLFS.sysによってエクスポートされた以下の関数のカーネルアドレスも必要です:

• ClfsEarlierLsn

• ClfsMgmtDeregisterManagedClient

そして、NTOSKRNL.exeからエクスポートされた関数:

• RtlClearBit/PoFxProcessorNotification

• SeSetAccessStateGenericMapping

これらのアドレスを取得するには、両方のモジュールのカーネルベースを取得するために使用されるのと同様の方法を使用します。NtQuerySystemInformationを2回呼び出しますが、今回は最初の引数がSYSTEM_INFORMATION_CLASSになります(PoCではこの目的のためにFindKernelModulesBase関数を使用します)。

テキスト、フォント、スクリーンショット、行を含む画像(自動生成)次に、CLFS.sysとNTOSKRNL.exeをLoadLibraryを呼び出して通常のモジュールとしてユーザーモードにロードし、GetProcAddressでユーザーモードのアドレスを取得し、それぞれからイメージベースを減算して関数のオフセットを取得します。最後に、各オフセットを対応するカーネルベースに加算し、必要なすべての関数のカーネルアドレスを取得します。

テキスト、フォント、行、スクリーンショットを含む画像(自動生成)

2-.blfファイルを作成するためのパスの準備:

createInitialTriggerBlfFileという関数を作成し、.blfファイルを生成して書き込みます。

CreateLogFileで引数として使用されるパスは通常のパスとは異なります。たとえば、C:\Users\Publicフォルダにあるファイル1280.blfを開くには、パスLOG:C:\Users\Public\1280を設定する必要があります。これはstored_name_CreateLog変数に保存されます。

私はwsprintfW()を使用してこれを行います。stored_envには、以前に環境変数から取得したパスC:\Users\Publicが格納されています。この文字列の先頭に文字列LOG: を付け、末尾にランダムな名前を.blf拡張子なしで追加します。

中程度の信頼度で自動生成されたコンピューターのスクリーンショット

これが、私が「トリガーblf」と呼ぶ初期ファイルへのパスになります。もちろん、同じファイルへの通常のパスも保存する必要があります。先頭のLOG: がなく、BLF拡張子が付いたもので、CreateFile()、**WriteFile()**を使用して他のファイルと同様に開いて変更するためのパスです。このパスは、たとえばC:\Users\Public\1280.blfとなり、stored_name_fopen変数に格納されます。

テキスト、フォント、行、スクリーンショットを含む画像(自動生成)

もちろん、両方のパスは同じファイルに対応しており、状況に応じて使い分ける必要があります。

3-CreateLogFile()関数を使用して「トリガーblf」ファイルを作成する。

CreateLogFile関数は、**CreateFile()**と非常によく似た機能を果たします(新しいファイルを作成したり、既存のファイルを開いてハンドルを取得したりします)。一部の引数も似ていますが、**CreateLogFile()**はblfファイルでのみ動作します。

さらに、既存のファイルを開く際には、各ブロックにチェックサムがあるかどうかなど、形式が正しいことを確認します。これが正しくない場合はエラーを返します。

私は2種類のBLFファイルを作成します:

  1. トリガーblf

  2. スプレーblf

どちらもblfファイルですが、異なる方法で変更されています。

低い信頼度で自動生成されたコンピューターコードのクローズアップこのようにして、PoCはまず「トリガーblf」ファイルをCreateLogFileを使用して作成します。パスは、例えばLOG:C:\Users\Public\1280で、以前に設定し、stored_name_CreateLog変数に保存されたものです。

5番目の引数fCreateDispositionは、***CreateFileA()***と同様に、以下の値を取ることができます:

テキスト、フォント、行、レシートを含む画像(自動生成)

この場合、私はOPEN_ALWAYS引数を使用します。これにより、ファイルが存在しない場合は作成され、存在する場合は開かれます。ファイルはまだ存在しないため、ランダムな名前で作成されます。

logFile = CreateLogFile(stored_name_CreateLog, GENERIC_READ | GENERIC_WRITE, 1, 0, 4, 0);

CreateLogFile()は、6つのブロックとそれに対応するチェックサムを持つ「トリガーblf」ファイルを作成し、ハンドルを返します。このハンドルはlogFile変数に格納されます。

テキスト、スクリーンショット、フォント、数字を含む画像(自動生成)

各ブロックは、左の列に示されたオフセットから始まり、サイズが0x70バイトのヘッダーを持ちます。

たとえば、CONTROL BLOCKのヘッダーはオフセット0x0から0x70までです。

低い信頼度で自動生成されたコンピューターのスクリーンショット

すべてのブロックのヘッダーは同じ構造、_CLFS_LOG_BLOCK_HEADERです。

これがヘッダー構造です:

中程度の信頼度で自動生成されたコンピューターのスクリーンショット

ヘッダーのオフセット0xCにchecksumがあります。したがって、CONTROL BLOCKはオフセット0から始まるため、チェックサムはファイルのオフセット0xCにあります。同様に、各ブロックはそのブロックの先頭からオフセット0xCにチェックサムを持ちます。

低い信頼度で自動生成されたコンピューターのスクリーンショット

4-「トリガーblf」ファイルを細工する:

トリガーblfファイルを変更するには、CreateFileAまたはfopenを使用して通常のファイルとして開き、それぞれWriteFileまたはfwriteで変更する必要があります。これはPoCのfun_prepare関数の先頭で実行します。

通常のパスはstored_name_fopen変数に格納されているので、それを使ってwfopen_s(Unicode文字列をサポートするfopenの変種)でファイルを開きます。

ファイルはfun_prepareから呼び出されるcraftTriggerBlfFile関数で変更されます。

テキスト、行、フォント、スクリーンショットを含む画像(自動生成)

次に、fseekを呼び出して変更するオフセットを指し、fwriteでファイルを変更します。

中程度の信頼度で自動生成されたコンピューターのスクリーンショット**「トリガーblf」**ファイルに加える変更は以下の通りです:

これらの変更を行った後、FixCRCFileを呼び出して新しいチェックサムを計算し、最初の4つのブロックのチェックサムを修正します。次の2つのブロックには変更がないため、チェックサムを再計算する必要はありません。

テキスト、フォント、スクリーンショット、数字を含む画像(自動生成)

5-トリガーblfのBASE BLOCKのカーネルアドレスを取得する:

CLFS.sysドライバーはファイルの6つのブロックを読み取り、その内容を保存するためにカーネルプールにメモリを割り当てます。

テキスト、スクリーンショット、フォント、数字を含む画像(自動生成)

以前のCVE-2022-37969のブログ記事で、リバースエンジニアリングを通じて見つけた、サイズ0x90の非常に重要な構造があります。私はそれをpool_0x90と呼びました。さらにリバースエンジニアリングを行った結果、その実際の名前はm_rgBlocksであり、コントローラーがファイルから各ブロックの内容をコピーするためにメモリを割り当てる際、各ブロックのサイズ、開始オフセット、および保存されたカーネルアドレスをそこに保存することがわかりました。

テキスト、スクリーンショット、フォントを含む画像(自動生成)

これは6つのCLFS_METADATA_BLOCKを持ち、それぞれがブロック番号に対応しています。

各CLFS_METADATA_BLOCK構造体の長さは0x18バイトです。(0x18*6=0x90)

テキスト、フォント、行、数字を含む画像(自動生成)オフセット0にはユニオンがありますが、少なくともこのエクスプロイトではpbImageフィールドのみが使用されるため、簡略化すると次のようになります:

この構造体の割り当ては、新しいファイルの作成時か既存のファイルのオープン時に、CLFS.sysドライバーの2つの異なる場所から行われます。新しいファイルが作成される場合、ドライバーはCClfsBaseFilePersisted::CreateImage+28Aから0x90バイトを割り当てます。既存のファイルの場合は、CClfsBaseFilePersisted::ReadImage+6Eから割り当てます。

その後、トリガーblfファイルのブロック2の開始アドレスを取得します。これはBASE BLOCKに対応し、オフセット0x800から始まり、長さは0x7a00です。

テキスト、スクリーンショット、フォント、数字を含む画像(自動生成)

fun_prepare関数内で、以下のコードを使用してこのアドレスをカーネル内で見つけます。

低い信頼度で自動生成されたコンピューターコードのスクリーンショット

まず、getBigPoolInfo関数は、"Clfs"タグとサイズ0x7a00を持つプール内のすべての割り当てを見つけ、それらを配列に格納します。

その後、以前に変更したトリガーblfファイルをCreateLogFileでOPEN_EXISTING引数を使用して再度開きます。これにより、既存のファイルが開かれ、そのBASE BLOCKの割り当てが行われます。

getBigPoolInfoが再度呼び出されると、サイズ0x7a00の新しい"Clfs"プールが1つ追加され、そのアドレスはNtQuerySystemInformationを2回呼び出すことで取得されます。

トリガーblfファイルのBASE BLOCKのアドレスは、CLFS_kernelAddrArray変数に格納されます。

変更されたトリガーblfファイルのチェックサムが正しくない場合、**CreateLogFile()**関数は失敗することに注意してください。

6-トリガーblfのハンドルを使用してAddLogContainerを呼び出す:

fun_prepare関数の最後の部分では、トリガーblfファイルのハンドルを使用してAddLogContainer APIを呼び出します。

低い信頼度で自動生成されたコンピューターコードのクローズアップ

7-スプレーblfファイルを準備する:

PoCの最後の関数to_triggerでは、2番目のタイプのblfファイルが作成されます。

これをスプレーblfと呼びます。

この種類のファイルはメモリ空間を埋める(スプレー)ために使用され、この種類のファイルが10個必要ですが、最初は1つだけ作成されます。テキスト、フォント、行、スクリーンショットを含む画像(自動生成)

これらのファイルのランダムな名前を格納するために3つの配列が作成されます:

stored_log_arrays: CreateLogFileで使用される.blfファイルの10個の新しいランダムな名前を格納します。

stored_container_arrays: 10個の新しいコンテナファイルを作成するためのランダムな名前を格納します。

stored_fopen_arrays: 最初の配列(stored_log_arrays変数)のログファイル名を格納しますが、通常のパス(「LOG:」文字列なし)で.blf拡張子が付いたものです。

中程度の信頼度で自動生成されたコンピューターコードのスクリーンショット

各反復で、CopyFileWを使用してblfファイルがコピーされ、配列に格納された名前が割り当てられます。

fun_trigger関数はcraftSprayBlfFileを呼び出し、そこで各ファイルに変更が加えられ、FixCRCFileがCRCを修正します。

中程度の信頼度で自動生成されたコンピュータープログラムのスクリーンショット

まとめると、以下の変更を加えた10個の類似ファイル(スプレーblf)をランダムな名前で作成しました:

中程度の信頼度で自動生成されたコンピューターのスクリーンショット

最後の変更は、ブロック0全体(CONTROL BLOCK)をブロック1(CONTROL BLOCK SHADOW)にコピーすることです。

低い信頼度で自動生成されたコンピューター画面のスクリーンショット

これらの変更と、トリガーblfファイルに加えた変更の効果については、後でデバッグの章で説明します。

これらの変更の一部は脆弱性を引き起こすものであり、他のものはドライバーのチェックをバイパスするためにのみ必要です。

この時点でファイルは作成および変更され、スプレーを実行する準備が整いました。その後、CreateLogFileで開かれると、後で示すように、目的のメモリ領域に配置されます。

8-スプレーを実行するためのメモリを準備する

to_trigger関数では、12要素の配列が作成され、トリガーblfファイルのBASE BLOCKのアドレスに0x30を加えたものが含まれます。

次に、fun_pipeSpray関数で、パイプのスプレーでメモリが埋められます。内部にはCreatePipeを呼び出すループがあり、最初の引数として渡された数のパイプを作成します。2番目の引数は、作成されたすべてのパイプのハンドルを格納する配列です。

ループ内で、CreatePipeを呼び出して読み取り/書き込みパイプを作成します。

このようにして、最初に0x5000個のパイプが作成され、次に再度0x4000個のパイプを作成するために呼び出します。

次に、WriteFileを使用して最初の5000個のパイプに、トリガーblfファイルのBASE BLOCKのアドレス+ 0x30を含む最近作成された配列を書き込みます。

低い信頼度で自動生成されたコンピューターコードのスクリーンショット

これでメモリ内にコンパクトなブロックが作成されました。次に、番号0x2000から0x2667までの0x667個のパイプを解放します。メモリ内ではパイプは作成された順序と同じではないため、このメモリブロック内に空きスペースができます。

テキスト、スクリーンショット、フォント、ソフトウェアを含む画像(自動生成)パイプの割り当てはユーザーサイズが0x90バイトであるため、解放されると次のようになります。

パイプで満たされたメモリ間にサイズ0x90のメモリスペースを解放します。
次に、ループしてCreateLogFileを10個のスプレーblfファイルで呼び出します。
CreateLogFileが既存のファイルを開くために呼び出されると、各スプレーblfファイルに対してm_rgBlocksの0x90バイトの割り当てが行われます。これらの割り当ては、パイプが解放されたときに残された隙間を占有します。これは同じサイズだからです。

中程度の信頼度で自動生成されたコンピューターコードのスクリーンショット

次に、最後の0x4000個のパイプに、トリガーblfのBASE BLOCK + 0x30のアドレスを持つ配列を書き込むプロセスを繰り返します。

9-バグをトリガーする

これらの操作はすべて、制御されたメモリ空間を作成します。デバッグ中の様子を後で示しますが、アイデアとしては、各スプレーblfファイルのm_rgBlocksが解放された0x90バイトの隙間を占有することです。

そして、最終部分では、バグがwhile(1)ループ内でスプレーblfファイルへのAddLogContainerの呼び出しを使用してトリガーされます。テキスト、フォント、線、番号が含まれた画像 自動生成された説明

このwhile内でバグが発生します:

コンピュータプログラムのスクリーンショット 低信頼度で自動生成された説明

このwhileは、NtFsControlFile関数を使用してパイプの属性を読み取り、Systemトークンを見つけると終了します。

コンピュータコードのスクリーンショット 低信頼度で自動生成された説明

次にCreateLogFileを使用して、見つけたSystemトークンでプロセスのトークンを再度上書きし、これにより特権昇格を達成します。

テキスト、フォント、線、スクリーンショットが含まれた画像 自動生成された説明

その後、いくつかの値を復元し、パイプとblfファイルのハンドルを閉じて、Systemとしてメモ帳を実行し、正しく昇格したことを確認します。

コンピュータのスクリーンショット 中程度の信頼度で自動生成された説明

PUBLICフォルダに作成されたblfファイルに注意してください。もう一度試行したい場合は、最初に作成されたファイルを削除する必要があります。一部のファイルはロックされて削除できませんが、PoCは引き続き動作します。

コンピュータのスクリーンショット 中程度の信頼度で自動生成された説明

デバッグ:

1- メモリスプレイの確認

エクスプロイトを実行するためにtrigger blfとspray blfファイルに変更を加える効果について説明する前に、パイプスプレイとその後の固定数のパイプ解放後に、spray blfファイルのm_rgBlocksがメモリ分布に現れるホールに配置されることを確認する必要があります。

この手順が終了すると、m_rgBlocksの0x90バイトの下にパイプが配置されるはずです。そのため、m_rgBlocksが使用されるとOUT OF BOUNDSが発生し、その下にあるパイプから読み取ります。

PoCにはブレークポイントを設定する理想的なポイントがあります:

コンピュータコードのスクリーンショット 低信頼度で自動生成された説明

この時点で、spray blfファイルのオープンが完了し、AddLogContainer関数はまだ呼び出されていません。

ユーザーモードでデバッグするにはx64dbgを、カーネルモードではWindbgプラグインを備えたIDAを使用します。

コンピュータのスクリーンショット 低信頼度で自動生成された説明

この時点でメモリはすでに準備されているはずなので、分布を確認できます。

IDAを一時停止して、ブレークポイントを設定する興味深いポイントを見つけます。

AddLogContainerから呼び出されるCClfsBaseFilePersisted::AddContainerにブレークポイントを設定します。関数の先頭では、RCXレジスタがCClfsBaseFilePersisted構造体を指しており、オフセット0x30にm_rgBlocksへのポインタがあります。

コンピュータのスクリーンショット 中程度の信頼度で自動生成された説明

ブレークポイントに到達したら、コールスタックでAddLogContainerがPoCから呼び出されていることを確認します。

コンピュータのスクリーンショット 中程度の信頼度で自動生成された説明

RCXレジスタが指す先:

コンピュータのスクリーンショット 低信頼度で自動生成された説明 コンピュータのスクリーンショット 低信頼度で自動生成された説明

最初のフィールドはvtable(CLFS! CClfsBaseFilePersisted::'vftable')へのポインタで、オフセット0x30にm_rgBlocksへのポインタがあります。

コンピュータコードのスクリーンショット 中程度の信頼度で自動生成された説明

ブロック0、1、4、5にはまだpbImageが保存されていませんが、ブロック2(BASE BLOCK)と3(SHADOW BLOCK)には保存されています。

m_rgBlocksテーブルの各ブロックには、cbOffset(ファイル内でのブロックの開始オフセット)、cbImage(ブロックサイズ)、eBlockType(ブロックタイプ)があります。

スプレイが正しければ、m_rgBlocksの下にはパイプがあり、その中にはtrigger blfのBASE BLOCK + 0x30へのポインタが含まれています。

コンピュータコードのスクリーンショット 低信頼度で自動生成された説明

Windbgの"!pool"コマンドでメモリ分布を表示します:

コンピュータのスクリーンショット 中程度の信頼度で自動生成された説明

各m_rgBlocksには"Clfs"タグがあり、サイズは0xa0(ユーザーサイズ0x90 + ヘッダ0x10)で、その下には"NpFr"タグのパイプがあり、同じユーザーサイズ0x90 + ヘッダ0x10です。

分布は正確な科学ではないため、一部の"Clfs"は連続して配置されており、これは望ましくありませんが、現在扱っているものはパイプの後ろに正しく配置されています。

2- trigger blfのRecordOffset[12]の調査

最初に影響を与える変更の1つは、trigger blfファイルのオフセット0x858に値0x369が格納されることです。

低信頼度で自動生成された記号のクローズアップの説明

BASE BLOCKはファイルのオフセット0x800から始まります。

テキスト、スクリーンショット、フォント、線が含まれた画像 自動生成された説明

_CLFS_LOG_BLOCK_HEADER内のオフセット0x800+0x58(BASE BLOCKヘッダの先頭から0x58)に位置します。

テキスト、スクリーンショット、フォント、ディスプレイが含まれた画像 自動生成された説明

オフセット0x28から配列RecordOffsets(DWORD)が始まります。

0x30バイト進むと、オフセット0x58(0x828+0x30=0x858)にRecordOffsetsのフィールド12があります。

テキスト、フォント、スクリーンショット、グラフィックスが含まれた画像 自動生成された説明

下の画像のようにPoCを実行してCreateLogFileを呼び出します:

コンピュータコードのスクリーンショット 低信頼度で自動生成された説明 コンピュータコードのスクリーンショット 低信頼度で自動生成された説明

コンピュータのスクリーンショット 自動生成された説明

CreateLogFileに入る前に、値0x369がまだ使用されていない場所にブレークポイントを設定します。

CreateLogFileが既存のファイルを開く場合、m_rgBlocks構造体はここで割り当てられます:

CClfsBaseFilePersisted::ReadImage+6E

したがって、IDAでここにブレークポイントを設定します:

コンピュータのスクリーンショット 中程度の信頼度で自動生成された説明

ブレークポイントがトリガーされたとき:

テキスト、スクリーンショット、フォント、番号が含まれた画像 自動生成された説明

m_rgBlocksにはまだガベージがあります(初期化されていないため)が、ブロック2のpbImageが割り当てられるとすぐに、アドレスは先頭からオフセット0x30に保存されます。各CLFS_METADATA_BLOCKの最初のフィールドはpbImageだからです。

コンピュータのスクリーンショット 中程度の信頼度で自動生成された説明

テキスト、スクリーンショット、フォント、線が含まれた画像 自動生成された説明

次に、書き込み用のハードウェアブレークポイントを設定します:ba w1 ffffd003'7f5bea30

ゼロに初期化された後、pbImageが保存されると停止します。

解析によると、これはblock0に対応しています。なぜなら、後でr14*8(これは0x30)という定数を考慮していないため、実際にはblock 2のpbImageを書き込んでいます。

テキスト、スクリーンショット、フォントが含まれた画像 自動生成された説明

CClfsBaseFilePersisted::ReadMetadataBlockは、引数として渡されたサイズを使用して、いずれかのブロックを割り当てるために使用されることに注意してください。

テキスト、フォント、スクリーンショット、線が含まれた画像 自動生成された説明

次に、ベースブロックの0x58に読み取り/書き込みブレークポイントを設定して、値0x369がいつ使用されるかを確認します。

ba r1 FFFF978A'16ECF000+0x58

ブレークポイントがヒットすると、RecordOffset[12]にある値0x369を読み取り、r14の奇妙なポインタに加算し、RAX+r14の内容をインクリメントします。

コードの数行上で、ESIは値0x13を持ち、0x18(各ブロックのサイズ)を乗算しています。

WINDBG>? 0x18*0x13

評価式: 456 = 00000000'000001c8

r8= 0x1c8(これは0x90より大きい)をm_rgBlocksの初期アドレスに加算すると、OUT OF BOUNDSの読み取りになります。

コンピュータのスクリーンショット 自動生成された説明

m_rgBlocksの下には、BASE BLOCK + 0x30へのポインタを持つパイプがあり、戦略的にパイプ内に配置されたこのポインタを読み取ります。

コンピュータプログラムのスクリーンショット 低信頼度で自動生成された説明コード内の現在の位置は、メインモジュールの**while(1)**文から呼び出されました。

spray blfファイル内では、オフセット0x48a(iFlushBlock)に値0x13を戦略的に配置しました。

テキスト、フォント、線、スクリーンショットが含まれた画像 自動生成された説明

3- spray blfファイルのiFlushBlock値の調査

spray blfファイルのオフセット0x8aには、BLOCK 0のiFlushBlockがあり、その値は4です。一方、オフセット0x48aはBLOCK 1のiFlushBlockに属し、その値は0x13です。

コンピュータのスクリーンショット 中程度の信頼度で自動生成された説明

ここで、なぜBLOCK 0のiFlushBlock = 4ではなく、BLOCK 1からiFlushBlock = 0x13を読み取るのかを調べる必要があります。

4- なぜBLOCK 0 CONTROLではなくBLOCK 1 SHADOWから読み取るのか?

0x13がどこから来たのかを調べるために遡ると、コールスタックでWriteMetadataBlockがCClfsBaseFilePersisted::ExtendMetadataBlock+416から呼び出されていることがわかります。そこでは、2番目のiFlushBlock引数はEDX=0x13であり、r9wから来ています。

コンピュータコードのスクリーンショット 低信頼度で自動生成された説明

テキスト、スクリーンショット、フォント、線が含まれた画像 自動生成された説明

コンピュータプログラムのスクリーンショット 低信頼度で自動生成された説明数行前では、CClfsBaseFile::GetControlRecordが呼び出されてBLOCK 0のアドレスを取得していました。おそらくここに問題があります。そのため、再起動してこの関数にブレークポイントを設定します。

GetControlRecordはCClfsBaseFile::AcquireMetadataBlockを呼び出し、m_rgBlocksテーブルをblock 0のアドレスで埋めるはずです。この関数をステップオーバーすると、block 1のアドレスが取得されます。したがって、問題はCClfsBaseFile::AcquireMetadataBlockの内部で発生します。

取得したアドレスに0x8Aを加算すると、BLOCK 1に属する0x13の値が存在することを確認できます。

コンピュータコードのスクリーンショット 中程度の信頼度で自動生成された説明

再起動して、そこにブレークポイントを設定します:

コンピュータプログラムのスクリーンショット 低信頼度で自動生成された説明

CClfsBaseFile::GetControlRecord+27 call CClfsBaseFile::AcquireMetadataBlock

コンピュータのスクリーンショット 自動生成された説明

AcquireMetadataBlockに渡される2番目の引数はゼロで、これはblock 0に対応します。ファイルからコピーし、そのアドレスをm_rgBlocksに格納します。コンピュータのスクリーンショット 中程度の信頼度で自動生成された説明

_CLFS_METADATA_BLOCK_TYPEブロックタイプの列挙では、私が使用したものとは異なる名前ですが、同じ6つのブロックです。

テキスト、スクリーンショット、フォント、線が含まれた画像 自動生成された説明

ブロックタイプが最大m_cBlocks=6未満であることを確認した後、同じブロックを2回読み取らないように参照値を保存します。

コンピュータコードのスクリーンショット 低信頼度で自動生成された説明

ReadMetadataBlockが呼び出されます。block 0ではなくblock 1を読み取る問題はこの関数内にあるでしょう。

テキスト、フォント、番号、線が含まれた画像 自動生成された説明

すべて問題なければ、cbImageをサイズとして割り当て、アドレスをm_rgBlocksのblock 0->pbImageフィールドに格納します。

テキスト、フォント、線、スクリーンショットが含まれた画像 自動生成された説明

!poolコマンドで割り当てられたタグとサイズを表示します。

テキスト、スクリーンショット、フォント、線が含まれた画像 自動生成された説明

テキスト、スクリーンショット、フォント、線が含まれた画像 自動生成された説明これで、m_rgBlocksに格納されたblock 0のpbImageのアドレスがわかりました。次に、なぜblock 0のバイトではなくblock 1のバイトがそこにコピーされるのかを調べる必要があります。

pbImageを含む変数へのポインタが渡されるCClfsContainer::ReadSectorの呼び出しにたどり着き、バイトを書き込みます。

ReadSectorをステップオーバーしたときのpbimageの内容の変化に注意してください。

pbImageに0x8aを加算すると、値4(正しい値)が見つかります。0x13ではありません。したがって、問題は後で発生するはずです。

ClfsDecodeBlockを呼び出した後、エラー0x0C01A000Aが返されます。

CClfsBaseFilePersisted::ReadMetadataBlock+153はClfsDecodeBlockを呼び出します。

このエラーの後、タイプに1を加算し、タイプ1でCClfsBaseFilePersisted::ReadMetadataBlockを再度呼び出してblock 1を読み取ります。

コンピュータのスクリーンショット 自動生成された説明

CClfsBaseFilePersisted::ReadMetadataBlock内で、block 1用の新しいpbImageをm_rgBlocksに割り当てて格納します。

コンピュータコードのスクリーンショット 低信頼度で自動生成された説明

Blocks 0と1は異なるアドレスを持ちます。ここでblock 1のアドレスに0x8aを加算すると、その値は0x13です。

おそらく、block 0がエラーを返したため、block 1を使用し、それをControl BlockとしてGetControlRecordに返します。

前に示したように、4ではなく0x13の値を使用すると、m_rgBlocksの範囲外になり、制御されたパイプスプレイの値を読み取ります。

テキスト、スクリーンショット、フォントが含まれた画像 自動生成された説明次に、block 0のpbImageを解放し、block 1のポインタをblock 0にコピーします。

コンピュータのスクリーンショット 自動生成された説明

ClfsDecodeBlock内でエラー0x0C01A000Aを引き起こす値を特定する必要があります。

ClfsDecodeBlock内で、最初のブロックのチェックサムはゼロです。これがエラー0xC01A000Aです。

コンピュータのスクリーンショット 中程度の信頼度で自動生成された説明

5- blf sprayファイルでチェックサムがなぜゼロになるのか?

AddLogContainerを呼び出す前に、任意のspray blfファイルを16進エディタで開くと、チェックサムがゼロに変更されていました。

コンピュータのスクリーンショット 自動生成された説明

これは、CreateLogFileで開かれたときに以前に変更されるべきでした。

テキスト、スクリーンショット、ディスプレイ、フォントが含まれた画像 自動生成された説明

何らかの理由で、spray blfファイルはCreateLogFileを終了した後、block 0のチェックサムが0になり、有効な ハンドルを返します。この理由を見てみましょう。

CreateLogFileで、いくつかのspray blfファイルを開く前に停止します。

コンピュータのスクリーンショット 中程度の信頼度で自動生成された説明

CreateLogFileを呼び出す前に、sprayファイルのblock 0には正しいチェックサムがありますが、関数が完了した後、チェックサムの値がゼロに変わります。

コンピュータコードのスクリーンショット 低信頼度で自動生成された説明

そのため、CClfsBaseFile::GetControlRecordにブレークポイントを設定して、内部を調べます。

テキスト、スクリーンショット、フォント、線が含まれた画像 自動生成された説明CClfsContainer::ReadSectorを通過した後、チェックサムはゼロではありません。

CRC32を計算する前に入る前に、メモリ内でチェックサムフィールドをゼロにしてCRCを計算し、結果は正しいです。

テキスト、フォント、スクリーンショットが含まれた画像 自動生成された説明

テキスト、フォント、線、スクリーンショットが含まれた画像 自動生成された説明

次に、eExtendState =2の値を確認し、WriteMetadataBlockに進みます。

コンピュータのスクリーンショット 中程度の信頼度で自動生成された説明

ここではチェックサムはまだメモリ内でゼロであり、この値がいつファイルに書き込まれるかを確認する必要があります。

テキスト、フォント、線、スクリーンショットが含まれた画像 自動生成された説明

blf sprayファイルに細工されたいくつかの値を確認し、CClfsBaseFilePersisted::ExtendMetadataBlockに到達します。

コンピュータプログラムのスクリーンショット 中程度の信頼度で自動生成された説明

まだ読み取られていないブロックを読み取るループの後、block 0はchecksum = 0のままです。

低信頼度で自動生成された黒いテキストの白い長方形の説明

WriteMetadataBlockに到達します。

block 0を1に置き換える前に実行しているため、blf sprayファイルのiFlushBlock値はまだ4(正しい値)です。

コンピュータのスクリーンショット 中程度の信頼度で自動生成された説明

現在はblock 4を処理しており、block 4をファイルに書き込みます。ここではまだ問題ではありません。

次に、CClfsBaseFilePersisted::FlushControlRecordに到達します。

コンピュータプログラムのスクリーンショット 中程度の信頼度で自動生成された説明

内部でWriteMetadataBlockに到達しますが、引数0でblock 0をファイルに書き込みます。

コンピュータのスクリーンショット 中程度の信頼度で自動生成された説明

次に、ClfsEncodeBlockがエラー0xC01A000Aを返しますが、その直下のCClfsContainer::WriteSectorで不良なblock 0でファイルに書き込みます。

コンピュータのスクリーンショット 中程度の信頼度で自動生成された説明

変数var_54にはエラー値0xC01A000Aが格納され、関数を終了する前にチェックされます。

コンピュータのスクリーンショット 中程度の信頼度で自動生成された説明

ただし、CClfsContainer::WriteSector(エラーを返さない)の呼び出し後、var_54の内容はゼロで上書きされます。

そのため、関数はエラーなしでゼロを返し、CreateLogFileがエラー値ではなくハンドルを返すため、処理を続行します。

コンピュータのスクリーンショット 自動生成された説明

6- エクスプロイトの終了。

iFlushBlockの値0x13により、範囲外になり、パイプ内のポインタ(trigger blfのBase Block +30を指す)が読み取られます。

コンピュータのスクリーンショット 自動生成された説明そして、そのポインタに0x28を加算します(トリガーblfのベースブロックの先頭から0x58の位置)には、値0x369が格納されています。

A screenshot of a computer program Description automatically generated with medium confidence

INC命令は値0x14を1ずつ増加させ、4回繰り返すため、0x14は最終的に0x18になります。

WINDBG>db r14+369

ffffcb82'091e7397 14 00 00 00

その後、CreateLogFileが呼び出され、0x1858の値が読み取られます。

A screenshot of a computer Description automatically generated with medium confidence

A close-up of a card Description automatically generated with low confidenceGetSymbolは、トリガーblf内に事前に作成された偽のブロックが、オフセット0x1858によって指されていることを確認し、正しい値を持っているかチェックします。

A picture containing text, font, line, screenshot Description automatically generated

もしポインタが複数回インクリメントされていなければ、元の値0x1458を保持し、正しいブロックを指していたでしょう。

GetSymbolを抜けた後、ここでその偽のブロックを使用します。

A screenshot of a computer Description automatically generated with medium confidence

次に、偽のブロックのオフセット0x18の値を読み取ります。ここには0x05000000を配置してあり、その場所にある内容にジャンプします。

WINDBG>dps 0x5000000

00000000'05000000 00000000'05001000

0x05000000の内容を読み取り、その内容は0x05001000であり、そこにはClfsEarlierLsnがあります。

A screenshot of a computer program Description automatically generated with low confidence

この関数は、RDXに値0xFFFFFFFFを返すために使用されますが、この最初の呼び出しではその値は使用されません。

2回目の呼び出しはここで行われ、0x501000 +8にあったPoFxProcessorNotificationを呼び出します。

A picture containing text, font, screenshot, line Description automatically generated A picture containing text, font, screenshot, line Description automatically generated

WINDBG>dps 00000000**'05001000**

00000000'05001000 fffff805'7ab13220 CLFS! ClfsEarlierLsn

00000000'05001008 fffff805'769dc3b0 nt! PoFxProcessorNotification

A screenshot of a computer screen Description automatically generated with low confidence

この関数ではRCX = 0x05000000であり、0x40バイト後ろがゼロ以外であることをチェックします。

WINDBG>dps rcx+40

00000000'05000040 00000000'05000000

ジャンプ先のアドレスは0x68後になります。

WINDBG>dps rcx+68

00000000'05000068 fffff805'7ab2bfb0 CLFS! ClfsMgmtDeregisterManagedClient

そして、引数は0x48バイト後になります。

WINDBG>dps rcx+48

00000000'05000048 00000000'05000400

ClfsMgmtDeregisterManagedClientは便利な関数です。なぜなら、引数を制御でき、さらに自分で制御する関数への2つのジャンプがあるからです。

A screenshot of a computer Description automatically generated

最初の呼び出しは再びClfsEarlierLsnで、これはRDX=0xFFFFFFFFを返します。

A screenshot of a computer Description automatically generated with medium confidence

A screen shot of a computer Description automatically generated with medium confidence

書き込み元として、RDX=0xFFFFFFFFの内容が使用されます。

WINDBG>dps rdx

00000000'ffffffff ffff8005'3a4ee000

アドレス0xFFFFFFFFには、system_EPROCESS & 0xfffffffffffff000を保存していました。

A picture containing text, font, line, number Description automatically generated

書き込み先は、0x5000400 +0x48にあるポインタです。

*(UINT64*)0x5000448 = para_PipeAttributeobjInkernel + 0x18;

A screenshot of a computer Description automatically generated

カーネル内のPipeAttributeポインタ("A"で埋められたバッファを指していた)は、SYSTEM EPROCESSポインタの上位部分で上書きされます。

このポインタは、以前に"A"で満たされたバッファを使って**_NtFsControlFile**を呼び出した際に作成されました。

A screenshot of a computer program Description automatically generated with medium confidence

その属性の内容は、NtFsControlFileを使用して読み取ることができます。

A screenshot of a computer screen Description automatically generated with medium confidence

これで、パイプ属性は"A"を含むバッファを指すのではなく、system_EPROCESS & 0xffffffffffffff000を指すようになります。

A screenshot of a computer program Description automatically generated with low confidence

このコードは、システムトークンが取得されるまで繰り返されます。

A screenshot of a computer code Description automatically generated with medium confidence

A screenshot of a computer Description automatically generated

Windows 11では、システムトークンは、先ほど読み取ったEPROCESS構造体のオフセット0x4b8にあります。

A screenshot of a computer Description automatically generated

CreateLogFileを呼び出して、そのシステムトークンを自分のプロセスに書き込むだけで済みます。

A picture containing text, font, line, screenshot Description automatically generated

この作業を行うには、システムトークンの読み取りに使用した手順を繰り返すだけです。

A screenshot of a computer Description automatically generated with medium confidence

2回の呼び出しでは、最初にClfsEarlierLsnを呼び出してRDXに0xFFFFFFFFを返させ、次にnt_SeSetAccessStateGenericMappingを呼び出します。

A picture containing text, screenshot, font, line Description automatically generated

RDXが指す値がシステムトークンであることを確認します。

A picture containing text, screenshot, font Description automatically generated

自分のプロセスのトークンは次のとおりです。

そこに書き込みが行われます。

WINDBG>dps rax+8

ffff9b8b'fc446578 ffffc402'f601c06c

WINDBG>dps rax+8

ffff9b8b'fc446578 ffffc402'ef841919

これで自分のプロセスはSystemになりました。メモ帳を実行して確認できます。

A picture containing text, screenshot, font, line Description automatically generated

A screenshot of a computer Description automatically generated

A screenshot of a computer Description automatically generated

7-実際のパッチ

BINDIFFは、多くの変更された関数を示しています。

A screenshot of a computer Description automatically generated

脆弱な関数はこちらです。

A screenshot of a computer Description automatically generated with medium confidence

A screenshot of a computer Description automatically generated

一次側がパッチ適用済みバージョン、二次側が脆弱なバージョンです。

パッチは、CflsEncodeBlockの戻り値(0xC01A000A)をテストし、それを変数var_54に格納します。値が負であるため、それをチェックし、WriteSectorを回避します。

パッチは、ファイルを書き込まないだけでなく、関数は正しく0xc01a000aを返します。これにより、CreateLogFileはハンドルを返さず、悪用は続行できなくなります。

A screenshot of a computer Description automatically generated with medium confidence

A screenshot of a computer Description automatically generated

ClfsDecodeBlockが負でない場合のみ、WriteSectorに進みますが、負の値0xC01A000Aを返したままになります。

A screenshot of a computer Description automatically generated with medium confidenceこれが実際のパッチであり、今添付したPoCを使用した悪用を実際に防止します。

ここまでで、バグがどのように悪用されたかを説明しました。これにより、SYSTEMトークンを読み取り、それを自身のプロセスに書き込んでローカル特権昇格を達成するための関数を制御できるようになります。動作するPoCはFortraのGitHubにあります。

お役に立てば幸いです。ご質問があれば、以下までご連絡ください。

[email protected] 
@ricnar456

 [email protected]
@solidclt

ツールをダウンロード