Android逆向之动态调试so库JNI_Onload函数-----基于IDA实现

之前看过吾爱破解论坛一个关于Android‘逆向动态调试的经验总结帖,那个帖子写的很好,对Android的脱壳和破解很有帮助,之前我们老师在上课的时候也讲过集中调试的方法,但是现在不太实用。对吾爱破解论坛的该贴,我也是看了很多遍,自己也查了不少资料,但是自己动手的时候总觉比较繁琐,并且很多细节的地方没有注意到,按照那个帖子尝试了几遍但是却出现了错误(后面会提到),今天周末重新拾起来试了试,终于把遇到的问题给解决了,顺便做个记录以免忘记了,其中的一些细节我也不是太明白,忘知道的人给指出。

第一步、给meke.exe工具添加系统环境变量

对Android的动态调试,要在cmd控制台手动输入命令,比较繁琐,下面我就偷懒用个简单一点的方法。为了能偷懒,我们需要将AndroidNDK提供的make.exe工具位于安装目录C:\AndroidDevlopment\android-ndk-r9d-windows-x86_64\android-ndk-r9d\prebuilt\windows-x86_64\bin下(具体的路径由自己安装目录决定),在进行Android动态调试之前,需要将该路径添加到系统或临时的PATH环境变量中,具体的操作如下:

技术分享

将该路径添加到系统环境PATH变量中:

技术分享

第二步、准备android_server文件和编写Android动态调试需要的mk文件

android_server文件是由IDA 6.6提供,具体的文件路径是在IDA的安装路径的IDA 6.6\dbgsrv目录下,这里直接拷贝过来使用(只有IDA6.1以上版本才支持Android的动态调试)。具体的mk文件我已经写好了,可以直接拷贝代码保存为.mk后缀文件使用,至于makefile文件的编写我也不知道。

cmd控制台1使用的listen.mk文件的编写:

#说明(控制台1)
#使用Android-NDK提供的make.exe程序,需将该程序的路径 xx\android-ndk-r9d-windows-x86_64\android-ndk-r9d\prebuilt\windows-x86_64\bin添加到环境变量
#记得要先开启Android模拟器或者将开发的手机连接到电脑
#这里android_server的版本是IDA 6.6的
#使用命令(控制台1) make -f listen.mk


#文件名称
MODALE_NAME=crackme.apk
	
#安装程序到手机
listen:
	adb push $(MODALE_NAME) /data/local/tmp
	adb shell chmod 755 /data/local/tmp/$(MODALE_NAME)

	adb push android_server /data/local/tmp
	adb shell chmod 755 /data/local/tmp/android_server
 
#调试模式启动程序
#此时,手机界面会出现Waiting For Debugger页面
#格式 adb shell am start -D -n 包名/.类名
	adb shell am start -D -n com.yaotong.crackme/.MainActivity
	
#端口转发
	adb forward tcp:23946 tcp:23946
	
#启动android_server
#adb shell su
	adb shell /data/local/tmp/android_server

cmd控制台2使用的conn.mk文件的编写

#接下来 IDA附加,设置调试的选项(控制台2)
#静态找到目标函数对应所在模块的偏移地址,Ctrl+S找到对应模块的基地址,两个地址相加得到最终地址
#G跳转至地址,然后下断,F9运行
#其中port=8700是从ddms中看到的
#IDA中,F9运行程序,此时是runing状态
conn:
	jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700

简要的说一下.mk文件中的命令的作用:

#使用命令(控制台1) make -f listen.mk
//注释 make -f listen.mk 说明是listen.mk文件在控制台使用的命令格式
#文件名称
MODALE_NAME=crackme.apk  
//crackme.apk是我们要动态调试的目标apk应用程序
adb push $(MODALE_NAME) /data/local/tmp
//使用adb程序将要调试的目标apk应用程序拷贝Android系统的文件/data/local/tmp目录下
adb shell chmod 755 /data/local/tmp/$(MODALE_NAME)
//adb shell命令的意思是进入到Android系统中
//上面这条命令的作用是修改/data/local/tmp目录下的目标apk应用程序的文件权限为755
adb push android_server /data/local/tmp
//使用adb程序将android_server程序文件拷贝Android系统的文件/data/local/tmp目录下
adb shell chmod 755 /data/local/tmp/android_server
//修改Android系统目录/data/local/tmp下的android_server程序文件的权限为755
#格式 adb shell am start -D -n 包名/.类名 或者 adb shell am start -D -n 包名/包名.类名
adb shell am start -D -n com.yaotong.crackme/.MainActivity
//使用adb shell am start -D -n 包名/.类名 命令以-D调试模式启动apk应用程序,apk应用程序调试模式启动以后,会停止在Waiting For Debugger界面上。am start命令的具体的使用参考网址:http://developer.android.com/tools/help/adb.html#IntentSpec
adb forward tcp:23946 tcp:23946
//adb端口转发
adb shell /data/local/tmp/android_server
//启动拷贝到Android系统中android_server程序,等待IDA6.6程序的连接
#使用命令(控制台2) make -f conn.mk
//注释 make -f conn.mk 说明是conn.mk文件在控制台使用的命令格式
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
//jdb调试器的使用,具体命令行含义不知道,貌似Linux下该命令还不是这样的

