新接触Android安全,一直在逃避总结,因为懒。。。虽然网上有大把的资料,但是很多都是转载,格式什么的乱七八糟的,对于有严重洁癖的我不能忍。现在刚接触,以后经常用到的东西,还是总结一下加深印象吧,省得总忘,好记性不如烂笔头。
具体环境的搭建这些基础的东西这里就不再说了,JDK,SDK,NDK,IDA这些东西如果还没装的话就先别看这个了。还有就是手机要root,百度一键root就可以,会有su文件。
但是要调试一个APK需要有点准备工作,摘自:http://www.kanxue.com/bbs/showthread.php?p=1291716
根据android的官方文档,如果要调试一个App里面的dex代码,必须满足以下两个条件中的任何一个:
1) App的AndroidManifest.xml中Application标签包含属性android:debuggable=true
2) /default.prop中ro.debuggable的值为1
由于正常的软件发布时都不会把android:debuggable设置为true,所以要达成条件1)需要对app进行重新打包,这不仅每次分析一个App都重复操作,而且很多软件会对自身进行校验,重打包后执行会被检测到,所以想办法满足第2)个条件是个一劳永逸的办法,我实际使用的方法就是满足第二个条件。由于default.prop是保存在boot.img的ramdisk中,这部分每次重新启动都会重新从rom中加载,所以要到目的必须修改boot.img中的ramdisk并重新刷到设备中。我测试使用的设备为Nexus 7,修改步骤如下:
a) 从Google官方网站下载到boot.img,
b) 使用工具(abootimg,gunzip, cpio)把boot.img完全解开,获取到default.prop
c) 修改default.prop
d) 把修改后的文件重新打包成boot_new.img
e) 使用fastboot工具把boot_new.img刷入设备(fastboot flash boot boot_new.img)
图1 修改后的default.prop内容
要调试一个Android应用程序,要么是调试Java层代码,要么是调试Native层代码。但是根据程序运行的情况不同,调试的方法有的适用有的不适用。比如我最近在调试一个程序,我要调试Native层的代码,但是等到程序起来以后Native层的代码就已经运行过去了,没办法断下来。
下面我介绍几种网上常用的调试方法,以后随着调试水平的逐步深入,我会继续完善。
在介绍调试方法之前首先要把IDA的调试通道配置一下,这个步骤只有在调试Native层代码的时候需要,有点类似于gdb和gdbserver。
- 首先把IDA文件夹中dbgsrv目录下的android_server文件push到手机的/data/local/tmp目录下,当然这个目录可以自己选。
- 然后在终端中adb shell登陆到手机的shell环境,获取root权限以后,chmod 655 android_server修改运行权限,然后运行之
图1
- 下一步在电脑启动一个终端,然后输入命令:adb forward tcp:23946 tcp:23946
- 搞定。
注意上面的android_server必须是在root权限下运行,否则有可能会有问题。
假设现在已经有一个Android应用程序了,我这边测试用的程序名字是DynamicLoad.apk,这个程序包括Java层代码和Native层代码,Native层代码封装成一个so库,名字是libmyloader.so。
一、调试Native层的代码
方法1,调试Native层非启动运行的代码
所谓Native层非启动运行的代码,就是说程序在启动的时候并不会运行到的代码,这样程序启动以后再用IDA去attach到程序,然后找地方下断,就可以调试想要调试的代码了。
这是最简单的方法,简单来说就是安装并启动应用,然后IDA attach就可以了,但是只能调试Native层的代码。步骤如下:
- 最简单的一种方法,安装程序到手机上,启动程序,然后打开IDA,点击Debugger->Attach->Remote ARMLinux/Android debugger
- 会弹出下面的对话框,HostName就填localhost就可以了,表示是本机。
图3
- 点击确定以后,会弹出一个窗口,里面列出了手机上所有的可以attach的进程。
图4
- 找到你要调试的进程,然后点击OK。会弹出一个Searching for crypto constants,这个非常耗时,而且貌似没有用,直接Cancel取消掉就行了。
图5
- 到此IDA就已经attach到你要调试的程序了,但是要怎么调试自己的代码呢?首先是要找到自己代码的位置,在IDA中按快捷键ctrl+s,会列出加载到内存的所有段的信息,包括对应的so库,及对应的地址和类型等问题。
图6
- Search到你要调试的so,如上图我找到我的库libmyloader.so,会有三个,看类型可以看到三个分别是CODE,CONST,DATA,记下类型为CODE的那个库的起始地址,我这里是0x732A2000。
- 起始地址已经有了,下一步就要找到你要调试的代码在你的so里面的偏移了。
- 首先要搞到libmyloader.so,这个直接把DynamicLoad.apk解压出来就可以搞到,apk文件实际就是zip文件嘛。然后另起一个IDA实例,打开libmyloader.so,在左边Function name栏里Alt+t搜索你想要调试的代码所在的函数名字
- 找到以后双击,光标就跳转到函数的起始代码位置了,然后找到你想要下断的代码的偏移量,我这里假设是00002510,将这个数加上上面记录的so库在内存的起始地址732A2000,得到0x732A4510,这个地址就是你要下断的代码在手机内存中的地址。
- 现在回到前一个IDA,也就是已经attach到手机应用的那个IDA,按g,会弹出一个对话框让你输入Jump address,把上一步计算出的位置输入进去,点击OK。就会跳转到目标位置了,然后按F2下断,再按F9开始运行。
- 后面就是你操作你的程序,当它运行到你下断的代码时,IDA这边就会断下来,然后就可以去干各种事情了
方法2,调试Native层启动运行代码
Native层启动运行代码就是我在文章开头的时候说的那种情况,在程序启动的时候Native层的代码就运行了,那么适用方法1的话就不行了,在IDA attach到进程之前代码就已经运行过了。另外还有一种情况就是现在很多APP都经过类似爱加密和梆梆等处理过,有反调试的功能,进程会去检测自己有没有被其他进程调试,IDA一attach上进程就自动退出了。这时候就要在进程启动最开始的时候就断下来,然后绕过它的检测部分。具体方法如下:
- 首先要以调试模式来启动APP,在电脑的终端中输入命令:adb shell am start -D -n com.example.dynamicload/com.example.dynamicload.MainActivity
- 手机上程序会启动,并显示Waiting For Debugger。这个时候进程虽然启动了,但APP还没有运行。
- 下一步就可以按照方法1的步骤attach到这个进程上,这时IDA会中断在某个位置,然后IDA需要设置一下Debugger->debugger options,如下图:
- 点击OK后按F9运行APP,然后在电脑的终端中输入命令:jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8600,命令的具体意义我也不太明白,但猜想应该是一个调试命令,告诉进程继续执行。注意,执行此命令时必须保证DDMS是打开的,否则调试端口是关闭的,命令执行就会失败。
图9
- 上面命令中后面那个port=8600不是固定的,你要打开DDMS看一下这个进程对应的端口多少。
图10
- 这时候IDA就自动断下来了,有时候它会弹出下面这个对话框提示找不到so。没关系,只要在Destination中输入so的绝对路径就OK了。
- 下面的关键就是找到要下断的地方,因为是要调试Native代码,所以入口肯定是JNI_OnLoad函数了,这也是我们首先要下断的地方。但是怎么找到这个函数的内存地址呢?首先调用这个函数的地方肯定在libdvm.so中,所以我们肯定要在这个库里面下断,相信看过方法1你肯定知道libdvm.so的起始地址怎么获得,然后就剩下找到下断的代码在libdvm.so里面的偏移量了,我的方法是把手机里面的libdvm.so pull出来,然后另起一个新的IDA,在里面搜索JNI_OnLoad函数,找到调用它的地方,记下偏移,然后加上libdvm.so的起始地址,在得到的地址上下断,F9运行,就断下来了。
- 这时候你自己的so(我这里是libmyloader.so)已经加载到内存里,但是还没有运行,下一步就在你自己的so里面再找位置下断调试就可以了,具体不再赘述了,方法1里面很详细。