使用 IDA Pro 的 REobjc 模块逆向 Objective-C 二进制文件

栏目: Objective-C · 发布时间: 7年前

内容简介:最近,在使用IDA Pro研究iOS应用的过程中,我发现,虽然IDA Pro和神奇的Decompiler插件能够以超高的还原度生成大部分的源代码,但如果想要针对某一个方法跟踪交叉引用(Cross Reference)的话,会发现其中缺失了许多实际存在的交叉引用,这对于静态分析工作的完整性造成了极大的挑战。DUO Labs在Objective-C是C语言的变体。用它开发的程序与Objective-C Runtime共享库链接。这个库实现了整个对象模型来支持Objective-C。

作者:PtGiraffe@银河安全实验室

公众号: https://mp.weixin.qq.com/s/p81UG0BKGhAeHjBuEhrx-g

最近,在使用IDA Pro研究iOS应用的过程中,我发现,虽然IDA Pro和神奇的Decompiler插件能够以超高的还原度生成大部分的源代码,但如果想要针对某一个方法跟踪交叉引用(Cross Reference)的话,会发现其中缺失了许多实际存在的交叉引用,这对于静态分析工作的完整性造成了极大的挑战。

DUO Labs在 https://duo.com/blog/reversing-objective-c-binaries-with-the-reobjc-module-for-ida-pro 发布了一篇文章,提到了IDA Pro中的这个问题,解释了其中的原理,并开发了一款工具,帮助逆向研究者更全面地获取交叉引用的信息。

Objective-C与IDA Pro交叉引用

Objective-C是 C语言 的变体。用它开发的程序与Objective-C Runtime共享库链接。这个库实现了整个对象模型来支持Objective-C。

Runtime的目标之一是尽可能动态。这样的设计会对对象的函数调用产生影响。在Objective-C中,函数方法的调用被称为消息传递。对象一旦接收到这些消息,会调用其中的一个对象的方法。Runtime在运行时动态解析方法调用。Objective-C源代码中的方法调用由编译器转换为Runtime函数 “objc_msgSend()” 的调用。

在这里,我们将仔细研究一下IDA Pro模块REobjc,该模块将调用 objc_msgSend() 的正确交叉引用添加到被调用的实际函数中。

如果从一个方法到另一个方法的调用被编译为对 objc_msgSend() 的调用,这样运行时调用的实际函数将不会反映在IDA Pro的交叉引用中。 objc_msgSend() 的函数签名定义如下:

id objc_msgSend(id self, SEL op, ...)

对于任何的Objective-C方法调用,前两个参数是对象的self指针以及selector,selector参数是调用方法对应的字符串表示。带参数的Objective-C方法将在selector之后按顺序传递参数。

编译Objective-C程序

下面的代码示例为一个简单的Objective-C源代码。此init方法包括4个Objective-C方法调用。

使用 IDA Pro 的 REobjc 模块逆向 Objective-C 二进制文件

从概念上讲,编译器读取上面的Objective-C方法,并将它们编译成类似于以下内容的C代码。这个C代码示例实际上来自于IDA Pro的反编译器输出,它很好地说明了编译器如何将Objective-C调用转换为C。

使用 IDA Pro 的 REobjc 模块逆向 Objective-C 二进制文件

如图所示,[super init]被转换为对 objc_msgSendSuper2() 的调用。这是初始化子类时的常见做法。[NSString string]被转换为 objc_msgSend() 调用,该调用被发送到NSString的对象。对于有参数的类方法的调用,[NSMutableData dataWithLength:_length]也被转换为对 objc_msgSend() 的调用。

示例中的最后一个Objective-C调用[[BTGattLookup alloc] init]展示了一个常见的allocate-initialize模式。首先,BTGattLookup类收到alloc消息,构造出了该类的实例。然后使用第二个 objc_msgSend() 调用init。

如果生成对应英特尔X64架构的二进制文件,调用将依照英特尔X64 ABI。函数参数按照RDI,RSI,RDX,RCX,R8,R9的顺序在寄存器中传递。这意味着RDI寄存器保存自指针,RSI寄存器保存选择器指针。如有参数,将从RDX寄存器开始保存。

使用 IDA Pro 的 REobjc 模块逆向 Objective-C 二进制文件

