风险接近于0Day:利用 Microsoft 内核流服务分析

admin 2024年2月23日21:02:09评论23 views字数 6506阅读21分41秒阅读模式

风险接近于0Day:利用 Microsoft 内核流服务分析

上个月,微软修补了 Microsoft Kernel Streaming Server 中的一个漏洞,该服务器是一个用于虚拟化和共享相机设备的 Windows 内核组件。该漏洞CVE-2023-36802允许本地攻击者将权限升级到 SYSTEM。

这篇博文详细介绍了我探索 Windows 内核中的新攻击面、查找 0day 漏洞、探索有趣的 bug 类以及构建稳定的漏洞利用的过程。尽管对内存损坏和操作系统概念的基本了解会很有帮助,但这篇文章不需要任何专门的 Windows 内核知识。我还将介绍对不熟悉的内核驱动程序执行初始分析的基础知识,并简化查看新目标的过程。

攻击面

Microsoft 内核流服务器 ( mskssrv.sys ) 是 Windows 多媒体框架服务 Frame Server 的组件。该服务虚拟化相机设备并允许在多个应用程序之间共享该设备。

在注意到CVE-2023-29360后,我开始探索这个攻击面,该漏洞最初被列为 TPM 驱动程序漏洞。该错误实际上存在于 Microsoft Kernel Streaming Server 中。虽然当时我对MS KS Server还不熟悉,但这个驱动程序的名字足以引起我的兴趣。尽管对目的或功能一无所知,但我认为内核中的流服务器可能是寻找漏洞的富有成果的地方。我盲目地试图回答以下问题:

  • 非特权应用程序可以以什么身份与该内核模块交互?

  • 模块直接处理来自应用程序的什么类型的数据?

为了回答第一个问题,我首先在反汇编器中分析二进制文件。我很快就发现了前面提到的漏洞,一个简单而优雅的逻辑错误。该问题看起来很容易触发并充分利用,因此我寻求开发一个快速的概念验证,以更好地了解mskssrv.sys驱动程序的内部工作原理。

初步分析

在 MS KS Server 内触发执行

首先,我们需要能够从用户空间应用程序访问驱动程序。可以从驱动程序的DispatchDeviceControl例程访问易受攻击的函数 ,这意味着可以通过向驱动程序发出IOCTL来访问该函数。为此,需要使用设备路径调用CreateFile来获取驱动程序设备的句柄。通常,查找设备名称/路径很容易识别:在驱动程序中找到对IoCreateDevice 的调用并检查包含设备名称的第三个参数。

风险接近于0Day:利用 Microsoft 内核流服务分析

mskssrv.sys 内的函数,使用设备名称的 NULL 指针调用 IoCreateDevice

在这种情况下,设备名称参数为NULL。调用函数名称表明mskssrv是一个PnP驱动程序,对IoAttachDeviceToDeviceStack 的调用表明创建的设备对象是设备堆栈的一部分。实际上,这意味着当 I/O 请求发送到设备时会调用多个驱动程序。对于 PnP 设备,需要设备接口路径才能访问设备。

使用WinDbg内核调试器我们可以看到哪些设备属于mskssrv驱动程序和设备堆栈:

风险接近于0Day:利用 Microsoft 内核流服务分析

!drvobj 和 !devobj 命令的输出显示上部和下部设备

上面我们看到mskssrv的设备附加到属于swenum.sys驱动程序的下层设备对象,并附加了属于ksthunk.sys的上层设备。

从设备管理器中我们可以找到目标设备实例ID:

风险接近于0Day:利用 Microsoft 内核流服务分析

显示设备实例 ID 和接口 GUID 的设备管理器

我们现在有足够的信息来使用配置管理器或SetupApi 函数获取设备接口路径。使用检索到的设备接口路径,我们可以打开设备的句柄。

