热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

浅析CVE20211647的漏洞利用技巧

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

 

概要

近期爆出的 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 函数):

上图中,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. 确定版本偏移

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

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

注意看函数最后两个字节: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 函数时会触发该函数调用)函数中被引用:

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

3. 获取任意读写能力

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

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

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

也就是说,索引数组后头就是 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 壳的特征。

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

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

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

 

总结

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

关于微步情报局

微步情报局,即微步在线研究响应团队,负责微步在线安全分析与安全服务业务,主要研究内容包括威胁情报自动化研发、高级 APT 组织&黑产研究与追踪、恶意代码与自动化分析技术、重大事件应急响应等。


推荐阅读
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了在Windows环境下如何配置php+apache环境,包括下载php7和apache2.4、安装vc2015运行时环境、启动php7和apache2.4等步骤。希望对需要搭建php7环境的读者有一定的参考价值。摘要长度为169字。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • Redis底层数据结构之压缩列表的介绍及实现原理
    本文介绍了Redis底层数据结构之压缩列表的概念、实现原理以及使用场景。压缩列表是Redis为了节约内存而开发的一种顺序数据结构,由特殊编码的连续内存块组成。文章详细解释了压缩列表的构成和各个属性的含义,以及如何通过指针来计算表尾节点的地址。压缩列表适用于列表键和哈希键中只包含少量小整数值和短字符串的情况。通过使用压缩列表,可以有效减少内存占用,提升Redis的性能。 ... [详细]
  • 第四章高阶函数(参数传递、高阶函数、lambda表达式)(python进阶)的讲解和应用
    本文主要讲解了第四章高阶函数(参数传递、高阶函数、lambda表达式)的相关知识,包括函数参数传递机制和赋值机制、引用传递的概念和应用、默认参数的定义和使用等内容。同时介绍了高阶函数和lambda表达式的概念,并给出了一些实例代码进行演示。对于想要进一步提升python编程能力的读者来说,本文将是一个不错的学习资料。 ... [详细]
  • This article discusses the efficiency of using char str[] and char *str and whether there is any reason to prefer one over the other. It explains the difference between the two and provides an example to illustrate their usage. ... [详细]
  • SpringMVC接收请求参数的方式总结
    本文总结了在SpringMVC开发中处理控制器参数的各种方式,包括处理使用@RequestParam注解的参数、MultipartFile类型参数和Simple类型参数的RequestParamMethodArgumentResolver,处理@RequestBody注解的参数的RequestResponseBodyMethodProcessor,以及PathVariableMapMethodArgumentResol等子类。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
author-avatar
速度coinmer
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有