对于加固,是将加密后的dex文件与壳文件合并生成新的class.dex
文件,然后签名重打包生成加固后的apk文件。而对于壳程序加载原dex文件,是dalvik虚拟机加载加固后的apk里的classes.dex文件,并使用attachBaseContext方法以及onCreate方法进行解密原dex文件并启动原程序。但是无论进行了怎样的加密,最后都是需要经历解密动态加载到内存中的,利用这一点即可进行dump出dex文件。
dump内存法一
通过分析底层加载dex源码发现libdvm.so中的dvmDexFileOpenPartial函数,该函数有两个参数,一个是dex的起始地址,一个是dex的大小。于是可以使用ida进行动态调试获取信息。
安装好apk,并以调试方式运行
启动调试服务并进行端口转发
pull出
/system/lib/libdvm.so
文件
将
libdvm.so
加载入ida并在dvmDexFileOpenPartial函数开始处下断点attach进程
点击Debugger->Attach to process
,在选择框中选择apk对应的包名(即apk运行的进程名)并记录下ID,然后点击OK在PC的xxx(此处为ID)端口与手机的相应端口之间建立连接
adb forward tcp:8700 jdwp:xxx(此处为ID)
使用jdb在java层将apk attach到电脑上
jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=8700
F9运行ida,一路ok程序停在预先下好的断点处
此时寄存器R0中对应的就是dex文件的起始地址,R1对应的就是dex文件的大小使用IDC进行dump内存获取dex,脚本如下:
1
2
3
4
5
6
7
8static main(void)
{
auto fp, dex_addr, end_addr;
fp = fopen("E:\\Android\\temp\\dump.dex", "wb");
end_addr = r0 + r1;
for(dex_addr = r0; dex_addr < end_addr; dex_addr++)
fputc(Byte(dex_addr), fp);
}因为Dalvik虚拟机下对运行的dex文件做了优化,真正在内存中运行的是odex文件
- 使用baksmali对odex文件进行反编译
- 使用smali进行编译成dex文件
得到了dex文件即可对其进行分析了
dump内存法二
按动态调试流程进行
在获取到进程ID后打开
adb shell
输入cat /proc/xxx(进程ID)/maps
,在结果里找到对应程序的dex文件地址在ida中使用IDC进行dump内存获取dex,脚本如下:
1
2
3
4
5
6
7
8static main(void){
auto fp, beg_addr, end_addr, dex_addr;
fp = fopen("E:\\Android\\atemp\\dump.dex", "wb");
beg_addr = 0xaaaaaaaa;
end_addr = 0xbbbbbbbb;
for (dex_addr = beg_addr; dex_addr < end_addr; dex_addr++ )
fputc(Byte(dex_addr), fp);
}但此法有问题,目前还没弄明白,有待研究,如下图:
圈中的是我用法一得到的,可以dump出来,但在此时进入法二的进程maps(图中横线)中却得不到真正的dex地址
dump内存法三
使用ZjDroid。ZjDroid是基于Xposed Framewrok的动态逆向分析模块,逆向分析者可以通过ZjDroid完成以下工作:
DEX文件的内存dump
基于Dalvik关键指针的内存BackSmali,有效破解加固应用
敏感API的动态监控
指定内存区域数据dump
获取应用加载DEX信息。
获取指定DEX文件加载类信息。
dump Dalvik java堆信息。
在目标进程动态运行lua脚本。
art虚拟机
上述都是Dalvik虚拟机下的。对于art虚拟机下,dump的点在于libart.so的OpenMemory函数。而且dump出来的文件是OAT文件,OAT文件类似于Dalvik的ODEX文件。由于OAT文件中包含完整的DEX文件,取出OAT文件的方法非常简单:定位OAT文件中的DexFile结构体然后将数据完整导出即可。