原文鏈接: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的文件:

mylog

cve-2022-37969_image_02_create_base_log_file

cve-2022-37969_image_03_create_log_file

創建多個隨機的BLF日志文件

接下來,它將使用循環創建多個具有隨機名稱的日志文件。

在循環內部,它調用我們的getBigPoolInfo()函數:

getBigPoolInfo

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

cve-2022-37969_image_05_system_big_pool_infor

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

cve-2022-37969_image_06_fn_nt_query_system_info

v5將接收SYSTEM_BIG_POOL_INFORMATION結構的信息。

cve-2022-37969_image_07_ulong_count

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

cve-2022-37969_image_08_system_big_pool_entry_layout

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

cve-2022-37969_image_10_clf_tag

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

cve-2022-37969_image_11_right_pool_array

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

cve-2022-37969_image_12_kerneladdrarray

這樣,a2始終指向具有CLFS標記和大小為0x7a00的right pool。

在調用getBigPoolinfo()之前,變量v26始終存儲之前找到的right pool,因為它等于v24 (v26=v24) ,但是當退出此調用時,v24會被更新,而v26保留在前一個right pool。

cve-2022-37969_image_13_v24_p_a2

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

cve-2022-37969_image_14_v32

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

cve-2022-37969_image_15_v23_break

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

cve-2022-37969_image_16_v23_v32

V32 還有最后一個區別。如果v32和v23相等,則會輸出并加一,但將計數器重置為零。

這個想法是找到六個連續的CLFS標簽和大小為0x7a00的比較,它們的差異是相等的,這個差異將是0x11000。執行此操作時,我們將看到當找到六個(因為它從零開始)連續的相等距離時,它將顯示它們之間的差異值。

cve-2022-37969_image_17_while_v22_5

cve-2022-37969_image_18_11000

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

在“public”文件夾中,我們可以看到創建的文件:

cve-2022-37969_image_19_my_log

制作初始日志文件

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

cve-2022-37969_image_20_pfile

在修改文件后,有必要更改CRC32,否則我們會收到一個損壞文件的錯誤消息。

該值位于文件的偏移量0x80C處。

cve-2022-37969_image_21_crccalculatorandfix

執行受控堆噴射

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

cve-2022-37969_image_22_int_heap_spray

準備CreatePipe() / NtFsControlFile()方法

CreatePipe()用于創建匿名管道,并使用0x11003c作為參數調用NtFsControlFile()以添加屬性。稍后可以使用參數0x110038再次調用此函數來讀取它。

可以在這里找到該方法的更多詳細信息。

cve-2022-37969_image_23_ntfs_control_file

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

cve-2022-37969_image_24_v9a

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

cve-2022-37969_image_25_pipe_attr_tag

找到后,將其保存在v30.Pointer中,這是該池的VirtualAddress。

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

cve-2022-37969_image_26_attribute_value

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

cve-2022-37969_image_27_attribute_value_size

cve-2022-37969_image_28_list_entry_structure

PipeAttribute結構的第一個字段是一個LIST_ENTRY,其大小為16字節。然后它有一個指向屬性名稱的指針,其大小為8字節。然后它的值為 0x18(十進制 24),這是我們存儲在 HeapSpray 中的AttributeValueSize字段。

之后,我們在用戶模式中加載CLFS.sys和ntoskrnl。通過使用GetProcAddress(),我們找到ClfsEarlierLsn()和SeSetAccessStateGenericMapping()函數的地址。

cve-2022-37969_image_29_load_library_exw

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

cve-2022-37969_image_30_nt_status

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

cve-2022-37969_image_31_calculate_offset

一旦內存準備好,將會觸發漏洞

pipeArbitraryWrite()函數被調用兩次,有一個標志在第一次調用時初始為零,第二次調用時它的值為1時,它將改變HeapSpray的值。

cve-2022-37969_image_32_pipe_arbitrary_write

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

cve-2022-37969_image_33_puint64

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

cve-2022-37969_image_34_alloc_heap_spray

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

cve-2022-37969_image_35_memory_after_first_call

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

