二进制安全一窍不通啊,写逆向作业还费劲呢就要复现这个真是难为我啊呜呜。
漏洞简介
- 漏洞名称:Microsoft Defender缓冲区溢出漏洞
- 漏洞编号:CVE-2021-1647
- 漏洞类型:堆溢出
- 漏洞影响:远程代码执行
- CVSS:7.8 HIGH
- 利用难度:中等
- 基础权限:不需要
环境搭建
安装Win10-1903虚拟机环境。
复现步骤
登入win10虚拟机并断网(防止Windows Defener自动更新),复制样本压缩包放入虚拟机,解压。
复现效果
弹出shell
窗口。同时Windows Defender
发生崩溃,并在数十秒后自动重新启动。
攻击分析
漏洞点
将虚拟机中Windows Defener
的文件MpEngine.dll
加载入ida
,找到函数CAsprotectDLLAndVersion::RetrieveVersionInfoAndCreateObjects
反汇编一下,来到漏洞代码段。
不知道是不是因为ida
版本的原因,我反汇编得到的结果和在网上参考的别人得到的反汇编的结果有很大的差别。个人经过一些分析处理后,简化为如下伪代码段。
1 | unsigned int section[4]; |
1 | v30 = malloc(max_rva_size + max_rva); |
this + 28
是section
数组的开始,section
数组包含四个元素,每个元素是8字节,前4字节代表section
的虚拟地址,后四个字节表示section
的大小。
遍历section
数组,最后获得一个max_rva
最大相对虚拟地址,和max_rva_size
存储大小,在后面malloc
申请一片大小为 max_rva + max_rva
的内存用于存储解压后的section
内容。
在判断语句if (*(asp_enc_table - 1) > max_rva)
,由于这里没有考虑等于的情况,导致如果给数组的赋值中虚拟地址相同,而在大小上后面的数比前面的数大(如[0,0],[0,0],[0x2000,0],[0x2000,0x3000])得到的结果会是max_rva = 0x2000
,max_rva_size = 0
,从而在解压最后一个section
的时候产生堆溢出问题。
漏洞利用样本分析
将漏洞利用样本拖入ida
分析。
首先call sub_409BC0
,跟进,反汇编一下。
看到这个函数最终是call 7C96C654h
这个地址且第一个参数是3,Windows Defender
在扫描样本时模拟执行样本的代码,因此内存地址并不是真实的地址,而是模拟的内存空间中的一个地址,即ntdll.dll
的地址。而7C96C654h
这个地址对应于ntdll.dll
中的这一段代码(我自己没找到,这是参考的代码):
函数sub_7C96C654
尾部两个字节0xff,0xff
表明函数是native
调用;函数尾部的0x9E9EFDF0
是标识native api
函数的crc
校验码;sub_7C96C654
函数第一个参数为3时表示获取defender
版本信息;根据不同defender
的版本信息可以硬编码关键偏移;call sub_7C96C654
最终会调用 mpengine!NTDLL_DLL_NtControlChannel
。也就是说,样本开头就获得了偏移信息。
在start
中,RegSetValueExW(phkResult, L"test", 0, 3u, lpData, dwSize);
修改的是 vmmcontrol
中的一个关键字段,这个关键字段描述的是索引数组一共有多少个元素。大量的ResumeThread
的调用代码用来进行内存布局和占位的,在堆溢出发生后,会修改布局在堆内存后的lfind
对象的两个关键字段,将分别由107e
和107f
修改为2f9b
和2f9c
,在在模拟执行ResumeThread
函数时调用lfind_switch::switch_in
函数中被引用。由于被修改之后的值比正常的大,造成越界写入。
Windows Defender
在模拟内存空间时会用到如下所示结构体(在内存中以数组形式存在),这个结构体用于维持模拟内存空间与真实内存地址的映射关系。
1 | Struct EmuVaddrNode{ |
EmuVaddrNode数组和它的索引数组在真实内存中的布局:
1 | 索引数组---EmuVaddrNode数组---Page模拟内存对应的真实内存 |
vmmcontrol
中的关键字段描述索引数组有多少元素。因为使其变大,所以索引数组元素变多,这时在Page
中伪造索引数组和EmuVaddrNode
结构,若Windows Defender
模拟执行*p=value
,会先从伪造的索引数组中取出伪造索引,然后根据伪造索引
访问伪造的EmuVaddrNode结构,最后访问攻击者构造的Vadder
,实现任意地址写入的功能。同理,若Windows Defender
模拟执行value=*p
,就可以实现任意地址读取的功能。
在Windows Defener
中会将常用的代码片段进行jit
(即时编译)处理,在取得任意地址读写能力后,样本中先将通过硬编码偏移获取到了jit
部分的真实地址,将EmuVaddrNode
的vaddr
设置为jit
的真实地址,并且利用模拟执行 memcpy(EmuPageNum,shellcode, sizeof (shellocde))
向jit
地址写入了shellcode
,最终只要jit
功能一使用便会执行shellcode
。从而实现了shellcode
的布置。
检测原理
MpEngine
对asprotect
壳解压后的内容校验如下:
可类似*(memory)=0x8d;*(memory+1)=0x85;*(memory+6)=0x50; *(memory+7)=0xc3
进行特征匹配。- 样本通过
NtControlChannel
获得版本信息以确定偏移信息,可以把NtControlChannel
函数的调用特征作为匹配依据。
参考
https://docs.microsoft.com/zh-cn/cpp/c-runtime-library/reference/lfind?view=msvc-170
Author: suyumen
Link: https://suyumen.github.io/2022/04/06/2022-04-06-CVE-2021-1647%E5%A4%8D%E7%8E%B0/
Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.