Android基于OpenMemory的脱壳

OpenMemory

Android ART加载外部dex本质上调用DexFile::OpenMemory()加载到内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::unique_ptr<const DexFile> DexFile::OpenMemory(const uint8_t* base,
size_t size,
const std::string& location,
uint32_t location_checksum,
MemMap* mem_map,
const OatDexFile* oat_dex_file,
std::string* error_msg) {
CHECK_ALIGNED(base, 4); // various dex file structures must be word aligned
std::unique_ptr<DexFile> dex_file(
new DexFile(base, size, location, location_checksum, mem_map, oat_dex_file));
if (!dex_file->Init(error_msg)) {
dex_file.reset();
}
return std::unique_ptr<const DexFile>(dex_file.release());
}

OpenMemory第一个参数为指向dex文件的指针,因此hook OpenMemory函数,读取第一个参数作为dump起始地址,根据dex文件格式,0x20偏移处为dex的长度,进而dump出整个dex文件。

Frida插件之frida-unpack

基于以上原理,github上有人给出了基于frida hook OpenMemory的插件(https://github.com/dstmath/frida-unpack),作者只给出了32位libart.so的代码,这里附上64位的代码。

那么如何确定该用32位还是64位呢?运行cat /proc/pid/maps |grep libart.so,如果libart.so的路径位于/system/lib64,则使用64位版本代码,否则使用32位。

附代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
//代码在android os: 7.1.2上测试通过
//32位的libart.so
var openmemory = Module.findExportByName("libart.so", "_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_");

console.log("openmemory addr: "+openmemory);
Interceptor.attach(openmemory, {
onEnter: function (args) {

//dex起始位置
var begin = args[1]
console.log(begin);
//打印magic
console.log("magic : " + Memory.readUtf8String(begin))
//dex fileSize 地址
var address = parseInt(begin,16) + 0x20
//dex 大小
var dex_size = Memory.readInt(ptr(address))

console.log("dex_size :" + dex_size)
//dump dex 到/data/data/pkg/目录下
var file = new File("/sdcard/unpack/" + dex_size + ".dex", "wb")
file.write(Memory.readByteArray(begin, dex_size))
file.flush()
file.close()
},
onLeave: function (retval) {
if (retval.toInt32() > 0) {

}
}
});



//64位的libart.so
var openmemory = Module.findExportByName("libart.so","_ZN3art7DexFile10OpenMemoryEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_");
console.log("openmemory addr: "+openmemory);
Interceptor.attach(openmemory, {
onEnter: function (args) {

//dex起始位置
//64位这里获取的args[1]有bug,这里直接读取r0寄存器
var begin = this.context.x0
//console.log(this.context.x0);
//打印magic
console.log("magic : " + Memory.readUtf8String(begin))
//dex fileSize 地址
var address = parseInt(begin,16) + 0x20
//dex 大小
var dex_size = Memory.readInt(ptr(address))

console.log("dex_size :" + dex_size)
//dump dex 到/data/data/pkg/目录下
var file = new File("/sdcard/unpack/" + dex_size + ".dex", "wb")
file.write(Memory.readByteArray(begin, dex_size))
file.flush()
file.close()
},
onLeave: function (retval) {
if (retval.toInt32() > 0) {

}
}
});

  1. dump出来的dex可能为非app本身的dex,如:

    • 系统自身注入的dex,如一些性能统计的dex,我的oneplus手机中出现了com.qualcomm.qti
    • 所有的xposed插件(xposed是注入的zygote,因此所有插件都会注入到app进程,所以谨慎使用xposed插件,一个恶意插件可以获得所有的app进程内数据)
    • Inspeckage也是基于xposed,属于它的一个插件

      这些工具都会注入到app进程,导致拖出来多余的dex。

  2. frida插件是以dex文件的大小来命名的,当两个dex文件大小相同时,会覆盖dump出来的dex

参考资料:

  1. https://bbs.pediy.com/thread-217864.htm
  2. https://github.com/dstmath/frida-unpack