最后,我们现在能够触发mskssrv.sys 内的代码执行。创建设备时,将调用驱动程序的 PnP调度创建函数。为了触发额外的代码执行,我们可以发送 IOCTL 来与将在驱动程序的调度设备控制函数中执行的设备进行通信。

调试幽灵驱动程序

执行二进制分析时,最佳实践是使用静态(反汇编器、反编译器)和动态(调试器)工具的组合。WinDbg可用于对目标驱动程序进行内核调试。通过在预期执行代码的地方设置一些断点(调度创建、调度设备控制)。

一开始我遇到了一些困难——我在驱动程序中设置的断点都没有被击中。我有些怀疑我是否打开了正确的设备,或者做了其他错误的事情。后来我意识到我的断点被取消设置,因为驱动程序正在卸载。我在互联网上搜索答案,但是在搜索mskssrv时并没有太多结果,尽管在 Windows 上默认已加载并可访问。在我发现的少数结果中,有一个关于 OSR 的线程,其中其他人遇到了类似的问题。

风险接近于0Day:利用 Microsoft 内核流服务分析

OSR海报中的鼓励话语

事实证明,如果 PnP 筛选器驱动程序有一段时间没有使用,则可以将其卸载,并在需要时按需加载回来。

我通过在打开设备句柄之后、调用DeviceIoControl之前设置断点来解决我遇到的问题,以确保驱动程序最近已加载。

驱动程序功能的快速调查

mskssrv驱动程序只是一个 72KB 大小的二进制文件,支持调用以下函数的设备 IO 控制代码:

  • FSRendezvousServer::InitializeContext

  • FSRendezvousServer::InitializeStream

  • FSRendezvousServer::RegisterContext

  • FSRendezvousServer::RegisterStream

  • FSRendezvousServer::DrainTx

  • FSRendezvousServer::NotifyContext

  • FSRendezvousServer::PublishTx

  • FSRendezvousServer::PublishRx

  • FSRendezvousServer::ConsumeTx

  • FSRendezvousServer::ConsumeRx

通过查看这些符号名称,我们可以推断出驱动程序的某些功能,即处理传输和接收流的功能。此时,我更深入地了解了驱动程序的预期功能。我找到了Michael Maltsev的有关 Windows 多媒体框架的演示文稿,其中我了解到该驱动程序是共享相机流的进程间机制的一部分。

由于驱动程序不是很大并且 IOCTL 也不多,因此我可以查看每个函数以了解驱动程序的内部结构。每个 IOCTL 函数都对上下文注册对象或流注册对象进行操作,这些对象是通过相应的“初始化”IOCTL 进行分配和初始化的。指向该对象的指针存储在Irp ->CurrentStackLocation->FileObject->FsContext2 中。FileObject指向每个打开文件创建的设备文件对象,FsContext2是一个用于存储每个文件对象元数据的字段。

漏洞

我在尝试了解如何直接与驱动程序通信时发现了这个错误,首先对用户模式组件fsclient.dll和frameserver.dll进行了分析。我差点错过了这个错误,因为我假设开发人员实例化了一个被忽视的简单检查。我们看一下PublishRx IOCTL函数:

风险接近于0Day:利用 Microsoft 内核流服务分析

FSRendezvousServer::PublishRx 反编译片段

从FsContext2检索流对象后,调用函数FSRendezvousServer::FindObject来验证指针是否与全局FSRendezvousServer存储的两个列表中找到的对象匹配。起初,我假设这个函数有某种方法来验证请求的对象类型。但是,如果在上下文对象列表或流对象列表中找到该指针,则该函数返回TRUE 。请注意,没有有关对象类型的信息传递给FindObject。这意味着上下文对象可以作为流对象传递。这是一个对象类型混淆漏洞!它出现在每个对流对象进行操作的 IOCTL 函数中。为了修补该漏洞,Microsoft 将FSRendezvousServer::FindObject替换为FSRendezvousServer::FindStreamObject,它首先通过检查类型字段来验证该对象是否为流对象。

Exploitation 开发

Primitive 原始

