3.4 IDA

3.4.1 IDA简介

即使你以前没有从事过iOS逆向工程相关的工作,也一定听说过IDA(The Interactive Disassembler)的鼎鼎大名。而对于绝大多数接触过逆向工程的人来说,IDA三个字则是如雷贯耳,它乃逆向工程中最负盛名的神器之一(如图3-24所示)。如果说class-dump能够帮我们罗列出要分析的点,那IDA就能进一步帮我们把这些点铺成面。

图3-24 IDA官网

笼统地说,IDA是一个支持Windows、Linux和Mac OS X的多平台反汇编器/调试器,它的功能非常强大,以至于连官方都不能给出一个详尽的功能列表。

IDA的正式版是收费的,但其作者也是程序员出身,深知我们生活不易,所以慷慨地提供了一个免费的试用版,对于逆向工程初学者来说,已完全够用了。IDA的下载和安装十分方便,具体可参考:https://www.hex-rays.com/products/ida/index.shtml ,本书不再赘述。

3.4.2 IDA使用说明

IDA启动时会短暂地显示如图3-25所示的窗口。

图3-25 IDA启动界面

这时可以点击“OK”,或等上几秒,它会自动关闭,之后就会看到IDA的主界面,如图3-26所示。

在该界面中,不用繁琐地在菜单里点击“打开文件”,然后一个目录一个目录地去翻找,只需把要分析的文件拖进IDA的灰色区域就行了。打开文件后,还需要做一些基本的配置,如图3-27所示。

图3-26 IDA主界面

图3-27 IDA初始配置

有一个地方需要注意:对于一些fat binary(指的是为了兼容不同架构的处理器,而把多种指令集糅合到一个binary)来说,图3-27中最上面的白框内会出现多个Mach-O文件供我们选择。建议先迅速查阅第4章“dumpdecrypted”这一节的ARM对照表,找到设备对应的ARM信息,例如笔者的iPhone 5对应的是ARMv7s。如果设备的ARM没有出现在这些选项中,就选那个向下兼容的选项,即如果选项里有ARMv7S,就选它;否则选ARMv7。这种方法应该可以应对碰到的99%的情况,如果你恰巧是那1%,请来http://bbs.iosre.com 分享这种百里挑一的喜悦,我们一起解决问题。

这里笔者选择了ARMv7S,然后点击“OK”,此时,会连续弹出好几个窗口,一路点击“Yes”和“OK”就可以了,如图3-28和图3-29所示。

图3-28 IDA启动选项

图3-29 IDA启动选项

因为试用版的IDA无法保存,所以即使在这些窗口上勾选“Don’t display this message again”,下次打开IDA还是会出现这些窗口;虽然有些麻烦,但这么强大的工具都让我们免费使用了,麻烦就麻烦点吧。

按钮都点完后,内容丰富的主界面再次进入我们眼帘,如图3-30所示。

在进入图3-30所示的界面时,你会看到上方的进度条不断滚动,下方的Output window也会打印出对文件的分析进度。当进度条的主色调变成蓝色(IDA界面上的颜色在黑白印刷页上看不出来,请谅解),Output window中显示“The initial autoanalysis has been finished.”时,表示IDA的初始分析已完成。

在初学阶段,由于主要用IDA作静态分析,基本用不上Output window,所以在开始分析之前,可以先关掉Output window。

现在看到的两个大窗口分别是左侧小部分的Functions window(如图3-31所示)和右侧大部分的Main window(如图3-32所示)。下面分别介绍一下这2个窗口的用途。

图3-30 IDA主界面

图3-31 Functions window

图3-32 Main window

(1)Functions window

顾名思义,这个窗口展示了IDA分析出来的所有函数(规范地讲,Objective-C的function应该称为method,即方法,此处统称为函数,请大家注意),双击一个函数,Main window会显示它的函数体。在选中Function Window时点击菜单栏上的“Search”,会弹出如图3-33所示的子菜单。

图3-33 查找函数

选择“Search”,并在图3-34的对话框中输入要查找的内容,可以在所有函数名里查找指定的字符串,十分方便;当要查找的内容出现在多个函数名里时,还可以点击“Search again”来遍历这些函数名。当然,上面的操作也都可以用IDA中显示的快捷键完成。

