野外0day – CVE-2023-36802:Microsoft 流服务代理权限提升漏洞

野外0day – CVE-2023-36802:Microsoft 流服务代理权限提升漏洞

Ots安全 2023-10-14 09:00

基础

披露或补丁日期:
 2023 年 9 月 12 日

产品:
窗户

咨询: https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-36802

受影响的版本:
– 没有 KB5030211 或 KB5030214 的 Windows 10

  • 没有 KB5030219 或 KB5030217 的 Windows 11

  • 没有 KB5030214 的 Windows Server 2019

  • 没有 KB5030216 或 KB503025 的 Windows Server 2022

第一个补丁版本:
– 带有 KB5030211 或 KB5030214 的 Windows 10

  • 带有 KB5030219 或 KB5030217 的 Windows 11

  • Windows Server 2019 与 KB5030214

  • 带有 KB5030216 或 KB503025 的 Windows Server 2022

问题/错误报告:
不适用

补丁 CL:
不适用

Bug 引入 CL:
 N/A

记者:
– 夏光辉 (@ze0r) 与河北华测

  • Quan Jin (@jq0904) 和 ze0r 与 DBAPPSecurity WebBin 实验室

  • Valentina Palmiotti 与 IBM X-Force

  • 微软威胁情报

  • 微软安全响应中心

代码

概念验证:

HANDLE h;
HRESULT hr;
DWORD bytesReturned;
char buf[0x100] = {0};

hr = KsOpenDefaultDevice(KSNAME_Server, GENERIC_READ | GENERIC_WRITE, &h);

memset(buf, 'A', sizeof(buf));
*(int32_t *)buf = 1;
*((int64_t *)buf + 3) = 0;
BOOL status = DeviceIoControl(h, IOCTL_FRAMESERVER_INIT_CONTEXT, &buf, sizeof(buf), &buf, sizeof(buf), &bytesReturned, 0);

memset(buf, 'A', sizeof(buf));
*((DWORD*)buf + 8) = 1;
*((DWORD*)buf + 9) = 1;
status = DeviceIoControl(h, IOCTL_FRAMESERVER_PUBLISH_RX, &buf, sizeof(buf), &buf, sizeof(buf), &bytesReturned, 0);

利用样本:
未公开

您在进行分析时是否有权访问漏洞利用样本?
是的

漏洞

Bug 类别:
类型混淆

漏洞详细信息:

当在设备驱动IOCTL_FRAMESERVER_PUBLISH_RX
程序上执行 IOCTL时mskssrv
,会执行一个调用,而FSRendezvousServer::PublishRx()
该调用又会调用FSStreamReg::PublishRx()

将获取the 字段FSStreamReg::PublishRx()
中的任何内容
并将其用作
进一步的对象,只要它是有效对象。FsContext2
FILE_OBJECT
FsStreamReg

攻击者可以
在同一设备句柄上IOCTL_FRAMESERVER_PUBLISH_RX
使用IOCTL,这会将
字段初始化为不同类型(即 类型)的对象
。IOCTL_FRAMESERVER_INIT_CONTEXT
FsContext2
FsContextReg

FSStreamReg::PublishRx()
对它认为是FsStreamReg
对象的对象执行多项操作,其中一些操作会导致可利用的场景,例如越界写入。

其他 3 个 IOCTL 中也存在类似的漏洞:
– IOCTL_FRAMESERVER_PUBLISH_TX

  • IOCTL_FRAMESERVER_CONSUME_TX

  • IOCTL_FRAMESERVER_CONSUME_RX

所有这些都通过 CVE-2023-36802 标识符引用。

补丁分析:

在补丁之前,FSRendezvousServer::PublishRx()
会FSRendezvousServer::FindObject()
在调用之前调用FSStreamReg::PublishRx()

仅当FSRendezvousServer::FindObject()
返回TRUE
,FSStreamReg::PublishRx()
时才被调用。
但是,FSRendezvousServer::FindObject()
不会检查对象的类型。

