使用IDA Pro进行静态分析

IDA Pro是目前功能最强大的静态反汇编分析工具,具备可交互、可编程、可扩展、多处理器支持等特点,是软件逆向分析必备的工具之一。

IDA Pro对Android的支持

IDA Pro是一款跨平台的商业软件,可以在Windows、Ubuntu、macOS系统上运行。IDA Pro每年都会有1~2次大规模的升级,升级的内容包括大量的Bug修正与新功能的添加。IDA Pro从6.1版本开始,提供了对Android的静态分析与动态调试的支持,包括Dalvik指令集的反汇编、原生库(ARM/Thumb代码)的反汇编、原生库(ARM/Thumb代码)的动态调试等。IDA Pro 6.95对Android的静态分析与动态调试的支持已经非常完善了。

分析DEX文件

以本章的实例Crackme0502为例,首先解压得到文件classes.dex,然后打开IDA Pro,将classes.dex拖到IDA Pro的主窗口中,此时会弹出加载新文件的对话框。IDA Pro解析得出,该文件属于Android DEX File。保持默认的选项设置,单击“OK”按钮,稍等片刻,IDA Pro就会完成对DEX文件的分析。

IDA Pro支持以结构化形式显示数据结构。单击“IDA View-A”选项卡,回到反汇编代码界面,然后单击菜单项“Jump”→“Jump to address”,或者按“G”键,将弹出地址跳转对话框。在其中输入“0”,让IDA Pro跳转到DEX文件的开头。可以看到,IDA Pro已经自动解释了结构体信息并加上了注释,如图5-3所示。

单击菜单项“Jump”→“Jump to segment”,或者按组合键“Ctrl+S”,将弹出段选择对话框。如图5-4所示,IDA Pro将DEX文件分成了11个段,每个段所对应的偏移量与DexHeader结构体相对应,最后两个段的偏移量可以通过计算得出。

DEX文件中所有方法的详细信息都可以在“Exports”选项卡中查看。方法的命名规则为“类名.方法名@方法声明”。

图5-3 IDA Pro自动解释了结构体信息并加上了注释

图5-4 IDA Pro将DEX文件分成了11个段

在“Exports”选项卡中任选一项,例如SimpleCursorAdapter.swapCursor@LL,然后双击跳转到相应的反汇编代码处,具体如下。

CODE:000D83C8 #Source file: SimpleCursorAdapter.java CODE:000D83C8 public android.database.Cursor android.support.v4.widget.SimpleCursorAdapter.swapCursor( CODE:000D83C8 android.database.Cursor c) #方法声明 CODE:000D83C8 this = v1 #this引用 CODE:000D83C8 c = v2 #第1个参数 CODE:000D83C8 .prologue_end CODE:000D83C8 .line 334 CODE:000D83C8 iget-object v0, this, SimpleCursorAdapter_mOriginalFrom CODE:000D83CC invoke-direct {this, c, v0}, <void SimpleCursorAdapter.findColumns(ref, ref) SimpleCursorAdapter_findColumns@VLL> CODE:000D83D2 .line 335 CODE:000D83D2 invoke-super {this, c}, <ref ResourceCursorAdapter.swapCursor(ref) imp. @ _def_ResourceCursorAdapter_swapCursor@LL> CODE:000D83D8 move-result-object v0 CODE:000D83DA CODE:000D83DA locret: CODE:000D83DA return-object v0 CODE:000D83DA Method End

IDA Pro的反汇编代码使用ref关键字来表示非Java标准类型的引用。方法第1行中invoke-super指令的前半部分如下。

invoke-super {this, C}, <ref ResourceCursorAdapter.swapCursor(ref)

前面的ref是swapCursor() 方法的返回类型,后面括号中的ref是参数类型,后半部分的代码是由IDA Pro智能地识别出来的。IDA Pro能够智能识别Android SDK的API函数,并使用imp关键字将其标识出来。例如,第1行中的invoke-super指令的后半部分如下。

imp. @ _def_ResourceCursorAdapter_swapCursor@LL

imp表明该方法为Android SDK中的API,@ 后面的部分为API的声明,类名与方法名之间用下画线分隔。

IDA Pro能识别隐式传递过来的this引用。在smali语法中使用p0寄存器传递this指针。在此处,由于this取代了p0,后面的寄存器命名值都要依次减1。

IDA Pro能识别代码中的循环、switch分支与try/catch结构,并能将它们以类似高级语言的结构形式显示出来,这对分析大型程序时了解代码的结构有很大的帮助。具体的代码反汇编效果,读者可以打开本章的SwitchCase与TryCatch实例的classes.dex文件自行查看。

定位关键代码

使用IDA Pro定位关键代码的方法在整体上与定位smali关键代码相差不大。定位关键代码的方法有如下三种。

  • 第一种方法是搜索特征字符串。按组合键“Ctrl+S”,打开段选择对话框,双击STRINGS段,跳转到字符串段,然后单击菜单项“Search”→“text”,或者按组合键“Alt+T”,打开文本搜索对话框,在“String”旁边的文本框中输入要搜索的字符串,单击“OK”按钮,稍等片刻就会定位搜索结果。不过,IDA Pro不支持对中文字符串的显示与搜索。如果字符串中的中文字符显示为乱码,需要编写相关的字符串处理插件。这项工作就交给读者去完成吧。
  • 第二种方法是搜索关键API。按组合键“Ctrl+S”,打开段选择对话框,双击第1个CODE段,跳转到数据起始段,然后单击菜单项“Search”→“text”,或者按组合键“Alt+T”,打开文本搜索对话框,在“String”旁边的文本框中输入要搜索的API的名称,单击“OK”按钮,稍等片刻就会定位搜索结果。如果API多次被调用,可以按组合键“Ctrl+T”来搜索下一项。
  • 第三种方法是通过方法名来判断方法的功能。这种方法比较笨拙,因为对混淆过的代码,定位其关键代码是比较困难的。例如,我们知道Crackme0502程序的主Activity类为MainActivity,在“Exports”选项卡中输入“Main”,代码会自动定位以“Main”开头的行(由此可以粗略判断每个方法的作用)。

