使用 IDA Pro 进行静态分析
- IDA Pro:目前功能最强大的静态反编译分析工具,具备可交互、可编程、可扩展、多处理器支持等特点,软件逆向分析必备工具
IDA Pro 对 Android 的支持
- 从 6.1 版本开始提供对 Android 的静态分析与动态调试的支持,包括 Dalvik 指令集的反汇编、原生库(ARM/Thumb 代码)的反汇编、原生库(ARM/Thumb 代码)的动态调试等
- IDA Pro 6.95 对 Android 的静态分析与动态调试的支持已非常完善
- 本笔记用的是 7.0 版本
分析 DEX 文件
- 实例:Crackme0502
- 解压 APK 取得 classes.dex,打开 IDA Pro,将 DEX 拖入 IDA Pro(或用 IDA Pro 打开 DEX),选择默认选项,等待分析完成
- IDA Pro 支持以结构化形式显示数据结构。单击“IDA View-A”选项卡,来到反汇编代码界面(其实默认进入的就是此界面),然后单击菜单项“Jump”->“jump to address”,或按“G”键,会弹出地址跳转对话框,输入 0,跳转到 DEX 文件的开头(一开始一般就在开头)。可看到, IDA Pro 已自动解释了结构体信息并加上了注释
- 单击菜单项“Jump”->“Jump to segment”,或按组合键“Ctrl+S”,会弹出段选择对话框。如下图,IDA Pro 将 DEX 分成了 11 个段,每个段所对应的偏移量与 DexHeader 结构体相对应,最后两个段的偏移量可通过计算得出
- DEX 中所有方法的详细信息可在“Exports”选项卡中查看,方法的命名规则为“类名.方法名@方法声明”
- 在“Exports”中任选一项,如
[email protected],双击,跳转到对应的反汇编代码处 - IDA Pro 的反汇编代码用 ref 关键字表示非 Java 标准类型的引用。方法第一行中
invoke-super指令的前半部分如下:invoke-super {this, p0}, <ref ResourceCursorAdapter.swapCursor(ref) - 前面的 ref 是 swapCursor() 的返回类型,后面括号里的 ref 是参数类型
- 后半部分的代码是 IDA Pro 智能识别出来的。IDA Pro 能智能识别 Android SDK 的 API 函数,并用 imp 关键字将其标识出来。如第一行中的
invoke-super指令的后半部分如下:imp. @ [email protected]> - imp 表示该方法为 Android SDK 中的 API,“@”后的部分为 API 的声明,类名和方法名间用下划线分隔
- IDA Pro 能识别隐式传递过来的 this 引用。在 smali 语法中用 p0 寄存器传递 this 指针。此处,由于 this 取代了 p0,后面的寄存器命名值都要依次减一
- IDA Pro 能识别代码中的循环、switch 分支、try/catch 结构,并能将它们用类似高级语言的结构形式显示出来,这对分析大型程序时了解代码结构帮助很大
定位关键代码
- 用 IDA Pro 定位关键代码的方法整体上与定位 smali 关键代码类似。方法有三种:
- 一、搜索特征字符串。按组合键“Ctrl+S”,打开段选择对话框,双击“STRINGS”,跳转到字符串段,然后单击菜单项“Search”->“text”,或按组合键“Alt+T”,打开文本搜索对话框,在“String”旁输入要搜索的字符串,单击“OK”,等待片刻即可
- 二、搜索关键 API。按组合键“Ctrl+S”,打开段选择对话框,双击第一个“CODE”,跳转到数据起始段,然后单击菜单项“Search”->“text”,或按组合键“Alt+T”,输入要搜索的 API 名称。若 API 被多次调用,可按组合键“Ctrl+T”搜索下一项
- 三、通过方法名判断方法的功能。此方法较笨拙,因为对混淆过的代码,定位其关键代码是较困难的。如,知道 Crackme0502 程序的主 Activity 类为 MainActivity,在“Exports”中输入“Main”,会自动定位以“Main”开头的行(由此可粗略判断每个方法的作用)
**思路一
- 现在尝试** Crackme0502
- 先安装 APK,运行后,会出现两个按钮,点击“获取注解”,会以 Toast 弹出三条信息
- 在文本框输入任意字符串,点击“检测***”,会弹出***错误的提示信息
- 以按钮点击事件为突破口查找关键代码
- 搜索字符串“Main”,发现有两个“onClick()”
- 分别点进去看看
- 第一个 onClick() 调用了
MainActivity.access$000(),在反汇编界面双击MainActivity.access,可看到其调用了 MainActivity 的 getAnnotations()。可看出,MainActivity$1.onClick()是“获取注解”按钮的事件响应代码 - 双击第二个 onClick() ,来到其对应的反汇编代码,按“空格”键切换到 IDA Pro 的流程视图,代码的“分水岭”就是
if-eqz p0, loc_116518,如下图二所示,在第一个方框下,左边的箭头表示条件不满足时程序执行的路线,右边的箭头表示条件满足时执行的路线。可看出,MainActivity$2.onClick()是“检测***”按钮的事件响应代码 - 经过上面的分析,可知只要修改
if-eqz指令为if-nez即可**程序。修改的过程见第四章的“classes.dex”小节,这里只以截图展示:
**思路二
- 通过前面的分析可知,
MainActivity$SNChecker.isRegistered()实际上返回了一个 boolean 值,通过判断它的返回值可确定***是否正确 - 但有个问题:若该程序是个大型 Android 程序,调用***判断的地方可能不止一处
- 通常有两个解决办法:一、使用 IDA Pro 的交叉引用功能找到所有的方法被调用的地方,然后修改所有的判断结果;二、直接修改 isRegistered(),让它的返回结果永远为 true
- 现在使用更为“一劳永逸”的第二种办法
- 下图是对 isRegistered() 方法的分析
- 分析后发现,
return v1为方法返回处,v1 为存储返回值的寄存器,而其值在第二行设为 0(即 false),之后的每次判断若满足条件,就会将 v1 设为 1(即 true),否则,v1 仍为 0。那么,修改就很简单了,只要将第二行的 v1 赋值为 1 即可(Opcode 为 12 11) - 但是出现了问题,安装之后运行会闪退
- 这时就要检查修改是否正确。用 IDA Pro 重新打开 DEX,发现没改错,推测是程序采取了某种保护措施
- 之前学过两种退出程序的办法:Context 的 finish() 和 android.os.Process 的 killProcess()。按组合键“Ctrl+S”并双击第一个 CODE,来到代码段,再按组合键“Alt+T”,搜索“finish”和“killProcess”,最后在 MyApp 类的 onCreate() 中找到相应的调用。查看此方法的反汇编代码,发现这段代码使用了 Java 的反射机制,手工调用了 isRegistered() 来检查字符串“11111”是否为合法***。若是合法***或调用 isRegistered() 失败,都说明程序被修改了,会调用 killProcess() 杀死进程。那么,只要把搜到的两处 killProcess() 的调用 NOP(将两处调用的 Opcode 全改成 0)即可
- 重新安装后运行,输入任意字符串,发现**成功