cve-2022-37969_image_36_attributevaluesize

讀取系統令牌

此序列將觸發漏洞:

cve-2022-37969_image_37_create_log_file

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

cve-2022-37969_image_38_call_add_log_containe

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

cve-2022-37969_image_39_ntsetinformationfile

HeapSpray 可防止此時發生 BSOD:

cve-2022-37969_image_40_call_guard_dispatch_icall_fptr

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

cve-2022-37969_image_41_set_breakpoint

cve-2022-37969_image_42_pointer_corrupt

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

cve-2022-37969_image_43_rax_takes_value_0x50000000

cve-2022-37969_image_44_pipe_arbitrary_write_puint64

所以第一次跳轉是到fnClfsEarlierLsn(),然后到fnSeSetAccessStateGenericMapping()。

從斷點開始追蹤,我們可以看到它到達了CLFS!ClfsEarlierLsn()。

cve-2022-37969_image_45_reaches_clfs_earlier_lsn

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

cve-2022-37969_image_46_called_exclusively

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

cve-2022-37969_image_47_stored_result_system_eprocess

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

cve-2022-37969_image_48_returning_clfs_clfsearlierlsn_rdx

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

cve-2022-37969_image_49_sesetaccessstategenericmapping

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

cve-2022-37969_image_50_useful_function_rcx

cve-2022-37969_image_51_controlled_content

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

cve-2022-37969_image_52_struct_pipe_attribute_attribute_value_size

cve-2022-37969_image_53_generic_mapping

cve-2022-37969_image_54_v30_pointer

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

cve-2022-37969_image_55_system_e_process

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

cve-2022-37969_image_56_overwrites_next_field_rax8

cve-2022-37969_image_57_ffffd107

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

cve-2022-37969_image_58_normally_point_to_attribute

現在,我們將用SYSTEM EPROCESS & 0xFFFFFFFFFFFFFFF00的結果的指針來覆蓋它。

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

cve-2022-37969_image_59_return_in_output_buffer

v9b是輸出緩沖區的起始地址,其中復制了System EPROCESS & 0xFFFFFFFFFFFFFFF000的結果的內容。

為此,我們將添加 v14,它是System EPROCESS的最后3個字節。然后,0x4b8(該版本的 Windows 11 的Token偏移量)將找到保存 System Token值的該地址的內容。

cve-2022-37969_image_60_v14_system_eprocess

cve-2022-37969_image_61_system_token_value

驗證令牌

cve-2022-37969_image_62_validating_token

請記住,最后4位已更改。由于這并不重要,因此該值仍然匹配。

用系統令牌覆蓋進程令牌

在第二次調用中,Flag 的值為 1,因為在第一次調用結束時已經遞增。

cve-2022-37969_image_63_overwrite_process_token

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

cve-2022-37969_image_64_order_of_values

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

cve-2022-37969_image_65_address_value_system_token

cve-2022-37969_image_66_ffffd1075e857117

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

cve-2022-37969_image_67_process_token_address_heapspray

cve-2022-37969_image_68_address_pointed_rax8

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

cve-2022-37969_image_69_rip_system_token_value

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

cve-2022-37969_image_70_name_of_other_container

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

cve-2022-37969_image_71_bug_triggered_second_time

又回到了CLFS!ClfsEarlierLsn()。

cve-2022-37969_image_72_comes_again_clfs

接著將RDX設為0xFFFFFFFF。

cve-2022-37969_image_73_sets_rdx_to_0xfffffff

然后是nt!SeSetAccessStateGenericMapping()。

cve-2022-37969_image_74_nt_sesetaccessstategenericmapping

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

cve-2022-37969_image_75_read_address_of_token

我們可以隨后讀取SYSTEM TOKEN。

cve-2022-37969_image_76_read_system_token

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

cve-2022-37969_image_242_adding_8

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

cve-2022-37969_image_77_process_with_system_token

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

以系統權限執行進程

cve-2022-37969_image_78_executing_process_as_system

cve-2022-37969_image_79_untitled_notepad