该补丁更改了FSRendezvousServer::FindObject()
检查对象的类型字段是否专门设置为2,否则返回FALSE
。FsStreamReg
构造对象时(在 中) 
,类型字段设置为 2 FSStreamReg::FSStreamReg()

此外,FSRendezvousServer::FindObject()
还被重命名为更合适的名称FSRendezvousServer::FindStreamObject()

关于如何发现此漏洞的想法(模糊测试、代码审计、变体分析等):

该错误可以通过手动代码审核或模糊测试来发现。
查看上面的概念证明,两个 IOCTL 与相对简单的输入缓冲区的组合会触发崩溃。Valentina Palmiotti 通过手动代码审核发现了此漏洞,如她的博客文章
中所述

(历史/现在/未来)错误的背景:
– 2023 年 6 月 16 日:mskssrv.sys
Synacktiv 在 Pwn2Own 2023 上使用的另一个权限提升漏洞 CVE-2023-29360 已被修补并公开披露。
尽管在同一驱动程序中,CVE-2023-29360 与 CVE-2023-36802 截然不同。

  • 2023 年 9 月 12 日:CVE-2023-36802 已修补并公开披露,据报道已在野外发现。

  • 2023 年 10 月 10 日:Valentina Palmiotti发布了
    CVE-2023-36802 及其利用的详细信息。

漏洞利用

(此处定义了
术语
“利用原语”
、“利用策略”
、“利用技术”
和“利用流程”
。)

利用策略(或多个策略):
– 通过众所周知的技术泄露相关内核对象的地址NtQuerySystemInformation

  • 创建内核池布局,以便将来的FSContextReg
    分配落在被攻击者控制的数据包围的洞中。

  • 分配一个FSContextReg
    对象(通过IOCTL_FRAMESERVER_INIT_CONTEXT
    IOCTL)。

  • 对对象执行越界操作FSContextReg
    (通过IOCTL_FRAMESERVER_PUBLISH_RX
    ),这将执行一些有趣的写入并最终将 的 设置PreviousMode
    为KTHREAD
    0。

  • 在内核地址上使用NtReadVirtualMemory
    和NtWriteVirtualMemory
    将系统令牌复制到EPROCESS
    当前进程的 。

  • 清理。

  • 产生感兴趣的命令,例如cmd.exe
    .

利用流程:

根据 Windows 版本的不同,分析的野生样本中存在两种不同的漏洞利用流程。
这是因为根据 Windows 版本:
– 代码FSStreamReg::PublishRx
略有不同,即最新版本包含
对对象字段之一的mskssrv.sys
调用
,而旧版本则不包含ObfDereferenceObject
FsStreamReg

  • 对象的结构布局FsStreamReg
    略有不同。
    这对于漏洞利用流程中引起的副作用很重要。

由于较新的mskssrv.sys
版本包含对 的方便调用ObfDereferenceObject
,因此它将使用该调用将PreviousMode
字段直接递减至 0。我们将首先查看此漏洞利用流程。

由于旧mskssrv.sys
版本不包含方便的ObfDereferenceObject
调用,因此利用流程有点复杂。
它会破坏 CLFS 特定的对象,以通过 vtable 调用来调用内核小工具。

