Dokany/Google Drive文件流内核栈缓冲区溢出漏洞
去年11月,我向CERT/CC报告了一个内核漏洞,请求他们协助协调披露过程,因为该漏洞影响了包括Google Drive文件流(GDFS)在内的数十家供应商。
该漏洞是Dokany内核模式文件系统驱动程序中的栈缓冲区溢出漏洞,已被分配CVE编号CVE-2018-5410。使用Dokany可以创建自己的虚拟文件系统而无需编写设备驱动程序。该代码是开源的,并被此处列出的数十个项目使用。测试了一些产品,除了GDFS部分代码已被Google更改并签名外,其他产品都随附了Dokany的编译包。
触发漏洞
连接到设备句柄"dokan_1"并向IOCTL 0x00222010发送超过776字节的输入缓冲区,就足以破坏栈cookie并导致系统蓝屏(BSOD)。
|
|
检查源代码以精确定位易受攻击的代码,发现位于notification.c中,其中RtlCopyMemory函数的szMountPoint->Length参数未经验证:
|
|
易受攻击的代码是从2016年9月20日发布的主要版本更新1.0.0.5000引入的。
时间线
以下是时间线,我们可以看到Dokany的维护者修复此错误的效率非常高:
- 2018年11月30日 – 通过CERT/CC在线表格提交
- 2018年12月3日 – 收到提交确认
- 2018年12月8日 – 更新Dokan代码提交[链接]
- 2018年12月18日 – Dokan变更日志[1.2.1.1000][链接]
- 2018年12月20日 – 发布编译版本[链接]
- 2018年12月20日 – CERT/CC发布漏洞说明VU#741315[链接]
Google的情况如何?
Google似乎对此漏洞保持沉默,并在没有任何发布的情况下悄悄修复了它。我在GDFS发布说明中找到的唯一网址是这里,最后一次更新是2018年10月17日的版本28.1。出于好奇,我在12月28日下载了GDFS,结果发现漏洞已经被修补。修补的版本是:
软件: GoogleDriveFSSetup.exe
版本: 29.1.34.1821
签名: 2018年12月17日
驱动程序: googledrivefs2622.sys
版本: 2.622.2225.0
签名: 2018年12月14日
我测试的最后一个易受攻击的版本是:
软件: GoogleDriveFSSetup.exe
版本: 28.1.48.2039
签名: 2018年11月13日
驱动程序: googledrivefs2544.sys
版本: 2.544.1532.0
签名: 2018年9月27日
CERT/CC漏洞说明仍标记为受影响。
GDFS驱动程序文件名、版本和句柄在每次更新中都会更改。以下是一些先前易受攻击的版本列表:
驱动程序文件名 | 驱动程序版本 | 设备句柄 |
---|---|---|
googledrivefs2285.sys | 2.285.2219.0 | googledrivefs_2285 |
googledrivefs2454.sys | 2.454.2037.0 | googledrivefs_2454 |
googledrivefs2534.sys | 2.534.1534.0 | googledrivefs_2534 |
googledrivefs2544.sys | 2.544.1532.0 | googledrivefs_2544 |
利用漏洞
由于该漏洞是一个栈基缓冲区溢出,编译时带有栈Cookie保护(/GS),该保护在我们的函数返回之前进行验证,因此使用返回地址控制执行流程是不可能的。为了绕过cookie验证,我们需要覆盖栈中的异常处理程序,然后在内核中触发异常,从而调用我们覆盖的异常处理程序。然而,这种技术仅适用于32位系统,因为当代码为64位编译时,异常处理程序不再存储在栈上,因此从"基于帧"变为"基于表"。对于64位,异常处理程序存储在PE映像中,其中有一个异常目录包含可变数量的RUNTIME_FUNCTION结构。OSRline上发布的一篇文章解释得很好。
为了在32位操作系统上利用,在异常处理程序被覆盖为指向我们的shellcode的地址后,我们需要触发异常。通常,读取超出我们输入的缓冲区会实现这一点,因为在使用CreateFileMapping()和MapViewOfFile()等API设置缓冲区后,它将是未分配的内存。不幸的是,这是不可能的,因为使用的IOCTL 0x00222010使用"缓冲I/O"传输方法,其中数据被分配到系统缓冲区,因此当我们的输入数据被读取时,它是从系统缓冲区读取的,因此我们的缓冲区之后没有未分配的内存。
对于Dokany驱动程序,仍然有一种利用方式,因为在溢出之后和cookie验证之前,它调用另一个子例程,该子例程最终调用api IoGetRequestorSessionId()。它从栈读取的参数之一是IRP地址,我们恰好控制该地址。我们需要做的就是确保我们的IRP地址指向未分配内存的区域。
至于GDFS,Google对其代码进行了一些更改,因此api IoGetRequestorSessionId()没有被调用,我找不到任何其他产生异常的方法,因此最终只产生BSOD。最后要提到的是,易受攻击的子例程没有包装在__try/__except块中,但父子例程是,并且这是栈下方被覆盖的异常处理程序。需要最小输入缓冲区大小为896字节才能覆盖异常处理程序。
|
|
溢出前后栈布局:
|
|
为了恢复,我们返回到父子例程。
漏洞利用可以从这里下载[zip],以及来自Github的易受攻击包的直接链接[exe]。zip文件仅包含针对Windows 7 32位操作系统的漏洞利用代码,以及针对早期GDFS 32/64位版本触发BSOD的代码。
请注意,该漏洞利用并不完美,因为一旦生成提升的shell,父进程需要大约7分钟才能返回到提示符。很可能shellcode的恢复部分需要一些工作。此外,由于某种奇怪的原因,漏洞利用仅在调试器附加时有效,我无法弄清楚原因。我注意到的一个观察是shellcode缓冲区变得未分配,因此可能存在一些时序问题。如果您有任何想法,请留言。
@ParvezGHH