ExplorerFrame.dll 的 BUG可能导致不良设计的用户程序危害系统稳定
source link: https://orbit.blog.csdn.net/article/details/125001297
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
ExplorerFrame.dll 动态链接库中的
GetInfoTipEx
函数实现缺陷,可能导致不良设计的用户代码破坏系统稳定性。如果用户程序对 IQueryInfo 接口的GetInfoTip
方法实现不当,可能会触发GetInfoTipEx
的缺陷,导致 explorer.exe 异常退出。
1. 问题发现
最近发现在 Windows 10 操作系统的桌面创建快捷方式的时候,总是时不时地会导致资源管理器进程explorer.exe 异常退出,经过一番摸索,发现做以下操作时,问题出现概率比较高:
-
在桌面单击鼠标右键,在右键菜单中选择“新建” -> “快捷方式”
-
在“创建快捷方式”窗口中点击“浏览”按钮
-
在“浏览”窗口中选择目标程序,鼠标在程序上停留一会儿,等待弹出 “info tip” 信息
不关闭窗口,重复第 3 步的操作多次后,有大概率会导致 explorer.exe 异常退出。
这让我想起了早先的“震网”病毒,但那都是几年前的事情了,难道最新的 Windows 10 没有补上这个漏洞?于是我就用 Windbg 挂上 explorer.exe,然后做复现操作,发现异常原因是 CoTaskMemFree 释放了一个无效的指针触发的异常:
(13e4.1004): Break instruction exception - code 80000003 (first chance)
ntdll!RtlReportCriticalFailure+0x56:
00007ffb`6dde83fa cc int 3
异常触发时的函数调用栈信息如下:
0:002> kb
00 00007ffb`6ddef97a : 00000000`ffffffff 00007ffb`6de4c6e0 00000000`02d60000 00000000`02d60000 : ntdll!RtlReportCriticalFailure+0x56
01 00007ffb`6dd8fc52 : 00000000`02d60000 00000000`077eae50 00000000`02d60000 00007ffb`6a3441a9 : ntdll!RtlpHeapHandleError+0x12
02 00007ffb`6ddaa260 : 00000000`00000001 00000000`0c34afd0 00007ffb`5523eeb8 00000000`00000000 : ntdll!RtlpLogHeapFailure+0x96
03 00007ffb`55208672 : 00000000`00000001 00000000`00000000 00000000`077eb8d0 00007ffb`6c4fc59e : ntdll!RtlFreeHeap+0x7b5a0
04 00007ffb`551aa7eb : 00000000`077eb8d0 00000000`00000000 00000000`0c34b05b 00000000`077eae50 : explorerframe!GetInfoTipEx+0x9a
05 00007ffb`55158062 : 00000000`00000000 00000000`00000000 00000000`0000ea60 00007ffb`6a370029 : explorerframe!CNscInfoTipTask::InternalResumeRT+0x5b
.... // 省略了一部分
2. 问题分析
看调用栈信息是GetInfoTipEx
函数中的内存释放操作触发的异常,于是把 GetInfoTipEx
反汇编出来,发现这个函数很短,简单分析了一下(加了点注释):
explorerframe!GetInfoTipEx:
00007ffe`ee2885d8 4c8bdc mov r11,rsp
00007ffe`ee2885db 49895b08 mov qword ptr [r11+8],rbx
00007ffe`ee2885df 49897310 mov qword ptr [r11+10h],rsi
00007ffe`ee2885e3 4d894318 mov qword ptr [r11+18h],r8
00007ffe`ee2885e7 57 push rdi
00007ffe`ee2885e8 4883ec50 sub rsp,50h
00007ffe`ee2885ec 33db xor ebx,ebx
00007ffe`ee2885ee 498bf9 mov rdi,r9
00007ffe`ee2885f1 66418919 mov word ptr [r9],bx
00007ffe`ee2885f5 8bf2 mov esi,edx
00007ffe`ee2885f7 4d85c0 test r8,r8
00007ffe`ee2885fa 0f8484000000 je explorerframe!GetInfoTipEx+0xac (00007ffe`ee288684)
00007ffe`ee288600 488b01 mov rax,qword ptr [rcx] //rcx是参数传入的IShellFolder 对象,第一个位置是 vtbl 的指针
00007ffe`ee288603 498d53e8 lea rdx,[r11-18h]
00007ffe`ee288607 498953d8 mov qword ptr [r11-28h],rdx
00007ffe`ee28860b 4d8d4b18 lea r9,[r11+18h]
00007ffe`ee28860f 488d15225f0400 lea rdx,[explorerframe!GUID_00021500_0000_0000_c000_000000000046 (00007ffe`ee2ce538)] // IID_IQueryInfo 的 RIID 定义
00007ffe`ee288616 49895bd0 mov qword ptr [r11-30h],rbx
00007ffe`ee28861a 498953c8 mov qword ptr [r11-38h],rdx
00007ffe`ee28861e 448d4301 lea r8d,[rbx+1]
00007ffe`ee288622 488b4050 mov rax,qword ptr [rax+50h] //rax 是IShellFolder 对象的vtbl,偏移 50H 是 CFSFolder::GetUIObjectOf 接口方法
00007ffe`ee288626 33d2 xor edx,edx // 第二个参数 cidl
00007ffe`ee288628 ff15fad80200 call qword ptr [explorerframe!_guard_dispatch_icall_fptr (00007ffe`ee2b5f28)] //call IShellFolder::GetUIObjectOf()
00007ffe`ee28862e 85c0 test eax,eax
00007ffe`ee288630 7852 js explorerframe!GetInfoTipEx+0xac (00007ffe`ee288684)
00007ffe`ee288632 488b4c2440 mov rcx,qword ptr [rsp+40h]
00007ffe`ee288637 4c8d442478 lea r8,[rsp+78h] //局部变量取地址,(作为 GetInfoTip 的第二个参数)GetInfoTip 函数会申请一块内存,然后把地址存在这个位置
00007ffe`ee28863c 8bd6 mov edx,esi
00007ffe`ee28863e 488b01 mov rax,qword ptr [rcx] //rcx 是IQueryInfo 对象指针
00007ffe`ee288641 488b4018 mov rax,qword ptr [rax+18h] //rax 是IQueryInfo::GetInfoTip 接口方法
00007ffe`ee288645 ff15ddd80200 call qword ptr [explorerframe!_guard_dispatch_icall_fptr (00007ffe`ee2b5f28)]
00007ffe`ee28864b 4c8b442478 mov r8,qword ptr [rsp+78h]
00007ffe`ee288650 4d85c0 test r8,r8
00007ffe`ee288653 741d je explorerframe!GetInfoTipEx+0x9a (00007ffe`ee288672)
00007ffe`ee288655 ba04010000 mov edx,104h
00007ffe`ee28865a 488bcf mov rcx,rdi
00007ffe`ee28865d bb01000000 mov ebx,1
00007ffe`ee288662 e86d35eaff call explorerframe!StringCchCopyW (00007ffe`ee12bbd4)
00007ffe`ee288667 488b4c2478 mov rcx,qword ptr [rsp+78h] //释放GetInfoTip 函数内部申请的内存
00007ffe`ee28866c ff1596d10200 call qword ptr [explorerframe!_imp_CoTaskMemFree (00007ffe`ee2b5808)]
00007ffe`ee288672 488b4c2440 mov rcx,qword ptr [rsp+40h]
00007ffe`ee288677 488b11 mov rdx,qword ptr [rcx]
00007ffe`ee28867a 488b4210 mov rax,qword ptr [rdx+10h]
00007ffe`ee28867e ff15a4d80200 call qword ptr [explorerframe!_guard_dispatch_icall_fptr (00007ffe`ee2b5f28)]
00007ffe`ee288684 488b742468 mov rsi,qword ptr [rsp+68h]
00007ffe`ee288689 8bc3 mov eax,ebx
00007ffe`ee28868b 488b5c2460 mov rbx,qword ptr [rsp+60h]
00007ffe`ee288690 4883c450 add rsp,50h
00007ffe`ee288694 5f pop rdi
00007ffe`ee288695 c3 ret
其中 _guard_dispatch_icall_fptr
并不是函数,它是“执行流保护(Control Flow Guard,CFG)” 技术的一项缓解措施。具体来说就是 CFG 为了防止恶意程序篡改间接调用的地址,进而控制了程序的执行流程,会对代码中通过地址间接调用函数的地方插入检查代码,检查地址的有效性。你可以理解为这就是一个桩,通过这个桩跳转到指定的地址。举个例子,假如代码中有这样的间接调用:
mov rcx,qword ptr [rax+50h]
call qword ptr [rcx]
如果开启了 /guard 编译选项,最终生成的代码就会是这样的:
mov rcx,qword ptr [rax+50h]
mov rax, rcx //是否使用 rax 寄存器,取决于编译器的代码生成
call qword ptr _guard_dispatch_icall_fptr
我在微软的网站上找到了一个叫 Raymond 技术人员在 2018 年发表的文章([Raymond 1]),截取部分内容如下:
The
call qword ptr [_guard_dispatch_icall_fptr]
doesn’t mean “call the function_guard_dispatch_icall_fptr
.” It means “read eight bytes from_guard_dispatch_icall_fptr
,
treat those eight bytes as a 64-bit value, interpret that value as an
address, and call to that address.” The bytes you see at_guard_dispatch_icall_fptr
are not code; they are data. Disassembling them as code is meaningless.
简单理解,就是代码中通过这个检查桩调用了 COM 接口,第一个_guard_dispatch_icall_fptr
代理的是IShellFolder::GetUIObjectOf
接口,第二个代理的是 IQueryInfo::GetInfoTip
接口。注意,问题就发生在调用 IQueryInfo::GetInfoTip
接口的时候,先来看看这个接口的定义:
HRESULT GetInfoTip(DWORD dwFlags, PWSTR *ppwszTip);
根据 MSDN 文档对 ppwszTip
参数的描述:
The address of a Unicode string pointer that, when this method returns successfully, receives the tip string pointer. Applications that implement this method must allocate memory for ppwszTip by calling
CoTaskMemAlloc
. Calling applications must callCoTaskMemFree
to free the memory when it is no longer needed.
问题是如果用户的实现不支持这种文件类型,没有 Tip 信息该如何对 ppwszTip
赋值呢?答案是您一定不能随它不管,没有信息就给它赋值 NULL,原因请看后面的具体分析。
请看GetInfoTipEx
函数汇编代码的第 28 行:lea r8,[rsp+78h]
,这里是取一个局部变量的地址,这个地址就是要传给GetInfoTip
的 ppwszTip
参数,我所说的 BUG 就是这里没有给[rsp+78h]
的数据初始化为 NULL,没有初始化的后果就是在交换频繁的栈上,这个位置的值是不确定的。在调用完GetInfoTip
之后,在使用这个指针的时候也没有做安全性检查,只是用 test r8,r8
结合je
跳转指令简单判断了一下是否是 NULL,接着就调用explorerframe!StringCchCopyW
访问这个地址,并最后调用 CoTaskMemFree
释放这个指针。
GetInfoTipEx
函数没有对[rsp+78h]
的数据初始化,就意味着用户实现 GetInfoTip
接口的时候必须给ppwszTip
赋值,要么是分配的 Tip 信息内存,要么是赋值为 NULL,总之不能空着不管。如果遇到这样不良设计的GetInfoTip
接口实现呢?不幸的是,我的系统上就有一个这样的不良实现。是哪个软件我就不说了,总之跟了一下它的代码,发现它需要打开一个文件,根据文件头获取一些信息,然后组成这种文件的 Tip 信息。但是,如果打开文件后发现是不支持的文件,就没搭理ppwszTip
,直接返回 E_FAIL 了。可是偏偏GetInfoTipEx
有 BUG,没有检查返回值,只看*ppwszTip
是否是 NULL,那么,问题来了,假如[rsp+78h]
的数据刚好是 NULL,于是躲过一劫;假如[rsp+78h]
的数据是随机值,要么explorerframe!StringCchCopyW
函数访问无效地址异常,要么CoTaskMemFree
报告错误地址异常,GetInfoTipEx
函数对这两个异常都没有防护,于是 explorer.exe 崩溃。
3. 故障原因验证
为了验证上述分析,我做了一个系统 Shell 扩展程序,实现了 IQueryInfo
接口,其中 GetInfoTip
接口的实现如果是这样,则不会导致 explorer.exe 崩溃:
//IQueryInfo
HRESULT CTestInfoShellExt::GetInfoTip(DWORD dwFlags, LPWSTR *ppwszTip)
{
*ppwszTip = NULL;
return S_OK;
}
如果是这样,则无论返回值是 S_OK,还是其他系统错误值,都会触发 explorer.exe 崩溃:
//IQueryInfo
HRESULT CTestInfoShellExt::GetInfoTip(DWORD dwFlags, LPWSTR *ppwszTip)
{
*ppwszTip = 0x1223344;
return S_OK;
}
到此为止,基本上验证我之前的分析。
GetInfoTipEx
函数在调用 GetInfoTip
接口之前将*ppwszTip
赋值为 NULL,可以避免因为用户的不良设计导致的 explorer.exe 崩溃,但是无法躲过恶意代码,就像第三节介绍的技术验证代码,故意返回一个随机值让 explorer.exe 崩溃,微软最好是升级一下 IQueryInfo
接口,增加一个安全的方法。作为设计接口应用的开发人员来说,不管三七二十八,上来第一行代码就给*ppwszTip
赋值为 NULL,也是没毛病的。
我测试了 Windows 10 的 1809、1903、2004 三个版本,都存在这样的问题。
参考文献:
[Raymond 1] The case of the buffer overflow vulnerability that was neither a buffer overflow nor a vulnerability, Raymond, December 7th, 2018
https://devblogs.microsoft.com/oldnewthing/20181207-00/?p=100435
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK