Dokany与Google Drive文件流内核栈缓冲区溢出漏洞分析

本文详细分析了Dokany虚拟文件系统驱动和Google Drive文件流中的内核栈缓冲区溢出漏洞CVE-2018-5410,包括漏洞触发条件、利用技术、时间线和受影响版本,并提供了针对32位系统的漏洞利用方法。

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
kd> !analyze -v
***************************************************************************
*                                                                         *
*                        Bugcheck Analysis                                *
*                                                                         *
***************************************************************************
DRIVER_OVERRAN_STACK_BUFFER (f7)
驱动程序已越界栈基缓冲区。此越界可能允许恶意用户获得对此计算机的控制权。
描述
驱动程序以某种方式越界了栈基缓冲区(或局部变量),该方式会在函数返回时覆盖函数的返回地址并跳转回任意地址。这是经典的"缓冲区越界"
黑客攻击,系统已被关闭以防止恶意用户获得对其的完全控制。
执行kb以获取栈回溯--在缓冲区越界处理程序和bugcheck调用之前,栈上的最后一个例程是越界其局部变量的例程。
参数:
Arg1: c0693bbe, 来自栈的实际安全检查cookie
Arg2: 1c10640d, 预期的安全检查cookie
Arg3: e3ef9bf2, 预期安全检查cookie的补码
Arg4: 00000000, 零

检查源代码以精确定位易受攻击的代码,发现位于notification.c中,其中RtlCopyMemory函数的szMountPoint->Length参数未经验证:

1
RtlCopyMemory(&dokanControl.MountPoint[12], szMountPoint->Buffer, 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字节才能覆盖异常处理程序。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
2   字节   - memcpy使用的尺寸
2   字节   - 用于检查输入缓冲区的尺寸
772 字节   - 实际数据缓冲区
4   字节   - Cookie
4   字节   - EBP
4   字节   - RET
4   字节   - 其他数据
4   字节   - IRP
96  字节   - 其他数据
4   字节   - 异常处理程序

溢出前后栈布局:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
a1caf9f4  00000000
a1caf9f8  fcef3710 <-- cookie
a1caf9fc  a1cafa1c
a1cafa00  902a2b80 dokan1+0x5b80  <-- 返回地址
...
a1cafa6c  902b2610 dokan1+0x15610  <-- 异常处理程序

kd> dps a1caf9f4
a1caf9f4  41414141
a1caf9f8  42424242 <-- cookie
a1caf9fc  41414141
a1cafa00  43434343 <-- 返回地址
a1cafa08  44444444 <-- IRP
...
a1cafa6c  000c0000 <-- 异常处理程序,现在指向shellcode

为了恢复,我们返回到父子例程。

漏洞利用可以从这里下载[zip],以及来自Github的易受攻击包的直接链接[exe]。zip文件仅包含针对Windows 7 32位操作系统的漏洞利用代码,以及针对早期GDFS 32/64位版本触发BSOD的代码。

请注意,该漏洞利用并不完美,因为一旦生成提升的shell,父进程需要大约7分钟才能返回到提示符。很可能shellcode的恢复部分需要一些工作。此外,由于某种奇怪的原因,漏洞利用仅在调试器附加时有效,我无法弄清楚原因。我注意到的一个观察是shellcode缓冲区变得未分配,因此可能存在一些时序问题。如果您有任何想法,请留言。

@ParvezGHH

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计