使用dex2jar反编译Apk中的classes.dex就能获取到Apk的java层源代码。那么有什么办法可以保护classes.dex,防止Apk的源代码被偷窥呢?那就是加壳了,关于Apk加壳的技术原理,请看这篇博客Android APK加壳技术方案【1】
小弟所了解的Apk脱壳方法有两种:
(1)使用脱壳神器ZjDroid进行脱壳,详细操作请看这两篇博客:Android动态逆向分析工具ZjDroid--脱壳神器 听鬼哥说ZJDROID脱壳的简单使用
(2)使用IDA Pro在dvmDexFileOpenPartial这个函数下断点进行脱壳。下面以第1届Alictf的EvilApk300(如图0所示)为例,简单介绍一下使用IDA Pro 6.5进行脱壳的步骤。
图0 第一届Alictf EvilApk 300的题目
0x00 反编译classes.dex
将Apk的classes.dex放入某个文件夹中,在该文件夹中打开命令提示符,
输入命令:dex2jar classes.dex
如图1所示:
图1 使用dex2jar反编译classes.dex
然后再使用jd-gui查看反编译classes.dex所得到的dex2jar.jar,我们会发现源代码似乎被“隐藏”起来了,再也无法愉快地去偷窥java层的源码了。具体如图2所示:
图2 使用jd-gui查看反编译classes.dex所得到的源代码
0x01 动态调试Apk
虽然加壳能防止源代码被偷窥,但是这只能防止静态分析,无法防止动态调试。不管怎么加壳保护,原始的classes.dex在App运行时都要加载到内存中。所以如果在App加载classes.dex处下个断点,然后再把classes.dex对应内存中的内容抠出来还原成原始的classes.dex文件,就能达到脱壳的目的了。(以上这段话是小弟在看了别人博客后的理解,不保证内容的正确性)下面开始动态调试Apk的过程(如果熟悉IDA Pro动态调试Apk的流程,可以跳过下面的内容,直接查看0x02部分的内容):
(1) 将手机连接到电脑上,打开命令提示符,
先输入”adb shell”,
然后输入”su root”获取root权限,
接着输入” chmod 777 /data/local/tmp/android_server” ,给android_server加上相应的权限
接着输入” /data/local/tmp/android_server”启动android_server
如图3所示:
图3 启动android_server
(2)另外再打开一个命令提示符,输入以下命令进行tcp端口转发:adb forward tcp:23946 tcp:23946
如图4所示:
图4 执行tcp端口转发
(3)因为Apk被加壳了,所以不能使用” com.ali.mobisecenhance”这个包名来动态调试Apk。因此需要使用apktool反编译apk,然后查看AndroidManifest.xml来确定Apk原始的包名。具体操作如图5和图6所示:
图5 使用Apktool反编译Apk
图6 查找AndroidManifest.xml文件中的Apk所对应的包名
通过图6可以得知Apk的包名是com.ali.tg.testapp。
(4)找到Apk对应的包名后,在root模式下启动App,具体操作是打开一个命令提示符,依次输入以下命令:
adb shell
su root
am start –D –n com.ali.tg.testapp/com.ali.tg.testapp.MainActivity
如图7所示:
图7 使用root权限在debug模式下启动App
如果成功,App会弹出”Waiting For Debugger”对话框,如图8所示:
图8 App中的Waiting For Debugger 窗口
(5)打开IDA Pro,依次点击”Debbuger -> Attach -> Remote ARMLinux/Android debugger”启动IDA Pro中的Android Debbuger,具体如图9所示:
图9 启动IDA Pro的Android debugger
然后在弹出的对话框中点击”Debug options”按钮,将“Suspend on process entry point”,“Suspend on thread start/exit”,“Suspend on library load/unload”这几个选项勾选上,再将Hostname配置为localhost,具体如图10所示:
图10 配置Debug options和Hostname
(6)附加进程,在弹出的”Choose process to attach to”对话框中选择进程com.ali.tg.testapp。如图11所示:
图11 附加进程
接着会弹出一个“please wait…”对话框(如图12所示),直接点击该对话框的”Cancel”按钮即可:
图12 Please wait... 对话框
0x02 在dvmDexFileOpenPartial函数处下断点
此时进程处于暂停状态,接下来就需要在dvmDexFileOpenPartial函数处下断点了,为什么选择在dvmDexFileOpenPartial函数处下断点?请看这篇博客:从源码中跟踪Dex的加载流程
下面是在dvmDexFileOpenPartial函数下断点的具体操作过程:
(1)依次点击“Debugger -> Debugger windows -> Module list”,找到so文件列表,如图13所示:
图13 打开Module list对话框
(2)在Module list中找到libdvm.so这个文件,如图14所示:
图14 找到libdvm.so文件
(3)双击libdvm.so,在弹出的函数列表中找到dvmDexFileOpenPartial函数,然后双击该函数就看到dvmDexFileOpenPartial函数的具体实现,如图15所示:
图15 找到dvmDexFileOpenPartial函数
(4)在dvmDexFileOpenPartial函数处下断点,如图16所示:
图16 在dvmDexOpenPartial函数处下断点
0x03 使用jdb命令动态调试Apk
(1)通过DDMS,查看调试进程所对应的端口,可以查看到进程com.ali.tg.testapp所对应的端口是8600和8700,如图17所示:
图17 通过DDMS查看调试进程所对应的端口
但是在使用jdb命令进行调试时,一般选择8700端口,因为8700是默认的调试端口,详情请看这篇文章:http://developer.android.com/tools/debugging/ddms.html
打开一个命令提示符,输入jdb命令连接8700端口调试Apk,如图18所示:
图18 使用jdb连接调试进程
(2)然后再回到IDA Pro中,点击绿色的三角形按钮,继续调试进程,如图19所示:
图19 继续调试进程
(3)在弹出的”Add map…”窗口中点击“Cancel”按钮,如图20所示:
图20 弹出Add map窗口
(4)此时进程就执行到了dvmDexOpenPartial函数断点处,dvmDexOpenPartial函数的定义请查看源码。
dvmDexFileOpenPartial函数的原型如下所示:
int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex)
其中 addr表示Dex文件在内存中的起始地址,
len 表示Dex文件的大小,
ppDvmDex是一个指向DvmDex类型的二级指针,具体表示什么,我也不知道。。。囧
脱壳只用到addr和len这两个参数,所以需要获取R0和R1寄存器的值(ARM的传递参数机制规定R0保存着函数从左至右的第一个参数,R1保存着函数从左至右的第二个参数,详情请看这篇博客:浅析ARM汇编语言子例程设计方法 ),可以查看到寄存器列表中的内容如图21所示:
图21 寄存器R0和R1的值
0x04 编写idc脚本dump内存还原dex文件
idc脚本的语法和C语言很类似,有兴趣的童鞋可以看看这篇博客ida idc函数列表全集
下面介绍一下使用idc脚本dump内存获取dex文件的过程:
(1)点击”File -> Script command…”,调出编写idc脚本的窗口,如图22所示:
图22 打开编写idc脚本的窗口
(2)编写idc脚本,然后点击”Run”按钮执行idc脚本,如图23所示:
图23 编写并执行idc脚本
待执行完idc脚本后,就能在E盘根目录下面找到dump.dex文件了,至此整个脱壳过程就已经完成了。
0x05 分析反编译dex所得到的 smali
不知什么原因,无法通过dex2jar反编译dump.dex获取到java源代码,但是可以通过baksmali反编译dump.dex得到相关的smali代码(baksmali的用法请看这篇博客:apk编译/反编译工具baksmali和smali用法),具体如图24所示:
图24 使用baksmali反编译dump.dex
因为题目的flag与toast有关,所以使用Notepad++的文件查找定位功能定位到smali文件夹,然后查找”toast”关键字,具体如图25,图26所示:
图25 使用Notepad++的文件查找功能
图26 查找关键字toast
搜索toast会找到一串unicode编码的字符串” \u7965\u9f99\uff01”,如图27所示:
图27 根据toast关键字查找到的信息
将这串unicode编码转化为中文(Unicode编码转换工具)就可以得到题目的flag了,如图28所示:
图28 将unicode编码转化为中文得到flag
因为我查看了别人的解题报告,所以知道flag是中文,╮(╯▽╰)╭。。。
到此所有的解题步骤,全部完成了。
因为记录了脱壳的每一个步骤,所以文章显得冗长,其实主要步骤很简单:就是在dvmDexFileOpenPartial函数处下断点,然后动态调试Apk,待App运行到断点处后,写一个idc脚本将dex文件所对应的内存dump出来,然后还原成dex文件就完成脱壳操作了,最后再分析反编译dex所得到的smali文件,找到关键信息就能得到题目的flag了。
本文资源下载地址:安卓逆向学习笔记(9)