使用 Binary Ninja 分析Parallels Desktop 虚拟机漏洞
2021-10-21 12:55:00 Author: www.4hou.com(查看原文) 阅读量:39 收藏

导语:Parallels Desktop 使用“Parallels ToolGate”半虚拟 PCI 设备在guest操作系统和host操作系统之间进行通信。此设备由 Parallels guest中的Vendor ID 0x1AB8 和Device ID 0x4000 标识。

Parallels Desktop 使用“Parallels ToolGate”半虚拟 PCI 设备在guest操作系统和host操作系统之间进行通信。此设备由 Parallels guest中的Vendor ID 0x1AB8 和Device ID 0x4000 标识。

guest驱动程序和host虚拟设备使用 ToolGate 消息传递协议进行通信。为了提供summary,guest驱动程序准备一条消息并将消息的物理地址写入TG_PORT_SUBMIT [PORT IO ADDRESS+0x8]. 然后,host映射guest提供的物理地址,解析消息,并将控制权转移到相应的请求处理程序以进行进一步处理。Parallels 中的许多漏洞都在这些请求处理程序中。

0x01 ToolGate 接口和协议格式

ToolGate 请求格式是一个可变大小的结构,可以跨多个页。客户机通过将TG_PAGED_REQUEST结构的物理地址写入虚拟设备的 IO 端口,将数据作为内联字节或指向分页缓冲区的指针发送到host。

图 1 - guest内存中的可变大小 TG_PAGED_REQUEST 结构

图 1 - guest内存中的可变大小 TG_PAGED_REQUEST 结构

图 2 - TG_PAGED_REQUEST 中的可变大小 TG_PAGED_BUFFER 结构

图 2 - TG_PAGED_REQUEST 中的可变大小 TG_PAGED_BUFFER 结构

然后host映射物理地址指向的页面,根据guest提供的信息准备host缓冲区,然后调用请求处理程序。请求处理程序可以使用内联字节或分页缓冲区以读取和写入数据。这些数据缓冲区是使用一组函数访问的,大致定义如下:

TG_DataBuffer *TG_GetBuffer(TG_ReqBuffer *buf, uint32_t index, uint32 writable)` `uint32_t TG_ReadBuffer(TG_DataBuffer *buf, uint64_t offset, void *dst, size_t size)` `uint32_t TG_WriteBuffer(TG_DataBuffer *buf, uint64_t offset, void *src, size_t size)`
`void *TG_GetInlineBytes(TG_DataBuffer *buf)

0x02 堆栈冲突漏洞分析

Pwn2Own 2021的上STARLabs 提交 ( ZDI-21-937 ) 的是一个不受控的内存分配漏洞,其中在堆栈中分配了guest提供的大小值。如果guest提供的大小大于堆栈的总大小,则可以将堆栈指针 (RSP) 移入进程内存的其他区域(例如,另一个线程的堆栈区域)。

图 3 - 正常堆栈操作(左)与由于大量分配引起的堆栈跳跃(右)

图 3 - 正常堆栈操作(左)与由于大量分配引起的堆栈跳跃(右)

处理TG_REQUEST_INVSHARING (0x8420)时,Parallels Desktop 不会验证guest提供的作为ByteCount 的值TG_PAGED_BUFFER, 在堆栈中分配时,此不受信任的大小值可用于将处理 ToolGate 请求的线程的堆栈顶部移动到另一个目标线程以覆盖其内容。有一个大小为 4KB 的保护页,但是可以跳过这个分配页面。Qualys早在 2017 年就针对这类 bug 发表了一篇名为The Stack Clash的详细论文,这也使得各种编译器增加了缓解措施,以防止这种保护页跳转。

https://www.qualys.com/2017/06/19/stack-clash/stack-clash.txt

以下是 Parallels Desktop 16.1.3 中存在漏洞的代码部分。在调用 TG_ReadBuffer()期间,另一个线程的堆栈内存可以被guest控制的值覆盖。

图 4 - TG_REQUEST_INVSHARING 处理中的漏洞

图 4 - TG_REQUEST_INVSHARING 中的漏洞

0x03 编译器缓解措施

这里最有趣的问题是Apple Clang 的编译器是否对堆栈冲突做了缓解?当alloca(value)在堆栈中进行可变大小分配char buffer[value]时,Apple Clang 编译器会检测分配___chkstk_darwin()以验证分配请求的大小。___chkstk_darwin()本质上在堆栈中分配 PAGE_SIZE 的内存,然后,对于每个分配的页面,如果可访问,则探测新的堆栈顶部。如果到达保护页,探测步骤将失败,会导致崩溃,不再可能将堆栈指针移动到任意位置。

图 5 - 具有堆栈冲突缓解的示例代码

图 5 - 具有堆栈冲突缓解的示例代码

很明显,Parallels Desktop 没有启用此缓解措施,因为___chkstk_darwin()在可变大小堆栈分配期间没有调用。在这一点上,有几个问题:

-- Parallels 是否使用-fno-stack-check compilerflag禁用了缓解措施?     -- 是不是使用的是缓解措施以前的配置?

Mac OS otool 可用于获取有关构建环境的大量信息。具体来说,LC_VERSION_MIN_MACOSX可以提供有关支持的 macOS 版本的信息。下下面是otool -l prl_vm_app输出:

图 6 - LC_VERSION_MIN_MACOSX  prl_vm_app 的信息

图 6 - prl_vm_app 的 LC_VERSION_MIN_MACOSX 信息

