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)