Functions window中的Objective-C函数与class-dump导出的内容吻合。除了Objective-C函数外,IDA还将所有subroutine罗列了出来,这是class-dump做不到的。class-dump导出的内容都是Objective-C函数名,可读性高,容易上手,是iOS逆向工程初学者的乐园;subroutine的名称只是一个代号,没有明显含义,分析难度大,大多数初学者看到这里就打了退堂鼓。但是,iOS的底层是用C和C++实现的,编译之后生成的大都是subroutine,class-dump拿它没辙,只能使用IDA这样的工具。要想深层次挖掘iOS中最有趣的部分,掌握IDA的用法是必经之路。

图3-34 查找函数

(2)Main window

绝大多数没有用过IDA的iOS开发者,包括笔者,在第一次看见初始分析完成后的Main window时都懵了——这都是些什么玩意儿?这是人类文字吗?还是把IDA关了,刷会儿微博压压惊吧。这跟很多工程师在写第一行代码时不知如何下手的感觉很类似。其实,跟写代码时需要定义一个入口函数一样,在逆向工程里,也需要找到自己感兴趣的入口函数。在Functions window中双击这个入口函数,使得Main window跳转到函数体,然后用鼠标选中Main window,按一下空格,界面会瞬间变清爽,可读性一下就强了起来,让人感觉到“我的天空,星星都亮了”(如图3-35所示)。

图3-35 Graph view

Main window有两种显示模式,分别是Graph view和Text view,它们之间可以通过空格键切换。Graph view把被分析的程序逻辑用方块的形式表现出来,方便我们分析程序各个分支之间的关系。各个方块之间的执行顺序用带箭头的线表示,当执行出现分支时,满足判断条件分支的线是绿色的,否则是红色的;当执行没有分支时,线是蓝色的。比如,在图3-36中,当最上面的方块执行完后,会判断R0是否为0,如果R0!=0,则满足判断条件(BNE,即Branch if Not Equal to zero),走绿色的那条路接着执行右边的方块;否则走红色的路执行左边的方块。此处是IDA的难点之一,后面的实例中会再次讲解。

图3-36 IDA的分支

细心的读者也一定注意到了,IDA中的字体色彩缤纷,事实上,不同颜色的字体表示的含义也各不相同,如图3-37所示。

图3-37 颜色指示栏

当选中一个符号时,相同的符号都会用黄色高亮显示,方便跟踪这个符号的轨迹,如图3-38所示。

图3-38 符号高亮

双击符号,可以查看它的实现,效果与图3-35类似。在任意符号上点击鼠标右键,会弹出如图3-39所示的菜单。

图3-39 在符号上点击右键

其中,常用的功能有“Jump to xref to operand...”(快捷键“x”),点击后出现的窗口罗列了这个文件中显式引用这个符号的所有信息,如图3-40所示。

图3-40 Jump to xref to operand...

如果觉得这种表示方法不够直观,更喜欢Main window里的Graph view形式,可以选择菜单下面的“Xrefs graph to...”,但如果运气不好,这个符号被引用过多的话,就会看到类似图3-41这样的一团乱麻的界面,真是剪不断,理还乱。

图3-41 符号引用的Graph view

在图3-41中,黑色的部分都是由一根根直线构成的,两侧基本已经黑成了一片,可以看出_objc_msgSendSuper2_stret这个符号被大量引用。

相对地,“Xrefs graph from...”则会显示这个符号显式引用的所有符号,如图3-42所示。

从图3-42可以看到,sub_1DC1C是个subroutine,它显式引用了j__objc_msgSend、_OBJC_CLASS_$_UIApplication和_objc_msgSend,而_objc_msgSend又显式引用了__imp__objc_msgSend。双击Main window里的_objc_msgSend,再双击__imp__objc_msgSend,可以看到它来自libobjc.A.dylib,如图3-43所示。

图3-42 查看sub_1DC1C显式引用的符号

在多数情况下,找到一个感兴趣的符号时,会想进一步查找与这个符号相关的所有线索。一种笨拙但有效的方法是鼠标选中Main Window时点击菜单栏上的“Search”,此时会弹出如图3-44所示的子菜单。

然后选择“text...”,会弹出如图3-45所示的窗口。

图3-43 查看外部符号来源

图3-44 在Main window里查找

图3-45 搜索文本

这时,可以根据自己的情况选择搜索是否对大小写敏感,搜索格式是不是正则表达式等,然后勾选“Find all occurences”,点击“OK”,IDA会将文件中所有满足搜索条件的符号列出,供我们一一查看。

