一招教你狂拽酷炫查Native Crash
如何机智地应用IDAPro分析AndroidNativeCrash...
一 / 前言
IDA Pro是一款可视化的二进制代码文件逆向分析工具,功能十分强大,常用于游戏破解、软件逆向以及其他需要对二进制机器码进行逆向分析的场景。
本文将要介绍的是如何在分析和排查android native crash的过程中运用IDA Pro来帮助我们快速地收集有效信息、定位问题原因。
下面将通过两类案例来介绍IDA Pro在实际过程中的基本用法和操作,一是android N 系统的native crash问题根因分析,二是定位和排查native crash堆栈。
二 / 分析android N native crash
问题源自于谷歌在android N系统上的改动,新增了对第三方native so限制,不再允许开发者自由使用非公开的系统native api,禁止第三方native so动态链接或者使用dlopen动态加载大部分的系统so库。
不符合规范的so在android N的正式发布版本上将会导致应用直接crash,而在目前谷歌放出的预览版上则会通过弹窗提示开发者,具体效果如下图所示:
所以必须结合白名单对我们的so进行检查,主要的检查点有:libandroid.so
libc.so
libdl.so
libEGL.so
libGLESv1_CM.so
libGLESv2.so
libGLESv3.so
libicui18n.so
libicuuc.so
libjnigraphics.so
liblog.so
libmediandk.so
libm.so
libOpenMAXAL.so
libOpenSLES.so
libRS.so
libstdc++.so
libwebviewchromium_plat_support.so
libz.so
1、检查so的外部依赖库链接了哪些白名单之外的系统so。
2、检查so使用了哪些非公开的系统api,也就是白名单之外的系统so所导出的函数。
众所周知,so就是ELF文件,至于具体的文件格式,在此就不作过多地描述了。我们需要关注的是so文件中的三个部分:
1、外部依赖库——记录了so文件动态链接了哪些外部so。
2、导入表——记录了so文件使用了哪些外部函数。
3、导出表——记录了so文件提供了哪些函数给其他so使用。
而IDA Pro就能够解析ELF文件格式,并且提供了可视化的窗口视图查看相关的解析数据。因此使用IDA Pro可以非常直观地查看我们所关注的信息,快速地确定问题。下面就是具体的使用过程:
首先,使用IDA Pro打开要检查的so,在主视图界面(IDA View)的顶部就可以看到so的外部依赖库信息,这是IDA Pro根据so文件的相应数据自动生成的一些注释性的文本信息,如下图所示:
视图中非常直观地显示了该so所链接的外部so,结合之前的白名单,就可以快速地确定so链接了哪些白名单之外的系统so。
然后,进入导入表界面(Import视图)查看该so所使用的外部函数,分析so使用的非公开的系统api,如下图所示:
导入表界面(Import视图)有排序和检索功能,根据函数名排序之后可以很方便地进行检索。
这里可以先使用排除法缩小目标范围,排除常见的linux c api、android ndk api和一些编译器自动添加的异常处理函数之后,再结合之前确定的非白名单的系统so,通过IDA Pro将其一一打开,并查看导出表界面(Export视图,和Import视图类似),利用界面的排序和检索功能快速地进行对比验证,就可以确定哪些是非公开的系统api了。
当然,很多时候其实并不需要最后的对比验证,ndk开发经验比较丰富或者对系统源码比较熟悉的话,查看导入表就可以基本确定哪些是非公开的系统api了。
三 / 分析native crash堆栈
对于native crash堆栈,也可以使用IDA Pro来分析。下面将介绍如何使用IDA Pro来进行具体的分析:
上图就是一个native crash堆栈,其中比较重要的信息就是crash的类型和crash的指令地址。段错误一般是由空指针或者越界访问引起的,而crash的指令地址则可以用来定位问题的相关代码。
在分析之前需要先介绍一下使用IDA Pro对so进行逆向分析时的一些常用操作:
1、快捷键g——可以快速跳转到输入的指定地址。
2、快捷键F5——可以查看与当前汇编代码对应的伪C代码。
3、快捷键x——可以查询变量、函数或其他标识的引用位置。
4、快捷键n——可以对变量、函数或者其他标识进行重命名
5、快捷键;——可以对某处位置添加备注
IDA Pro的常用操作还有很多,这里就就不再一一列举了。下面是具体的分析过程:
首先,使用IDA Pro打开发生crash的so,并使用快捷键g跳转到crash的指令地址。
这里要注意的是,有些情况下需要对crash堆栈进行一些处理,利用模块加载基址进行地址换算,而这里的crash指令地址经过确认已经是该模块内的偏移地址,所以并不需要进行地址换算,但是在分析定位crash的真正位置时还是要进行一些判断,比如是否要将crash的指令地址减去模块加载基址,这些往往要根据实际情况来决定,不能一概而论。
上图就是发生crash时代码的具体位置,从汇编代码中可以看出,当前指令是想访问R3寄存器中存放的内存地址,也因此可以基本断定crash是由于访问了空指针或者无效的内存地址导致的。
然后,使用快捷键F5查看对应的伪C代码,并根据变量的存放位置初步分析是在使用某个全局作用域的变量时出现了问题,由此推断可能是该变量没有进行初始化或者没有被正确赋值。
至于如何去关联对应的源代码,可以先查看对应的伪C代码的函数名,如果发现函数是sub_xxx之类的名字,则说明在编译的过程中并没有生成该函数的符号链接信息,这时候就需要使用快捷键x查看调用该函数的地方,如果调用者也是如此,则继续往上回溯,最后追溯到一个全局的数据结构中,具体信息如下图所示:
从最后的图中可以很明显的看出,这就是利用jni注册native方法时所需要用到的一个数据结构,而在这里也可以关联到具体的函数代码,此时再结合源码、伪C代码以及整个回溯过程进行倒推,就可以找到crash的源码了。
除此之外,对于一些由系统so所触发的native crash,在没有太多分析手段的时候,依然能够使用IDA Pro对crash进行一定程度上的分析,方法还是和前一个crash案例类似,需要对应的so,并且没有对应源码(当然一定程度上可以参考某些版本的系统源码),所以更多的是对汇编和伪C代码的分析。
上图就是一个发生在系统模块的crash分析,需要拿到对应的系统so才能进行具体的分析,否则将无法定位正确的crash指令地址。定位之后再去分析具体的汇编指令、查看伪C代码、参考对应版本的系统源码。
如果静态分析无法定位问题根因的话,可以通过动态调试去获取一些运行时数据来帮助自己进一步分析问题。IDA Pro自带安卓调试器,支持动态调试安卓应用,可以调试应用加载的任何模块(包括系统模块在内)。下面就简单介绍一下
1、将IDA Pro自带的安卓调试器android_server放入被调试机器并通过控制台直接运行,这里可能需要使用chmod命令授予可执行权限。
2、使用adb forward命令建立PC和Android之间的调试连接通道(adb forward tcp:23946 tcp:23946)。
3、使用IDA Pro连接android_server(Debugger -> Attach -> Remote ARMLinux/Android debugger)。
4、调试异常捕获设置中忽略可能的干扰信号,比如SIGILL信号和SIGTRAP信号。
5、附加到具体的进程进行调试。
附加成功之后就可以开始调试了,下面介绍下
2、Registers窗口可以查看寄存器信息。
3、Stack窗口可以查看当前函数的局部变量信息,这里需要根据汇编代码确定局部变量的存放位置。
在调试的过程中,需要重点观察的一般就是函数参数、函数返回值以及函数局部变量。根据arm的调用约定,前四个参数都会通过R0 - R3寄存器进行传递,多出来的函数则会通过压栈的方式进行传递,而函数返回值则通过R0寄存器返回给caller,至于局部变量,则需要通过汇编代码确定其在栈中的位置之后再去栈中查看。
四 / 结语
在动态调试的过程中也会遇到一些坑,以下是个人一些经验和总结:
1、需要根据调试的实际情况忽略某些信号的捕获,否则会有很多干扰信号影响调试。
2、一些会导致进程退出的信号(除非是正常退出,但大部分都是异常退出)不要传递给进程。
3、调试器中断暂停过程的时间不能太长,否则会有resume失败的风险。
4、调试器有时候会跑飞,断点停不下来,这里可能就需要反复调试。
✎ 如果您想了解更多关于移动终端安全的内容,请收听我们的微信公众号(终端安全那些事儿),我们将定期为您分享;
✎ 如果您对移动终端安全有什么问题和建议,关注我们的公众账号后直接回复消息联系我们。
关注 终端安全那些事儿
微信扫一扫关注公众号