使用的 SDK 是 10.15 (Catalina),这是一个很新的版本。此外,Parallels 还将支持的最低 macOS 版本设置为 10.13,使其与 High Sierra 兼容。这与他们的知识库文章中提供的兼容性信息一致。尽管如此,10.13 的向后兼容性是否会禁用编译器缓解措施?下面是-mmacosx-version-min=10.13使用和不使用编译的示例代码的比较:

图 7 - 向后兼容 10.13 禁用 ___chkstk_darwin()(右)

图 7 - 向后兼容 10.13 禁用 ___chkstk_darwin()(右)

目前还不清楚 Parallels 是否___chkstk_darwin()明确禁用了 -fno-stack-check,但设置-mmacosx-version-min=10.13有相同的效果。在-mmacosx-version-min=10.14(Mojave) 中也观察到相同的行为。有趣的是,GCC 已经内联了堆栈探测检查,而不是依赖于外部库函数。这可能是由于外部依赖性,因为在 Apple Clang 中使用向后兼容标志(macosx-version-min、target 等)进行编译最终使缓解措施失效。

在较旧的编译器(例如 Mojave 上的 Apple LLVM 版本 10.0.1)中,除非-fstack-check明确提供标志,否则默认情况下不会启用堆栈缓解措施。因此,当macosx-version-min为低于 10.15 的任何版本编译时,最近的编译器似乎完全放弃了缓解措施。这可以通过同时提供-fstack-check和-mmacosx-version-min标志来解决。但是,High Sierra 的兼容性可能又是一个问题。重点是,macosx-version-min即使在最新版本的 Mac OS 上,单独使用也可以使漏洞可利用。

图 8 - 图 8. Apple Clang 调用 ___chkstk_darwin(左)与 GCC 缓解内联(右)

图 8 - 图 8. Apple Clang 调用 ___chkstk_darwin(左)与 GCC 缓解内联(右)

0x04 Binary Ninja 漏洞发现

Parallels Desktop 中是否还有其他类似的漏洞?我们如何使这个过程自动化,堆栈帧的大小在编译时通常是已知的。此外,还可以跟踪移动堆栈指针的操作。Binary Ninja 具有静态数据流功能,可以跟踪函数中任何点的堆栈帧偏移量。但是,当堆栈中存在可变大小分配时,无法静态确定堆栈偏移量。这个确切的属性可用于查找其他类似漏洞。

图 9 - 图 9. 已知堆栈偏移量(左)与 alloca() 后的未确定值(右)

图 9 - 图 9. 已知堆栈偏移量(左)与 alloca() 后的未确定值(右)

上述低级 IL 中的索引 88,其中加载了 RSP 寄存器。

88 @ 10080bcce  rsp = r15

在这里,新的栈顶使用guest提供的大小计算并加载到 RSP 中。Binary Ninja提供get_possible_reg_values()和get_possible_reg_values_after()API来对寄存器取静态确定的值。寄存器值还与类型信息 (RegisterValueType) 相关联。这是加载操作前后 RSP 中的堆栈帧偏移值:

>>> current_function.get_low_level_il_at(here).get_possible_reg_values("rsp")  
 
>>> current_function.get_low_level_il_at(here).get_possible_reg_values_after("rsp")

RSP 始终与StackFrameOffset RegisterValueType关联. 但是,当 RSP 值未知时,它被标记为 UndeterminedValue。使用此类型信息,搜索TG_ReadBuffer()未确定 RSP 值的所有引用。如果在调用 之前未确定 RSP,则可以推断在调用TG_ReadBuffer()之前在堆栈中进行了可变大小的分配。

>>> tg = bv.get_symbol_by_raw_name("TG_ReadBuffer") 
>>> for ref in bv.get_code_refs(tg.address): 
...     function = ref.function 
...     il = function.get_low_level_il_at(ref.address) 
...     if il is not None: 
...         rsp = il.get_possible_reg_values("rsp") 
...         if rsp.type == RegisterValueType.UndeterminedValue: 
...             print(hex(ref.address)) 
...  
0x1001c6e35 
0x10025591a 
0x10080bcd9

上面的查询产生了 3 个结果;一个是 Pwn2Own 提交和另外两个未知的漏洞。

0x1001c6e35 - ZDI-21-1056 - TG_REQUEST_GL_CREATE_CONTEXT  0x10025591a - ZDI-21-1055 - TG_REQUEST_DIRECT3D_CREATE_CONTEXT  0x10080bcd9 - ZDI-21-937 - (Pwn2Own) - TG_REQUEST_INVSHARING

图 10 - 使用 Binary Ninja 发现的 Pwn2Own 错误及其变体

图 10 - 使用 Binary Ninja 发现的 Pwn2Own 漏洞和其他同类漏洞

0x05 分析总结

在某些情况下,为了向后兼容实现编译可能会默默地放弃某些缓解措施,从而使整类漏洞都可以被利用,也许供应商应该考虑在这种情况下发布单独的文件版本。

Binary Ninja 的静态数据流功能和 Python API 在自动化漏洞查找中有较好的使用。

本文翻译自:https://www.zerodayinitiative.com/blog/2021/9/9/analysis-of-a-parallels-desktop-stack-clash-vulnerability-and-variant-hunting-using-binary-ninja如若转载,请注明原文地址


文章来源: https://www.4hou.com/posts/MWQG
如有侵权请联系:admin#unsafe.sh