7

Windows 通用日志文件系统驱动本地权限提升漏洞(CVE-2022-37969 )分析

 11 months ago
source link: https://paper.seebug.org/3038/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Windows 通用日志文件系统驱动本地权限提升漏洞(CVE-2022-37969 )分析

2023年09月21日2023年09月21日漏洞分析

原文链接: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 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/3038/


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK