
CVE-2023-28252(Windows Common Log File System (CLFS) ドライバの特権昇格の脆弱性)の技術的分析と概念実証エクスプロイト。Nokoyawa ランサムウェア攻撃で使用されました。
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.sysによって処理され、system32内のドライバーフォルダにある**.blf**ファイル形式を知る必要があります。
このファイルタイプの詳細については、以下のリンクを参照してください:
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-実際のパッチ
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でユーザーモードのアドレスを取得し、それぞれからイメージベースを減算して関数のオフセットを取得します。最後に、各オフセットを対応するカーネルベースに加算し、必要なすべての関数のカーネルアドレスを取得します。

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変数に格納されます。

もちろん、両方のパスは同じファイルに対応しており、状況に応じて使い分ける必要があります。
CreateLogFile関数は、**CreateFile()**と非常によく似た機能を果たします(新しいファイルを作成したり、既存のファイルを開いてハンドルを取得したりします)。一部の引数も似ていますが、**CreateLogFile()**はblfファイルでのみ動作します。
さらに、既存のファイルを開く際には、各ブロックにチェックサムがあるかどうかなど、形式が正しいことを確認します。これが正しくない場合はエラーを返します。
私は2種類のBLFファイルを作成します:
トリガーblf
スプレー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にチェックサムを持ちます。

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