cve-2022-37969_image_80_exe_authority_system

請記住,這個POC只適用于Windows 11。在Windows 10中,它會產生 BSOD,因此你需要進行一些修改以使其正常工作,但本文未介紹此部分。

逆向補丁:分析結構

我們從IONESCU關于CLFS Internals的優秀工作中獲取了CLFS文件格式的結構和大部分文檔。

我們可以看到在函數ClfsBaseFilePersisted::LoadContainerQ中添加了一個檢查。

cve-2022-37969_image_81_analyzing_structures

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

cve-2022-37969_image_82_clfs_base_record_header

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

cve-2022-37969_image_83_base_block

作為一個好的實踐,我們可以在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字節。

cve-2022-37969_image_84_base_record_holder

如果你想將其導入到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; 

cve-2022-37969_image_85_ulong_cbsymbol_zone

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

cve-2022-37969_image_86_clfs_base_record_cbsymbolzone

請記住,cbSymbolZone已經在經過處理的日志文件中從0x000000F8修改為了0x0001114B。

0x800(基本塊開始的偏移量)+ 0x70(logBlockHeader)+ 0x1328(cbsymbolZone)

0x800 + 0x70 + 0x1328 = 0x1b98

在MyLog.blf 文件中經過處理的cbsymbolZone:

cve-2022-37969_image_87_cbsymbolzone_mylog_blf

cve-2022-37969_image_88_char_cb_symbol_zone_fseek_fwrite

由于補丁位于CClfsBaseFilePersisted::LoadContainerQ函數中,我們必須查看CClfsBaseFilePersisted對象。

在CLFS!CClfsBaseFilePersisted::LoadContainerQ中設置一個斷點,并在調用帶有經過處理的文件句柄的CreateLogFile時停止。

cve-2022-37969_image_89_clfsbase_file_persisted_load_container_q

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

cve-2022-37969_image_90_base_log_record_header_call

RAX將指向CLFS_BASE_RECORD_HEADER的地址。 cve-2022-37969_image_91_rax_will_point_to_clfs

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

cve-2022-37969_image_92_note_clfs_base_record_in_mem

cve-2022-37969_image_93_field_bytes_forward

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

cve-2022-37969_image_94_r14_stores_the_structure

內存中的CClfsBaseFilePersisted結構:

cve-2022-37969_image_95_cclfs_base_file_persisted

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

cve-2022-37969_image_96_create_a_structure

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

cve-2022-37969_image_97_mov_rcx_r14

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

cve-2022-37969_image_98_offset_0x28_0x30

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

cve-2022-37969_image_99_field_0x28_is_a_word

cve-2022-37969_image_100_field_28_struct_cclfs_base_file

cve-2022-37969_image_101_public_cclfs_base_file_persisted

cve-2022-37969_image_102_rename_it_constant_6

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

cve-2022-37969_image_103_metadata_blocks

根據文檔,6 是塊的數量CLFS_METADATA_BLOCK_COUNT。該字段可以引用該值。

并且該指針位于偏移量0x30處。

cve-2022-37969_image_104_pointer_offset_0x30

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

cve-2022-37969_image_105_includes_the_header

cve-2022-37969_image_106_length_0x10

當調用ExAllocatePoolWithTag函數時,會請求一些字節,但不包括標頭,因此,在調用時將請求0x90字節(0xa0 - 0x10)。

通過搜索文本+30h,寫入偏移量0x30的指令有很多,是通過對象CClfsBaseFilePersisted的類型過濾列表我們得到的結果很少,立即找到該大小的分配位置和相同的標記(提示:總是首先查看Create和initialize函數名)。

cve-2022-37969_image_107_create_image

cve-2022-37969_image_108_create_image_mov

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

cve-2022-37969_image_109_pool_0x90

cve-2022-37969_image_110_field_0

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

cve-2022-37969_image_111_vftable

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

cve-2022-37969_image_112_other-pointer

cve-2022-37969_image_113_base_block_starts_offset_0x800

以下圖片來自Zscaler博文

cve-2022-37969_image_114_zcaler_blogpost

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

cve-2022-37969_image_243_entire_base_block_0x7a00

cve-2022-37969_image_115_huge_allocation

cve-2022-37969_image_116_base_block

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

cve-2022-37969_image_117_new_structure

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

cve-2022-37969_image_118_70_bytes

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

cve-2022-37969_image_119_adding_start_base_block

內存中的CLFS_BASE_RECORD_HEADER。

cve-2022-37969_image_120_add_memory

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

cve-2022-37969_image_121_look_other_methods

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

cve-2022-37969_image_122_stores_cboffset

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

cve-2022-37969_image_123_offset_to_addr

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

cve-2022-37969_image_124_container_offset

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

cve-2022-37969_image_125_gets_handle_my_log

我們快進一點到CLFS!CClfsLogFcbPhysical::AllocContainer,在這里設置一個斷點并運行。

當POC達到AddLogContainer()時,我們在斷點處停止。

cve-2022-37969_image_126_add_log_container_fast_forward

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

cve-2022-37969_image_127_also_set-breakpoint

cve-2022-37969_image_128_keep_starting_index

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

cve-2022-37969_image_129_debugger_breaks

RAX將返回CLFS_CONTAINER_CONTEXT結構的地址。

cve-2022-37969_image_130_rax_return_the_address

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

cve-2022-37969_image_131_still_empty

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

cve-2022-37969_image_132_clfs_log_block_header

cve-2022-37969_image_133_char_signature_offset

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

cve-2022-37969_image_134_50_00_ff_ff

在某個時候,該值被程序修改了,為了查看何時發生了這種情況,在下一次執行中,我們將在寫入時設置內存斷點。

偏移量存儲在r15 + 0x328處(r15指向CLFS_BASE_RECORD_HEADER結構)。

cve-2022-37969_image_135_ulong_rg_containers

cve-2022-37969_image_136_ebx

RBX存儲偏移量0x1468。

cve-2022-37969_image_137_0x1468

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

cve-2022-37969_image_138_base_block_0x70

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

cve-2022-37969_image_139_pcontainer

cve-2022-37969_image_140_breakpoint_on_write

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

cve-2022-37969_image_141_r15_plus_18

cve-2022-37969_image_142_rdi

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

cve-2022-37969_image_143_pcontainer_offset_rdi

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

cve-2022-37969_image_144_moment_of_corruption

當調用格式錯誤的文件的第二個AddLogContainer()時,會發生這種情況,前一個MyLogxxx的指針已損壞。

出現此問題的原因是SignaturesOffset本來應該是0x50,但現在是0xFFFF0050 ,因此它允許在后面的 memset中越界寫入。

cve-2022-37969_image_145_malformed_file

cve-2022-37969_image_146_memset

損壞“pContainer”指針

memset() 函數將破壞下方的CLFS_CONTAINER_CONTEXT結構,該結構對應于MyLogxxx 文件,因為在創建時,它們彼此相隔0x11000字節。

通過這種方式,它可以準確計算寫入下一個結構的位置,并將指針的頂部清零,因此它指向創建 HeapSspray 的用戶堆。

調用格式錯誤的文件的基本塊結構僅位于MyLogxxx 文件的前面0x11000字節。

格式錯誤:

cve-2022-37969_image_147_malformed

MyLogxxx:

cve-2022-37969_image_148_my_logxxx

cve-2022-37969_image_149_python

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

cve-2022-37969_image_150_rcx_smaller_than_rdx

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

cve-2022-37969_image_151_pcontainer_five_high_bytes

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

cve-2022-37969_image_152_corrupted_pointer

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

cve-2022-37969_image_153_remaining_pointing_to_memory

cve-2022-37969_image_154_zeros

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

cve-2022-37969_image_155_handle_mylogxxx_closed

重新審視補丁

現在我們有了更多信息,我們注意到在這里它讀取了Base_Block.LOG_BLOCK_HEADER.SignaturesOffset和Base_Block.LOG_BLOCK_HEADER.TotalSectorCount。

在補丁的第一部分中,SignaturesOffset不應大于0x7a00,在我們的版本中,它最初是0x50,如果它到達的值大于 0x7a00,則會將我們拋出。

cve-2022-37969_image_156_revisiting_patch

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

cve-2022-37969_image_157_running_poc_patched_machine

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

cve-2022-37969_image_158_result1

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

cve-2022-37969_image_159_80_79

base_block的最大地址為0x7a00,現在允許SymbolZone的最大值為在限制之前達到的0x80。

它將把它存儲在result_2中,也就是說,SymbolZone在base block內的最大限制將是result_2,然后比較兩個結果,如果第一個大于第二個,則意味著它超出了范圍。

cve-2022-37969_image_160_base_block

cve-2022-37969_image_161_compare_results

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

cve-2022-37969_image_162_exceeds_the_limit

損壞 SignatureOffset

我們需要弄清楚的最后一件事是,SignatureOffset值從0x50變為0xFFFF0050的地方。

因此,讓我們重新開始,重新啟動并在CLFS!CClfsBaseFilePersisted::LoadContainerQ處停止,其中內存中的值尚未更改,仍然是0x50。

在SignatureOffset的偏移量0x68處設置一個訪問斷點。

cve-2022-37969_image_163_access_breakpoint

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

cve-2022-37969_image_164_detect_the_right_moment

這個函數沒有被補丁,所以它可能是由于0x50的低值和其他值的操作導致的行為。

在所構建的值中,我們可以看到ccoffsetArray的值,其在CLFS_BASE_RECORD_HEADER結構中的名稱為rgClients,它表示指向 Client Context對象的偏移量數組。

rgClients字段位于CLFS_BASE_RECORD_HEADER結構的偏移量0x138處(0x9a8-0x800-0x70)。

cve-2022-37969_image_165_30_1b

cve-2022-37969_image_166_copy_of_508

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

cve-2022-37969_image_167_value_is_malformed

cve-2022-37969_image_168_fake_client_context

這是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。 cve-2022-37969_image_169_estate_value

cve-2022-37969_image_170_20

該值顯示了日志的狀態。

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標志集。

cve-2022-37969_image_171_other_malformed_value

cve-2022-37969_image_172_ccoffset_array

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

cve-2022-37969_image_173_value_falattributes

cve-2022-37969_image_174_file_attribute_temporary

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

cve-2022-37969_image_175_block_name_offset

cve-2022-37969_image_176_void_craftfile

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

cve-2022-37969_image_177_offset_to_client_context

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

cve-2022-37969_image_178_offset_0x23a0

cve-2022-37969_image_179_000023ao

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

cve-2022-37969_image_180_block_name_offset_0x2390

cve-2022-37969_image_181_b8_1b_00_00

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

cve-2022-37969_image_182_0xc_before_client_context

這最后兩個值屬于一個長度為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;

cve-2022-37969_image_183_command_prompt_python

cve-2022-37969_image_184_long_cbsymname

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

cve-2022-37969_image_185_block_attribute_offset_is_cboffset

cve-2022-37969_image_186_blockname_blockattribute

這些都是格式錯誤的值,現在我們需要看看它們是如何影響將我們的SignaturesOffset從0x50值更改為0xFFFF0050。

讓我們來看看CClfsBaseFile::AcquireClientContext()函數,它應該返回客戶端上下文。

cve-2022-37969_image_187_struct_clfs_client_context

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

cve-2022-37969_image_188_psuedocode_a

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

cve-2022-37969_image_189_basefile_rdx

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

cve-2022-37969_image_190_ida_view_rip

cve-2022-37969_image_191_my_log_30_1b

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

cve-2022-37969_image_192_offset_to_addr

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

cve-2022-37969_image_193_call_base_file

cve-2022-37969_image_194_00002390_30_1b

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

cve-2022-37969_image_195_get_symbol_cmp

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

cve-2022-37969_image_196_jnz_short_error

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

cve-2022-37969_image_197_compares_cidclient

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

cve-2022-37969_image_198_client_context_saved

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

