Windows 通用日志文件系统驱动本地权限提升漏洞(CVE-2022-37969 )分析
2023-9-21 16:25:0 Author: paper.seebug.org(查看原文) 阅读量:220 收藏

原文链接: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/


文章来源: https://paper.seebug.org/3038/
如有侵权请联系:admin#unsafe.sh