CVE-2022-37969
🏘️

CVE-2022-37969

Tags
Windows NT
Vulnerability
Published
April 26, 2023
Author
Mas0n
复现环境:Windows 10 19041.1766

简介

Windows通用日志文件系统驱动程序(CLFS.sys)是一个Windows内核组件,用于管理日志文件。在Windows系统中,日志文件是记录系统事件和错误信息的关键组成部分。CVE-2022-37969通过构造BLF文件利用越界写(OOB)漏洞:BLF日志块头的SignaturesOffset字段在分配Symbol时可导致越界写,并破坏某些对象的虚拟函数表指针。攻击者可利用此漏洞来实现本地权限提升。

文件格式简介

CLFS的元数据块总数默认为6个,也就是如下的元数据类型
数据块类型
元数据块类型
描述
Control Record
Control Metadata Block
包含了有关布局(layout)、扩展(extend)区域以及截断(truncate)区域的信息
Base Record
General Metadata Block
包含了符号表信息,其中包括该BLF有关的客户端、容器和安全上下文信息
Truncate Record
Scratch Metadata Block
包含了因为截断操作而需要对扇区进行更改的客户端信息,以及具体更改的扇区字节.
另外三个实际上是上面三个元数据的影子块。
CLFS.sys驱动调用CClfsBaseFilePersisted::ReadImage 读取并解析文件,首先读取头部0x400固定大小的块,这个块包含了文件所有的元数据块配置。
在这个块中,最终要的是以下两个结构
CLFS_LOG_BLOCK_HEADER
typedef struct { UCHAR MajorVersion; UCHAR MinorVersion; UCHAR Usn<format=hex>; UCHAR ClientId; USHORT TotalSectorCount<comment="Number of Sectors, Size = Num * 512">; USHORT ValidSectorCount; DWORD Reserved1<format=hex>; DWORD Checksum<format=hex>; CLFS_LOG_BLOCK_FLAGS Flags; DWORD Reserved2<format=hex, comment="Unknown (empty value) 0x00">; CLFS_LSN CurrentLsn; CLFS_LSN NextLsn; DWORD RecordOffsets[16]<format=hex>; DWORD SignaturesOffset<format=hex>; DWORD Reserved3<format=hex>; // TODO: PADDING } CLFS_LOG_BLOCK_HEADER<fgcolor=cPurple>;
CLFS_CONTROL_RECORD
typedef struct { CLFS_METADATA_RECORD_HEADER RecordHeader; ULONGLONG Magic<comment="MAGIC",format=hex, fgcolor=cLtBlue>; if (Magic != 0xC1F5C1F500005F1C) { Printf("[!] CLFS_CONTROL_RECORD Magic Error: 0x016X\n", Magic); } UCHAR Version; UCHAR Reserved1; UCHAR Reserved2; UCHAR Reserved3; CLFS_EXTEND_STATE ExtendState; USHORT ExtendBlock; USHORT FlushBlock; DWORD NewBlockSectors; DWORD ExtendStartSectors; DWORD ExtendSectors; CLFS_TRUNCATE_CONTEXT Truncate; DWORD Blocks; DWORD Reserved4; CLFS_METADATA_BLOCK RgBlocks[Blocks]; } CLFS_CONTROL_RECORD<bgcolor=cLtPurple>;
在得到CLFS_LOG_BLOCK_HEADER的解析后,我们随即就可以根据RecordOffsets解析得到CLFS_CONTROL_RECORD、再根据CLFS_CONTROL_RECORD 中的RgBlocks 解析三大块。
notion image
具体文件结构的解释可以参考,这里就不作叙述了。
 

漏洞成因分析

简单分析利用样本,能看到其构造blf patch如下
地址
原始值
目标值
备注
0x80C
?? ?? ?? ??
?? ?? ?? ??
CRC32 CheckSum
0x868
80 79 00 00
50 00 00 00
SignatureOffset
0x9A8
68 13 00 00
30 1B 00 00
ClientContextOffset <ClientArray[0]>
0x1B98
F8 00 00 00
4b 11 01 00
cbSymbolZone
0x2390
00 00 00 00
B8 1B 00 00
Symbol Name Offset
0x2394
00 00 00 00
30 1B 00 00
Symbol Context Offset
0x23a0
00 –
07 F0 FD C1 88 00 00 00 00 00 00 01
Fake Client Context Part 1
0x2418
00 –
20 00 00 00
Fake Client Context Part 2
 
简单介绍下修改的字段

CRC32 CheckSum

这个是在我们构造完BLF文件后,需要重新计算CheckSum绕过文件校验。

SignatureOffset

配合我们构造的Fake Client Context Part 1完成SignatureOffset的覆写,实现OOB(详细见后)

ClientContextOffset

用于指向我们构造的Fake Client Context
notion image
 

Symbol Name Offset & Symbol Context Offset

这里注意到0x2394-0x2398这块内容的修改在模板匹配的文件格式上似乎并没有什么关联。
首先在正常情况下,ClientSymbolTableClientContext是相邻的,下图展示了一个正常的BLF文件。
notion image
看到这里其实已经有了大概的猜想了,究其原因,自然离不开CLFS驱动本身对BLF文件的解析
之所以样本设置0x2394 上的内容,是因为CLFS.sys中获取符号(CClfsBaseFile::GetSymbol)是通过相对Context 的偏移实现的。
具体来讲,CClfsBaseFile::GetSymbol 是通过获取CLFS_CLIENT_CONTEXT后往前推0xC个字节获得对应符号(CLFS_HASH_SYM)中的Offset
notion image
以正常BLF文件举例,ClientContext-0xC 对应的是Symbol的Offset,也就是对应ClientContext相对CLFS_BASE_RECORD_HEADER的偏移
notion image
样本通过修改ClientContextOffsetClientContext指向我们构造的fakeClientContext,通过patch 0x2394 处的内容绕过CClfsBaseFile::GetSymbol 中对ClientContextOffset的验证。
同样的,样本通过修改0x2390 处的内容绕过校验。
notion image
 

Fake Client Context Part 1

0x23a0 处的patch实际上就是伪造了一个ClientContext,通过构造State为CLFS_LOG_SHUTDOWN使其通过CClfsLogFcbPhysical::Initialize进入CClfsLogFcbPhysical::ResetLog
notion image
这个函数会将ClientContext+0x58上的内容覆写
notion image
在这里即下图所示
notion image
显而易见这会覆盖掉对应索引为13的chunk末尾两字节的signature
即覆盖10 01FF FF
notion image
而后CClfsLogFcbPhysical::Initialize将会执行CClfsLogFcbPhysical::FlushMetadata
notion image
继而执行CClfsBaseFilePersisted::FlushImageCClfsBaseFilePersisted::WriteMetadataBlockClfsEncodeBlockClfsEncodeBlockPrivate
ClfsEncodeBlockPrivate 函数将每个chunk末尾两字节的signature放置到SignaturesOffset偏移对应的位置上,如下图所示
notion image
在这里用上了在此之前构造的SignaturesOffset ,简单计算下我们会发现之前通过CClfsLogFcbPhysical::ResetLog 构造的FF FF会被覆盖到0x86A 上,即0x800 + 0x50 + 0x2 * 13
notion image
 

cbSymbolZone

如下图所示,我们需要调用AddLogContainer触发OOB Write
notion image
上面提到,样本通过fakeClientContext将SignaturesOffset 覆盖为0xffff0050 ,绕过了CClfsBaseFilePersisted::AllocSymbol 中的大小校验,从而实现任意位置的大小为0xB0的置零操作
notion image
样本将其SymbolZone设置为0x01114B
notion image
实际上这个值是通过一系列操作计算得到与下一个LogFile的pContainer之间的偏移,完成对pContainer内核指针的覆盖。
对于打开和创建的BLF文件,在之后内存池空间没有被占位的情况下,Base Block和下个BLF文件的Base Block几个间隔块大小经调试得出的结构偏移是常量0x11000,样本通过Windows提供的API查询SystemBigPoolInformation具体的堆地址和TAG,反复调用查询两者在Pool上的位置,保证偏移恒定后(即0x11000),当我们关闭这个两个占位文件,在这之后再次创建BLF,两者偏移量即为之前所获得的偏移量。
参见下图
notion image
调试后可以看到symbolzone偏移对应的内存处-0x1b就是另一个blf文件的CLFS_CONTAINER_CONTEXT
notion image
 
0x1b的偏移+0x18 对应的即为pContainer指针
notion image
我们覆盖了pContainer的高位5个字节为0,将内核指针pContainer改到我们自己伪造的用户态地址上,即范围0x000000~0xFFFFFF
notion image
做完这些,用户调用CloseHandle关闭文件,触发CClfsBaseFilePersisted::RemoveContainer 调用pContainer指向的vftable对应的函数
对应调用链如下图
notion image
CloseHandle时, 会调用CClfsBaseFilePersisted::RemoveContainer ,这个函数会获取CClfsContainer结构体, 并根据结构体虚表执行函数
notion image
通过结合先前OOB,我们构造一个虚表引用,结合堆(池)喷射完成gadgets调用。
notion image
这里调用两个gadgets都是精心构造的
CLFS!ClfsEarlierLsn 写edx寄存器为0xFFFFFFFF
notion image
nt!SeSetAccessStateGenericMapping 实际上就是将poi(rdx)写到poi(poi(rcx+0x48)+0x8)
notion image
结合这两个gadget我们可以通过以下方式,进行内核的任意写操作
  • 将我们需要写入的数据放在0xFFFFFFFF
  • 将需要写入的地址-0x8 然后放在我们构造的vftable的引用位置上,放置位置要求需要满足offset % 8 == 0, offset ≠ 0 而后执行堆喷。
 
CLFS部分的漏洞利用告一段落,下面我们就来分析利用样本

样本利用概述

  • 版本识别校验
    • 指定Token_EPROCESS中的Offset
    • 指定PreviousMode_ETHREAD中的Offset
    • 还有一些基本的初始化
      • 进程ID、进程句柄、进程在内核中的地址
      • 线程ID、进程句柄、线程在内核中的地址
      • Windows 10 / 11 的区分
      • Win API:NtQuerySystemInformationNtWriteVirtualMemory
  • 获取进程、内核进程(System)的_EBPROCESS及Token所在地址
  • 调用OpenProcessToken并校验ProcessToken
    • 调用VirtualAlloc,初始化空间
    • 堆喷射,写入伪造的vftable ptr
    • 循环创建BLF文件,寻找满足条件(偏移量恒定)时机,而后获取到偏移0x110000
    • 创建BLF文件,构造文件
    • 创建另一个BLF文件,添加Container
    • Windows 11
      • 利用PipeAttribute实现内核任意位置读, 结合CLFS中存在的任意位置写,实现System进程的_EPROCESS读取,得到System进程Token
      • 再次利用CLFS漏洞替换进程Token为System Token
      • 提权完成,调用system("cmd")
    • Windows 10
      • 利用CLFS中发现的任意位置写漏洞,将用户线程_ETHREAD.Token.PreviousMode 修改为0
      • 调用NtWriteVirtualMemory 替换进程Token为System Token
      • 恢复PreviousMode
      • 提权完成,调用system("cmd")
下面这个图非常全面的展示了样本的利用过程
notion image

样本分析

下面来详细分析一下,其是如何一步一步利用上述漏洞实现内核提权。
获取ProcessToken并校验存在于SystemHandleInformation
notion image
申请shellcode利用空间
notion image
堆喷,指向构造的虚表
notion image
 
循环创建BLF文件,寻找满足条件(偏移量恒定)时机,而后获取到偏移(0x110000
notion image
patch 文件,构造漏洞,具体分析见上文漏洞成因分析。
notion image
验证池上的偏移量并创建BLF文件、添加Container
notion image
下面分析不同系统版本的利用过程

windows 10

通过clfs漏洞构造调用gadgets实现将样本进程上的PreviousMode置为0xFFFFFFFF 上的值,即0x00
notion image
CloseHandle 时触发
notion image
 
内核调试下,下图是覆盖PreviousMode
notion image
驱动执行完SeSetAccessStateGenericMapping 后完成PreviousMode置0
notion image
当我们替换PreviousMode0后,这意味着我们可以使用NtReadVirtualMemoryNtWriteVirtualMemory在整个内核内存中进行不受约束的RW。
 
随及进行Token替换
notion image
在Token替换完后,我们将PreviousMode 还原为1
notion image
自此完成提权。
下图很好的解释了在Windows 10上的利用过程
notion image
 

Windows 11

Windows 11这里相对复杂,利用了两次CLFS漏洞
首先是利用PipeAttribute 结合CLFS漏洞将System进程的_EPORCESS复制到我们的构造的变量上,随即完成读取Token操作。
 
其使用到了一个PipeAttribute 结构如下
struct PipeAttribute { LIST_ENTRY list; // + 0x00 char *AttributeName; // + 0x10 ULONGLONG AttributeValueSize; // + 0x18 char *AttributeValue; // + 0x20 char data[]; // + 0x24 };
 
先是调用了CreatePipe创建读写管道
调用NtFsControlFile执行写操作,使内核申请到PipeAttribute
NtFsControlFile( Pipe.WritePipe, 0i64, 0i64, 0i64, (PIO_STATUS_BLOCK)&IoStatusBlock, 0x11003Cu, pSystemEPROCESS, 0xFD8u, Dst, 0x100);
而后遍历SystemBigPoolInformation 拿到PipeAttribute 在内核上的地址+0x18偏移写入并堆喷
notion image
这里做的一些操作跟我们选择的gadget SeSetAccessStateGenericMapping 相关
先前提到SeSetAccessStateGenericMapping 其实就做了这个操作:poi(poi(rcx+0x48)+0x8)
poi(rcx+0x48) 也就意味着是0x010000~0xFFFFFF 任意0x8*N (N>0)上的内容
notion image
这就是为什么堆喷如此操作,并且这里为什么是+0x18而不是直接+0x20定位到AttributeValue 也是此原因。
做完这些,我们需要再次利用CLFS漏洞替换Token
notion image
自此完成提权。
下图很好的总结了在Windows 11中的利用过程
notion image
 

参考