cve-2022-37969_image_199_output_of_function_r14

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

cve-2022-37969_image_200_cclfs_log_fcb_physical_initialize

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

cve-2022-37969_image_201_fattbributes

此函數屬于CClfsLogFcbPhysical類。

cve-2022-37969_image_202_cclfs_log_fcb_physical

cve-2022-37969_image_203_clfc

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

cve-2022-37969_image_204_allocated_here

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

cve-2022-37969_image_205_create_a_structure

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

cve-2022-37969_image_206_saves_address

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

cve-2022-37969_image_207_saving_many_values

cve-2022-37969_image_208_rbx_clfs_client_context

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

cve-2022-37969_image_209_test_return_1

cve-2022-37969_image_210_initialize_bba_rsi

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

cve-2022-37969_image_211_vftable

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

cve-2022-37969_image_212_multiplexed

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

cve-2022-37969_image_213_reset

cve-2022-37969_image_214_initialize_bdd_rsi

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

cve-2022-37969_image_215_several_fields_initialized

這里檢索Client Context

cve-2022-37969_image_216_client_context

它存儲值0xFFFFFFFF00000000

cve-2022-37969_image_217_stores_value_of_0xfffff00000

cve-2022-37969_image_218_rax

cve-2022-37969_image_219_00_00_00_00_ff_ff_ff

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

cve-2022-37969_image_220_offset_0x5c

cve-2022-37969_image_221_clfs_lsn_lsnrestart

cve-2022-37969_image_222_ull_offset

接下來,執行ClfsEncodeBlockPrivate()函數,這個函數負責將0x50覆蓋為0xFFFF0050,正如前面所展示的。

在這里,它讀取了SignatureOffset = 0x50的值,然后將其添加到CLFS_LOG_BLOCK_HEADER的開頭。

cve-2022-37969_image_223_mov_eax_lea_r8

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

其目的是改變寫入的目標,試圖損壞SignatureOffset的值。

普通文件:

cve-2022-37969_image_224_f8_03

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

cve-2022-37969_image_225_mov_r8_cx

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

cve-2022-37969_image_226_inc_eax

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

cve-2022-37969_image_227_increasing_from_0x200

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

cve-2022-37969_image_228_iteration

cve-2022-37969_image_229_db_rcx_r10

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

cve-2022-37969_image_230_written_0xffffffffffff

cve-2022-37969_image_231_0f

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

cve-2022-37969_image_232_last_two_bytes

然后將其復制到R8中。

cve-2022-37969_image_233_copy_then_in_r8

cve-2022-37969_image_234_50_ff

正如我們前面所看到的,這個值非常關鍵,因為它允許繞過檢查并越界寫入,以破壞memset()之后的文件的pContainer指針,然后在memset()中寫入零,將其指向我們控制的內存(HeapSpray)。

CbSymbolZone= 0x1114B

添加到CLFS_BASE_RECORD_HEADER最終地址的格式錯誤的值將使其寫入越界,而比較的另一個成員(Base Block + SignatureOffset的地址)仍然是SignatureOffset =0xFFFF0050,允許此檢查通過,在memset()中寫入越界,并將仍指向 HeapSpray 的指針頂部歸零。

cve-2022-37969_image_235_remain_pointing_heap_spray

由于RCX小于RDX

cve-2022-37969_image_236_rcx_smaller_rdx

正如我們之前所看到的(值可能會有所不同,因為它們屬于先前的執行)。

它將破壞指針,將最高字節設置為0

cve-2022-37969_image_237_highest_byte_to_0

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

cve-2022-37969_image_238_controlled_memory_arae

cve-2022-37969_image_239_controlled_through_heap_spray

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

cve-2022-37969_image_240_rax_qword

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

cve-2022-37969_image_241_already_corrupt_pointer

此時,我們已經成功利用了漏洞,從而控制了允許讀取SYSTEM令牌并寫入我們自己進程的函數,從而實現了本地特權升級。點擊Fortra’s GitHub可以找到驗證PoC。


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/3038/