无 CLFS 的漏洞利用流程

  • mskssrv
    通过调用
    打开设备KsOpenDefaultDevice

  • 通过以下方式泄漏一些所需的内核地址NtQuerySystemInformation(SystemExtendedHandleInformation, …)

  • 当前KTHREAD
    地址(以及当前PreviousMode
    地址)

  • EPROCESS
    系统进程和当前进程的地址(以及它们各自的Token
    地址)

  • FILE_OBJECT
    mskssrv
    设备文件句柄
    的地址

  • 使用众所周知的
    NtFsControlFile
    使用 fsctl 代码 0x119ff8
    进行调用的命名管道技术
    ,将大小为 0x80 的对象(不包括 0x10 字节池标头)喷射到池中。

  • 关闭一些管道,在泳池喷雾上打孔。

  • 调用IOCTL_FRAMESERVER_INIT_CONTEXT
    ioctl:

  • FsInitializeContextRendezVous()
    来电FSRendezvousServer::InitializeContext()

  • FSRendezvousServer::InitializeContext()
    分配一个FsContextReg
    大小为0x78字节的对象,并将 中的FsContext2
    字段设置为FILE_OBJECT
    指向该对象。
    该FsContextReg
    对象将占据先前创建的孔的位置。

  • 重新填满剩余的孔。

  • 在单独的线程中
    调用IOCTL_FRAMESERVER_PUBLISH_RX
    ioctl 
    :****

  • FSRendezvousServer::PublishRx()
    随后
    将调用和FSStreamReg::PublishRx()

  • FSStreamReg::PublishRx()
    将期望FsStreamReg
    字段中存在一个FsContext2
    大小为 0x1d8 字节的对象,而实际上FsContextReg
    存在一个较小的对象,与受控数据相邻。

  • FSStreamReg::PublishRx()
    然后将调用ObfDereferenceObject()
    从相邻对象中取出的超出范围的字段。
    攻击者将PreviousMode
    当前线程的地址放在那里。
    这会将主线程的 递减PreviousMode
    至 0。此时,攻击者可以
    从主线程调用内核地址上的NtReadVirtualMemory
    和。NtWriteVirtualMemory

  • 如果不加以处理,FSStreamReg::PublishRx()
    现在将调用KeSetEvent
    另一个越界字段,该字段与ProcessBilled
    相邻对象(不受攻击者控制)的池标头中的字段一致。
    由于这是一个无效的指针,它会对系统进行错误检查。
    为了防止这种情况,攻击者保持FSStreamReg::PublishRx()
    锁定在 while 循环中。
    这是通过伪造自引用链表条目来实现的。FSFrameMdlList::MoveNext
    将继续返回相同的列表条目。
    列表条目被放置在用户模式中,因此该漏洞可以根据需要打破这个 while 循环(见下文)。

  • 同时在主线程中:

  • 尝试NtReadVirtualMemory
    在循环中使用读取系统进程令牌,直到成功。PreviousMode
    在另一个线程中被覆盖
    后,这将成功。

  • 使用将系统令牌写入EPROCESS
    当前进程的NtWriteVirtualMemory

  • FsContext2
    从 中
    读取该字段的值FILE_OBJECT
    即可获取该对象的地址FsContextReg

  • 读取(以便以后恢复)并将ProcessBilled
    相邻池块标头之一覆盖为 NULL,因此KeSetEvent
    不会使系统崩溃。

  • FSStreamReg::PublishRx()
    通过更改自引用列表条目
    来打破当前锁定的 while 循环。FSStreamReg::PublishRx()
    现在将继续但不会调用,KeSetEvent
    因为该字段为 NULL。

  • 等待 FSStreamReg::PublishRx()
    另一个线程完成。

  • 将损坏的ProcessBilled
    字段恢复为其原始值。

  • 增加当前的引用计数EPROCESS

  • 将 重置PreviousMode
    为 1。

  • 启动感兴趣的命令,例如cmd.exe
    .

使用 CLFS 来利用流程

在较旧的系统上,FSStreamReg::PublishRx
不包含对ObfDereferenceObject

这是不幸的,因为ObfDereferenceObject
这是一个非常容易减少PreviousMode
.

但是,FSStreamReg::PublishRx()
仍然会调用FSFrameMdl::UnmapPages()
从相邻(喷射的)对象中获取的超出范围的地址,这将在超出范围的地址上进行一些有用的写入:
– 在该地址的偏移 0xc8 处写入 QWORD 0

  • 在该地址的偏移 0x10 处写入 DWORD 2