由于上下文注册对象(0x78 字节)小于流注册对象(0x1D8 字节),因此可以在越界内存上执行流对象操作:

风险接近于0Day:利用 Microsoft 内核流服务分析

对象类型混淆漏洞说明

Pool spray

为了利用漏洞原语,我们需要能够控制访问的越界内存。这可以通过触发在易受攻击的对象的同一内存区域中分配许多对象来完成。这种技术称为堆喷射或池喷射。易受攻击的对象分配在非分页低碎片堆池中。我们可以使用Alex Ionescu的经典技术来喷射缓冲区,从而完全控制 0x30 字节DATA_QUEUE_ENTRY标头以下的内存内容。通过使用这种技术进行喷射,我们可以获得如图所示的内存布局:

风险接近于0Day:利用 Microsoft 内核流服务分析

NpFr 缓冲液喷涂示意图

使用所选的池喷射方法,可以控制对象偏移量在0xC0-0x10F和0x150-0x19F范围内的字段。我再次回顾了流对象的 IOCTL 函数来寻找漏洞利用原语。我搜索了访问和操作可控对象字段的地方。

持续写入

我在PublishRx IOCTL中发现了一个很好的常量 write-where 原语。该原语可用于在任意内存地址写入常量值。让我们看一下FSStreamReg::PublishRx函数的片段:

风险接近于0Day:利用 Microsoft 内核流服务分析

FSStreamReg::PublishRx 反编译片段

流对象在偏移量 0x188 处包含一个列表头,它描述了FSFrameMdl对象的列表。在上面的反编译片段中,迭代此列表,如果FSFrameMdl对象中的标记值与从应用程序传入的系统缓冲区中的标记匹配,则调用函数FSFrameMdl::UnmapPages 。

使用上述利用原语,可以完全控制FSFrameMdlList  以及pFrameMdl指向的FsFrameMdl对象。现在让我们看看UnmapPages:

风险接近于0Day:利用 Microsoft 内核流服务分析

FSFrameMdl:UnmapPages反编译

在上面反编译函数的最后一行,常量值 2 被写入到此( FSFrameMdl对象)的可控偏移值中。这种持续写入可以与I/O Ring技术结合使用,以获得任意内核读写和权限提升。您可以在此处和此处阅读有关此技术的更多信息。

尽管我选择使用常量写入原语,但另一个有用的利用原语也出现在该函数中。调用MmUnmapLockedPages 的参数BaseAddress和MemoryDescriptorList都是可控的。这可用于取消映射任意虚拟地址处的映射并构造类似释放后使用的原语。

问题

至此,已经确定了几个可以提供任意内核读写的合适的漏洞利用原语。您可能已经注意到,必须对流对象的内容进行多项检查才能触发所需的代码路径。在大多数情况下,物体的正确状态可以通过池喷射来实现。然而,我遇到了一个问题,造成了一些困难。下面显示了FSStreamReg::PublishRx循环遍历FSFrameMdlList后的代码片段:

风险接近于0Day:利用 Microsoft 内核流服务分析

FSStreamReg::PublishRx 反编译片段

在上面的反编译中,bPagesUnmapped是一个布尔变量,如果调用FSFrameMdl::UnmapPages,则会设置该变量。如果是,则检索流对象的偏移量 0x1a8,如果不为 null,则对其调用KeSetEvent 。

此偏移量对应于越界内存和POOL_HEADER内的点,POOL_HEADER 是分隔池中缓冲区分配的数据结构。特别是它指向ProcessBilled字段,该字段用于存储指向“负责”分配的进程的_EPROCESS对象的指针。这用于计算特定进程可以拥有的池分配数量。并非所有池分配都会针对进程“收费”,以及那些没有  在POOL_HEADER中将ProcessBilled字段设置为 NULL 的池分配。此外,存储在 ProcessBilled 中的 EPROCESS 指针实际上是与随机 cookie 进行异或的,因此ProcessBilled不包含有效的指针。

这带来了困难,因为NpFr缓冲区由调用进程负责,因此设置了ProcessBilled 。当触发所需的漏洞利用原语时,bPagesUnmapped将设置为TRUE。如果将无效的指针传递给KeSetEvent,系统将崩溃。因此,有必要确保 POOL_HEADER用于免费分配。此时,我注意到上下文注册(Creg)对象本身不收费。但是,该对象不允许控制FSFrameMdl偏移处的内存内容。因此,NpFr和Creg对象都需要喷涂,它们也需要正确排序。

与大池分配不同,您不能通过NtQuerySystemInformation泄漏 LFH 池分配的地址。此外,分配顺序是随机的。因此,无法知道易受攻击对象的相邻缓冲区是否处于正确的顺序,以触发漏洞利用原语并避免系统崩溃。幸运的是,该漏洞可用于触发相邻缓冲区的池泄漏。我们看一下ConsumeTx的IOCTL函数:

风险接近于0Day:利用 Microsoft 内核流服务分析

FSRendezvousServer::ConsumeTx 反编译片段

上面,函数FSStreamReg::GetStats被调用:

风险接近于0Day:利用 Microsoft 内核流服务分析

FSStreamReg::GetStats 反编译

此处,易受攻击的流对象的越界内存内容被复制到 SystemBuffer 中,该 SystemBuffer返回到调用用户空间应用程序。该池信息泄漏原语可用于对与易受攻击的对象相邻的缓冲区执行签名检查。可以对许多易受攻击的对象执行扫描,直到找到所需内存布局内的对象。一旦找到所需的对象,内存布局如下:

风险接近于0Day:利用 Microsoft 内核流服务分析

CVE-2023-36802 低碎片堆池修饰布局

现在,将目标易受攻击的对象定位在内存中的正确位置后,可以触发目标对象上的上述漏洞利用原语,而不会导致系统崩溃。

在野外开采

在向 MSRC 报告该问题后,发现了该漏洞的疯狂利用。

本博文中介绍的利用方法是可以采用的多种方法中的一些。目前,还没有关于攻击者如何利用该漏洞的公开信息。您可以在这里找到漏洞利用代码。

结论

追溯补丁分析显示, Windows 10 1809 版本中的mskssrv.sys中添加了很大一部分新代码。监视新代码添加通常对于查找漏洞非常有效。

从这个分析中可以学到另一个令人厌倦但经典的教训:不要对执行的检查做出假设。一位朋友和同事建议,使用FsContext2 的类型混淆可能是“常见但正在研究的错误类别”。我相信对于这个错误类需要进行更多的变体分析,特别是在处理进程间通信的驱动程序中。

这个漏洞的发现是在尝试与不熟悉的攻击面进行交互时发现的。拥有一个系统的“知识几乎接近于零”也意味着拥有打破它的新思维。

参考

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

  • https://big5-sec.github.io/posts/CVE-2023-29360-analysis/

  • https://community.osr.com/discussion/291188/ioregisterplugplaynotification-and-ksfilter-drivers

  • https://www.virusbulletin.com/uploads/pdf/conference_slides/2019/VB2019-Maltsev.pdf

  • https://www.crowdstrike.com/blog/sheep-year-kernel-heap-fengshui-spraying-big-kids-pool/

  • https://windows-internals.com/one-io-ring-to-rule-them-all-a-full-read-write-exploit-primitive-on-windows-11/

  • https://connormcgarr.github.io/swimming-in-the-kernel-pool-part-1/

  • https://www.cisa.gov/news-events/alerts/2023/09/12/cisa-adds-two-known-vulnerability-catalog

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

原文始发于微信公众号(Ots安全):风险接近于0Day:利用 Microsoft 内核流服务分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年2月23日21:02:09
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   风险接近于0Day:利用 Microsoft 内核流服务分析http://cn-sec.com/archives/2114376.html

发表评论

匿名网友 填写信息