原文链接:https://www.zerodayinitiative.com/blog/2018/12/4/directx-to-the-kernel
操作系统内核是每个漏洞利用链的最终目标,你可以从这些年来the Zero Day Initiative (ZDI) Pwn2Own竞赛的题目上看出来。Windows内核受到了许多方面的攻击,我最喜欢的一个是对DeviceIoControl调用各种驱动程序的滥用,因为这导致能够访问许多由不同供应商编写的驱动程序,而其中很多驱动程序的编写和测试并不是很好。
多年来,大多数侵入Windows内核的攻击都是通过win32k.sys进行的,win32k.sys是一种内核模式的设备驱动程序,用来控制窗口图形和窗口管理系统。当微软在20年前将这一功能从CSRSS移到内核时,它立即将针对Windows内核的攻击面增加了一倍或两倍——从此,这里就成为了一个漏洞百出的地方。
自从WDDM (Windows显示驱动程序模型)在过去十年取代早期的XDDM以来,就出现了另一个大型攻击面。在显示系统通过win32k.sys调用初始化操作之后,用户进程可以通过GDIPlus的入口点直接调用dgxkrnl.sys和其他驱动,这个扩展的攻击面对研究人员是很具有吸引力的。
2018年春天,ZDI从腾讯湛泸实验室的ChenNan和RanchoIce手上购买了5个针对DirectX内核接口的漏洞。这些购买的漏洞造成了4个针对微软的CVE漏洞,本文涵盖了这些漏洞并提供了在我们网站上发表的PoC。
此外,Rancho和ChenNan在9月份的44CON会议演讲中对其中一个攻击(ZDI-18-946/CVE-2018-8405)有着重介绍,我强烈建议研究人员可以去回顾一下演讲的课件。
在深入讨论这些漏洞之前,我们来简要了解一下DirectX接口和驱动程序。
DirectX图形内核子系统由三个内核模式驱动程序组成:dxgkrnl.sys,dxgmms1.sys和dxgmms2.sys。这些驱动程序通过win32k.sys和它们自己的接口集来和用户通信,它们也和BasicRender.sys, BasicDisplay.sys和显示微型端口驱动程序通信。
DirectX定义了许多复杂的内核对象,大多数的名字都是以DXG开头。用户通过许多复杂的API入口点和DirectX通信,这些API切入点很多名字都是以D3DKMT开头的,还有一些是以DXGK开头的。
以下是一些更有趣的切入点:
D3DKMTEscape——这个切入点接受一个完全由用户控制的blob数据作为输入。这个数据块可能非常大,因此在向内核处理的转换过程中,更倾向将它留在用户内存中,而不是在内核中捕获它。这种模式使得被调用的内核例程可以被当为备选用到TOC/TOU漏洞中。这个数据不是标准化的结构,所以每个驱动程序都有不同的定义。
D3DKMTRender——这个切入点是实际渲染图形数据的核心。用户地址命令和补丁缓冲区是由内核驱动程序解释,实际上是传递给迷你端口驱动程序的。同样,也可以在条件竞争中利用。此外,渲染程序会生成大量的工人线程,使得条件竞争漏洞更可能出现。
D3DKMTCreateAllocation——这个切入点用来分配内存。由于传入API调用的不同标志和句柄之间复杂的相互作用,可能会出现问题(参阅下面的ZDI-18-946)。
IOActive的Ilja van Sprundel在2014年发表的题为“Windows内核图形驱动程序攻击表面”的黑帽子报告是一个很好的从攻击角度对WDDM的概述。
PoC源码可以在这里找到。如果你想重现这些崩溃,你需要一个2018年8月之前的Windows版本(在微软修复这些漏洞之前的版本)。记得要将内核调试器附加到被测试的机器上,并在受攻击的驱动程序上设置特殊池。我是在Windows 10 x64上测试了这些漏洞报告。
我们讨论的第一个漏洞是在dgxkrnl.sys中的DXGDEVICE::CreateAllocation方法中,通过D3DKMTCreateAllocation方法暴露,可以允许本地攻击者将特权升级到系统权限。这是我们对此的建议,微软的补丁在这里。错误的原因是缺乏对用户提供数据的正确验证,可能导致类型混淆。
要查看此操作,在运行PoC之前请在dxgkrnl.sys上启用特殊池。类型混淆是由于在分配中不恰当地使用CrossAdapter标志造成的。PoC代码使用一个为0的CrossAdapter标志进行分配,然后将结果传递给第二个分配,在第二个分配中设置一个为1的CrossAdapter标志。
这是蓝屏分析:
错误代码在DXGDEVICE::CreateAllocation中,是一种经典的类型混淆:
下一个漏洞存在于dxgmms2.sys驱动程序中,通过D3DKMTRender方法暴露出来。这个漏洞也是允许本地攻击者将特权升级到系统权限。这是我们对此的建议,微软的补丁在这里。与第一个示例一样,该错误会导致类型混淆,虽然本质上类似,但是这些bug有不同的根源。
同样,你需要在dxgkrnl.sys和dxgmms2.sys上启用特殊池才能发现这些漏洞,当然,还要目标机器上添加内核调试器。这种类型混淆是由于分配操作混淆了两个不同适配器。
相关PoC代码:
PoC崩溃细节:
漏洞代码:
下一个漏洞也是通过D3DKMTRender例程暴露的,这个漏洞在dxgkrnl.sys的DGXCONTEXT::ResizeUserModeBuffers 方法中。这是我们对此的建议,微软的补丁在这里。在这个例子中,错误是由于在将用户提供的值作为指针解除引用之前缺乏适当的验证所导致的。造成这个结果的原因是因为驱动程序信任用户设置的标志。以下是PoC的相关详情:
导致了下面的崩溃:
调用源:
漏洞代码:
显然,来自用户的这个标志不应该导致内核中的任意解除引用。
最后一个漏洞稍微复杂一点,因为这个漏洞出现在BasicRender驱动程序处理D3DKMTMarkDeviceAsError API和D3DKMTSubmitCommand API的过程中。这是我们对此的建议,微软的补丁在这里。共享资源没有得到适当的保护,可能会导致内存泄露。攻击者可以利用它将特权提升到系统权限。这种漏洞经常会被一些恶意软件利用,一旦用户点击了他们不应该点击的东西,恶意软件就会使用这种方法来安装自己。微软为这个bug和ZDI-18-949提供了一个CVE,指出了相同的根本原因。
这两种情况的PoC代码是相关的,但有所不同。
第一个PoC的关键部分:
对SubmitCommand的每次调用都会通过VidSchiWorkerThread生成一个线程,对MakeDeviceError的调用会更改相同对象的状态,导致条件竞争出现。
这是造成的崩溃:
条件竞争存在于对同一位置的两次修改之间:
对于ZDI-18-949,尽管根源相同,但是可以看到PoC代码中的差异。这是PoC的关键部分:
执行此PoC会导致Run方法崩溃:
这里是漏洞代码:
代码会在第二次运行时崩溃,并不是第一次。
WDDM和DirectX图形内核代码为Windows提供了一个非常强大和灵活的图形系统。它们通过使用许多非常复杂的对象和为用户代码创建许多新的复杂接口来实现这一点。这里给出的PoC应该会让你了解DirectX中实现的对象的复杂性,以及该领域未来研究的范围。我相信这个领域还是有很多东西可以研究的。
直接静态分析可以提供攻击信息,然而,这个任务是十分艰巨的。另一种可能的想法是建立一个fuzzing框架,将不同的值设置为不同的标志,并以不同的顺序调用DirectX方法,以查找崩溃。当然,您还可以添加多个线程来更改和释放数据,以研究条件竞争和TOC/TOU的可能性。记住要在所有相关驱动程序上设置专用池。
一如既往,当你发现新的漏洞时,Zero Day Initiative很乐意和您交流。在此之前,你可以在Twitter上@FritzSands上找到我,并跟随团队了解最新的利用技术和安全补丁。