这里也是的
http://crazysprite.gitcafe.com/2014/12/23/dexloadinmemory/
0x00
Android提供了dex文件动态加载的接口。在加载未安装的dex文件是可以使用Dexclassloader函数来完成。但是这个函数存在一个问题,被加载的dex文件必须以明文的形式存放在文件系统中,这样dex文件很容易被获取到并被分析。
于是,在Android 4.0以上的系统中有了对字节流形式dex文件的加载。在4.4以前java层也有Dex文件按字节流方式加载的接口,但是4.4开始对应的java层接口传入参数变为文件路径而不是字节流。因此直接在JAVA层进行Dex文件内存加载的方式失效了(如果有大大知道其他方法可以一起交流下)。
可以参见Jack_Jia的帖子。
http://blog.csdn.net/androidsecurity/article/details/9674251
本文也是在对该贴C层的实现过程进行一些补充。
实现过程还参见了看雪帖Dex内存加载的Native实现过程中出现的问题中的讨论。
http://bbs.pediy.com/showthread.php?p=1256698
谢谢所有人的无私分享。
0x01
Jack_Jia的帖子已经给出了C层获取接口的方法,这里不在赘述。
本文主要叙述其中ArrayObject结构体的构造方法。
在Android源码中找到ArrayObject结构体的定义在 dalvik/vm/oo/Object.h
struct Object {
/* ptr to class object */
ClassObject* clazz;
/*
* A word containing either a "thin" lock or a "fat" monitor. See
* the comments in Sync.c for a description of its layout.
*/
u4 lock;
};
struct ArrayObject : Object {
/* number of elements; immutable after init */
u4 length;
/*
* Array contents; actual size is (length * sizeof(type)). This is
* declared as u8 so that the compiler inserts any necessary padding
* (e.g. for EABI); the actual allocation may be smaller than 8 bytes.
*/
u8 contents[1];
};
u4和u8是android中的定义,分别对应uint32_t和uint64_t。
Android的数据类型对应如下表:
类型 含义
u1 等同于uint8_t,表示1字节的无符号数
u2 等同于uint16_t,表示2字节的无符号数
u4 等同于uint32_t,表示4字节的无符号数
u8 等同于uint64_t,表示8字节的无符号数
sleb128 有符号LEB128,可变长度1~5字节
uleb128 无符号LEB128,可变长度1~5字节
uleb128p1 无符号LEB128值加1,可变长度1~5字节
实际使用中并没有用到Object结构体,下面解释下其他连个个字段的含义。
length 代表传入Dex文件流的长度
contents 代表存放Dex数据流的首位置
查看Dalvik_dalvik_system_DexFile_openDexFile_bytearray函数对ArrayObject的处理。源码位置在dalvik\vm\native\dalvik_system_DexFile.cpp
static void Dalvik_dalvik_system_DexFile_openDexFile_bytearray(const u4* args,
JValue* pResult)
{
ArrayObject* fileContentsObj = (ArrayObject*) args[0];
u4 length;
u1* pBytes;
RawDexFile* pRawDexFile;
DexOrJar* pDexOrJar = NULL;
if (fileContentsObj == NULL) {
dvmThrowNullPointerException("fileContents == null");
RETURN_VOID();
}
/* TODO: Avoid making a copy of the array. (note array *is* modified) */
length = fileContentsObj->length;
pBytes = (u1*) malloc(length); // 位置A
if (pBytes == NULL) {
dvmThrowRuntimeException("unable to allocate DEX memory");
RETURN_VOID();
}
memcpy(pBytes, fileContentsObj->contents, length); //位置B
if (dvmRawDexFileOpenArray(pBytes, length, &pRawDexFile) != 0) {
ALOGV("Unable to open in-memory DEX file");
free(pBytes);
dvmThrowRuntimeException("unable to open in-memory DEX file");
RETURN_VOID();
}
……………………
}
可以看到在位置A处分配了length大小的空间,位置B处从ArraryObject的contents的位置开始向分配内存空间中拷贝Dex流数据。
因此猜测ArrayObject结构体应该如下方式构造:
char* arr;
length =获取读出来的dex文件流的长度;
arr=(char*)malloc(16+length);
ArrayObject *ao=(ArrayObject*)arr;
ao->length=length;
memcpy(arr+16,dex文件流指针,length);
通过IDA调试来进行验证,跟进入系统SO代码处可以看到汇编代码如下
libdvm.so:41674A38 ADD.W R1, R6, #0x10 //拷贝位置
libdvm.so:41674A3C MOV R2, R4
libdvm.so:41674A3E BLX unk_41621E14 //执行拷贝
libdvm.so:41674A42 MOV R0, R5
libdvm.so:41674A44 MOV R1, R4
libdvm.so:41674A46 ADD R2, SP, #4
libdvm.so:41674A48 BL _Z22dvmRawDexFileOpenArrayPhjPP10RawDexFile
libdvm.so:41674A4C CBZ R0, loc_41674A5E
libdvm.so:41674A4E MOV R0, R5
libdvm.so:41674A50 BLX unk_41621DF0
libdvm.so:41674A54 LDR R0, =(aUnableToOpenIn - 0x41674A5A)
libdvm.so:41674A56 ADD R0, PC ; "unable to open in-memory DEX file"
拷贝是从偏移0x10开始的,也就是偏移16个字节。因此印证以上数据的构造方式。
(注: 在评论区有人讨论偏移是12, 可是我做出来始终是16.之前一直没搞懂这个问题,最近才发现,问题的出现的原因在于处理器平台不同. ARM处理器的偏移是16,x86处理器的偏移是12)
0x03
通过这种内存加载的方式,可以在文件系统中只存放一个加密Dex文件。首先用so将文件流读入内存,在内存中解密。然后用上面的方式将解密后的数据流传给系统函数加载。这样就不会在文件系统中存在明文的Dex文件。
最后,谢谢所有分享和提供过帮助的人。我的理解若不正确的地方,请大家原谅并帮助指出。谢谢。
阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开
发者可享99元/年,续费同价!