一.研究背景
VMware Workstation是一款主流的虚拟机软件,近期启明星辰ADLab安全研究员在使用VMware虚拟机的过程中遇到虚拟机异常崩溃的问题,当从7zip中直接将文件拖拽到VMware虚拟机中,会造成虚拟机异常关闭。目前已测试过VMware 15.5.0、15.5.2、15.5.5 以及7zip 19.0、20.02等版本。本文将通过对VMware和7zip程序进行跟踪分析,最终定位虚拟机异常关闭原因。
二.VMware端调试分析
使用WinDbg-I指令将WinDbg设置为即时调试器,VMware-vmx.exe程序崩溃后自动弹出WinDbg。堆栈信息如下:
调试信息显示stack buffer overrun异常,最初推断可能是缓冲区溢出漏洞。
通过查询资料后发现,从Windows 8开始,Windows设计了一个新的中断INT 29H,用以快速抛出失败,在sdk中被声明为__fastfail, __fastfail内部函数不会返回。
在上图中,程序终止于int 29h,而它的参数为0xa,对应FAST_FAIL_GUARD_ICALL_CHECK_FAILURE,由此推断问题可能出现在CFG的检查过程中。
从函数调用栈中vmware_vmx+0x58b21地址向上追溯,动态调试程序,比较程序正常运行与异常崩溃的函数调用区别,定位到与程序崩溃相关的函数sub_1400965A0。
使用Windbg Attach vmware-vmx.exe程序,在sub_1400965A0函数设置断点,开始动态调试。从7z打开的压缩文件中拖拽cdp.pcapng的文件,程序在断点处停下。通过动态调试可知该函数中calloc分配了三个堆空间,分别用于存放:主机临时文件路径temp_path、目标文件名file_name以及VMware中的缓存目录名vm_cache_dir_name。
但是打开主机Temp目录下却没有发现该文件,于是初步断定这是程序崩溃原因。继续往下看,3个文件相关参数全都传入了sub_140579b30函数。
进入函数sub_140579b30,定位temp_path参数的处理。其中,sub_14057FF90函数对传入的temp_path进行了逐一遍历,sub_1405B2080函数对传入的temp_path进行了非法性检查。下面重点分析sub_140576460函数。
sub_140576460函数将路径参数temp_path传入了sub_14049DA50。
首先,函数sub_14049DA50通过sub_140477C70对字符串进行了处理。然后,调用wstat64获取相应路径的文件状态,如果成功获取则保存到一个结构体中,否则返回0xffffffff。由于Temp目录下并未发现备份文件,导致获取状态失败,从而返回0xffffffff。
返回0xffffffff后,重新回到sub_140579b30函数中,程序跳出while循环到达如下位置,输出错误信息并跳转至sub_140572A70。
从sub_140572A70最终执行到sub_1400960C0,到达如下位置将vmware_vmx+0xb1ed90处的值赋给了rsi,即为0。
继续往下执行,将rsi中0值赋值到rax中,然后调用0x7ff8fab0c510处,即ntdll!LdrpDispatchUserCallTarget。
此处与静态下的过程有一点不同,静态下该处调用如下:
如果按照静态过程执行,应当到达sub_1407C7650,即如下位置:
在ntdll.dll被加载之前,该处数据依旧为上图所示地址:
后来在ntdll.dll中实施CFG(ControlFlowGuard)保护机制,将vmware_vmx+0x7c9668地址处数据进行了改写,从而执行到ntdll!LdrpDispatchUserCallTarget中。
在ntdll!LdrpDispatchUserCallTarget函数中,取r11+r10*8处的值赋值给r11时出现了问题,该地址为空,就造成了空指针引用,从而执行了int 29h,造成异常。然而,即使没有CFG机制,程序也会在执行“jmp rax”处崩溃,通过下图可以看出,CFG机制仅仅是在原本程序跳转指令前添加了一些检查。
至此,VMware崩溃的原因基本分析清楚了。另一个疑问是,为什么7zip已经在系统Temp下生成了文件,并且VMware也已经获取到了路径参数,却在移动前自动删除了文件呢。这就需要从7zip中寻找答案。
三. 7zip端调试分析
由上一节分析可知,Vmware crash原因是Temp目录下文件被删除。阅读7zip源码,锁定了CPP/Windows/FileDir.cpp中的文件删除函数。
使用WinDbg加载7zip,然后在Remove函数位置进行下断,程序运行后进行拖拽操作,在Remove函数中断后对应的调用堆栈如下所示。
堆栈中7zFM+0x5b212地址位于函数CPanel::OnDrag中,该函数为鼠标拖拽操作函数。当检测到对7zip打开的目录进行操作时,便会在Temp目录下生成一个以7zE开头的随机命名文件夹。
然后,将该文件夹设置为目标目录,并且设置了一些数据及IpDropSourse结构体。
继续往下可以看到一个DoDragDrop函数,该函数功能是进行OLE拖放相关操作,通过检测光标的行为分别调用一些方法并返回对应的数值。
然后,根据DoDragDrop函数的返回值来判断光标的拖拽是否有效,从而执行对应的操作。
从7zip中拖拽文件到虚拟机,由于无法获知文件拖拽的目标路径,因此DoDragDrop会返回DRAGDROP_S_CANCEL(0x40101),不会执行拷贝操作的分支,而是直接将Temp目录下生成的临时目录删除。
四.小 结
7zip压缩包中文件拖拽操作会触发DoDragDrop函数调用,该函数会获取文件数据及光标停止的位置。但是将文件拖拽到VMware窗口时,DoDragDrop函数不能获取准确的目标路径,因此无法将文件拷贝到目标位置,从而直接删除临时文件,最终导致VMware无法获取文件状态造成崩溃。
参考链接:
[1]https://0cch.com/2016/12/13/int29h/
[2]https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-dodragdrop
[3]https://github.com/kornelski/7z/tree/20e38032e62bd6bb3a176d51bce0558b16dd51e2