大佬可直接轉至https://www.zscaler.com/blogs/security-research/technical-analysis-windows-clfs-zero-day-vulnerability-cve-2022-37969-part照著文章復現一遍。

通用日志文件系統(CLFS)是一種通用的日志記錄子系統,可供運行在內核模式和用戶模式下的應用程序使用,用于構建高性能的事務日志,并在驅動程序CLFS.sys中進行實現。通用日志文件系統在基本日志文件(BLF)中生成事務日志。

CLFS的官方文檔:https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/introduction-to-the-common-log-file-system

CLFS的非官方文檔:https://github.com/ionescu007/clfs-docs/blob/main/README.md

一、blf 日志文件格式

基本日志文件由六個不同的元數據塊組成,控制塊(Control Block)、基本塊(Base Block)和截斷塊(Truncate Block)以及它們對應的影子塊。三種類型的記錄(控制記錄、基本記錄和截斷記錄((Control Record, Base Record, and Truncate Record))可以駐留在這些塊中。Base Record包含符號表,這些符號表存儲有關與基本日志文件關聯的客戶端上下文、容器上下文和安全上下文的信息。

每個日志塊的都有一個日志塊頭CLFS_LOG_BLOCK_HEADER,大小為0x70字節的CLFS_LOG_BLOCK_HEADER結構的內存布局如上圖所示,結構如下:

typedef struct _CLFS_LOG_BLOCK_HEADER 
{     
    UCHAR MajorVersion;
    UCHAR MinorVersion;
    UCHAR Usn;
    CLFS_CLIENT_ID ClientId;
    USHORT TotalSectorCount;
    USHORT ValidSectorCount;
    ULONG Padding;
    ULONG Checksum;
    ULONG Flags;
    CLFS_LSN CurrentLsn;
    CLFS_LSN NextLsn;
    ULONG RecordOffsets[16];
    ULONG SignaturesOffset;
} CLFS_LOG_BLOCK_HEADER, *PCLFS_LOG_BLOCK_HEADER;

基本塊在.BLF文件的偏移0x800到0x71FF,以Log Block Header (0x70 bytes)開始,然后是基礎記錄頭與記錄。

基礎記錄頭的的CLFS_BASE_RECORD_HEADER結構如下:

Base Record以大小為 0x1338 字節的標頭 (CLFS_BASE_RECORD_HEADER) 開始,后面是相關的上下文數據。在CLFS_BASE_RECORD_HEADER中,與該漏洞相關的一些重要字段說明如下:

rgClients表示指向客戶端上下文對象的偏移數組。

rgContainers表示指向容器上下文對象的偏移數組。

cbSymbolZone表示符號區域中新符號的下一個可用偏移量。

在Base Record中 Client Context, Container Context, 和Shared Security Context 用符號表示,他們前面是CLFSHASHSYM。

typedef struct _CLFS_NODE_ID {
   ULONG cType;
   ULONG cbNode;
 } CLFS_NODE_ID, *PCLFS_NODE_ID;
  typedef struct _CLFSHASHSYM
{    CLFS_NODE_ID cidNode;
     ULONG ulHash;
     ULONG cbHash;
     ULONGLONG ulBelow;
     ULONGLONG ulAbove;
     LONG cbSymName;
     LONG cbOffset;
     BOOLEAN fDeleted;
 } CLFSHASHSYM, *PCLFSHASHSYM;

CLFSHASHSYM 的內存布局如下:

在Base Record,中,客戶端上下文(client context )用于標識日志文件的客戶端。在基礎日志文件中至少可以創建一個客戶端上下文。

CLFS_CLIENT_CONTEXT結構如下:

typedef struct _CLFS_CLIENT_CONTEXT
 {
     CLFS_NODE_ID cidNode;
     CLFS_CLIENT_ID cidClient;
     USHORT fAttributes;
     ULONG cbFlushThreshold;
     ULONG cShadowSectors;
     ULONGLONG cbUndoCommitment;
     LARGE_INTEGER llCreateTime;
     LARGE_INTEGER llAccessTime;
     LARGE_INTEGER llWriteTime;
     CLFS_LSN lsnOwnerPage;
     CLFS_LSN lsnArchiveTail;
     CLFS_LSN lsnBase;
     CLFS_LSN lsnLast;
     CLFS_LSN lsnRestart;
     CLFS_LSN lsnPhysicalBase;
     CLFS_LSN lsnUnused1;
     CLFS_LSN lsnUnused2;
     CLFS_LOG_STATE eState; //+0x78
     union
     {
         HANDLE hSecurityContext;
         ULONGLONG ullAlignment;
     };
 } CLFS_CLIENT_CONTEXT, *PCLFS_CLIENT_CONTEXT;

在Base Record中,container context與為一個base log文件添加一個容器文件有關。

CLFS_CONTAINER_CONTEXT結構如下:

typedef struct _CLFS_CONTAINER_CONTEXT 
{
     CLFS_NODE_ID cidNode; //8 bytes
     ULONGLONG cbContainer; //8 bytes
     CLFS_CONTAINER_ID cidContainer; // 4 bytes
     CLFS_CONTAINER_ID cidQueue; // 4 bytes
     union
     {
         CClfsContainer* pContainer; //8 bytes
         ULONGLONG ullAlignment;
     };
     CLFS_USN usnCurrent;
     CLFS_CONTAINER_STATE eState;
     ULONG cbPrevOffset; //4 bytes
     ULONG cbNextOffset; //4 bytes
 } CLFS_CONTAINER_CONTEXT, *PCLFS_CONTAINER_CONTEXT;

pContainer是指向運行時表示容器的CClfsContainer對象的內核指針,該指針位于CLFS_CONTAINER_CONTEXT結構的偏移0x18處。

二、poc編寫

文章中給出了部分poc。

0x09a8: |68 13 00 00| => |30 1B 00 00|

把0x1386的Offset修改為了0x1B30,棄用0x1BD8=0x1386+0x870的Client Context,在0x23A0=0x1B30+0x870的地方重新偽造一個

0x1B98: |F8 00 00 00| => |4B 11 01 00|

cbSymbolZone 0x1114B

改大cbSymbolZone,以使得AddLogContainer時,造成堆越界寫

poc中包含的步驟:

1 通過CreateLogFile 在C:\Users\Public\ 目錄下創建日志文件MyLog.blf。

2 創建數十個MyLog_xxx.blf基礎日志文件,使用一個常數計數器來創建基本日志文件,因此可能不會使連續創建的兩個內存區域之間的偏移量保持恒定(0x11000字節)。因此,常量值必須針對這種情況進行調整。

3 重新計算Base Block的crc32,然后將新的校驗值寫入偏移量0x80C處。然后打開MyLog.blf。

4 調用CreateLogFile 在文件夾C:\Users\Public\中創建一個基本日志文件MyLxg_xxx.blf。

5 調用AddLogContainer 為步驟4中創建的MyLxg_xxx.blf添加日志容器。

6 調用GetProcAddress(LoadLibraryA("ntdll.dll"), "NtSetInformationFile")來獲取NtSetInformationFile的函數地址。

7 調用AddLogContainer 為步驟3中打開的MyLog.blf添加日志容器。

8 調用NtSetInformationFile(v55, (PIO_STATUS_BLOCK)v33, v28, 1, (FILE_INFORMATION_CLASS)13),最后一個參數是FileInformationClass類型。當值為FileDispositionInformation(13)時,該函數將在關閉文件時刪除該文件或取消之前請求的刪除。

9 調用CloseHandle 關閉MyLxg_xxx.blf的句柄,觸發此漏洞。

還原poc,poc見附件。

運行poc,觸發崩潰。

三、分析原因

poc中的第5和第7步分別調用AddLogContainer將容器添加到與日志句柄關聯的日志。

在 CLFS.sys 中,CClfsRequest類負責處理來自用戶空間的請求。CClfsRequest ::AllocContainer函數用于處理向物理日志添加容器的請求。

CClfsRequest ::AllocContainer函數調用CClfsLogFcbPhysical::AllocContainer。

CClfsLogFcbPhysical::AllocContainer聲明如下:

__int64 __fastcall CClfsLogFcbPhysical::AllocContainer(CClfsLogFcbPhysical *this, struct _FILE_OBJECT *a2, struct _UNICODE_STRING *a3, unsigned __int64 *a4)

bu CLFS!CClfsLogFcbPhysical::AllocContainer斷點被觸發,rcx存放的是CClfsLogFcbPhysical類的this指針。

CClfsLogFcbPhysical類中vftable的地址存儲在偏移量 0x00 處。在this+0x30 處,存儲了指向日志名稱的指針。

this +0x2B0 存儲了指向CClfsBaseFilePersisted類的指針。

AddLogContainer fail的情況下這里為0(win10 this +0x2B0指向的是自己(this +0x2B0),找不到CLFS!CClfsBaseFilePersisted::vftable)。

win11

在內存中 一個CLFS基本日志文件可以由CClfsBaseFile類表示,可由CClfsBaseFilePersisted 類拓展,CClfsBaseFilePersisted的指針+0x30處儲存了一個指向0x90字節大小的堆的指針。

在CClfsBaseFilePersisted的this指針偏移 0x1C0 處,存儲了指向CClfsContainer對象的指針,該指針來自CLFS_CONTAINER_CONTEXT 結構中的字段pContainer。添加容器成功后可看到:

此時可以在CLFS_CONTAINER_CONTEXT+0x18處設置內存寫斷點,跟蹤CLFS_CONTAINER_CONTEXT結構中CClfsContainer對象的指針何時損壞。CClfsBaseFilePersisted 對象中偏移量 0x1C0 處的另一個內存寫入斷點可以設置如下:

1: kd> ba w8 ffffe689 255374f0 //CLFS_CONTAINER_CONTEXT+0x18

1: kd> ba w8 ffffe689 25159b20 //CClfsBaseFilePersisted+0x1C0

到第七步給Mylog AllocContainer時

最初在 MyLog.blf 文件中設置為 0x00000050 的 SignaturesOffset 字段在內存中已設置為 0xFFFF0050。

CLFS_CONTAINER_CONTEXT 結構中偏移量 0x18 處的CClfsContainer指針損壞。FFFF E689 2514 9B20==》30c1fdf0`06159b20

繼續運行代碼。將命中內存寫入斷點CLFS_CONTAINER_CONTEXT: +0x18。

CClfsBaseFilePersisted::AllocSymbol函數中調用memset函數觸發內存寫斷點,Base Record 中的CLFS_CONTAINER_CONTEXT產生越界寫入,這會導致CClfsContainer對象中的指針損壞。

根據棧回溯,CClfsBaseFilePersisted::AllocSymbol函數中調用memset函數觸發內存寫斷點。

下圖展示越界寫入是如何發生的,以及如何導致CClfsContainer對象中的指針損壞。

當在用戶空間調用CloseHandle函數時, CClfsRequest::Close(PIRP Irp)處理這個請求。在內核中,另一個內存斷點 (0x1c0+CClfsBaseFilePersisted) 在ClfsBaseFilePersisted::WriteMetadataBlock函數中命中。

從CLFS_CONTAINER_CONTEXT中的CClfsContainer將損壞的指針復制到Base Record中CClfsBaseFilePersisted+0x1c0處。

最后,在CLFS!CClfsBaseFilePersisted::RemoveContainer中取消引用指向CClfsContainter對象的損壞指針會導致內存沖突,造成崩潰。

CClfsBaseFilePersisted::RemoveContainer函數偽代碼:

CClfsBaseFilePersisted::RemoveContainer函數執行有以下步驟。

1.獲取 Base Record 中偏移 0x398 處的container context的偏移量(0x1468)。

2.調用CClfsBaseFile::GetSymbol獲取指向 CLFS_CONTAINER_CONTEXT 結構的指針并將其存儲在第 4 個參數中。

3.將步驟2中獲取的CLFS_CONTAINER_CONTEXT結構中指向CClfsContainter對象的指針的值賦給局部變量v13。

4.將CLFS_CONTAINER_CONTEXT 結構中指向CClfsContainter對象的指針清零。

5.依次調用CClfsContainer::Remove和CClfsContainer::Release刪除關聯的

容器日志文件并釋放CClfsContainer對象。

補丁

補丁補在了CClfsBaseFilePersisted::LoadContainerQ中,如下:

當用戶空間調用CreateLogFile函數時,CLFS!CClfsRequest::Create負責處理這個請求。當 CreateLogFile函數用于打開現有的基本日志文件時,會調用CClfsLogFcbPhysical::Initialize。

CClfsLogFcbPhysical::Initialize調用CClfsBaseFilePersisted::LoadContainerQ

SignaturesOffset被覆蓋的過程

CClfsLogFcbPhysical::Initialize中的5步:

1.調用CClfsBaseFilePersisted::OpenImage函數為基礎日志文件中的基礎塊創建一個bigpool(大小:0x7a00)。

OpenImage最終會調用ReadMetadataBlock

CClfsBaseFilePersisted::OpenImage -> CClfsBaseFilePersisted::ReadImage -> CClfsBaseFile::AcquireMetadataBlock -> CClfsBaseFilePersisted::ReadMetadataBlock

2.調用CClfsBaseFile::AcquireClientContext函數從基塊獲取客戶端上下文。

對.blf文件的修改

3.檢查eState字段是否為CLFS_LOG_SHUTDOWN。

4.調用CClfsLogFcbPhysical::ResetLog函數。

(文中的原圖)

5.調用CClfsLogFcbPhysical::FlushMetaData函數。

ClfsEncodeBlockPrivate從基塊中的每個扇區獲取扇區簽名,并用扇區簽名覆蓋扇區簽名數組,扇區簽名數組位于偏移 0x50 處,與基塊中的 SignaturesOffset 字段重疊。第 14 個扇區的扇區簽名已設置為 0xFFFF, 在基本塊中的偏移量 0x6C (0x50+0xE*2) 處的兩個字節被覆蓋(0xFFFF)。

SignaturesOffset 字段的值為 0xFFFF0050。

(文中的原圖)

覆蓋 SignaturesOffset 字段的過程: