原文鏈接:Understanding the CVE-2022-37969 Windows Common Log File System Driver Local Privilege Escalation
譯者:知道創宇404實驗室翻譯組
在本文中,我們將分享CVE-2022-37969的分析內容,并基于Zscaler先前發布的信息構建一個驗證PoC。在這里,我們將通過添加詳細信息,引導讀者深入理解該漏洞,利用它逆向補丁,并創建一個驗證PoC。
以下是本文概述:
- 創建初始的BLF日志文件
- 創建多個隨機的BLF日志文件
- 制作初始日志文件
- 執行受控的堆噴射
- 準備CreatePipe() / NtFsControlFile()方法
- 一旦內存準備好,將觸發漏洞
- 讀取系統令牌
- 驗證令牌
- 用系統令牌覆蓋我們的進程令牌
- 以系統權限執行進程
- 逆向補丁:分析結構
- 破壞“pContainer”指針
- 重新審視補丁
- 破壞SignatureOffset
- 破壞更多數值
- 控制允許讀取SYSTEM令牌的函數
- 自己寫一個流程來實現本地提權
- PoC源代碼
本文使用的場景是在Windows 11 21H2 (OS Build 22000.918)中完成的,clfs.sys版本為v10.0.22000.918。
創建初始的BLF日志文件
首先,通過使用CreateLogFile()函數,在公共文件夾(%public%)中創建一個名為MyLog.blf的文件:



創建多個隨機的BLF日志文件
接下來,它將使用循環創建多個具有隨機名稱的日志文件。
在循環內部,它調用我們的getBigPoolInfo()函數:

它調用NtQuerySystemInformation(),并將0x42(十進制66)作為第一個參數。它將返回5中有關 bigpool 中進行的 raid 的信息,其結構類型為SYSTEM_BIGPOOL_INFORMATION。

需要調用此函數兩次。第一次會返回一個錯誤,但會給我們提供第二次調用以獲取所需信息的正確緩沖區大小。

v5將接收SYSTEM_BIG_POOL_INFORMATION結構的信息。

在bigpool中的分配數量存儲在名為Count的第一個字段中。第二個字段中有一個名為SYSTEM_BIGPOOL_ENTRY的結構數組。

從那里開始,我們將在所有結構中搜索包含“Clfs”標記和大小為0x7a00的項。

VirtualAddress存儲在一個名為kernelAddrArray的數組中,該數組是具有CLFS標記和大小為0x7a00的每個結構的第一個字段。我們將滿足這兩個條件的池稱為“right pools”。

除了存儲數組中的 right pool之外,它還將最后一個找到的right pool存儲在a2變量的內容中,該變量用作函數的參數。

這樣,a2始終指向具有CLFS標記和大小為0x7a00的right pool。
在調用getBigPoolinfo()之前,變量v26始終存儲之前找到的right pool,因為它等于v24 (v26=v24) ,但是當退出此調用時,v24會被更新,而v26保留在前一個right pool。

然后,它對兩個方向進行相減,如果結果為負,則反轉操作數,使其始終為正。

接著進行類似的操作。在這種情況下,v23最初為零,因此第一次v23=v32。

下一次循環時,v23仍然保持相同的值,不為零,因此會跳出循環并執行以下操作。

V32 還有最后一個區別。如果v32和v23相等,則會輸出并加一,但將計數器重置為零。
這個想法是找到六個連續的CLFS標簽和大小為0x7a00的比較,它們的差異是相等的,這個差異將是0x11000。執行此操作時,我們將看到當找到六個(因為它從零開始)連續的相等距離時,它將顯示它們之間的差異值。


在執行這個操作時,我們將看到找到了六個連續的比較,留下了日志創建文件的循環。
在“public”文件夾中,我們可以看到創建的文件:

制作初始日志文件
我們的craftFile()函數打開原始文件(MyLog.blf)并對其進行修改以觸發漏洞。

在修改文件后,有必要更改CRC32,否則我們會收到一個損壞文件的錯誤消息。
該值位于文件的偏移量0x80C處。

執行受控堆噴射
接下來,它執行 HeapSpray,使用VirtualAlloc()函數在任意地址0x10000和0x5000000分配內存,并在第二次分配 ( 0x10000 ) 中每 0x10 字節保存值0x5000000。

準備CreatePipe() / NtFsControlFile()方法
CreatePipe()用于創建匿名管道,并使用0x11003c作為參數調用NtFsControlFile()以添加屬性。稍后可以使用參數0x110038再次調用此函數來讀取它。
可以在這里找到該方法的更多詳細信息。

接下來我們看到了輸入緩沖區,這是我們要添加的屬性。如果我們再次使用參數0x11038調用NtFsControlFile(),它應該返回相同的屬性。

在池中搜索已創建屬性(NpAt)的標簽。

找到后,將其保存在v30.Pointer中,這是該池的VirtualAddress。
v30.Pointer+24指向內核池中的AttributeValueSize,并將其保存在我們之前創建的HeapSpray之一中。

這個想法是寫入該內核地址+8,以覆蓋AttributeValue。


PipeAttribute結構的第一個字段是一個LIST_ENTRY,其大小為16字節。然后它有一個指向屬性名稱的指針,其大小為8字節。然后它的值為 0x18(十進制 24),這是我們存儲在 HeapSpray 中的AttributeValueSize字段。
之后,我們在用戶模式中加載CLFS.sys和ntoskrnl。通過使用GetProcAddress(),我們找到ClfsEarlierLsn()和SeSetAccessStateGenericMapping()函數的地址。

然后,我們調用FindKernelModulesBase()函數,該函數將使用NtquerySystemInformation())查找兩個相同模塊的內核庫,這次使用SystemModuleInformation參數返回有關所有模塊的信息。

通過這種方式,我們可以計算出每個函數的偏移量,然后在內核中獲取它們。

一旦內存準備好,將會觸發漏洞
pipeArbitraryWrite()函數被調用兩次,有一個標志在第一次調用時初始為零,第二次調用時它的值為1時,它將改變HeapSpray的值。

在0x5000000內存地址的第一次調用中,位于以下值:

請記住,這個值除了在該方向上分配之外,還存儲在我們的HeapSpray中。

這是第一次調用后的內存狀態,位于0x5000000v左右的地址:

在來自內存0x10000的HeapSpray中,它將在每0x10字節存儲指向AttributeValueSize的指針,此外還有指向0x5000000的指針。

讀取系統令牌
此序列將觸發漏洞:

首先對經過處理的文件調用CreateLogFile(),然后使用隨機名稱調用另一個文件。然后使用這些文件的句柄調用AddLogContainer()。

NtSetinformationFile ()被調用,且句柄被關閉,指針被損壞。(這將在后面解釋。)

HeapSpray 可防止此時發生 BSOD:

在那里設置一個斷點,我們可以看到指針已經被破壞,指向我們的HeapSpray,通過它我們可以處理接下來的vtable函數調用。


RAX獲取值0x5000000,并首先跳轉到位于0x5000000+18的函數,然后跳轉到0x5000000+8。


所以第一次跳轉是到fnClfsEarlierLsn(),然后到fnSeSetAccessStateGenericMapping()。
從斷點開始追蹤,我們可以看到它到達了CLFS!ClfsEarlierLsn()。

這個函數被專門調用,因為當它返回時,它將EDX設置為0xFFFFFFFF。

在地址0xFFFFFFFF處,我們存儲了SYSTEM EPROCESS & 0xFFFFFFFFFFFFFFF000的結果。

正如我們之前提到的,從CLFS!ClfsEarlierLsn()返回時,RDX的值為0x00000000FFFFFFFF。

然后我們來到了nt!SeSetAccessStateGenericMapping()的第二個函數。

這個函數很有用,因為RCX指向我們的HeapSpray,而我們控制其內容的RDX值是0xFFFFFFFF。


RCX+0x48的內容指向了在v30.Pointer+24中存儲的AttributeValueSize的指針值。



AttributeValueSize的指針值被移動到RAX中。然后它讀取了地址0xFFFFFFFF的內容,其中存儲了 SYSTEM EPROCESS 和 0xFFFFFFFFFFFFFFFF000 的地址。

然后它覆蓋了RAX+8中的下一個字段,也就是AttributeValue()。


當然,AttributeValue通常會指向我們在內核中添加的屬性。

現在,我們將用SYSTEM EPROCESS & 0xFFFFFFFFFFFFFFF00的結果的指針來覆蓋它。
這意味著當我們再次調用NtFsControlFile()函數時(這次使用0x110038參數來讀取屬性,而不是返回AttributeValue指針指向的"A”),它將從EPRROCESS & 0xFFFFFFFFFFFFFFFFF000中讀取請求的字節數,并將其返回到輸出緩沖區中,通過這個,在第一次調用時我們可以獲取SYSTEM TOKEN的值。

v9b是輸出緩沖區的起始地址,其中復制了System EPROCESS & 0xFFFFFFFFFFFFFFF000的結果的內容。
為此,我們將添加 v14,它是System EPROCESS的最后3個字節。然后,0x4b8(該版本的 Windows 11 的Token偏移量)將找到保存 System Token值的該地址的內容。


驗證令牌

請記住,最后4位已更改。由于這并不重要,因此該值仍然匹配。
用系統令牌覆蓋進程令牌
在第二次調用中,Flag 的值為 1,因為在第一次調用結束時已經遞增。

在這里我們可以看到數值被存儲的順序。

我們可以看到地址0xFFFFFFFF,以及我們剛剛找到的系統進程令牌的值。


我進程的Token地址的值存儲在HeapSpray中,我們將從中減去8,這個值加上8將被用作目標。請記住,我們寫在RAX+8指向的地址上。


以下是從0x5000000開始的內存地址。

我們還可以看到它使用了另一個容器的名稱,因為系統進程正在使用的前一個容器無法再次打開或刪除。

然后以與第一次嘗試相同的方式觸發漏洞。

又回到了CLFS!ClfsEarlierLsn()。

接著將RDX設為0xFFFFFFFF。

然后是nt!SeSetAccessStateGenericMapping()。

讀取要寫入的進程的令牌地址(減8)。

我們可以隨后讀取SYSTEM TOKEN。

然后寫入該進程的 Token 地址(加 8),這就是System Token。

這樣,進程就獲得了系統令牌。

一旦令牌被寫入,我們可以啟動一個進程來檢查權限。在這種情況下,我們將啟動Notepad.exe。
以系統權限執行進程



請記住,這個POC只適用于Windows 11。在Windows 10中,它會產生 BSOD,因此你需要進行一些修改以使其正常工作,但本文未介紹此部分。
逆向補丁:分析結構
我們從IONESCU關于CLFS Internals的優秀工作中獲取了CLFS文件格式的結構和大部分文檔。
我們可以看到在函數ClfsBaseFilePersisted::LoadContainerQ中添加了一個檢查。

執行加法操作的值屬于_CLFS_BASE_RECORD_HEADER結構。

請注意,基本塊從文件的偏移量0x800開始,到偏移量0x71FF結束,對應于日志塊標頭的前 0x70 字節。

作為一個好的實踐,我們可以在IDA中添加CLF_LOG_BLOCK_HEADER結構:
struct _CLFS_LOG_BLOCK_HEADER
{
UCHAR MajorVersion;
UCHAR MinorVersion;
UCHAR Usn;
char ClientId;
USHORT TotalSectorCount;
USHORT ValidSectorCount;
ULONG Padding;
ULONG Checksum;
ULONG Flags;
CLFS_LSN CurrentLsn;
CLFS_LSN NextLsn;
ULONG RecordOffsets[16];
ULONG SignaturesOffset;
};
接下來是基本記錄頭 (CLFS_BASE_RECORD_HEADER),它從文件開頭的偏移量0x870開始,長度為0x1338字節。

如果你想將其導入到IDA中,首先你需要添加以下類型和缺失的結構:
typedef GUID CLFS_LOG_ID;
typedef UCHAR CLFS_LOG_STATE;
struct _CLFS_METADATA_RECORD_HEADER
{
ULONGLONG ullDumpCount;
};
現在準備添加:
typedef struct _CLFS_BASE_RECORD_HEADER
{
CLFS_METADATA_RECORD_HEADER hdrBaseRecord;
CLFS_LOG_ID cidLog;
ULONGLONG rgClientSymTbl[0x0b];
ULONGLONG rgContainerSymTbl[0x0b];
ULONGLONG rgSecuritySymTbl[0x0b];
ULONG cNextContainer;
CLFS_CLIENT_ID cNextClient;
ULONG cFreeContainers;
ULONG cActiveContainers;
ULONG cbFreeContainers;
ULONG cbBusyContainers;
ULONG rgClients[0x7c];
ULONG rgContainers[0x400];
ULONG cbSymbolZone;
ULONG cbSector;
USHORT bUnused;
CLFS_LOG_STATE eLogState;
UCHAR cUsn;
UCHAR cClients;
} CLFS_BASE_RECORD_HEADER, *PCLFS_BASE_RECORD_HEADER;

在包括這些結構之后,我們注意到在cbSymbolZone和CLFS_BASE_RECORD_HEADER結束的地址(起始地址 + 1338h)之間執行了一個加法操作。

請記住,cbSymbolZone已經在經過處理的日志文件中從0x000000F8修改為了0x0001114B。
0x800(基本塊開始的偏移量)+ 0x70(logBlockHeader)+ 0x1328(cbsymbolZone)
0x800 + 0x70 + 0x1328 = 0x1b98
在MyLog.blf 文件中經過處理的cbsymbolZone:


由于補丁位于CClfsBaseFilePersisted::LoadContainerQ函數中,我們必須查看CClfsBaseFilePersisted對象。
在CLFS!CClfsBaseFilePersisted::LoadContainerQ中設置一個斷點,并在調用帶有經過處理的文件句柄的CreateLogFile時停止。

調用CClfsBaseFile::GetBaseLogRecord函數以獲取基本日志記錄(CLFS_BASE_RECORD_HEADER)的地址。

RAX將指向CLFS_BASE_RECORD_HEADER的地址。

請注意內存中的CLFS_BASE_RECORD_HEADER結構以及向前0x1328字節的cbsymbolZone字段。


r14存儲與“this”對應的結構,即CClfsBaseFilePersisted,因為它是函數CClfsBaseFilePersisted::LoadContainerQ的this。

內存中的CClfsBaseFilePersisted結構:

因此,讓我們創建一個長度為0x21c0的結構,同時反轉它(這是一個未記錄的結構),我們將其稱為struct_CClfsBaseFilePersisted。

在函數CClfsBaseFile::GetBaseLogRecord()中獲取指向CLFS_BASE_RECORD_HEADER的指針,我們知道該函數中的“this”是結構體struct_CClfsBaseFilePersisted。

讀取兩個字段(偏移量0x28和0x30)。

字段0x28是一個詞,并且其值為6,因此我們將在結構中將其類型更改為word。




目前,我們將其重命名為常量6(const_6)。

根據文檔,6 是塊的數量CLFS_METADATA_BLOCK_COUNT。該字段可以引用該值。
并且該指針位于偏移量0x30處。

請注意,此處顯示的大小包括長度為 0x10 的標頭。


當調用ExAllocatePoolWithTag函數時,會請求一些字節,但不包括標頭,因此,在調用時將請求0x90字節(0xa0 - 0x10)。
通過搜索文本+30h,寫入偏移量0x30的指令有很多,是通過對象CClfsBaseFilePersisted的類型過濾列表我們得到的結果很少,立即找到該大小的分配位置和相同的標記(提示:總是首先查看Create和initialize函數名)。


由于我們仍然不知道名稱,我們將其命名為pool_0x90,這是另一個未記錄的結構,并創建一個相應大小的結構。


內存中的pool_0x90在其自身偏移0x30處有另一個指針。

這個指針指向文件中的基本塊(基本塊從偏移0x800開始)。


以下圖片來自Zscaler博文:

分配量很大,因為它包含整個基本塊。



因此,我們將創建一個新的大小為0x7a00的結構,并將其稱為BASE_BLOCK。

前70字節我們已經知道對應于CLFS_LOG_BLOCK_HEADER,接下來的0x1338對應于_CLFS_BASE_RECORD_HEADER。

所以,將Base Block的起始地址與下一個記錄的偏移量(即0x70)相加,我們得到了CLFS_BASE_RECORD_HEADER。

內存中的CLFS_BASE_RECORD_HEADER。

查看同一CClfsBaseFilePersisted對象的其他方法,在CClfsBaseFilePersisted::AddContainer中,你會通過CClfsBaseFile::GetBaseLogRecord獲得CLFS_BASE_RECORD_HEADER的地址。

接下來,調用CClfsBaseFile::OffsetToAddr并使用cbOffset,它會得到CLFS_CONTAINER_CONTEXT的地址,并將cboffset存儲在_CLFS_BASE_RECORD_HEADER的偏移量0x328處的rgbcontainers數組中。

CClfsBaseFile::OffsetToAddr函數用于從偏移量找到結構的地址。

在這一點上,將存儲在0x328處的容器偏移量仍然是0,因為我們還沒有添加容器。

POC調用CreateLogFile兩次,第一次使用格式錯誤的文件MyLog.blf,第二次使用正常的MyLogxxx.blf文件,所以我們必須在上述所有地方停止調試兩次,并在記事本中記錄兩個文件的上述結構的地址。

我們快進一點到CLFS!CClfsLogFcbPhysical::AllocContainer,在這里設置一個斷點并運行。
當POC達到AddLogContainer()時,我們在斷點處停止。

讓我們還在CClfsBaseFilePersisted::AddContainer+176處設置一個斷點,在前面我們看到這將找到CLFS_CONTAINER_CONTEXT結構的偏移量和指針。


當調試器中斷時,我們可以看到偏移量是0x1468。

RAX將返回CLFS_CONTAINER_CONTEXT結構的地址。

該結構仍然為空,因為尚未添加容器。

請注意,在我們在格式錯誤文件的偏移量0x868處寫入的SignatureOffset=0x50值,減去基本塊開始的0x800,將在偏移0x68處的CLFS_LOG_BLOCK_HEADER結構中。


當POC使用格式錯誤文件調用AddLogContainer()函數時,在CLFS_LOG_BLOCK_HEADER的偏移0x68處,而不是我們在那里寫入的0x50值,內存中當前是0xFFFF0050。

在某個時候,該值被程序修改了,為了查看何時發生了這種情況,在下一次執行中,我們將在寫入時設置內存斷點。
偏移量存儲在r15 + 0x328處(r15指向CLFS_BASE_RECORD_HEADER結構)。


RBX存儲偏移量0x1468。

因此,在Base Block地址 + 0x70 + 我們找到的偏移量0x1468,將會是CLFS_CONTAINER_CONTEXT容器的地址。

在CLFS_CONTAINER_CONTEXT結構的偏移0x18處將是pContainer指針,我們可以設置一個寫入斷點,并查看何時寫入。


這是我們必須破壞的指針,因為在漏洞所在的函數中,它首先讀取CLFS_CONTAINER_CONTEXT,然后將其移動到r15,接下來讀取r15+18的值,這是我們剛剛設置了寫入斷點的這個指針。


它將pContainer存儲在struct_CClfsBaseFilePersisted結構的偏移0x1c0處。

在多次中斷后,我們到達了它被損壞的時刻。指針地址的頂部已經從FFs變為零。

當調用格式錯誤的文件的第二個AddLogContainer()時,會發生這種情況,前一個MyLogxxx的指針已損壞。
出現此問題的原因是SignaturesOffset本來應該是0x50,但現在是0xFFFF0050 ,因此它允許在后面的 memset中越界寫入。


損壞“pContainer”指針
memset() 函數將破壞下方的CLFS_CONTAINER_CONTEXT結構,該結構對應于MyLogxxx 文件,因為在創建時,它們彼此相隔0x11000字節。
通過這種方式,它可以準確計算寫入下一個結構的位置,并將指針的頂部清零,因此它指向創建 HeapSspray 的用戶堆。
調用格式錯誤的文件的基本塊結構僅位于MyLogxxx 文件的前面0x11000字節。
格式錯誤:

MyLogxxx:


由于添加了0xFFFF0050而不是應該的0x50,所以RCX小于RDX。

然后我們進入memset()函數,用 0 設置 0xb0 字節的數量,RCX 指向MyLogxxx 文件的CLFS_CONTAINER_CONTEXT結構,特別是pContainer的五個高字節。

該指針將因覆蓋第一個字節而被損壞:

剩余指向之前我們通過HeapSpray控制的內存地址:


然后, MyLogxxx文件的句柄將被關閉,并到達CClfsBaseFilePersisted::RemoveContainer,最終觸發漏洞。

重新審視補丁
現在我們有了更多信息,我們注意到在這里它讀取了Base_Block.LOG_BLOCK_HEADER.SignaturesOffset和Base_Block.LOG_BLOCK_HEADER.TotalSectorCount。
在補丁的第一部分中,SignaturesOffset不應大于0x7a00,在我們的版本中,它最初是0x50,如果它到達的值大于 0x7a00,則會將我們拋出。

在已打了補丁的機器上運行 PoC 時,它會將0x50與0x7a00進行比較,因為它更小,所以它會繼續執行。

在接下來的塊中,格式錯誤的cbSymbolZone添加到CLFS_BASE_RECORD_HEADER的最終地址的值中,然后將此和存儲在result_1中。

然后,將Base_Block的地址與SignatureOffset的值相加,正常文件中的值為0x7980。

base_block的最大地址為0x7a00,現在允許SymbolZone的最大值為在限制之前達到的0x80。
它將把它存儲在result_2中,也就是說,SymbolZone在base block內的最大限制將是result_2,然后比較兩個結果,如果第一個大于第二個,則意味著它超出了范圍。


顯然,第一個成員將大于第二個成員,它將不會繼續執行,因為cbSymbolZone + CLFS_BASE_RECORD_HEADER的最終地址的第一次求和超過了限制(即result_2),并導致“越界”。

損壞 SignatureOffset
我們需要弄清楚的最后一件事是,SignatureOffset值從0x50變為0xFFFF0050的地方。
因此,讓我們重新開始,重新啟動并在CLFS!CClfsBaseFilePersisted::LoadContainerQ處停止,其中內存中的值尚未更改,仍然是0x50。
在SignatureOffset的偏移量0x68處設置一個訪問斷點。

經過幾次停止后,我們檢測到它修改ClfsEncodeBlockPrivate中的值的正確時刻。

這個函數沒有被補丁,所以它可能是由于0x50的低值和其他值的操作導致的行為。
在所構建的值中,我們可以看到ccoffsetArray的值,其在CLFS_BASE_RECORD_HEADER結構中的名稱為rgClients,它表示指向 Client Context對象的偏移量數組。
rgClients字段位于CLFS_BASE_RECORD_HEADER結構的偏移量0x138處(0x9a8-0x800-0x70)。


在 PoC 中,該值的格式錯誤,指向一個名為FakeClientContext的偽造客戶端上下文對象。


這是Client Context結構CLFS_CLIENT_CONTEXT:
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;
union
{
HANDLE hSecurityContext;
ULONGLONG ullAlignment;
};
};
eState值位于結構開始處的偏移量0x78處,在構建的文件中是0x23a0+0x78。


該值顯示了日志的狀態。
typedef UCHAR CLFS_LOG_STATE, *PCLFS_LOG_STATE;
const CLFS_LOG_STATE CLFS_LOG_UNINITIALIZED = 0x01;
const CLFS_LOG_STATE CLFS_LOG_INITIALIZED = 0x02;
const CLFS_LOG_STATE CLFS_LOG_ACTIVE = 0x04;
const CLFS_LOG_STATE CLFS_LOG_PENDING_DELETE = 0x08;
const CLFS_LOG_STATE CLFS_LOG_PENDING_ARCHIVE = 0x10;
const CLFS_LOG_STATE CLFS_LOG_SHUTDOWN = 0x20;
const CLFS_LOG_STATE CLFS_LOG_MULTIPLEXED = 0x40;
const CLFS_LOG_STATE CLFS_LOG_SECURE = 0x80;
此值設置為CLFS_LOG_SHUTDOWN,對應于0x20。
另一個篡改的值是fAttributes,它對應于與基本日志文件(例如 System 和 Hidden)關聯的 FILE_ATTRIBUTE標志集。


由于該字段從第0xa個字節開始并跨足兩個字節,因此fAttributes的值為0x100。


最后,還有一個指向偏移量0x1bb8的blocknameoffset值,也就是說,通過添加0x78和0x800,它指向文件的偏移量0x2428。


請注意,指向Client Context的偏移量是0x1b30。

所以,Client Context在偏移量0x23a0處。


再往前 0x10個字節,就是對應于blocknameoffset的值。


它將指向字符串名稱。最后一個是blockattributeoffset,它在0x2394處的Client Context之前的0xC個字節。

這最后兩個值屬于一個長度為0x30個字節的CLFSHASHSYM結構的前一個結構,名為CLFSHASHSYM。
typedef struct _CLFSHASHSYM
{
CLFS_NODE_ID cidNode;
ULONG ulHash;
ULONG cbHash;
ULONGLONG ulBelow;
ULONGLONG ulAbove;
LONG cbSymName;
LONG cbOffset;
BOOLEAN fDeleted;
} CLFSHASHSYM, *PCLFSHASHSYM;


它們分別位于CLFSHASHSYM結構的起始處的第0x20和0x24個字節,因此在CLFSHASHSYM結構中,PoC中稱blockNameOffset的值實際上是 cbSymName字段,而blockAttributteoffset是cbOffset字段。


這些都是格式錯誤的值,現在我們需要看看它們是如何影響將我們的SignaturesOffset從0x50值更改為0xFFFF0050。
讓我們來看看CClfsBaseFile::AcquireClientContext()函數,它應該返回客戶端上下文。

它使用第四個參數調用了CClfsBaseFile::GetSymbol,它將是CLFS_CLIENT_CONTEXT,它將存儲指向Client Context的指針。

在 CClfsBaseFile::GetSymbol函數內部,我們將格式錯誤的ccoffsetArray偏移量傳遞給CClfsBaseFile::OffsetToAddr,并獲取客戶端上下文的地址,我們在那里設置一個斷點,以便在調用使用CreatelogFile創建的文件時停止。

在那里它被ccoffsetArray精心設計的參數停止。


CClfsBaseFile::OffsetToAddr函數返回了錯誤的客戶端上下文。

并檢查cbOffset的值是否為零,因為在 RAX 中找到了CLFS_CLIENT_CONTEXT結構之前的0xC。


然后它將cbOffset與ccoffsetArray(在 RSI 中)進行比較,它們必須相等,否則將會出現錯誤。

它還檢查cbSymName是否等于cbOffset+0x88,如果不是,我們也會收到錯誤。

最后,它將cidClient字節與零進行比較。

如果所有這些檢查都成功,將保存client context。

函數r14的輸出指向客戶端上下文。

在退出CLfsLogFcbPhysical::Initialize時,我們將擁有CLFS_CLIENT_CONTEXT的地址。

接下來,它讀取了fAttributes (0x100)的值。

