android脱壳之dvmDexFileOpenPartial分析

逆向加壳apk的典型方法

我们知道,逆向加壳apk的时候,可以对libdvm.so中的int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex)函数打断点,然后IDA中使用下面的代码根据addrlen将内存中的dex文件dump到文件中。

// dump memory dex to file
static main(void)                                                               
{
    auto fp, begin, end, dexbyte;
    fp = fopen("C:\\dump.dex", "wb");
    begin = r0; 
    end = r0 + r1; 
    for ( dexbyte = begin; dexbyte < end; dexbyte ++ )
        fputc(Byte(dexbyte), fp);
}

dvmDexFileOpenPartial函数的原型为

int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex)
  • addr,加载的DEX文件在内存中的基址.(也就是DEX.035)
  • len,加载的DEX文件的文件长度,
  • ppDvmDex,DEX 文件转成DvmDex结构,里面包含Dex文件的类,字段,方法,字符串信息。dalvik操作Dex文件的对象这是结构结构体

流程分析

Android系统启动时会先启动init进程,init进程会启动zygote进程,该进程会为每个要启动的App进程孵化出一个Davlik虚拟机实例。Zygote 进程还会启动optdex进程,这个进程将要运行App的DEX文件映射到内存中,然后校验,优化,转成虚拟机能够操作的Dex对象DvmDex.

首先来看一下dexopt对应的入口main()函数:
dalvik/dexopt/OptMain.cpp

/*
 * Main entry point.  Decide where to go.
 */
int main(int argc, char* const argv[])
{
    set_process_name("dexopt");
    setvbuf(stdout, NULL, _IONBF, 0);

    if (argc > 1) {
        if (strcmp(argv[1], "--zip") == 0)
            return fromZip(argc, argv);
        else if (strcmp(argv[1], "--dex") == 0) // 重点看dex
            return fromDex(argc, argv);
        else if (strcmp(argv[1], "--preopt") == 0)
            return preopt(argc, argv);
    }

    ...
    return 1;
}

跟进函数fromDex函数:

{
    int result = -1;
    char* bootClassPath = NULL;
    int fd, flags, vmBuildVersion;
    long offset, length;
    const char* debugFileName;

    DexClassVerifyMode verifyMode;
    DexOptimizerMode dexOptMode;

    ...

    if (dvmPrepForDexOpt(bootClassPath, dexOptMode, verifyMode, flags) != 0) {
        ALOGE("VM init failed");
        goto bail;
    }

    ...

    /* do the optimization */
    if (!dvmContinueOptimization(fd, offset, length, debugFileName,
            modWhen, crc, (flags & DEXOPT_IS_BOOTSTRAP) != 0))
    {
        ALOGE("Optimization failed");
        goto bail;
    }

    ...
    return result;
}

dvmPreForDexOpt函数的作用在对函数进行优化前,检测Dalivk虚拟机是否进入工作状态

int dvmPrepForDexOpt(const char* bootClassPath, DexOptimizerMode dexOptMode,
    DexClassVerifyMode verifyMode, int dexoptFlags){
	...
    if (!dvmGcStartup())
        goto fail;
    if (!dvmThreadStartup())
        goto fail;
    if (!dvmInlineNativeStartup())
        goto fail;
    if (!dvmRegisterMapStartup())
        goto fail;
    if (!dvmInstanceofStartup())
        goto fail;
    if (!dvmClassStartup())
        goto fail;
    ...
    return 0;

fail:
    dvmShutdown();
    return 1;
}

如果通过dvmPreForDexOpt检测,则会继续调用dvmContinueOptimization对Dex文件进行优化

{
    ...
    gDvm.optimizingBootstrapClass = isBootstrap;
    {
        bool success;
        void* mapAddr;
        mapAddr = mmap(NULL, dexOffset + dexLength, PROT_READ|PROT_WRITE,
                    MAP_SHARED, fd, 0);// 1
        ...

        success = rewriteDex(((u1*) mapAddr) + dexOffset, dexLength,
                    doVerify, doOpt, &pClassLookup, NULL);

        if (success) {
            DvmDex* pDvmDex = NULL;
            u1* dexAddr = ((u1*) mapAddr) + dexOffset;

            if (dvmDexFileOpenPartial(dexAddr, dexLength, &pDvmDex) != 0) { // 2
                ALOGE("Unable to create DexFile");
                success = false;
            } else {
                ...
            }
        }
    }
    ...
}

首先通过mmap将Dex文件映射到内存中,之后会调用 dvmDexFileOpenPartial , 这个函数主要功能就是完成将内存DexDile 转化成Dalvik的Dex文件DvmDex

int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex)
{
    DvmDex* pDvmDex;
    DexFile* pDexFile;
    int parseFlags = kDexParseDefault;
    int result = -1;


    pDexFile = dexFileParse((u1*)addr, len, parseFlags);
    ...
    pDvmDex = allocateAuxStructures(pDexFile);
    ...
    
    *ppDvmDex = pDvmDex;
    result = 0;

    ...
    return result;
}

我们来看一下DvmDex结构,里面有我们熟知的字符串对象内对象方法字符等等信息

struct DvmDex {
    /* pointer to the DexFile we're associated with */
    DexFile*            pDexFile;

    ...
    
    /* interned strings; parallel to "stringIds" */
    struct StringObject** pResStrings;

    /* resolved classes; parallel to "typeIds" */
    struct ClassObject** pResClasses;

    /* resolved methods; parallel to "methodIds" */
    struct Method**     pResMethods;

    /* resolved instance fields; parallel to "fieldIds" */
    /* (this holds both InstField and StaticField) */
    struct Field**      pResFields;

    ...
};

我们再来看下dexFileParse方法

DexFile* dexFileParse(const u1* data, size_t length, int flags)
{
    DexFile* pDexFile = NULL;
    const u1* magic;
    ...
    pDexFile = (DexFile*) malloc(sizeof(DexFile));
    if (pDexFile == NULL)
    goto bail;      /* alloc failure */
    memset(pDexFile, 0, sizeof(DexFile));

    /*
     * Peel off the optimized header.
     */
    if (memcmp(data, DEX_OPT_MAGIC, 4) == 0) {
        ...
        pDexFile->pOptHeader = (const DexOptHeader*) data;
        ...
    }

    dexFileSetupBasicPointers(pDexFile, data);
    ...

bail:
    ...
    return pDexFile;
}

dexFileParse方法的功能是完成dex的解析,也有人逆向的时候在这里dump dex的。有兴趣的同学可以看看DexExtractor的原理分析和使用说明
在这里插入图片描述

参考文章:
https://www.cnblogs.com/jiaoxiake/p/6813127.html

猜你喜欢

转载自blog.csdn.net/QWE123ZXCZ/article/details/84959759