FLARE 脚本系列(新) 一:Objective-C代码的自动化模拟分析
这篇博客属于 FLARE (FireEye Labs Advanced Reverse Engineering) 团队脚本系列中的一个全新的系列。
今天,我们给大家分享一个新的 IDAPython 库 flare-emu,这个库是依赖于 IDA Pro 和 Unicorn 模拟框架,并为让逆向工程师可以通过脚本对代码的功能进行模拟,Unicorn 支持 x86, x86_64, ARM 以及 ARM64 架构。
此外,我们还分享了一个利用 flare-emu 来进行 Objective-C 代码分析的 IDAPython 脚本。
接下来,我们将介绍一些新的方法,利用模拟器来解决代码分析中的一些问题,以及如何使用我们的 flare-emu 库来提高分析的效率。
为什么使用模拟器?
如果你还没用过代码模拟来分析过代码,那么你就有点落伍了。
下面,我将展示一些代码模拟的优点和一些使用场景,来让你更好的了解一些它的用途。
代码模拟的优点在于其灵活性,并且现在已经有许多的成熟的代码模拟框架了,如 Unicorm,这是一种跨平台的模拟框架。
借助于代码模拟,你可以选择指定要模拟执行的代码,并且可以控制其在运行时的上下文。
由于模拟代码无法访问运行它的操作系统的系统服务,因此几乎不会造成损坏。
所有这些优势使模拟器成为 ad-hoc 测试,解决问题或自动化测试的绝佳选择。
使用场景
-
解码/解密/反混淆/解压:
在恶意代码分析时,你可能会遇到用于解码,解压缩,解密或反混淆的函数来处理某些有用数据(如字符串,配置信息或其他有效负载)。
如果它是一种常见算法,你可以通过阅读代码或一些插件(如signsrch)就能识别出来。
然而,这种情况并不常见。
通常情况下,这些算法无法被有效的识别出来,你只能打开调试器并检测样本来识别这些算法,或者手动将函数转换为适合你当前需要的编程语言。
这些方法的效率非常低,并且也有一定的问题,具体取决于代码的复杂程度和你正在分析的样本。
这时,代码模拟通常可以为第三种选择。
编写一个脚本来模拟这个函数,就类似于你可以像自己写的函数一样使用它或调用它,你可以不断的调用它,可以指定不同的输入,并且不需要借助于调试器。
这种情况也同样适用于自解密的shellcode,你可以让它在模拟器中为自己解密。 -
数据追踪
借助于模拟器指令钩子,你可以在程序运行的任何时候暂停程序并观察其上下文。
将反汇编程序与模拟器配对允许您在关键指令处暂停程序并检查寄存器和内存的数据。
这使你可以在调试经过任何函数的时候,密切关注感兴趣数据。
这可以有几个应用程序。正如之前在 FLARE 脚本系列(Automating Function Argument Extraction, Automating Obfuscated String Decoding)中的其他博客中介绍的一样,这项技术可用于跟踪在整个程序中传递给特定函数的参数。
函数参数跟踪是本文在后面分析 Objective-C 代码时所使用的其中一个技术。
此外,数据追踪技术还可以用来追踪 C++ 代码中的 this 指针,以便标记对象成员的引用,或者通过调用GetProcAddress/dlsym返回值来重命名存储在其中的变量。
Flare-emu 介绍
FLARE 团队开发的 flare-emu 是一个 IDAPython 库,这个库结合了 IDA Pro 的二进制代码分析能力与 Unicorn 的模拟框架,为用户的脚本模拟提供了一个易于使用并且灵活的接口。
flare-emu 旨在为不同的体系架构设置灵活且健壮的模拟器的所有基础工作,这样你就可以专注于解决代码分析的问题。
它目前提供了三个不同的接口来处理你的代码模拟需求,而且它还有一系列相关的帮助和工具函数。
-
emulateRange
这个API能够在用户指定的上下文中模拟一系列指令或函数。
对于用户定义的钩子,它既可用于单个指令,也可以用于在调用 call 指令的时候。
用户可以决定模拟器是单步跳过还是单步执行(进入函数调用)。
图 1 展示了 emulateRange 使用单指令钩子和 call 钩子来跟踪 GetProcAddress 调用的返回值并将全局变量重命名为它们将要指向的 Windows API 的名称。
在本例中,仅将其设置为模拟从 0x401514 到 0x40153D 的指令。
该接口能够让用户很简单的为指定寄存器和堆栈参数赋值。
如果要指定一个 bytestring,则先将其写入模拟器的内存,并将其指针写入寄存器或堆栈中。
在模拟结束之后,用户可以使用 flare-emu 的工具函数从模拟内存或寄存器中读取数据。
如果 flare-emu 没有提供你需要的一些功能,你可以直接使用返回的 Unicorn 模拟器对象。此外, flare-emu 还提供了一个对于 emulateRange 的一个小的封装,名为 emulateSelection,可以用来模拟当前在 IDA Pro 中突出显示的部分指令。
图1 使用emulateRange来追踪 GetProcAddress 的返回值 -
iterate
这个API是用来强制模拟函数中的特定分支,以达到预期的运行路径。
用户可以指定一个目标地址列表,或者指定一个函数的地址,从该函数中使用对该函数的交叉引用列表作为目标,以及一个用于到达目标时的回调。
程序会执行到给定的目标地址上,尽管当前的条件可能会跳转到其他的分支上。
图 2 演示了一组代码分支,利用 iterate 强行使其执行到了目标地址的指令上。
cmp指令设置的标志无关紧要。
就像 emulateRange API 一样,对于用户定义的钩子,iterate 既可用于单个指令,也可以用于在调用 call 指令的时候。
iterate 的一个应用场景就是本文前面提到的函数参数跟踪技术。
图2 一种由iterate 确定的执行路径,以达到目标地址 -
emulateBytes
这个API提供了一种简单模拟一个外部 shellcode 的方法。
提供的字节不会添加到IDB,只是直接的模拟执行。
这对于准备模拟环境非常有用。
例如,flare-emu 本身使用此 API 来操作 ARM64 CPU 的 Model Specific Register(MSR),以便启用 Vector Floating Point(VFP)指令和寄存器访问, Unicorn并未公开该模块。
图 3 表示实现该功能的代码片段。
与emulateRange一样,如果 flare-emu 没有提供你需要的一些功能,你可以直接使用返回的 Unicorn 模拟器对象。
图3 flame-emu 使用emulateByte为ARM64启用VFP
API 钩子
如前所述,flare-emu 的设计目的是使你能够轻松地使用模拟来解决代码分析需求。
如何调试进入库函数调用,是代码模拟的一个问题。
尽管使用 flare-emu 你可以对该指令单步跳过,或者为处理调用钩子例程中的特定函数定义自己的钩子,但是它预定义的函数钩子也超过了80个!
这些函数包括许多常见的C运行时函数(用于字符串和内存操作),以及它们的一些 Windows API 对应函数。
示例
图4展示了几个代码块,它们调用一个将时间戳转换为字符串的函数。
图5展示了一个简单的脚本,该脚本使用 flare-emu 的 iterate API 来为每个调用该函数的位置打印传递给该函数的参数。
该脚本还模拟简单的XOR解码函数并打印生成的解码字符串。
图6展示了脚本的结果输出。
图4 调用时间戳转换函数
图5 flare-emu使用的简单示例
图6 图5脚本的输出
这里是一个示例脚本,它使用 flare-emu 跟踪 GetProcAddress 的返回值,并相应地重命名它们存储的变量。
查看我们的 README 以获取更多示例,并且可以深入的了解 flare-emu。
objc2_analyzer 介绍
去年,我写了一篇博文reverse engineering Cocoa applications for macOS,主要介绍如何在macOS中逆向Cocoa的应用程序。
这篇文章有一部分简单介绍了如何在 hood 下调用 Objective-C 方法,以及它如何影响 IDA Pro 和其他反汇编程序中的交叉引用的。
此帖中还介绍了一个名为 objc2_xrefs_helper 的 IDAPython 脚本,它能够修复这些交叉引用的问题。
如果您还没有阅读该博客文章,我建议您在继续阅读本文之前阅读它,因为它提供了一些背景信息,对于理解 objc2_analyzer 特别有用。
objc2_xrefs_helper 的一个主要缺点是,如果选择器(selector)名称不明确,即两个或多个类实现了具有相同名称的方法,则脚本无法确定引用的选择器属于二进制文件中的哪个类,并且在修复交叉引用时不得不忽略这种情况。
现在,有了模拟器的支持,这都已经不再是问题了。
objc2_analyzer 使用 flare-emu 的 iterate API 指令以及调用 Objective-C 反汇编分析的钩子,以确定每次调用 objc_msgSend 时传递的 id 和 selector。
这样做的另一个好处是,当函数指针存储在寄存器中时,它还可以捕获对 objc_msgSend 的调用,这是Clang(当前版本的Xcode使用的编译器)中非常常见的一种方法。
IDA Pro 自身试图捕获这些,但它并没有捕获住它们全部。
除了x86_64 之外,还为 ARM 和 ARM64 架构添加了支持,以支持逆向 iOS 程序的需求。
此脚本已经替代了旧的 objc2_xrefs_helper 脚本,旧的脚本已经在我们的 repo 中删除了。
并且,由于脚本可以通过使用模拟器在 Objective-C 代码中执行数据跟踪,因此它还可以确定 id 是类实例还是类对象本身。
另外还增加了对跟踪作为id传递的ivar的支持。
有了所有这些信息,每次调用 objc_msgSend 时都会添加 Objective-C 样式的伪代码注释,这些变量表示在每个方法调用的位置。
如图7和图8展示了脚本的功能。
图7 运行objc2_analyzer之前的 Objective-C IDB 片段
图8 运行objc2_analyzer之后的 Objective-C IDB 片段
为了便于转换,我们将指令引用指向了选择器,而不是实现函数本身。
每次调用都会添加注释,这样使得分析更加容易。
图9表明,对于实现函数的交叉引用,也增加了指向对 objc_msgSend 的调用。
图9 为实现函数将交叉引用添加到IDE中
IDA Pro 从7.0开始的每个版本都对Objective-C代码分析和处理进行了改进。
然而,在撰写本文时, IDA Pro 的最新版本是7.2。
objc2_analyzer 工具弥补了 IDA Pro 的一些缺点,并且还有非常详细的注释。
objc2_analyzer以及其他 IDA Pro 插件和脚本都可以在GitHub页面上找到。
总结
flare-emu 是一个可以灵活应用于各种代码分析问题的工具。
在这篇文章中,我们给出了几个它的几个使用场景,但这只是全部功能的一小部分。
如果你还没有尝试过使用代码模拟来解决代码分析问题,我们希望这篇文章能够成为你的开始。
对于所有人来说,我们希望您能发现这些新工具的价值!
这篇文章发表于2018年12月12日,由James T.Bennett, tools, FLARE 和 analysis 提交。