此函數屬于CClfsLogFcbPhysical類。


它被分配在這里,大小是0x15d0,標簽是“ ClfC ”。

創建一個結構來存儲我們要逆向的內容,我們將其命名為:struct_CClfsLogFcbPhysical。

請注意,在0x2b0處保存了CClfsBaseFilePersisted結構的地址。

在結構中保存了許多值后,它進入了一個重要的部分,使用0x20測試了eState。


由于設計的值為0x20,所以測試將返回 1。


我們可以看到,在構造函數中的vtable是

此處它將檢查文件是否為多路復用。

然后,它按預期路徑繼續執行,進入到CClfsLogFcbPhysical::ResetLog函數。


在該函數中,除了一個初始化為 0xFFFFFFFF00000000 的字段外,幾個字段都初始化為零。

這里檢索Client Context

它存儲值0xFFFFFFFF00000000



它在偏移量0x5c 處寫入0xFFFFFFFF ,這是CLFS_LSN lsnRestart.ullOffset的高位部分。



接下來,執行ClfsEncodeBlockPrivate()函數,這個函數負責將0x50覆蓋為0xFFFF0050,正如前面所展示的。
在這里,它讀取了SignatureOffset = 0x50的值,然后將其添加到CLFS_LOG_BLOCK_HEADER的開頭。

