二进制安全一窍不通啊,写逆向作业还费劲呢就要复现这个真是难为我啊呜呜。
漏洞简介
- 漏洞名称: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.