AndrewDavis最近发布了他的新Windows仿真框架Speakeasy。本文将重点介绍该框架的另一个强大用途:自动对恶意程序进行大规模分析。我将通过代码示例演示如何以编程方式使用Speakeasy:绕过不受支持的WindowsAPI以继续模拟和分析;使用API??hooks来保存动态分配代码的虚拟地址;使用代码钩子直接在键区执行代码;从模拟器内存??中转储分析的PE并修复其节头;通过查询Speakeasy的符号信息来帮助重建导入表;初始设置与Speakeasy交互的一种方法是创建Speakeasy类的子类。图1显示了Python代码片段,它设置了一个将在后面的示例中扩展的类。创建一个Speakeasy子类图1中的代码接受一个Speakeasy配置字典,该字典可用于覆盖默认配置。Speakeasy附带了几个配置文件。Speakeasy类是基本模拟器类的压缩器类。当加载二进制文件或根据其PE标头将其指定为shellcode时,会自动选择模拟器类。子类化Speakeasy使得访问、扩展或修改接口变得容易,它还有助于在模拟期间读写状态数据。模拟二进制文件图2显示了如何将二进制文件加载到Speakeasy模拟器中。将二进制文件载入模拟器load_module函数为磁盘上提供的二进制文件返回一个PeFile对象。它是speakeasy/windows/common.py中定义的PeFile类的实例,它是pefile的PE类的子类。或者,您可以使用数据参数而不是指定文件名来提供二进制字节。图3显示了如何模拟加载的二进制文件。StartMockAPIHooksSpeakeasy框架为数百种WindowsAPI提供支持,并且经常添加更多。这是通过在speakeasy/winenv/API目录中的适当文件中定义的PythonAPI处理程序来完成的。可以安装API挂钩以在模拟期间调用特定API时执行您自己的代码。无论是否存在处理程序,都可以为任何API安装它们。API挂钩可用于覆盖现有的处理程序,并可选择从您的挂钩中调用该处理程序。Speakeasy中的API挂钩机制提供了灵活性和对模拟的控制。让我们在模拟分析代码以检索分析的有效负载的上下文中检查API挂钩的几种用法。不受支持的API绕过当Speakeasy遇到不受支持的WindowsAPI调用时,它会停止模拟并提供不受支持的API函数的名称。如果所讨论的API函数对于分析二进制文件并不重要,您可以添加一个API挂钩,它只返回一个允许执行继续的值。例如,最近样本的分析代码包含对分析过程没有影响的API调用。一个这样的API调用是GetSysColor,为了绕过这个调用并允许执行继续,可以添加一个API挂钩,如图4所示。添加的API挂钩根据MSDN,此函数采用1个参数并返回一个RGB颜色值,表示为一个双字。如果被hook的API函数的调用约定不是stdcall,调用约定可以在可选的call_conv参数中指定。调用约定常量定义在speakeasy/common/arch.py??文件中。由于GetSysColor返回值不影响分析过程,我们可以简单地返回0。图5显示了图4中指定的getsyscolor_hook函数的定义。GetSysColor钩子返回0如果API函数需要更细粒度的处理,则可以实现更细粒度的处理具体且有意义的挂钩以满足您的需求。如果您的钩子实现足够稳定,您可以考虑将其作为API处理程序添加到Speakeasy项目中!添加API处理程序在speakeasy/winenv/api目录中,您会找到usermode和kernelmode子目录,其中包含相应的二进制文件模块的Python文件。这些文件包含每个模块的API处理程序。在usermode/kernel32.py中,我们看到为SetEnvironmentVariable定义的处理程序,如图6所示。SetEnvironmentVariable的API处理程序处理程序以函数修饰符(第1行)开始,该修饰符定义了API的名称及其接受的参数数量。最佳做法是将MSDN记录的原型作为注释(第3-8行)包含在处理程序的开头。处理程序的代码首先将argv参数的元素存储在以相应API参数命名的变量中(第9行),处理程序的ctx参数是一个包含有关API调用的上下文信息的字典。对于以“A”或“W”结尾的API函数(例如,CreateFileA),可以通过将ctx参数传递给get_char_width函数(第10行)来检索字符宽度。然后可以将此宽度值传递给类似read_mem_string的调用(第12和13行),该调用读取仿真器内存中给定地址并返回一个字符串。最佳做法是用相应的字符串值覆盖argv参数中的字符串指针值(第14和15行)。这使Speakeasy能够在其API日志中显示字符串值而不是指针值。为了说明更新argv值的影响,请检查图7中所示的Speakeasy输出。在VirtualAlloc条目中,符号常量字符串PAGE_EXECUTE_READWRITE替换值0x40。在GetModuleFileNameA和CreateFileA条目中,指针值替换为文件路径。SpeakeasyAPI日志保存分析过的代码地址压缩后的样本通常会使用VirtualAlloc等函数来分配内存用于存储分析过的样本。捕获分析代码的位置和大小的一种有效方法是首先挂钩分析存根使用的内存分配函数。图8显示了挂钩VirtualAlloc以捕获API调用分配的虚拟地址和内存量的示例。VirtualAlloc钩子可以保存内存转储信息。图8中的挂钩在第12行调用Speakeasy的VirtualAllocAPI处理程序来分配内存。API处理程序返回的虚拟地址将保存到名为rv的变量中。由于VirtualAlloc可用于分配与分析过程无关的内存,因此在第13行使用额外的检查来确认截获的VirtualAlloc调用是在分析代码中使用的调用。根据之前的分析,我们正在寻找一个VirtualAlloc调用,它将接收一个lpAddress值0和一个flProtect值PAGE_EXECUTE_READWRITE(0x40)。如果存在这些参数,虚拟地址和指定大小将存储在第15和16行,因此在分析代码完成后,它们可用于从内存中获取分析的有效负载。最后,在第17行,挂钩返回VirtualAlloc处理程序的返回值。使用API和代码挂钩的外科代码模拟Speakeasy是一个强大的模拟框架,但是,您可能会遇到包含大量可疑代码的二进制文件。例如,一个示例可能会调用许多不受支持的API,或者只是花费太长时间来模拟。以下场景描述了克服这两个挑战的示例。在MFC项目中取消隐藏存根一种流行的屏蔽恶意负载的技术涉及将它们隐藏在大型开源MFC项目中。MFC是MicrosoftFoundationClass的缩写,是一种用于构建Windows桌面应用程序的流行库。这些MFC项目通常是从CodeProject等流行网站中任意选择的,虽然MFC库使创建桌面应用程序变得容易,但由于MFC应用程序的大小和复杂性,它们很难进行逆向工程。由于它们调用许多不同的WindowsAPI的大型初始化例程,因此它们特别难以模拟。以下是我使用Speakeasy编写Python脚本来自动分析将其分析存根隐藏在MFC项目中的自定义压缩器的经验的描述。对压缩程序进行逆向工程表明,分析存根最终在CWinApp对象的初始化期间被调用,这发生在C运行时和MFC初始化之后。在尝试绕过不受支持的API后,我意识到,即使成功,模拟也需要很长时间才能实现。我考虑过完全跳过初始化代码并直接跳转到分析存根。不幸的是,为了成功模拟分析存根,需要执行C运行时初始化代码。我的解决方案是在C运行时初始化之后,在MFC初始化例程的早期代码中识别一个位置,在检查图9中所示的SpeakeasyAPI日志后很容易发现。图形相关的API函数GetDeviceCaps被提前调用在MFC初始化例程中。这是基于以下事实推断的:1.MFC是一个图形相关的框架和2.GetDeviceCaps不太可能在C运行时初始化期间被调用。在SpeakeasyAPI日志中识别MFC代码的开头为了在这个阶段拦截执行,我为GetDeviceCaps创建了一个API挂钩,如图10所示。这个挂钩确认该函数在第2行中第一次被调用。为GetDeviceCaps设置的API钩子的第4部分显示了使用Speakeasy类的add_code_hook函数来创建代码钩子。代码挂钩允许您指定在模拟每条指令之前调用的回调函数。Speakeasy还允许您通过指定开始和结束参数来指定代码挂钩有效的地址范围。在第4行添加代码钩子后,GetDeviceCaps钩子完成,在执行示例的下一条指令之前,会调用start_unpack_func_hook函数。此函数如图11所示。更改指令指针的代码挂钩代码挂钩接收模拟器对象、当前指令的地址和大小以及上下文字典(第1行)。在第2行,代码挂钩自行禁用。因为代码挂钩是针对每条指令执行的,所以这会大大减慢模拟速度。因此应慎用,尽早停用。在第3行,挂钩计算分析函数的虚拟地址。使用正则表达式定位用于执行此计算的偏移量。为简洁起见,省略了该示例。self.module属性先前已在图2所示的示例代码中设置。它继承自pefile的PE类,它使我们能够访问有用的函数,例如第3行的get_rva_from_offset()。该行还包括一个使用self的示例。.module.get_base()检索模块的基本虚拟地址。最后,在第4行,使用set_pc函数更改指令指针,并在分析的代码处继续进行仿真。图10和11中的代码片段允许我们在C运行时初始化完成后将执行重定向到分析代码,而避免使用MFC初始化代码。发布和修复未压缩的PE一旦模拟到达分析样本的原始入口点,就该移除PE并修复它了。通常,钩子会将被分析的PE的基地址保存在此类的一个属性中,如图8的第15行所示。如果被分析的PE在其PE标头中不包含正确的入口点,则真正的入口点可能也需要在模拟过程中捕获。图12显示了如何将仿真器内存转储到文件的示例。转储未压缩的PE如果要转储加载到内存中的PE,由于部分对齐方式不同,其布局将不同于磁盘上的布局。因此,可能需要修改转储的PE标头。一种方法是修改每个部分的PointerToRawData值以匹配其VirtualAddress字段。为了符合PE可选标头中指定的FileAlignment值,可能需要填充每个部分的SizeOfRawData值。请记住,生成的PE不太可能成功执行。然而,这些努力将使大多数静态分析工具能够正常运行。修复转储PE的最后一步是修复它的导入表,这是一项复杂的任务,这里不做详细讨论。但是,第一步涉及收集库函数名称列表及其在模拟器内存??中的地址。如果您知道分析器存根使用GetProcAddressAPI来解析所分析PE的导入,则可以调用get_dyn_imports函数,如图13所示。参见动态导入否则,您可以通过以下方式查询仿真器类以检索其符号信息调用get_symbols函数,如图14所示。从仿真器类中检索符号信息此数据可用于发现未压缩PE的IAT并修复或重建它们的导入相关表。总结编写Speakeasy脚本来分析恶意程序样本可以分为以下步骤:(1)对分析存根进行逆向工程以识别:被分析的代码将驻留在何处或其内存分配的位置;执行被转移到分析代码的位置;任何可能引入问题的有问题的代码,例如不受支持的API、缓慢的模拟或反分析检查。(2)如有必要,设置一个钩子来绕过违规代码;(3)设置一个钩子来识别虚拟地址,以及可选的分析二进制文件的大小;(4)设置一个钩子(5)收集WindowsAPI的虚拟地址,重建PE的导入表;(6)修复PE的标头并将字节写入文件以供进一步分析;(7)有关分析UPX样本的脚本示例,请查看Speakeasy存储库中的UPX分析脚本。Speakeasy框架提供了一个易于使用、灵活且功能强大的编程接口,使分析人员能够解决复杂的问题,例如分析恶意程序。使用Speakeasy自动化这些解决方案可以让它们大规模执行。本文翻译自:https://www.fireeye.com/blog/threat-research/2020/12/using-speakeasy-emulation-framework-programmatically-to-unpack-malware.html
