浅析 CVE-2021-1647 的漏洞利用技巧 – 作者:Threatbook

概要

近期爆出的 CVE-2021-1647 是 Windows Defender MpEngine 模块中的一处 rce 漏洞,本文旨在从漏洞利用层面对相关样本所使用的技巧进行分析,笔者使用的样本哈希为:6e1e9fa0334d8f1f5d0e3a160ba65441f0656d1f1c99f8a9f1ae4b1b1bf7d788。

漏洞原理

Windows Defender 采用模拟执行的策略,对可执行文件进行黑白判定,模拟执行分为以下两个层面:指令模拟和运行环境模拟。指令模拟部分负责将相应平台指令(包括 arm/x86/x64 等)转为中间指令(IL),然后通过 jit/ 解释执行的方式运行相应的中间指令;运行环境模拟则包括内存系统模拟/文件系统模拟/api 模拟/DLL 模拟(与 MpEngine.dll 同目录的 vdm 文件是 dll 模拟文件的压缩集/合),在指令模拟执行的过程中,碰到压缩壳的情况,defender 也会模拟解压过程(详见 UnpackerContext::Unpack 函数),当前 defender 支持的压缩方式有:Upxw64/Upxw/WExtract/NSPacker/Shrinker/PECompact2/Area51/Crypter1337/Aspack/PKLite/SfxCab/Asprotect 等。本次漏洞出现在 Asprotect 模拟解压过程中(CAsprotectDLLAndVersion::RetrieveVersionInfoAndCreateObjects 函数):

图片[1]-浅析 CVE-2021-1647 的漏洞利用技巧 – 作者:Threatbook-安全小百科

上图中,v2+28 代表一个 section 数组的开始,该数组包含四个元素,每个 section 元素 8 字节,前 4 字节用于描述该 section 的虚拟地址,后四个字节用于描述该 section 的大小,上图描述的是这样一个过程:遍历一个包含 4 项的 section 数组,最后获得一个 sectionva 和 sectionsize,申请一片大小为 sectionva+sectionsize 的内存用于存储解压后的 section 内容,但其在计算 sectionva 和 sectionsize 时存在错误,代码中只考虑了 section[i+1].va>section[i].va 的情况,但并没有考虑两者相等的情况,倘若 section 数组中四个元素的值如下:[0,0],[0,0],[0x2000,0],[0x2000,0x3000],按照上述代码的逻辑最终 sectionva=0x2000,sectionsize=0,那最终申请的内存大小为 0x2000+0=0x2000,因此在解压最后一个 section 时,由于其大小为 0x3000,这样便会产生堆溢出问题,这便是本漏洞的产生根源。

利用技巧

1. 确定版本偏移

在样本开头处调用了如下函数:

图片[2]-浅析 CVE-2021-1647 的漏洞利用技巧 – 作者:Threatbook-安全小百科

在刚开始分析该样本时,笔者以为该处地址是 defender 中某个未开启 ASLR 模块的地址或是类似于利用异常处理进行反调试的措施,但其实这时笔者犯了一个巨大的错误,要明确的一点是,当样本在被 defender 模拟执行时,一切内存地址并不是真实的 host 上的地址,而是 defender 模拟内存空间中的一个地址,也就是说 0x7c96c654 其实是 defender 模拟内存空间中的一段地址,该地址其实对应于模拟 dll 模块 –ntdll.dll(模拟的 ntdll.dll 可以通过解压与 mpengine.dll 同目录的 mpasbase.vdm 获得)中的一段代码:

图片[3]-浅析 CVE-2021-1647 的漏洞利用技巧 – 作者:Threatbook-安全小百科

注意看函数最后两个字节:0xf 0xff,这两个字节说明这是一个 native api 调用,其后的四个字节 0x9E9EFDF0 是用于标识最终 native api 函数的一个 crc 校验码,所谓的 native api 即是有 mpengine.dll 提供的一系列功能 api,最终该函数会由 mpengine!NTDLL_DLL_NtControlChannel 实现,也就是说样本中 call 7C96C654 其实最终是调用 mpengine!NTDLL_DLL_NtControlChannel 完成功能,该函数第一个参数代表功能号,样本中的 3 代表的是获取 defender 版本信息,样本就是通过该函数获取版本信息,然后根据不同的版本信息硬编码关键偏移。

2. 内存占位/修改关键字段

在样本中包含了大量的 SuspendThread 和 ResumeThread 的调用代码,这部分代码其实是用来进行内存布局和占位的,在堆溢出发生后,会修改布局在堆内存后的 lfind 对象,lfind 对象中的两个关键字段分别被修改为 2f9b 和 2f9c(原始值为 107e 和 107f),这两个字段在 lfind_switch::switch_in(在模拟执行 ResumeThread 函数时会触发该函数调用)函数中被引用:

图片[4]-浅析 CVE-2021-1647 的漏洞利用技巧 – 作者:Threatbook-安全小百科

上图中的 v20 即是会被引用的 2f9b 和 2f9c,很明显由于被修改之后的值比正常的大,这会造成一个越界写的行为,上图中的 *(v20+*(v16+144))|=3 是漏洞利用过程的关键,该部分代码修改的是 vmmcontrol 中的一个关键字段,我们将在第三部分说明这个字段的用途。

3. 获取任意读写能力

在 defender 进行内存空间模拟的过程中涉及到这样几个比较重要的结构:

图片[5]-浅析 CVE-2021-1647 的漏洞利用技巧 – 作者:Threatbook-安全小百科

该结构用于维持模拟内存地址和真实内存地址之间的映射关系,该结构在内存中以数组的形式存在。

为了实现高效的地址转换,defender 还引入了一个索引数组(每个索引用 2 字节存储),该数组中存储的是 EmuVaddrNode 结构数组的索引,并且是按照 EmuPagenum 从小到大进行排序的,也就是说假设此时 EmuVaddrNode 数组中包含三个元素,且三个元素的 EmuPageNum 字段内容为 0x2000,0x1000,0x5000,那么正常情况下此时索引数组内容为1,0,3(1、0、3 都代表 EmuVaddrNode 数组的索引),并且 EmuVaddrNode 数组和索引数组在真实内存中的布局如下(假设从左到右代表地址从低到高):

图片[6]-浅析 CVE-2021-1647 的漏洞利用技巧 – 作者:Threatbook-安全小百科

也就是说,索引数组后头就是 EmuVaddrNode 数组,EmuVaddrNode 数组紧跟着的是 Page,Page 代表的是 defender 申请的用于映射模拟内存空间的真实内存,即 EmuVaddrNode 中的 Vaddr 都是从 page 中切割出来的。

最后,我们回顾一下第二部分中提到的修改了 vmmcontrol 中的关键字段,这个关键字段描述的是索引数组一共有多少个元素。如果我们将该数值改的尽可能的大,使得索引数组的起始地址+索引数组数目*2>Page 地址,而 Page 中的内容是我们可控的,我们通过让 defender 模拟执行 *p=value 的方式,就能在 Page 中布置我们想要的内容。首先,我们在 Page 中伪造索引数组,并且该索引远大于 EmuVaddrNode 数组的项数,那么当 defender 模拟执行 *p=value 这类指令时,先是从我们在 Page 中伪造的索引数组中取到一个伪造的索引(该索引远大于 EmuVaddrNode 数组的项数);接着,我们在 Page 中继续伪造一个 EmuVaddrNode 结构,那么 defender 通过伪造的索引便会访问到我们在 Page 中伪造的 EmuVaddrNode 结构,这时 EmuVaddrNode 结构的 Vaddr 是我们可控的,那么我们便获得了任意地址读写能力,“写”通过让 defender 模拟执行 *p=value 来实现,“读”通过让 dfender 模拟执行 value=*p 来实现。有浏览器或者内核漏洞相关经验的同事应该很熟悉这种场景,通过修改长度字段来伪造对象及其 pointer 字段最终获得任意地址读写的能力。

4. 获取代码执行能力

在 defender 中会将常用的代码片段进行 jit 处理,通常 jit 内存中存放的是 prolog 和 epilog 片段,在取得任意地址读写能力后,样本中先将通过硬编码偏移获取到了 jit 部分的真实地址,将某个 EmuVaddrNode 的 vaddr 设置为 jit 的真实地址,并且利用模拟执行 memcpy(EmuPageNum,shellcode, sizeof (shellocde))向 jit 地址写入了 shellcode,最终只要 jit 功能一使用便会执行 shellcode。

检测原理

由于样本是在 defender 模拟执行的过程中触发的漏洞,那么,如果我们仅仅简单取样本中的几个特征字串作为匹配规则,显然是十分容易被绕过的。笔者建议可以综合以下几方面信息作为特征:

1. asprotect 壳的特征

图片[7]-浅析 CVE-2021-1647 的漏洞利用技巧 – 作者:Threatbook-安全小百科

2. 上图所示内容是 MpEngine 中对解压后的内容做的一个校验,只有满足条件才会触发堆溢出操作,因此可以对类似于 ”*(memory)=0x8d; *(memory+1)=0x85;  *(memory+6)=0x50; *(memory+7)=0xc3” 的赋值操作进行特征匹配。

3. 样本通过 NtControlChannel 获得版本信息以确定偏移信息,因此可以把 NtControlChannel 函数的调用特征作为匹配依据。

4. 样本中其他为了实现稳定的内存布局而进行的模拟调用的特征。

总结

整个样本的利用过程,可以用精妙绝伦来形容。可以看出,漏洞作者对于 defender 模拟执行的过程已经研究的十分深入。因此,无论是从漏洞挖掘还是从漏洞利用的角度来说,该样本以及 Windows Defender 都还有非常多的地方值得笔者继续深入分析探索,也欢迎有兴趣的读者一同交流探讨。

来源:freebuf.com 2021-02-18 19:15:26 by: Threatbook

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论