通过在 dvmDexFileOpenPartial函数入口点下断点,动态dump出完整dex
准备工作
-
使用avd
我用的是轻量级的sdk-tools(被集成在android studio中),里面带了如ddms,android设备管理器等调试apk的必要工具。
-
题目来源和下载地址
题目来自2016年厦门美亚柏科信息安全邀请赛crackme8-9
下载地址:
链接: https://pan.baidu.com/s/1gfJAtb1 密码: 7eh9
-
下载安装合适版本的AVD
由于android5.0以上(包括5.0)使用
libart.so
代替libdvm.so
来优化dex加载和启动过程,而dvmDexFileOpenPartial
函数在libdvm.so
中被调用,因此我装了一个android版本为4.3.1的虚拟机,配置如下:{% asset_img markdown-img-paste-20171216104905646.png android4.3.1 %}
原理分析
这种方法只能脱一些比较简单的壳,而且不适用于使用libart.so的安卓系统。脱壳原理是,简单壳程序一般使用自定义的类,在apk运行之后动态解密加载真实dex文件到内存(dvm)中执行,但是加载dex过程中需要调用系统函数dvmDexFileOpenPartial
来解析dex到dvm可运行的形式,dvmDexFileOpenPartial
源码如下:
int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex)
{
DvmDex* pDvmDex;
DexFile* pDexFile;
int parseFlags = kDexParseDefault;
int result = -1;
/* — file is incomplete, new checksum has not yet been calculated
if (gDvm.verifyDexChecksum)
parseFlags |= kDexParseVerifyChecksum;
*/
pDexFile = dexFileParse((u1*)addr, len, parseFlags);
if (pDexFile == NULL) {
ALOGE(“DEX parse failed”);
goto bail;
}
pDvmDex = allocateAuxStructures(pDexFile);
if (pDvmDex == NULL) {
dexFileFree(pDexFile);
goto bail;
}
pDvmDex->isMappedReadOnly = false;
*ppDvmDex = pDvmDex;
result = 0;
bail:
return result;
}
可以看到这个函数的作用是解析dex文件,第一个参数是dex文件的基址,第二个参数是dex文件的大小,如果在加载主类前断在这个函数中,就可以dump出完整的dex。
题目简述
用jeb打开看到主antivity被隐藏,判断加了壳
markdown-img-paste-20171216120958373.png开始脱壳
-
安装apk
-
将ida dbgsrv目录下的android server
adb push
到虚拟机中,启动运行 -
终端下运行
adb forward tcp:23946 tcp:23946
将本机的23946端口转发到虚拟机的23946端口,方便ida附加调试。 -
运行 adnroid-sdk/tools 中的ddms,这里运行ddms的作用是因为之后需要使用jdb继续运行程序,ddms可以方便的查看jdb需要连接的端口。(注:ddms最好在ida attach操作之前打开,因为我好几次在之后打开都把ida挤掉了,原因还不知道)
-
终端下运行
markdown-img-paste-20171216110255820.png markdown-img-paste-20171216110314602.pngadb shell ”am start -D -n 包名/入口activity“
,以调试模式运行apk,运行结果会在android虚拟机上看到”waitting for debugger“的字样。一个apk的包名和入口activity名可以在apk的
AndroidManifest.xml
中看到,这个文件存储的是一个apk的所有配置信息。用apktools解包apk,可以在解压后的根目录下看到这个文件,打开之后是这样:
markdown-img-paste-20171216110651161.png关注红框中的元素,android系统中,使用包名来区分各个apk,因此package名称可以定位到具体的某个apk,而这个apk的入口activity则是带有
android.intent.action.MAIN
的name属性的action标签对应的activity。根据这两个信息,上述命令就可以以调试模式断在系统加载这个apk的主类对应的dex之前。
-
使用ida远程attach相应进程,attach上之后在modules中搜索
libdvm.so
,找到之后双击,再搜索dvmDexFileOpenPartial
这个函数,找到之后双击进入,在函数的第一句下断点。
设置调试选项debugger option
如下(很重要,不设置会断不下来):
- F9继续运行,此时发现android虚拟机的程序还没有继续运行,因此需要jdb触发其自动执行(具体分析见 http://blog.a7vinx.me/2016/10/25/android_debug_analysis/ ),在ddms中选中这个apk:
会发现最后一列的端口号后面多了一个8700.这是ddms设置的端口转发,ddms会将选定的apk的端口转发到8700端口,这样调试器就可以附加到这个特定端口来调试程序,而不需要寻找这个程序使用的端口。
如果嫌jdb麻烦的话,可以用一条端口转发命令来手动转发到8700:
adb forward tcp:8700 jdwp:<pid>
其中pid是待attach的进程的pid。
终端下运行jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
发现ida断下来了,但是不是断在刚刚下断点的地方,而是段在linker段:
再次F9运行:
markdown-img-paste-20171216113424181.png发现断在了我们之前下的断点处,这时看R0寄存器的值(第一个参数)就是dex在内存中的基址,R1寄存器的值就是dex的大小。
markdown-img-paste-20171216113625632.png-
写idapython脚本,dump出这段内存中的数据,就是完整的dex,dump脚本如下:
import idaapi data = idaapi.dbg_read_memory(0xB7D080B8, 0xA40C) fp = open('/home/vccxx1337/Desktop/dump.dex', 'wb') fp.write(data) fp.close()
在ida中运行后得到完整dex。
脱壳结果
aftershell.png坑
多次实验验证,当程序已经在android虚拟机上运行过之后,再使用上述方法进行附加之后是不能在dvmDexFileOpenPartial
函数处断下的,原因猜想是上次运行时解析的dex还驻留在dvm中,再次运行时就不会调用这个函数来再次解析dex了。具体原理还得看看android源码。比较粗暴的解决方法是卸载重装apk。
网友评论