這里有一個循環,每次寫入兩個字節,類似于SignatureOffset,但是不是指向正常文件中的正確值,例如0x3f8,使其向前寫入,而是寫入到相同的CLFS_LOG_BLOCK_HEADER中。
其目的是改變寫入的目標,試圖損壞SignatureOffset的值。
普通文件:

此時,它將開始循環并寫入兩個字節。

計數器必須達到0x3d才能退出循環。

RCX正在從0x200增加,我們已經進入第三個周期,它的值是0x600。

在0xe迭代中,RCX是0x1a00。


那就是它寫入0xFFFFFFFF000000的地方。


它正在讀取最后的兩個字節FFFF。

然后將其復制到R8中。


正如我們前面所看到的,這個值非常關鍵,因為它允許繞過檢查并越界寫入,以破壞memset()之后的文件的pContainer指針,然后在memset()中寫入零,將其指向我們控制的內存(HeapSpray)。
CbSymbolZone= 0x1114B
添加到CLFS_BASE_RECORD_HEADER最終地址的格式錯誤的值將使其寫入越界,而比較的另一個成員(Base Block + SignatureOffset的地址)仍然是SignatureOffset =0xFFFF0050,允許此檢查通過,在memset()中寫入越界,并將仍指向 HeapSpray 的指針頂部歸零。

由于RCX小于RDX

正如我們之前所看到的(值可能會有所不同,因為它們屬于先前的執行)。
它將破壞指針,將最高字節設置為0

使其指向我們通過HeapSpray控制的內存區域。


因此,在觸發漏洞時,我們到達了CClfsBaseFilePersisted::RemoveContainer。

將會存在已經損壞的指針,并且可以像我們之前看到的那樣被利用。

此時,我們已經成功利用了漏洞,從而控制了允許讀取SYSTEM令牌并寫入我們自己進程的函數,從而實現了本地特權升級。點擊Fortra’s GitHub可以找到驗證PoC。
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/3038/
暫無評論