该漏洞如何利用这一点?
– 以与无 CLFS 漏洞利用流程相同的方式泄漏所需的内核地址。

  • 创建一个 CLFS 日志文件,打开它并泄漏其内核地址(使用NtQuerySystemInformation(SystemExtendedHandleInformation, …)
    )。

  • 解决一些nt
    内核小工具:PoFxProcessorNotification
    ,IoSizeofWorkItem
    和RtlClearBit

  • CClfsContainer
    在 0x1000000 处
    伪造一个假对象,在 0x1000800 处伪造一个虚函数表。

  • 在 0x1000400 处伪造一个假的BitMapHeader
    ,并使用指向该地址的位图指针PreviousMode

  • 喷射池(再次使用NtFsControlFile
    ),但精心制作的对象现在包含 CLFS 日志文件的内核地址 + 0x2c9。

  • 创建孔,分配FsContextReg
    孔中的对象并重新填充剩余的孔,就像以前一样。

  • 触发FSStreamReg::PublishRx()
    。FSFrameMdl::UnmapPages()
    将在该地址读取越界时调用,因此写入:

  • QWORD 0 位于内存中 CLFS 日志文件开头的偏移量 0x2c9+0xc8=0x391 处

  • DWORD 2 位于内存中 CLFS 日志文件开头偏移量 0x2c9+0x10=0x2d9 处

  • 调用CreateLogFile
    最终会调用CClfsBaseFilePersisted::CheckSecureAccess

  • ClfsBaseFilePersisted::CheckSecureAccess
    将使用偏移量 0x398 处的 DWORD 作为距日志块头(大小为 0x70)末尾的偏移量来查找对象CLFS_CONTAINER_CONTEXT
    (通过调用CClfsBaseFile::GetSymbol()
    )。
    但是 QWORD 写入损坏了该 DWORD 的最低有效字节,这使得偏移量从 0x1460 更改为 0x1400。
    因此,现在 CLFS 日志文件+0x70+0x1400 中的任何内容都将被解释为CLFS_CONTAINER_CONTEXT
    对象,即攻击者控制的数据。CClfsContainer
    在该对象的偏移量 0x18 处,取消引用
    指向对象的指针。
    该漏洞利用程序将地址 0x1000000 作为指针,之前它在那里准备了一个伪造的CClfsContainer
    对象。

  • CLFS!CClfsBaseFilePersisted::CheckSecureAccess
    然后将调用该对象的 vtable 中的第一个函数(在正常情况下CClfsContainer::AddRef
    ),并将对象本身作为第一个参数传递。
    该漏洞利用将该nt!PoFxProcessorNotification
    函数放置在伪造的 vtable 中。
    (请注意,CLFS 驱动程序中有 CFG,因此该漏洞必须使用允许的函数地址作为小工具。)

  • nt!PoFxProcessorNotification
    将取消引用其参数的几个地址,并使用这些地址中的另一个作为参数来调用这些地址之一。
    所以现在漏洞利用控制了被调用的函数及其第一个参数。
    该漏洞利用程序选择nt!RtlClearBit
    要调用的函数。

  • nt!RtlClearBit
    需要 2 个参数:指向位图标头的指针(包含指向位图本身的指针)和要清除的位数。
    回想一下,该漏洞利用程序准备了一个伪造的位图标头,其中有一个指向该地址的位图指针PreviousMode

    第二个参数 – 要清除的位数 – 不受控制,但rdx
    可以方便地设置为 0。nt!RtlClearBit
    因此将将该PreviousMode
    字段设置为 0。

  • 为了稳定性,该漏洞利用程序准备了第二个 vtable 条目nt!IoSizeofWorkItem

  • 它除了设置之外什么也不做eax
  • 因为稍后CClfsBaseFilePersisted::CheckSecureAccess
    会调用它认为的内容CClfsContainer::Release

  • 将系统进程令牌复制到当前的EPROCESS
    .

  • 恢复 CLFS 日志文件中原始的 0x1460 偏移量。

  • 恢复PreviousMode
    为1。

  • 启动感兴趣的命令,例如cmd.exe
    .