Graph view提供的功能非常多,以上只是简单介绍了几个常用的功能,熟练地使用它们是进行更深入研究的保障。Graph view的界面比较简洁,各代码块间逻辑清晰,适合肉眼观看。相对来说,现阶段切换到Text view的机会比较少,一般是在配合LLDB进行动态调试时,才会在Text view中对界面左侧罗列的符号地址特别关注,如图3-46所示。

图3-46 Text view

需要注意的是,IDA的某些Bug会导致Graph view的末端显示不全(例如一个subroutine本来有100行指令,但只显示了80行),当你对某一个Graph view块中的指令产生明显怀疑时,可切换到Text view看看Graph view是不是漏显示了某些代码。这类Bug出现的概率不大,如果你不幸中彩,欢迎来http://bbs.iosre.com 交流解决方案。

3.4.3 IDA分析示例

说了IDA的这么多使用方法,下面用一个简单的例子向大家演示IDA的威力。越狱iOS的用户都知道,在Cydia中安装完一个tweak后,Cydia会建议我们“Restart SpringBoard”,那么这个respring的操作是如何实现的呢?请大家快速浏览3.5节,用iFunBox将iOS中的“/System/Library/CoreServices/SpringBoard.app/SpringBoard”拷贝到OSX中,并用IDA打开它,等待初始分析结束后在Function window里搜索“relaunch SpringBoard”,定位到这个函数,双击跳转到它的实现代码中,如图3-47所示。

图3-47 [SpringBoard relaunchSpringBoard]

可以看到,这个函数的实现流程既简单又清晰。根据从上到下的执行顺序,首先调用beginIgnoringInteractionEvents,开始忽略所有用户交互事件;然后调用hideSpringBoardStatusBar来隐藏状态栏;接着连续执行2个subroutine,分别是sub_35D2C和sub_350B8。接下来,双击sub_35D2C,跳转到它的实现,看看它做了什么,如图3-48所示。

在图3-48中,一眼就能看到好多含有“log”字样的关键词,先“initialize”,然后判断是否“enabled”,最后“log”。稍微懂一点英语的朋友都能猜到,sub_35D2C的作用是将respring的一些操作记录下来,与respring的主体功能无关。点击IDA菜单栏上的蓝色后退按钮(如图3-49所示),或直接按ESC,回退到relaunchSpringBoard函数体中,继续往下分析。

图3-48 sub_35D2C

图3-49 后退按钮

双击sub_350B8,跳转到如图3-50所示的界面。

从图3-50可以看到,这个subroutine只是在为调用sub_350C4作一些准备工作而已。双击sub_350C4,跳转到它的实现,你会发现sub_350C4的上半部分与图3-48所示的sub_35D2C非常类似,都只是做了一些操作记录。但与sub_35D2C不同的是,sub_350C4还做了一些实际工作,如图3-51所示。

图3-50 sub_350B8

我们现在还不了解汇编语言,只能大致浏览一下这些关键词,不过,可以猜测出这个subroutine的作用是生成一个名为“TerminateApplicationGroup”的事件,指定其处理方法为sub_351F8,然后把生成的事件加入一个处理队列里,并依次处理队列里的事件,从而关掉所有的App——商城关门之前,要关闭里面的所有店铺;respring之前,自然也要关掉所有的App。现在去看看sub_351F8的实现,如图3-52所示。

图3-51 sub_350C4

图3-52 sub_351F8

从BKSTerminateApplicationGroupForReasonAndReportWithDescription的名字来看,其作用已经很明显了,它印证了刚刚对sub_350C4的分析。再次回退到relaunchSpringBoard函数体里,到这里,分析已经接近尾声了:函数调用_relaunchSpringBoardNow,完成respring操作。

不需要了解汇编代码,也不用熟悉调用规则,我们以几乎零基础的水平成功完成了这次逆向工程,不是吗?当然,这不能说明我们的水平有多么高深,而是提醒我们应该为拥有IDA这样强大且免费的神器感到幸运,从而激励我们加倍努力。在绝大多数情况下,对IDA的使用跟上面的示例没有本质区别,你只需要耐着性子,仔细咀嚼IDA呈现出的每一行代码,要不了多久,你就会深切感受到逆向工程的艺术之美。

IDA的用法远不止本节所示的这么简单,如果你在使用过程中有任何疑问,都欢迎来http://bbs.iosre.com 讨论交流。