一、概述
为了能够大规模模拟恶意软件样本,研究人员近期开发了Speakeasy模拟框架。这一框架能够帮助非恶意软件分析人员的用户能轻松自动获取分类报告,同时可以帮助逆向工程师为难以分类的恶意软件系列编写自定义插件。
Speakeasy最初是为了模拟Windows内核模式的恶意软件而创建的,现在也开始支持用户模式样本。这一项目的主要目标是针对x86和amd64平台进行动态恶意软件分析的Windows操作系统的高度模拟。我们知道,目前也有类似的模拟框架可以实现对用户模式二进制文件进行模拟,但Speakeasy的特点包括:
1、专为模拟Windows恶意软件而设计;
2、支持内核模式二进制文件的模拟,借此可以分析难以分类的rootkit;
3、结合当前恶意软件趋势进行模拟和API支持,提供了无需额外工具就可以提取威胁指标的方法;
4、提供了无需其他代码就完全可配置的模拟环境。
该项目目前支持内核模式驱动程序、用户模式Windows DLL和可执行文件、Shellcode。可以自动模拟恶意软件样本,并生成报告,以让分析人员进行后期处理。我们的下一个目标是继续增加对新恶意软件家族或常见恶意软件家族的支持。
在这篇文章中,我们将展示一个案例,说明Speakeasy是如何有效地从在线恶意软件集获取的Cobalt Strike Beacon样本中自动提取威胁指标的。
二、背景
Windows恶意软件的动态分析一直是恶意软件分析过程中的一个关键步骤。要想评估恶意软件对网络产生的实际影响,了解恶意软件如何与Windows API交互,并提取有价值的主机、网络威胁指标(IoC)就变得至关重要。通常情况下,研究人员都使用自动化或有针对性的方式进行动态分析。例如将恶意软件按顺序在沙箱中运行以监视其功能,或者手动进行调试以确认沙箱运行期间没能执行的代码路径。
在以前,代码模拟一直被用于测试、验证甚至是恶意软件分析。如果能够模拟恶意软件代码,无论对于人工分析还是自动分析来说,都是很有收益的。通过对CPU指令的模拟,可以对二进制代码进行全面检测,在这一过程中,可以让控制流最大程度地覆盖代码。在模拟过程中,可以监控并记录所有功能,以便迅速提取威胁指标,或者其他有用的情报。
与在虚拟机沙箱中执行相比,模拟具有一些优势,其中一个关键优势就是减少环境中的噪音(误报情况)。在模拟过程中,只有由恶意软件作者编写的活动才会被记录下来,或者是在二进制文件中静态编译的。在虚拟机中的API Hooking(特别是在内核模式中),可能很难被归因于恶意软件本身。举例来说,沙箱解决方案经常会Hook堆分配器API调用,但它们通常无法判断恶意软件作者是否打算分配内存,还是在较低级别API负责内存分配。
但是,模拟的方式也有缺点。由于我们在分析的阶段就脱离了操作系统,因此模拟器需要负责提供API调用和模拟期间所发生的内存访问的预期输入和输出。为了成功模拟在正常Windows系统上运行的恶意软件样本,这需要大量的精力。
三、Shellcode攻击方式分析
通常,Shellcode是让攻击者在受感染系统上保持隐身的绝佳选择。Shellcode在可执行的内存中运行,不需要依赖于磁盘上的任何文件。这就使得攻击者的代码可以轻松隐藏在大多数形式的传统取证都无法识别的内存之中。必须首先确定加载Shellcode的原始二进制文件,或者必须从内存中转储Shellcode本身。为了逃避检测,攻击者可以将Shellcode隐藏在合法的加载程序中,然后注入到另一个用户模式进程之中。
在文章的第一部分,我们将展示在应急响应过程中遇到的一个较为常见的Shellcode恶意软件样本,并分析如何对其进行模拟。Cobalt Strike是一个商业的渗透测试框架,通常使用分阶段的程序来执行其他代码。其中的一个阶段,往往是通过HTTP请求下载其他代码,并执行HTTP响应的数据内容。在这种情况下,这个响应的数据就成为了Shellcode,通常要对内容进行解码,然后得到有效的PE,PE中包含反射加载其自身的代码。在Cobalt Strike框架中,这种情况的Payload通常是被称为Beacon的植入工具。Beacon被设计为驻留在内存中的后门,用于在受感染的Windows系统上维持命令与控制(C2)。由于它是使用Cobalt Strike框架构建的,所以无需进行任何代码层面上的修改,就能够轻松调整其核心功能和命令与控制信息。
上述这些特点,促使攻击者能够在受感染的网络中快速构建和部署新的Beacon注入工具变种。因此,我们就需要使用一种工具来快速提取Beacon的变种组件,并且在理想情况下,最好不要占用恶意软件分析人员的过多宝贵时间。
四、Speakeasy原理分析
Speakeasy当前使用基于QEMU的模拟器引擎Unicorn来模拟x86和amd64体系结构的CPU指令。Speakeasy未来希望通过抽象层支持任意模拟引擎,但目前还是依赖于Unicorn。
如果要借助沙箱分析所有样本,可能需要使用完整的操作系统沙箱来分析全量样本,这种情况下就很难模拟所有版本的Windows系统。沙箱可能难以按照需要去扩展,并且运行样本需要花费大量的时间。但是,如果能够确定特定的恶意软件家族(例如案例中的Beacon),我们可以大幅减少需要进行逆向工程的变种的数量。在对恶意软件的变种进行分析的过程中,最好是可以以自动化的方式来生成一个高级的分类报告。这样一来,就会让恶意软件分析人员有更多的时间专注于可能需要更深入分析的样本。
使用Speakeasy时,Shellcode或Windows PE被加载到模拟地址空间中。在尝试模拟恶意软件之前,首先创建了Windows内核模式和用户模式要进行基本模拟所需的Windows数据结构。进程、驱动程序、设备和用户模式库都是伪造的,这样可以为恶意软件提供一个近乎真实的执行环境。恶意软件可以与模拟的文件系统、网络、注册表继续你功能交互。所有这些模拟的子系统都可以使用独立的配置文件进行配置。
Windows API由Python API处理程序进行处理。这些处理程序将尝试模拟这些API的预期输出,以便恶意软件样本继续预期的执行路径。在定义API处理程序时,我们所需要的就是API的名称、API期望的参数数量以及可选的调用约定规范。如果没有提供任何调用约定,则假设为stdcall。在目前版本中,如果尝试了不支持的API调用,Speakeasy将记录不支持的API,并继续进行下一个入口点。下图展示了由kernel32.dll导出的Windows HeapAlloc函数的示例处理程序。
默认情况下,会模拟所有入口点。例如,对于DLL来说,会模拟所有导出;对于驱动程序,会分别模拟IRP主要函数。此外,还会跟踪在运行时发现的动态入口点。其中一些动态入口点包括创建的线程或注册的回调。在尝试确定恶意软件的影响时,将活动与特定的入口点关联起来对于全局来说至关重要。
五、导出报告
当前,模拟工具捕获的所有事件都会记录在JSON格式的报告中,以便后续处理。报告中包含在模拟过程中记录的关键事件。与大多数模拟工具一样,所有Windows API调用及其参数都会被记录。所有的入口点都会被模拟,并且标记相应的API列表。除了API跟踪外,还会记录其他特定事件,包括文件、注册表和网络访问。所有解码后或驻留在内存中的字符串都会被转储并展现在报告中,从而揭示出通过静态字符串分析无法找到的关键信息。下图展示了Speakeasy的JSON报告中记录的文件读取事件的示例:
六、运行速度
由于该框架是使用Python编写的,所以速度是一个明显需要关心的维度。Unicorn和QEMU是使用C编写的,可以提供非常快的模拟速度。但是,我们编写的API和事件处理程序使用了Python。本地代码和Python之间的转换过程会花费一定时间,应该尽量减少。所以,我们的目标就是仅在绝对必要时再执行Python代码。默认情况下,我们用Python处理的唯一事件就是内存访问异常或Windows API调用。为了捕获Windows API调用并在Python中进行模拟,导入表中加入了无效的内存地址,因此我们仅在访问导入表时才会切换到Python。当Shellcode访问恶意软件的模拟地址空间中加载的DLL导出表时,也会使用类似的技术。通过执行尽可能少的Python代码,我们可以控制该框架在一个合理的速度,同时我们允许用户开发该框架的功能。
七、内存管理
Speakeasy在模拟工具引擎的内存管理方面实现了轻量级。跟踪并标记由恶意软件分配的每个内存块,就可以获取到有意义的内存转储。对于分析人员来说,能够将恶意活动与特定的内存块进行关联会非常有帮助。通过记录对敏感数据结构的内存读写,就可以揭示出API调用记录可能无法体现的恶意软件真实意图,这对于rootkit样本来说特别有用。
Speakeasy提供了可选的“内存跟踪”功能,该功能将记录样本涉及到的所有内存访问。会记录所有读取、写入和执行的内存。由于模拟工具标记了所有分配的内存块,因此可能会从该数据中收集到更多上下文。如果恶意软件对关键数据结构进行了Hook,或者将执行转移到动态映射的内存中,那么通过这一过程就能有效发现,对于调试或分析来说很有帮助。但是,这一功能的资源消耗较高,默认情况下是关闭的。
提供给恶意软件的模拟环境中包括常见的数据结构,Shellcode可以在其中查找并执行导出的Windows系统功能。为了调用Win32 API,必须解析导出的函数,这样才能对目标系统产生有意义的影响。在大多数情况下(包括Beacon),这些函数是通过遍历进程环境块(Process Environment Block,PEB)来定位的。Shellcode可以从PEB访问进程的虚拟地址空间中所有已加载模块的列表。
下图展示了通过模拟Beacon Shellcode样本生成的内存报告。在这里,我们可以看到恶意软件遍历PEB,以查找kernel32.dll的地址。然后,恶意软件会手动解析并调用VirtualAlloc API的函数指针,然后继续进行解码,将其自身复制到新缓冲区中,以控制执行。
内存跟踪报告:
八、配置
Speakeasy是高度可配置的,允许用户创建自己的配置文件。其中,可以指定不同的分析程度,以优化各个用例。最终目标是让用户在不调整代码的情况下轻松切换配置选项。当前,配置文件的结构是JSON文件。如果用户没有提供配置文件,则使用框架的默认配置。哥哥字段都记录在Speakeasy的项目文档中。
下图展示了网络模拟工具配置部分的代码片段。在这里,用户可以指定在进行DNS查找时返回哪些IP地址,或者在某些Beacon样本的案例中,指定在TXT记录查询期间返回哪些二进制数据。同样,也可以自定义HTTP响应。
网络配置:
许多恶意软件在HTTP阶段都会使用HTTP GET请求来检索Web资源。比如Cobalt Strike或Metasploit在这一阶段,会立即执行响应的内容,以继续下一阶段。可以使用Speakeasy的配置文件对响应的内容进行轻松配置。在上图的配置中,除非被覆盖,否则框架会返回default.bin文件中包含的数据进行响应。该文件当前包含调试中断指令(int3),因此,如果恶意软件尝试执行数据,就会退出,并记录在报告中。使用这个工具,我们可以轻松的将恶意软件分类为“可下载其他代码”的下载工具。类似地,还可以配置文件和注册表路径,将返回数据变为希望在Windows系统上运行的样本。
九、限制
如前所述,模拟的方式还带来了一些挑战。如何与被模拟的系统保持一致的功能,这对我们来说是一场持续的战斗。但是,这也提供了控制恶意软件和包含更多功能的独特机会。
在模拟没有完全完成的情况下,仍然可以生成模拟报告和内存转储,以收集尽可能多的数据。例如,后门可能会成功安装其持久性机制,但无法连接到C2服务器。在这种情况下,仍然能够记录下来主机层面的指标,为分析人员提供价值。
同时,可以轻松地将缺少的API处理程序添加到模拟工具中,以应对这些情况。对于许多API处理程序来说,仅返回成功代码就足以使恶意软件继续执行。尽管不可能对每个恶意软件进行完全模拟,但针对特定恶意软件家族的功能可以极大地减少逆向工程相同家族变种的需求。
十、使用方法
Speakeasy目前已经在GitHub上发布。可以使用项目中的Python安装程序脚本安装,也可以使用提供的Dockerfile安装在Docker容器中。该框架不受限于平台,可以在Windows、Linux或macOS中模拟Windows恶意软件。有关更多信息,请参见项目的README文件。
安装后,Speakeasy可以作为独立的库,也可以使用提供的run_speakeasy.py脚本直接调用。在这篇文章中,我们将演示如何从命令行模拟恶意软件样本。关于如何将Speakeasy作为库使用的信息,请参考项目的README文件。
其中包含的脚本用于模拟单个样本,并根据记录的事件生成JSON报告。下图展示了run_speakeasy.py的命令行参数。
Speakeasy还提供了丰富的开发和Hooking接口,用于编写自定义插件。
十一、实战:Beacon注入工具的模拟过程
对于这一样本,我们将模拟Shellcode,该Shellcode解码后执行Beacon植入程序变种,这个变种的SHA-256哈希值为7f6ce8a8c2093eaf6fea1b6f1ef68a957c1a06166d20023ee5b637b5f7838918。我们首先要验证样本的文本格式。该样本应该由加载程序启动,或者作为漏洞利用Payload的一部分。
恶意软件的Hex转储示例:
从上图,我们可以清楚地看到该文件不是PE文件格式。有Shellcode样本分析经验的分析人员可能会注意到前两个字节——0xfc 0xe8。这些字节可以反汇编为Intel的汇编指令cld和call。cld指令通常是独立Shellcode的前奏,因为它将清除方向标记,使恶意软件可以轻松地分析系统DLL导出表中的字符串数据。Shellcode通常使用后面的call指令,通过在其后跟随pop指令来获取当前程序计数器。这样,恶意软件就可以从内存中获取其执行位置。
由于我们确定该样本是Shellcode,那么我们就使用命令行调用Speakeasy,如下图所示。
使用命令行模拟恶意软件样本:
该命令将指示Speakeasy将偏移量为0的样本模拟为x86 Shellcode。请注意,即使我们正在模拟代码,并没有实际执行代码,但它们仍然是攻击者生成的二进制文件。因此,如果要使用本地CPU模拟引擎,最好还是在虚拟机中模拟恶意代码。
在模拟后,将生成一个名为report.json的报告。此外,还会将模拟环境的完整内存转储压缩后写入memory_dump.zip。该恶意软件将被加载到伪造的容器进程内部的模拟内存中,以模拟Shellcode期望的真实执行环境。一旦开始模拟,模拟的API调用将会连同其参数和返回值一起记录到屏幕上。下图展示了Beacon示例,该示例分配了一个新的内存缓冲区,将在其中复制自身。然后,恶意软件开始手动解析它需要执行的导出。
网络配置:
经过额外的解码和设置后,恶意软件尝试连接到其C2服务器。在下图中,我们可以看到恶意软件使用Wininet库,通过HTTP连接并读取了C2服务器中的数据。
用于连接到C2的Wininet API调用:
恶意软件将无限次循环,直到从C2服务器接收到期望的数据为止。Speakeasy将在预定时间后超时,并生成JSON报告。
网络C2事件:
在生成报告的“network_events”和“traffic”部分中,汇总了网络指标。在上图中,我们可以看到IP地址、端口号,还可以看到与恶意软件建立的连接相关的HTTP标头。
对于这个样本来说,当我们模拟样本时,我们指示Speakeasy创建模拟地址空间的内存转储。这会为每个内存分配及周围的上下文创建一个ZIP压缩包,其中包括基址、大小、由模拟工具分配的标签(用于标识内存分配对应的内容)。下图展示了在模拟过程中创建的内存转储文件的摘要。文件名包含与每个内存分配关联的标签和基址。
通过模拟获得的单个内存块:
如果仅在这些内存转储上运行字符串,就可以快速找到值得关注的字符串和Beacon配置数据,如下图所示。
恶意软件的配置字符串数据:
在分类的分析中,我们可能仅仅关心已知家族的恶意软件变种的威胁指标。但是,如果需要对样本进行完整的逆向工程,我们还可以以DLL形式恢复Beacon恶意软件的解码后版本。通过简单地对MZ魔术字节执行原始的grep,我们唯一得到的结果就是与原始样本分配相关的内存转储,以及恶意软件将自身复制到的虚拟分配缓冲区。
包含已解码恶意软件的内存转储:
如果我们查看原始Shellcode缓冲区中的字节,就可以看到它在复制之前已经被解码,并且位于内存中,准备从偏移量0x48进行转储。现在,我们可以成功地将解码后的Beacon DLL加载到IDA Pro中进行全面分析。
已解码的恶意软件成功加载到IDA Pro中:
十二、总结
在这篇文章中,我们展示了如何使用Speakeasy模拟框架自动分类Beacon恶意软件样本。我们用它来发现有价值的网络指标,从内存中提取配置信息,并得到了解码后的Beacon DLL,以进行进一步分析。
欢迎大家访问项目GitHub并尝试使用Speakeasy,也可以关注我们后续的文章,将进一步展示如何使用模拟工具进行内核恶意软件分析。
本文翻译自:https://www.fireeye.com/blog/threat-research/2020/08/emulation-of-malicious-shellcode-with-speakeasy.html如若转载,请注明原文地址