请注意,在这个版本的mskssrv.sys
漏洞利用流程中,漏洞利用程序不必处理尴尬的KeSetEvent
调用。
原因是KeSetEvent
旧版本中传递给字段的偏移量mskssrv.sys
并不落在池块标头内,而是落在受控喷射数据中,并以这种方式设置为 NULL。

与 Valentina Palmiotti 的漏洞利用存在显着差异

了解上述自然利用与 Valentina Palmiotti 在她的博客文章
中描述的利用之间的差异是一个有趣的练习

Valentina 的漏洞利用基于最新mskssrv.sys
版本。
可以从ObfDereferenceObject
屏幕截图中是否存在呼叫来推断。
因此,让我们将她的漏洞利用与“无 CLFS 漏洞利用流程”进行比较。
– Valentina 使用调用提供的“write 2 where”原语FSFrameMdl::UnmapPages()
,“与 I/O 环技术结合”。
我们分析的漏洞利用了旧mskssrv.sys
版本的原语(与 CLFS 技术结合),但ObfDereferenceObject()
在新mskssrv.sys
版本中使用了调用提供的“递减位置”原语。

  • Valentina 结合信息泄漏进行了一些更复杂的池整理,FSStreamReg::GetStats
    以防止稍后的KeSetEvent
    调用因无效指针而崩溃。
    这里分析的漏洞使用了不同的解决方案:它FSStreamReg::PublishRx
    从单独的线程触发该函数,并将其保持在锁定的 while 循环中(在用户模式下使用自引用链表条目),从而阻止调用到达KeSetEvent

    一旦它具有内核读/写,它就会将触发 的字段更改KeSetEvent
    为 NULL,然后更改自引用链表条目,以便 while 循环中断。
    这意味着KeSetEvent
    呼叫将被跳过。
    (之后它恢复会触发调用的字段,KeSetEvent
    因为它是池块中的拥有进程 EPROCESS)。

相同漏洞利用流程的已知案例:

正如Zscaler 的博客文章
所述,野外利用的 CVE-2022-37969 CLFS 漏洞遵循非常相似的利用流程

漏洞利用链的一部分?
此漏洞很可能被用作独立的本地权限升级。

下一步

变异分析

变体分析的领域/方法(以及原因):
鉴于此漏洞以及 CVE-2023-29360 相对简单的性质,更多的模糊测试和代码审核可能会mskssrv.sys
产生更多错误。

发现的变种:
不适用

结构改进

有哪些结构性改进,例如消除 bug 类、防止引入此漏洞、减轻漏洞利用流程、使此类漏洞更难利用等方法?

杀死 bug 类的想法:

CastGuard
可能会捕获此错误,因为看起来(基于它们的 vtable)FsStreamReg
和FsContextReg
type 都派生自同一个FsRegObject
类。

减少漏洞利用流程的想法:
– MTE
和CHERI
不会捕获类型混淆,但会捕获类型混淆导致的越界访问。

  • 在无 CLFS 漏洞利用流程中FSStreamReg::PublishRx
    取消引用假指针时,
    SMAP
    将捕获用户模式访问。FSFrameMdl
    在 CLFS 漏洞利用流程中,SMAP 将捕获CClfsContainer
    用户模式下的虚假对象 vtable 访问。

  • 通过调用消除内核地址泄漏NtQuerySystemInformation

其他潜在的改进:

不适用

0day检测方法

类似 0day 的潜在检测方法有哪些?
这意味着是否有任何关于如何将此漏洞或类似漏洞检测为 0day 的
想法?
– 使用的漏洞利用技术的静态签名(例如使用 Yara)。

  • 根据有趣的动态信号分析样本,例如NtQuerySystemInformation
    使用SystemExtendedHandleInformation
    参数的调用。


阅读原文可通过点击阅读原文

感谢您抽出

.

.

来阅读本文

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