如果要在IDA中添加从一个Objective-C函数到另一个Objective-C函数正确的交叉引用的话,就必须知道RDI和RSI寄存器中的值。对于大多数 objc_msgSend() 调用,这两个寄存器中的值通常很容易获取。

不过,除了上述方法调用方式之外,编译器可能也会使用其他方式。在X64上,编译器通常使用CALL和JMP指令生成函数调用。其他的可能性有使用条件跳转指令或直接分配方法给指令指针,但是概率很小。

编译器还可以将函数调用编码为间接调用或直接调用。在间接调用的情况下,指令参数是寄存器。在直接调用的情况下,指令参数是对内存位置的引用。不管哪种情况,都必须确定CALL或JMP是否引用了 objc_msgSend()

此外,为了正确生成交叉引用,必须跟踪函数调用的返回值。在X64中,函数调用的返回值存储在RAX寄存器中。如果在源码中首先alloc一个对象,然后对生成的对象实例执行方法调用,则需要跟踪存储在RAX中的对象指针的类型,以正确理解调用 objc_msgSend() 时传递的对象。

REobjc Idapython模块

REobjc idapython模块的主要目的是在Objective-C方法之间创建交叉引用。要使用REobjc,打开IDA Pro命令窗口并执行以下 Python 脚本:

idaapi.require(“reobjc”)

r = reobjc.REobjc(autorun = True)

REOBjc深入

要定位到我们关注的Objective-C Runtime调用,首先需要理解编译器可以通过多种方式在二进制中对调用进行编码。无论是直接的还是间接的调用,都有多种方式对调用指令进行编码。 所有Objective-C程序都链接了Objective-C Runtime,因此,所有调用最终都会进入导入的libobjc.dylib库。

通常,程序将包含一个插桩(stub)函数,它只执行无条件跳转到 objc_msgSend() 函数。这允许程序在任意地址加载并且正确调用库。

在REobjc模块中,将通过识别 objc_msgSend() 的所有调用形式来识别方法调用。调用的目标可能是_objc_msgSend,可能是 __imp__objc_msgSend 形式的导入指针。模块将查找当前数据库中所有相关的内容。

模块使用API idautils.Names()检索IDA数据库中所有的名称,然后通过正则表达式匹配目标函数,将匹配存储在数组中。分析时,每个候选调用或跳转指令都与Objective-C Runtime函数列表进行比较,记录使用任意形式的objc_msgSend()调用,并添加到交叉引用列表中。

随后模块迭代二进制文件中的所有函数,并且对于每个函数,迭代其中所有的指令。当识别出CALL或JMP指令时,模块确定指令的对象。如果对象是寄存器,则从CALL或JMP指令向前寻找寄存器的值。直接调用相比较为简单,可以立即获得CALL或JMP的对象。无论哪种情况,如果目标是 objc_msgSend() ,则CALL或JMP很有可能添加交叉引用。

当识别出 objc_msgSend() 的调用时,必须标识前两个函数参数。如上文所述,第一个参数指向接收Objective-C消息对象的指针。第二个参数是指向传递给对象的selector或消息的指针。由 resolve_register_backwalk_ea() 方法解析寄存器的值。

如上文所述,这里有两个寄存器需要特别关注。 RAX寄存器将保存先前CALL指令的返回值,因此 REobjc.resolve_register_backwalk_ea() 将通过CALL来跟踪RAX。此外,由于RDI寄存器用作Objective-C中的self指针,因此有些情况下RDI未在函数内显式设置。这是因为这些目标函数是在自己的self指针上调用方法。因此,当往回寻找时,如果RDI目标寄存器并且未显式设置,则代码将确定该方法来自当前类。

一旦为RDI和RSI寄存器解析了self和selector指针,模块将对可能方法尝试创建交叉引用。这是通过利用IDA Pro中现有的Objective-C支持来完成的。模块函数 REobjc.objc_msgsend_xref 将处理交叉引用的创建。该函数获取CALL的位置,以及设置RDI和RSI的位置,并尝试添加适当的交叉引用。

目前模块只能添加在当前二进制文件中方法的交叉引用,之后会尝试添加导入的库中方法的交叉引用。

REobjc:实践

接下来,我将通过搭建实际的iOS测试工程来实践不同方法调用方式的效果以及REobjc模块对完善交叉引用记录的效果。