下面我们来尝试破解Crackme0502。首先,安装并运行APK程序。程序运行后,会出现两个按钮,单击“获取注解”按钮会以Toast方式弹出三条信息。在文本框中输入任意字符串,单击“检测注册码”按钮,程序会弹出注册码错误的提示信息。在这里,我们以按钮事件响应为突破口来查找关键代码。通过搜索字符串“Main”,可以发现两个名为“OnClick()”的方法。那么,具体是哪一个呢?我们分别进去看看。前者调用了MainActivity.access$000() 方法,在IDA Pro的反汇编界面双击MainActivity_access,可以看到它其实调用了MainActivity的getAnnotations() 方法。看到这里我们应该能够明白,MainActivity$1.onClick() 方法是前面按钮的事件响应代码。接下来,查看MainActivity$2.onClick() 方法。双击代码行,来到相应的反汇编代码处,按“空格”键切换到IDA Pro的流程视图,代码的“分水岭”就是if-eqz v2, loc_AAC64处。如图5-5所示,在第一个方框下面,左边的箭头表示条件不满足时程序执行的路线,右边的箭头表示条件满足时程序执行的路线。

使用我们自己编写的字符串处理插件后,IDA Pro已经能够正确显示中文字符串了。从字符串信息中可以看出,直接修改if-eqz指令即可将程序破解。将光标定位到指令if-eqz v2, loc_AAC64所在的行,然后单击IDA Pro主界面的“Hex View-A”选项卡,可以看到这条指令所在的文件偏移为0xAAC46,相应的字节码为“38 02 0F 00”。通过前面的学习我们知道,只需将if-eqz指令的Opcode值38改成if-nez的Opcode值39即可。

说干就干。首先,使用010 Editor打开classes.dex文件,将偏移0xAAC46处的值中的“38”改为“39”。然后,运行DexFixer.1sc脚本,修复DEX的校验和,保存并退出。最后,将修改后的classes.dex文件导入APK文件,对APK重新签名后进行安装。测试发现,程序已经被破解了。

为了让读者了解一种常见的Android程序的保护手段,这里更换一下破解思路。通过前面的分析可以发现,MainActivity$SNChecker.isRegistered() 方法实际上返回了一个Boolean值,通过判断它的返回值可以确定注册码是否正确。现在的问题是:如果该程序是一个大型Android软件,调用注册码判断的地方可能不止一处,该如何处理?在这种情况下通常有两种解决方法:第一种方法是使用IDA Pro的交叉引用功能找到所有的方法被调用的地方,然后修改所有的判断结果;第二种方法是直接给isRegistered() 方法“动手术”,让它的返回结果永远为真。显然,第二种方法更加干脆,而且一劳永逸。

下面我们尝试使用第二种方法进行破解。按“空格”键切换到反汇编视图,发现直接修改方法的第2条指令为“return v9”即可完成破解(对应的机器码为“0F 09”)。重新修复DEX文件头的散列值并进行签名。安装程序,测试发现程序启动后就立即退出了,因此我们要先考虑程序的修改是否正确。使用IDA Pro重新导入修改后的classes.dex文件,发现修改的地方没有错,看来程序采取了某种保护措施。回想一下前面提到的两种退出程序的方法—— Context的finish()方法与android.os.Process的killProcess() 方法。首先按组合键“Ctrl+S”并双击CODE,回到代码段,接着按组合键“Alt+T”,搜索“finish”与“killProcess”,最后在MyApp类的onCreate() 方法中找到相应的调用。查看相应的反汇编代码,发现这段代码使用了Java的反射机制,手工调用了isRegistered() 方法来检查字符串“11111”是否为合法注册码。如果是合法字符串或者调用isRegistered() 方法失败,都说明程序被修改了,将调用killProcess() 方法来“杀死”进程。理解了保护手段,解决方法就很简单了:直接将两处killProcess() 的调用NOP掉(修改相应的指令为0)即可。

图5-5 程序执行的路线

本文节选自《Android软件安全权威指南》一书,丰生强 著,电子工业出版社出版。

丰生强,网名"非虫”,独立软件安全研究员,资深安全专家,ISC2016安全训练营独立讲师,有丰富的软件安全实战经验。自2008年起,在知名安全杂志《黑客防线》上发表多篇技术文章,从此踏上软件安全研究道路,常年混迹于国内各大软件安全论坛,著有畅销安全图书《Android软件安全与逆向分析》与《macOS软件安全与逆向分析》。

《Android软件安全权威指南》从平台搭建和语言基础开始,循序渐进地讲解了Android平台上的软件安全技术,提供了对Windows、Linux、macOS三个平台的支持,涉及与Android软件安全相关的环境搭建、文件格式、静态分析、动态调试、Hook与注入、软件保护技术、软件壳等主题,涵盖OAT、ELF等新的文件格式。

将Java与Native层的软件安全技术分开讲解,加入了与软件壳相关的章节,内容安排细致、合理。

每一章都以实例讲解的方式来展开内容,实践性较强。

原文发布于微信公众号 - 玄魂工作室(xuanhun521)

原文发表时间:2019-03-26

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券