首页
论坛
专栏
课程

[原创]分享一个快速加载dex文件的方法

总有SB想害我 2015-11-7 13:12 19023
在Android系统下有一个DexClassLoader类,可以动态加载dex文件,但是,这个类有一个缺陷,就是第一次启动并加载一个dex文件时,会执行一次dex2oat或 dexopt操作,正常情况下不会感觉到它的不足,但是如果将它用于加固,就会出现第一次启动时间特别长的问题,为此可以使用下面的方法去提升第一次启动的速度:
首先是art模式下,art模式下支持解释执行dex文件,不需要编译,也不需要oat文件,所以直接hook execv函数,让调用dex2oat的进程直接退出就好,
hook回调函数代码如下:
int hook_execv(const char *name, char **argv) {
        int ret = 0;
        char tmp[512];
        if (!IsDex2oat(name)) {
                return ori_execv(name, argv);
        }
        exit(0);
}
hook的方式是got hook,代码如下:
void* GotHook(soinfo *si, char *Target_Name, void* Hook_Callback) {
        Elf32_Rel *rel = (Elf32_Rel*)si->plt_rela;
        int count = si->plt_rela_count;
        void *Target_Addr = 0;
       
        int idx = 0;
        for (idx = 0; idx<count; ++idx, ++rel) {
                unsigned type = ELF32_R_TYPE(rel->r_info);
                unsigned sym = ELF32_R_SYM(rel->r_info);
               
                Elf32_Addr reloc = (Elf32_Addr)(rel->r_offset + (u4)si->base);
                Elf32_Addr sym_addr = 0;
                const char* sym_name = 0;
               
               
                if (type == 0) {
                        continue;
                }
               
                Elf32_Sym *s = 0;
                struct soinfo_mine *lsi = 0;
               
                if (sym != 0) {
                        sym_name = get_string(((Elf32_Sym*)si->symtab)[sym].st_name, si);
                       
                        printf("%d sym_name=%s\n", idx, sym_name);
                       
                        if (strcmp(sym_name, Target_Name))
                                continue;
               
                        printf("%d sym_name=%s\n", idx, sym_name);
                       
                        Target_Addr = *(void**)reloc;
                        if (Hook_Callback != 0) {
                                Elf32_Addr seg_page_start = PAGE_START(reloc);
                                Elf32_Addr seg_page_end = PAGE_END(reloc + 4);
                                errno=0;
                                int ret = mprotect((void*)seg_page_start, seg_page_end-seg_page_start,
                                                                    PROT_READ | PROT_WRITE);
                               
                                *(void**)reloc = Hook_Callback;
                        }
                }
        }
       
       
        return Target_Addr;
}

int JNI_onLoad() {

        libart = (soinfo*)dlopen("libart.so", RTLD_NOW);
        ori_execv = (fnexecv)GotHook((soinfo*)libart, "execv", (void*)hook_execv);

}

其中使用到的结构体,可以从android的linker的源码里拿取。

在dalvik虚拟机下,正常的情况下需要执行一个dexopt的过程,但同时,dalvik虚拟机里,也支持一种从内存里直接加载dex文件的字节码的办法:
http://androidxref.com/4.4.2_r2/xref/dalvik/vm/native/dalvik_system_DexFile.cpp#249,使用这个函数可以秒加载内存中的dex文件。
但是可悲的是这个函数并没有导出,我们无法直接去调用。
为此有两种解决办法:
1、        使用函数dvmLookupInternalNativeMethod去查找Dalvik_dalvik_system_DexFile_openDexFile_bytearray的地址。
2、        用Dalvik_dalvik_system_DexFile_openDexFile_bytearray内部使用的已导出的函数去重写一个自己的my_Dalvik_dalvik_system_DexFile_openDexFile_bytearray函数。
为了使这种快速加载技术支持2.x的版本,所以最好使用的是第2种方法。
void* LoadByte(JNIEnv *env, u1 *byDexFile, int len) {
        jbyte *bytes = (jbyte*)byDexFile;
        u1 *pnc = (u1*)bytes;
       
       

       
        RawDexFile *pRawDexFile = (RawDexFile*)malloc(sizeof(RawDexFile));
       
       
        DvmDex *pDvmDex = NULL;
       
       
        void* pClassLookup = NULL;
        LOGI2("dvmDexFileOpenPartial_ptr=%p %p\n", dvmDexFileOpenPartial_ptr, dexCreateClassLookup_ptr);
       
        dvmDexFileOpenPartial_ptr(pnc, len, &pDvmDex);
        pClassLookup = (void*)dexCreateClassLookup_ptr(pDvmDex->pDexFile);
       
       
        LOGI2("pDvmDex=%p pDvmDex->pDexFile=%p\n", pDvmDex, pDvmDex->pDexFile);
       
       
        pDvmDex->pDexFile->pClassLookup = pClassLookup;
       
        pRawDexFile->pDvmDex = pDvmDex;
       
        DexOrJar *pd = (DexOrJar*)malloc(sizeof(DexOrJar));
       
        LOGI2("pnc=%p\n", pnc);
        pd->isDex = true;
        pd->pRawDexFile = pRawDexFile;
        pd->pDexMemory = pnc;
        pd->filename = strdup("<memory>");
       
        void *libdvm = dlopen("libdvm.so", RTLD_NOW);
        unsigned addrOfgDvm = (unsigned)dlsym(libdvm, "gDvm") + 0x330;
        fndvmHashTableLookup addrOfdvmHashTableLookup = (fndvmHashTableLookup)dlsym(libdvm, "_Z18dvmHashTableLookupP9HashTablejPvPFiPKvS3_Eb");
        //int result = dvmHashTableLookup_ptr(addrOfgDvm, (int)pd, (int)pd, hashcmpDexOrJar, true);
       
       
        LOGI2("result=%p\n", pd);
       
        if (old_DvmDex == NULL)
        old_DvmDex = pDvmDex;
       
        return pd;
}