首先,我们看一下最基础的一个调用。每一个ViweController都会默认调用父类中的viewDidLoad方法。

使用 IDA Pro 的 REobjc 模块逆向 Objective-C 二进制文件

把工程编译后,在IDA Pro中看到的汇编代码如下:

使用 IDA Pro 的 REobjc 模块逆向 Objective-C 二进制文件

使用Decompiler插件生成伪代码后:

使用 IDA Pro 的 REobjc 模块逆向 Objective-C 二进制文件

可以发现,IDA Pro很好地还原了方法调用的逻辑。查看交叉引用信息,发现IDA也识别出了该调用。只不过,识别出来的被调用函数为msgSendSuper2,并不是我们想要的viewDidLoad。

使用 IDA Pro 的 REobjc 模块逆向 Objective-C 二进制文件

我们运行REobjc后发现,交叉引用中添加了一条记录,表示ViewController的viewDidLoad方法被调用。DUO Labs作者在文章中也提到,目前的版本在正确识别父类方法调用上会有问题,可能会将子类的方法调用交叉引用添加到父类中,这就导致了我们在子类的交叉引用中看到了这条指向自己的记录。

使用 IDA Pro 的 REobjc 模块逆向 Objective-C 二进制文件

接下来我们自己编写一个方法,在Lion类中新建一个实例方法lionFirstMethod如下:

使用 IDA Pro 的 REobjc 模块逆向 Objective-C 二进制文件

可以看到方法中仅调用了NSLog用来打印调试语句。我们在工程其他方法中有两处引用了该方法,可以在搜索中看到:

使用 IDA Pro 的 REobjc 模块逆向 Objective-C 二进制文件

接下来我们在IDA Pro中查看交叉引用信息,发现IDA Pro一个也没有识别出来(因为都识别成了msgSend)。

使用 IDA Pro 的 REobjc 模块逆向 Objective-C 二进制文件

其实,这个结果是让我有点吃惊的,毕竟代码中写的还是非常直白的。

使用 IDA Pro 的 REobjc 模块逆向 Objective-C 二进制文件 使用 IDA Pro 的 REobjc 模块逆向 Objective-C 二进制文件

从生成的伪代码中也可以清晰地看出方法调用

使用 IDA Pro 的 REobjc 模块逆向 Objective-C 二进制文件

使用 IDA Pro 的 REobjc 模块逆向 Objective-C 二进制文件

随后我们尝试使用REobjc模块进行补完,发现交叉引用中正确地添加上了两条引用记录。

使用 IDA Pro 的 REobjc 模块逆向 Objective-C 二进制文件

接下来,我们再做一个尝试。我们在Lion类中添加一个类方法,并且使用反射的方式引用类和该类方法。

使用 IDA Pro 的 REobjc 模块逆向 Objective-C 二进制文件

使用 IDA Pro 的 REobjc 模块逆向 Objective-C 二进制文件

不出意外,我们发现,IDA Pro并没有识别出该调用(仅识别出了msgSend,没有识别出performSelector,更不用说lionClassMethod了)。

使用 IDA Pro 的 REobjc 模块逆向 Objective-C 二进制文件

当我们尝试使用REobjc进行补完,发现也没有识别出来最终的lionClassMethod。

使用 IDA Pro 的 REobjc 模块逆向 Objective-C 二进制文件

总体来说,REobjc对一些基础的方法调用进行了补充,可以说完善了IDA Pro在Objective-C方面的一些缺陷,但是在更加细节的方面需要进行加强。另外,REobjc还没有针对arm架构的版本,我们使用模拟器进行编译的可执行文件可以使用REobjc,但是对于实际需要进行逆向分析的目标应用,通常无法获取非arm版本,所以后续我们也会尝试针对arm平台进行补充完善。

参考: https://duo.com/blog/reversing-objective-c-binaries-with-the-reobjc-module-for-ida-pro


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Designing Data-Intensive Applications

Designing Data-Intensive Applications

Martin Kleppmann / O'Reilly Media / 2017-4-2 / USD 44.99

Data is at the center of many challenges in system design today. Difficult issues need to be figured out, such as scalability, consistency, reliability, efficiency, and maintainability. In addition, w......一起来看看 《Designing Data-Intensive Applications》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

SHA 加密
SHA 加密

SHA 加密工具