技术分享

OK,需要的文件已经准备齐全了,Android逆向动态调试的脚步又就进了一步。

第三步、打开Eclipse应用程序和为要调试的目标apk应用程序添加android:exported="true"选项,生成符合调试的apk程序

在Android逆向动态调试的时候,必须要打开Eclipse程序,并且还要运行Android的模拟器DDMS以方便使用adb程序和jdb程序,有钱的土豪可以使用真机调试。

技术分享

在进行Android动态调试的动手之前,我们还需要做一件事情,为要调试的目标apk程序android:exported="true"以及修改listen.mk文件。前面看别人的写的帖子和博客也可能是人品问题!在Android动态调试的时候am start -D命令不能以调试模式启动要调试的目标apk应用程序,也可能是人品爆发,今天百度查资料解决了,解决的方案就是为启动Activity添加android:exported="true"选项,加了这个选项之后执行am start -D命令,被调试的目标Apk程序能够出现Waiting For Debugger界面并停在那儿。那么现在的问题就是如何添加android:exported="true"选项,很简单。我们使用Android逆向工程利器AndroidKiller工具,这个工具的使用很久简单。我这里要动态调试的目标程序为crackme.apk应用程序,接下来打开AndroidKiller程序,直接拖拽crackeme.apk程序到AndroidKiller程序的界面内进行crackeme.apk应用程序的逆向反汇编,在解压的工程选项中找到crackme.apk的配置文件AndroidManifest.xml打开,如下图依次找到crackme.apk文件的包名、主活动的Activity以及正确的在活动中添加android:exported="true"选项:

技术分享

在对要动态调试的目标apk程序crackeme.apk文件进行解包逆向修改添加android:exported="true"选项以后,千万不能忘记对修改后的目标程序的工程进行再次打包、签名处理生成新的crakeme.apk程序以备后面调试的时候使用,新生成的crakeme.apk程序在Android Killer的安装目录的C:\AndroidDevlopment\AndroidKiller\projects\crackme\Bin路径下(具体的路径由Android Killer的安装目录决定)。现在轻松多了,要动态调试的目标应用程序crackme.apk文件有了。

对了,对应着要调试的目标apk应用程序的名称以及它的主启动Activity相应的修改 listen.mk文件中的变量 MODALE_NAME=crackme.apk以及 命令adb shell am start -D -ncom.yaotong.crackme/.MainActivity

第四步、开启一个cmd控制台以调试模式启动目标apk程序

将第三步中修改后重新打包生成的C:\AndroidDevlopment\AndroidKiller\projects\crackme\Bin路径下的crackme.apk文件以及android_serverlisten.mk文件conn.mk文件拷贝到同一目录下,然后开启一个cmd控制台cd命令进入到该目录,执行命令 make -f listen.mk 如下图:

技术分享

技术分享

其实在这里的时候要注意一下,如果在第三步中没有为要调试的目标apk应用程序添加android:exported="true" 选项,在执行adb shell am start -D -n com.yaotong.crackme/.MainActivity命令的时候会出现如下的错误,但是如果为要调试的目标apk应用程序添加android:exported="true" 选项以后,执行该命令不会出现下面的错误提示,具体的原因我也不知道,之前在尝试Android动态调试的时候也是卡在了这里,可能是Android‘应用程序的权限问题吧,在注册Android内容提供者的时候也需要导出,可能道理一样,也可以参考一下这篇文章http://chenxuebinbj.blog.163.com/blog/static/42869151201302235215832/。对于此种错误的情况,我在看雪论坛上也遇到过了。

:我真的很无语,当我写完这篇笔记想将该错误重现的时候,无语的事情出现了,我没有android:exported="true" 选项再次执行adb shell am start -D -n com.yaotong.crackme/.MainActivity命令的时候,竟然没有报找不到com.yaotong.crackme/.MainActivity类的错误。我还连续试了几次,之前遇到的这个am start -D命令的错误提示都没有出现,真无语。更正一下,看来添加android:exported="true" 选项不是必须的。

此时Android‘模拟中被动态调试的目标crackme.apk程序的运行状态如下,停止在了Waiting For Debugger界面:

技术分享

启动一个IDA主程序,点击菜单 Debugger->Attach->Remote ArmLinux/Android debugger,打开调试程序对话框,在hostname一栏输入localhost,点击ok,然后在IDA弹出的窗口中,选择自己要附加的进程com.yaotong.crackme后点击OK即可,如下图:

技术分享

技术分享

技术分享

此时,要动态调试的目标apk应用程序的进程被IDA6.6程序附加成功以后的状态如下图:

技术分享

下面我们为目标apk应用程序的动态调试进行IDA6.6的一些调试设置,点击该IDA菜单项Debugger->Debugger Opitions 在弹出的Debugger setup窗口的Events中选择 suspend on thread start/exit 以及 suspend on library load/unload,再点击OK退出。通过此操作可以设置程序在创建新线程和加载so时自动中断。具体操作见下图:

技术分享

技术分享

OK,IDA6.6的设置完成,cmd控制台不要关闭保持状态,IDA6.6程序不要关闭保持状态,Eclipse程序和Android模拟器也不能关闭继续保持状态,在后面的动态调试中还会用到。

第五步、再开启一个cmd控制台,在目标apk应用程序的so库的JNI_OnLoad函数处下断点

根据第四步中com.yaotong.crackme进程调试端口8612/8700修改conn.mk文件中的jdb命令为jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700,如下图:

技术分享

再开启一个cmd控制台,cd命令进入到conn.mk所在的目录,执行make -f conn.mk 命令,如下图:

技术分享

连接成功后,在第三步的IDA6.6程序中按F9后Android模拟上的“waiting for debugger"提示会自动消失,这个时候应该已经断在新线程,或者加载so处,具体的状态如下图。不过有一点不太明白,在我的调试运行过程中IDA6.6出现了如下的提示,不知道是否出错,请指导的大神帮忙指点一下。

技术分享

IDA6.6下方的提示如下图,对于上图中的这个对话框我没有理会,直接一路确定迷迷糊糊的这个Add map对话框就被我给无视了。

技术分享

现在就可以在IDA6.6中按下快捷键CTRL+S来查看要调试的so是否已经加载了,如果没有就F9直到加载了为止;如果已经有了,就记下该so的start内存基地址,然后直接用压缩软件解压crackme.apk文件,得到crackme.apk文件要加载的so库,并且再另开一个IDA6.6静态分析该.so库,找到JNI_Onload函数的内存相对虚拟地址(RVA)JNI_Onload_Offset,那么该JNI_OnLoad函数在内存中的真实地址为so.start+ JNI_OnLoad_Offset(so库的内存加载基址+JNI_Onload函数的内存相对虚拟地址)。

技术分享

技术分享

注意:在快捷键CTRL+S跳出的窗口中有几个个同名的so库,我们应当选择class类型为CODE即代码段的内存加载基址so.Start也就是权限为RX的这个,RX一般是代码段,RW一般是数据段。这里的so.Start=AA238000.

技术分享

通过另开的IDA6.6静态分析目标apk应用程序的so库文件得到libcrackme.so文件中JNI_Onload函数的内存相对虚拟地址为00001B9C即JNI_OnLoad_Offset=00001B9C,因此JNI_Onload函数在内存中真实地址为so.start+ JNI_OnLoad_Offset=AA238000+00001B9C=AA239B9C.得到真实地址后,在附加目标进程的IDA6.6中按下快捷键G跳转到地址AA239B9C,然后按下快捷键F2就完成在JNI_OnLoad函数入口处下断点了。

技术分享

OK,在JNI_Onload函数处下断点成功,下面就可以进行SO文件的动态调试F7、F8单步或者直接F9运行JNI_Onload函数断点处。

在apk应用程序的JNI_Onload函数进行下断点,对于从arm汇编的角度来产看apk程序的本地方法做了什么操作还是很有效果的,但是这种方法也不是经常有效的。在Android的JNI编程也可以不使用JNI_Onload函数进行Android的JNI编程。

这篇调试笔记也花费了几个小时的时间,但是结果很遗憾的是F9运行跑飞了,估计是我在进行IDA6.6的操作的那个Add map对话框出错导致的吧,希望大神能告诉其中的原因,也请知道的大神告知Android‘比较好的动态调试的方法以及adb shell am start -D -n com.yaotong.crackme/.MainActivity命令不添加导出选项,启动出错的原因。

参考网址:

http://www.cnblogs.com/wanyuanchun/p/3760825.html?utm_source=tuicool

http://www.52pojie.cn/forum.php?mod=viewthread&tid=293648


2015/4/12 2:08

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。