安卓逆向实践5——IDA动态调试so源码

以前的安卓逆向都是在Java层上面的,可是当前大多数App,为了安全或者效率问题,会把一些重要功能放到native层,因此这里经过例子记录一下使用IDA对so文件进行调试的过程并对要点进行总结。android

1、IDA经常使用快捷键总结web

Shift+F12:快速查看so文件中的字符串信息,分析过程当中经过一些关键字符串可以迅速定位到关键函数;shell

F5: 能够将ARM指令转化为可读的C代码,同时可使用Y键,对JNIEnv指
针作一个类型转换,从而对JNI里常用的JNIEnv方法可以识别;安全

Ctrl + S: 有两个用途,在IDA View页面中能够查看文件so文件的全部段信息,在调试页面能够查看程序中全部so文件映射到内存的基地址。tips:在进行so调试过程当中,颇有用的一个小技巧就是IDA双开,一个用于进行静态分析;一个用于动态调试。好比说调试过程当中要找到一个函数的加载到内存中的位置,tcp

G:能够在调试界面快速跳转到指定的绝对地址,进行下断点调试。若是跳转到目的地址以后,发现是DCB数据的话,可使用P键进行转化;编辑器

F7:单步进入调试; F8: 单步调试。svg

2、IDA动态调试so步骤函数

对于没有反调试的步骤:
1)adb push d:\android_server(IDA的dbgsrv目录下) /data/local/tmp/android_server(这个目录是能够随便放的)。
2) adb shell
3) su(必定要有root权限)
4) cd /data/local/tmp
5) chmod 777 android_server (给android_server可执行权限)
./android_server对本地设备端口进行监听
6)再开一个cmd:
adb forward tcp:23946 tcp:23946(端口转发,调试手机上的某个进程要有协议支持通讯)让远程调试端IDA能够链接到被调试端
7)使用IDA链接上转发的端口,查看设备的全部进程,找到须要调试的进程。具体步骤方法为:在Debugger选项卡中选择Attach,选择android debugger,点击Ok。
8)动静结合方式(基地址+相对地址)肯定函数地址进行调试。学习

对于有反调试的步骤:
1)启动android_server
2)端口转发adb forward tcp:23946 tcp:23946
3)adb shell am start -D -n 包名/类名;出现Debugger的等待状态
(说明:以启动模式启动,是停在加载so文件以前,包名能够在androidmanifest文件中找到)
4)打开IDA,附加上对应的进程以后,设置IDA中的load so时机,即在debug options中设置;
5)运行命令:jdb -connect com.sun.jdi.SocketAttach:hostname=localhost, port=8700
6)点击IDA运行按钮,或者F9快捷键。ui

3、IDA动态调试实例

这里咱们结合一个实例,来使用IDA对so文件进行调试。首先看一下apk在手机上的运行效果:

这里须要输入密码,也就是拿到flag。
下面仍是常规操做,将apk放到android killer中对其进行反编译。反编译完成,查看AndroidManifest.xml文件:

程序的包名为com.yaotong.crackme,主活动为MainActivity. apk而且apk没有通过加固处理。
使用jd-gui来查看对应的反编译后对应的Java代码:

这里的程序逻辑仍是很简单的,在btn的onClick方法中获取用户输入的密码,经过crackme库中的原生函数securityCheck对输入密码进行校验,若是正确则启动一个新的活动显示输入正确;不然提示验证码校验错误。
嗯,因此这里的关键仍是对于原生函数securityCheck的分析。
接下来,从apk中提取出libcrackme.so文件(使用解压缩软件打开),并用IDA首先进行静态分析。

这里以Java_com_yaotong_crackme开头的就是原生函数代码了。
点进函数便可查看其ARM指令,这里对于ARM指令就不作分析了,直接F5查看C代码:

这里有个while循环,能看出来是比较两个字符串是否相等,而v6就是内嵌的比较字符串,点进去查看内容,发现字符串“awojiushidaan”,猜测这多是flag,在apk程序中输入发现校验错误,说明程序在运行时确定对其进行了处理。那么接下来,咱们就对so进行动态调试。
首先获取android_server文件,位于IDA的安装目录\dbgsrv\android_server,并将其放在设备的/data目录下,修改运行权限,最后在root环境下运行:

接下来用adb forward tcp:23946 tcp:23946使得Pc端的IDA连上这个端口,在IDA中Debugger选项中选择Remote ARMLinux/Android debugger进行Attach操做,

因为android_server在设备中具备root权限,所以可以获取到设备中的进程以及其调试信息:

选择对应的进程便可进入调试窗口。接下来就是找到函数下断点了,这里打开两个IDA,经过对libcrackme.so静态分析获取到原生函数的相对地址,在动态分析过程当中(此时so文件已经动态加载),crtl+s可以得到libcrackme.so在内存中的基址,基址+相对地址就定位到咱们要动态调试分析的函数了。在函数的开始指令处下断点,F9运行,可是动态调试并无断在咱们指望的地方,而是直接退出了。

所以这里确定是作了反调试检测。基本原理是这样的:IDA使用android_server在root环境下注入到被调试的进程中,用到的技术是Linux中的ptrace,当Android中的一个进程被另一个进程ptrace以后,在其status文件中有一个字段TracerPid能够标识是被哪个进程trace了。(Linux中的/proc/pid/status文件,操做系统课程设计中有涉及到)。这里有两个地方是so动态加载完毕前执行的,.init_array是一个so最早加载的一个段信息,时机最先,如今通常so解密操做都是在这里作的;JNI_OnLoad是so被System.loadLibrary调用的时候执行的,它的时机早于native方法的执行。

接下来咱们就尝试断在JNI_OnLoad函数指令处,首先在IDA调试选项中作以下设置:

可是因为被调试程序一运行就会执行static中的语句,所以须要让程序停在加载so文件以前,这里能够添加watiForDebugger,或者使用更加简单的方法,使用debug方式来启动:
adb shell am start -D -n com.yaotong.crackme/.MainActivity
接下来在IDA中进行attach,可是此时发现没有RX权限的so文件,说明so并无被加载到内存中去,那么就须要让程序跑起来,使用以下命令:
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1, port=8700
可是此时出现以下错误:

出现这种问题大多数是被调试的程序不可调试,能够查看apk的android:debuggable属性,因此这里须要添加这个属性为true再进行回编译:

重编译以后按照以下步骤操做断在so加载过程当中:
1)运行命令:
adb shell am start -D -n com.yaotong.crackme/.MainAcitivity
出现Debugger等待状态
2)启动IDA进行目标进程的Attach操做
3)运行命令:
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
4)设置Debugger Option选项
5)点击IDA运行按钮
这样咱们就能够看到可执行的libcrackme.so了。

其在动态加载过程当中内存的起始地址为75545000,在静态分析的IDA中查看JNI_Onload函数的相对地址为1B9C,从而在动态分析对JNI_Onload函数下断点。

F8单步调试,结合反汇编出来的C代码对arm进行分析,调试到以下所示地方时发现调试结束:

观察此时R7寄存器的值,注释提示为pthread_create函数的地址,在操做系统课程中学过,这是Linux中建立一个线程的操做,进而能够猜错,程序就是在这里进行了反调试的检测,相关的函数即为pthread_create函数第三个参数所对应的回调函数。
在静态分析中咱们查看这个回调函数sub_16A4对应的反汇编代码:

这里应该就是在新的线程中不断重复检测是否被调试,继续跟到sub_130C()函数中看下:


在动态调试过程当中,经过查看函数的参数对应的内存地址和函数地址对应的注释能够发现其中调用了getpid,fopen,sprintf等函数,而且fopen传入的参数是/proc/31305/status。所以这里就是反调试检测的代码了。接下来的代码应该就是用fgets循环读取一行,当读到TrackerPid字段值时比较后面的值是否为0.在这里调试的过程当中咱们是直接单步查看寄存器的值以及内存发现fopen函数的,还能够用一个小技巧:直接在libc.so中的fopen函数处下一个断点,而后再hex view窗口中设置数据与R0同步,也就是fopen函数的第一个参数,这样也能够跟到相应的关键位置;一样,接下来能够继续在strstr函数处下断点,并判断什么时候读到TrackerPid字段值。

肯定了apk中的反调试代码,咱们只须要将BLX R7这段指令干掉便可,这样apk就不会新建线程去执行检测代码了。在静态分析的IDA中查看:

指令的机器码为37 FF 2F E1, 这里咱们在编辑器中查找这段机器码并将其替换为Nop指令,在arm中对应00 00 00 00.
在IDA中查看修改后的so文件对应指令,

能够看到指令成功修改了。
接下来将修改以后的so文件替换到apk中并从新编译安装到手机上。
咱们从新开始动态调试,这里不须要再给JNI_Onload下断点了,由于已经修改了反调试功能,因此只须要进行如下步骤:
1)启动程序
2)Attach进程
3)关键函数下断点
首先定位到Libcrackme.so在内存中的基地址,

在静态分析IDA中查看原生函数的相对地址为11A8,所以在动态调试中基地址加上相对地址处下个断点。

能够看到,反调试功能失效了,此时成功断在了原生函数的arm指令代码处。咱们接下来进行调试分析,经过查看静态arm汇编指令,咱们知道这里就是最后比较字符串是否一致的关键代码:

这里重点关注两个寄存器中存放的字符串的起始地址,咱们在这里下个断点,直接运行到此处:

咱们在hex-view中直接跟随寄存器R0,能够看到这里正好是咱们输入的密码,这里运气比较好,并无对输入的密码进行处理,那么此时R2寄存器对应的地址确定就是真正的flag了,观察内存中的字符串以下:

到这里咱们应该就成功得到了flag。

4、小结
这里咱们学习了如何经过IDA调试so文件,并学习了如何移除程序中的反调试功能,收获良多!
参考:安卓动态调试七种武器之孔雀翎 – Ida Pro