【转】安卓逆向实践5——IDA动态调试so源码
之前的安卓逆向都是在Java层上面的,但是当前大多数App,为了安全或者效率问题,会把一些重要功能放到native层,所以这里通过例子记录一下使用IDA对so文件进行调试的过程并对要点进行总结。
一、IDA常用快捷键总结
Shift+F12:快速查看so文件中的字符串信息,分析过程中通过一些关键字符串能够迅速定位到关键函数;
F5: 可以将ARM指令转化为可读的C代码,同时可以使用Y键,对JNIEnv指
针做一个类型转换,从而对JNI里经常使用的JNIEnv方法能够识别;
Ctrl + S: 有两个用途,在IDA View页面中可以查看文件so文件的所有段信息,在调试页面可以查看程序中所有so文件映射到内存的基地址。tips:在进行so调试过程中,很有用的一个小技巧就是IDA双开,一个用于进行静态分析;一个用于动态调试。比如说调试过程中要找到一个函数的加载到内存中的位置,
G:可以在调试界面快速跳转到指定的绝对地址,进行下断点调试。如果跳转到目的地址之后,发现是DCB数据的话,可以使用P键进行转化;
F7:单步进入调试; F8: 单步调试。
二、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快捷键。
三、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。
四、小结
这里我们学习了如何通过IDA调试so文件,并学习了如何移除程序中的反调试功能,收获良多!
参考:安卓动态调试七种武器之孔雀翎 – Ida Pro
from:https://blog.csdn.net/hbhgyu/article/details/81321923