成功调用上面的两种内存加载dex文件方法中的任意一种后,得到的返回值是一个DexOrJar结构体的指针。这个指针就是http://androidxref.com/4.4.2_r2/xref/libcore/dalvik/src/main/java/dalvik/system/DexFile.java#37这里的mCookie。
得到mCookie后,该怎么将它的值传给DexFile呢?
我使用的方法是hook openDexFileNative/openDexFile函数。
这两个函数没有导出,但它们是一个jni函数,hook的办法是替换这两个方法的Method结构体的里的回调函数。
这两个JNI方法与一般的JNI方法不同,它们是内部JNI,因此,它的回调函数保存在这两个位置之中的一个:
1、          ((u4**)Method->insns)[10];
2、        Method->nativeFunc(当Method->insns==0时)

Hook代码:
addr = (u4**)openDexFileNative_med->insns;
       
        if (openDexFileNative_med->insns == 0) {
                Dalvik_dalvik_system_DexFile_openDexFileNative_ptr = (Dalvik_dalvik_system_DexFile_openDexFileNative_func)openDexFileNative_med->nativeFunc;
                openDexFileNative_med->nativeFunc = (u4)Dalvik_dalvik_system_DexFile_my_openDexFileNative;
        }
        else {
                Dalvik_dalvik_system_DexFile_openDexFileNative_ptr = (Dalvik_dalvik_system_DexFile_openDexFileNative_func)addr[10];
                addr[10] = (u4*)Dalvik_dalvik_system_DexFile_my_openDexFileNative;
        }

至此,总结一下dalvik下加速启动的过程:
1、        调用DexClassLoader,加载一个payload.dex文件
2、        DexClassLoader调用openDexFileNative函数
3、        由于openDexFileNative被hook了,实际调用的是my_openDexFileNative
4、        my_openDexFileNative里调用my_Dalvik_dalvik_system_DexFile_openDexFile_bytearray加载内存中的dex文件,并将结果返回
5、        成功实现秒加载payload.dex文件。

不知道怎么插入图片,很抱歉,先写原理,过些时间,放出源码。

爱讨论的加个群456853837,补个群号282215163

快讯:看雪智能设备漏洞挖掘公开课招生中!

最新回复 (14)
kllei 2015-11-7 14:10
2
几个小问题:
1、dlsym(libdvm, "gDvm") + 0x330;
   这个地方+0x330得到的userDexFiles的偏移不通用,比如4.0.3和4.2.2有差别
2、mCookie和DexOrJar的映射
   这个可以使用Jack_jia加固原理中的修改生命周期的反射方式建立映射
总有SB想害我 2015-11-7 14:48
3
mCookie就是DexOrJar呀
后来我注释掉了那段的代码,不往userDexFiles里插数据了,另外,我也重写了一个DefineClass,让他不去检测是否已经插入了userDexFiles,因为觉得不是最好的办法,所以没写出来。
龙飞雪 2015-11-7 23:05
4
顶一个,多谢你的分享。
Fido 2015-11-8 11:24
5
谢谢分享...学习一下.....
QEver 2015-11-9 10:25
6
xx加固获取userDexFiles用了一点小技巧。可以学习一下,麻烦点,不过准确
beyond池 2015-11-20 14:35
7
请问dalvik虚拟机上除了4.0-4.3以外的系统有什么办法可以加速dex的加载呢?
qgesxcwt 2015-12-7 12:04
8
可以请教一下您说的是哪个加固吗,想去看看学习下
beyond池 2016-1-19 15:48
9
求 小技巧!
NISL 2016-2-23 10:30
10
能不能提供一下源码,谢谢啦!!!!!!!!!!!
大王叫我挖坟 2016-5-10 10:28
11
过段时间是要过多久啊大大
IceMaker 2016-8-21 00:02
12
半年了,没见到代码,楼主
思路明白,离自己写差太多了,(@﹏@)~
koftnt 2016-11-16 00:13
13
楼主不见了。唉。。。。。
王小东 2016-11-21 08:16
14
几个小问题:
1、dlsym(libdvm, "gDvm") + 0x330;
   这个地方+0x330得到的userDexFiles的偏移不通用,比如4.0.3和4.2.2有差别
2、mCookie和DexOrJar的映射
   这个可以使用Jack_jia加固原理中的修改生命周期的反射方式建立映射
liumengde 2018-6-10 06:52
15
fnexecv  这是结构体?
返回