CVE-2022-37969
简介
Windows通用日志文件系统驱动程序(CLFS.sys)中存在越界写漏洞,在解析精心构造的BLF文件时,BLF日志块头的SignaturesOffset字段,会导致分配Symbol时的越界写,并破坏某些对象的虚拟函数表指针,成功利用此漏洞可实现本地权限提升。
成因
zscaler已于2022年10月将细节公布出来了, 该漏洞为已在野使用的漏洞,且内容较为详细。
下图为该文章原文:
文章内容简单总结下,该漏洞问题出在Initialize
函数,ResetLog
函数,AllocSymbol
函数与CClfsContainer
结构体:
Initialize
函数会根据文件内容调用ResetLog
, ResetLog
函数会在特定情况下对指定位置的赋值0xffffffff
, 从而绕过AllocSymbol
函数写位置的校验, 最终造成越界写CClfsContainer
结构体的指针, 从而在关闭Handle
的时候任意代码执行。
基本流程
blf文件中需要修改如下值, 其中偏移0x800
为文件中修改块的偏移
地址 | 原始值 | 目标值 | 备注 |
---|---|---|---|
0x868 | 80 79 00 00 | 50 00 00 00 | SignatureOffset |
0x9a8 | 68 13 00 00 | 30 1b 00 00 | ClientContextOffset |
0x1b98 | f8 00 00 00 | 4b 11 01 00 | cbSymbolZone |
0x2390 | 00 – | b8 1b 00 00 30 1b 00 00 | Client Context0 |
0x23a0 | 00 – | 07 fd fd c1 88 00[0x03] 00 00 00 01 | Client Context1 |
0x2418 | 00 – | 20 00 00 00 | Client Context2 |
step1
其中ClientContextOffset
修改后, 指向了我们的fakeClientContext
, 根据已有的文档我们可以发现, 因为fakeClientContext->eState=CLFS_LOG_SHUTDOWN
, 所以在Initialize
函数初始化的时候根据该值, 会调用ResetLog
函数,
其中在ResetLog中会将指定位置赋值为0xffffffff00000000
step2
其中SignaturesOffset
修改后, 在Initialize
函数初始化的时候根据该值, 将指定位置的0x2*0x3d
长度的值, 展开写到固定的位置, 并在Initialize
函数的结尾, 将数据回写到上述指定的位置
其中原始位置与展开位置在Initialize
函数的结尾时, 分别如下
原始位置的内容如下
展开位置的内容如下
其中在展开位置0x0110
即代表该块已回写完毕.
在step1中写入值0xffffffff0000
的位置, 既是由SignaturesOffset
展开的位置中某一项
step3
其中cbSymbolZone
修改后, 会在AllocSymbol
函数中使用, 并造成任意位置写,
其中0x1328
即是cbSymbolZone
的偏移, 0x1338
既是结构体Header头的大小
通过调试, 我们可以在指定位置进行长度为0xb0
的清零
该函数中同时存在校验, 其中校验判断是在如下图的范围内,
其中0x68
即是 SignaturesOffset
的偏移
那么即是判断写入末尾的位置是否小于等于SignaturesOffset
指定的值
在step2中修改了SignaturesOffset
, 使得其值大于0xff00
, 所以该函数的校验可以被绕过, 使得其可以任意写长度为0xb0
的\x00
在0x70+cbSymbolZone+0x1338
step4
在CloseHandle
时, 会获取CClfsContainer
结构体, 并根据结构体执行函数.
其中在函数中会依次执行CClfsContainer
结构体中+0x18
与+0x08
位置处的函数
其默认指向的是CClfsContainer::vftable
也就是说实际上默认调用的是Remove
与Release
这两个函数
在step3中可以修改CClfsContainer
结构体的指针, 我们构造两个Clfs
的LogFile
, 并使前一个结构体越界写入到下一个结构体中, 最终我们可以控制我们要执行的函数.
step5
我们可以由step4执行任何我们可以控制的内核函数地址, 再使用一些通用的泄露内核地址的方法, 既可成功利用
Exp: https://github.com/0x79H/CVE-2022-37969
提权技术参考
PreviousMode
PreviousMode
位于KTHREAD
中, 若PreviousMode
为0, 则可以直接使用NtWriteVirtualMemory
读写内核地址
Token
Token
位于Eprocess
中, 指定了进程的权限
PipeAttribute
类似于Bitmap
的读写源语, 当NtFsControlFile的参数FsControlCode
为0x110038/0x11003C
时, 会对内核中的PipeAttribute->AttributeValue(+0x20)
进行读写操作
不同的是这个一般用来读.
泄露内核地址
使用SystemExtendedHandleInformation
去SystemHandleInformation
, 可以获得handle
对应的kernel地址
参考ProcessHacker
的实现即可, 其中Process
是需要duplicate
之后才能显示地址的
SeSetAccessStateGenericMapping
SeSetAccessStateGenericMapping实际上就是将poi(rdx)
写到poi(poi(rcx+0x48)+0x8)
中
ClfsEarlierLsn
主要用处就是写rdx为0xffffffff
RtlClearBit
RtlClearBit即清除比特位
使用RtlClearBit
对poi(rcx+8)
的rdx
指定位清零
技术逻辑
原始技术逻辑
首先使用ClfsEarlierLsn
与SeSetAccessStateGenericMapping
的组合, 将0xffffffff
处的东西写到poi(poi(rcx+0x48)+0x8)
中
然后构造rcx+0x48
位置的为我们控制的地址, 这样我们就可以吧任意内容写到任意位置了, 我们使用PipeAttribute
来将其转换为任意读写源语
之后替换Token
即可完成提权
1 | *(QWORD*)0x00010000(+0x01000000) = {0x05000000, WHERE_TO_WRITE} |
新的技术逻辑
直接使用RtlClearBit
对PreviousMode
清零, 其中标志位rdx
在调用时恰好为0x0
完成后可以直接使用NtWriteVirtualMemory
, 可以把任意内容写到任意位置了, 同时也可以读任意位置了
之后替换Token
即可完成提权
1 | *(QWORD*)0x00010000(+0x01000000) = {0x05000000, WHERE_TO_WRITE} |
补丁修复方式
在新版本中, 在上文的Step1时, 即直接在打开文件调用Initialize
函数时, 精心构造的fakeClientContext
在解析的时候对相应数据增加了两条判断
检测的是CLFS_CLIENT_CONTEXT->CLFS_NODE_ID
中的cType
和cbNode
, 其中前者是类型, 后者是大小.
若cType == CLFS_NODE_TYPE_CONTAINER_CONTEXT(0xC1FDF007)
或cbNode == 0x88
那么直接报错退出
如上图所示, 在已修补的版本中, 其通过新增的判断条件, 返回0xc01a000d(Log service encountered a corrupted metadata file.)
这一错误代码, 直接终止流程.
参考文章:
Windows CLFS Zero-Day Vulnerability CVE-2022-37969 | Zscaler
CVE-2022-37969 | Windows CLFS Zero-Day - Zscaler Blog
Unofficial Common Log File System (CLFS) Documentation (by ionescu007)
BLF.bt (by CERTCC)
ProcessHacker
PipeAttribute (by hzshang)