首页
社区
课程
招聘
[原创]dvm探讨之odex绕过
发表于: 2016-4-21 11:50 8527

[原创]dvm探讨之odex绕过

2016-4-21 11:50
8527

最近研究有关odex的相关资料,目的是想在user模式下,修改system.img中framework的odex,因为绕不过签名检测这关总是自动重启。
因为看了很多,也想了很多,这里只是抛砖引玉,说的不好请各位见谅。
分析步骤:
1、寻找dvm初始化代码,弄明白dvm初始化流程。
2、寻找dexopt优化和签名流程。

dalvik虚拟机里面我们主要分析两个目录:“vm”和“dexopt”
他们构建后分别生成:“libdvm.so”和“dexopt”
1、libdvm.so 初始化流程分析
vm/Init.cpp ->dvmStartup

/*
 * VM initialization.  Pass in any options provided on the command line.
 * Do not pass in the class name or the options for the class.
 *
 * Returns 0 on success.
 */
std::string dvmStartup(int argc, const char* const argv[],
        bool ignoreUnrecognized, JNIEnv* pEnv)
{
    ScopedShutdown scopedShutdown;

    assert(gDvm.initializing);

    ALOGV("VM init args (%d):", argc);
    for (int i = 0; i < argc; i++) {
        ALOGV("  %d: '%s'", i, argv[i]);
    }
    setCommandLineDefaults();

    /*
     * Process the option flags (if any).
     */
    int cc = processOptions(argc, argv, ignoreUnrecognized);
    if (cc != 0) {
        if (cc < 0) {
            dvmFprintf(stderr, "\n");
            usage("dalvikvm");
        }
        return "syntax error";
    }

#if WITH_EXTRA_GC_CHECKS > 1
    /* only "portable" interp has the extra goodies */
    if (gDvm.executionMode != kExecutionModeInterpPortable) {
        ALOGI("Switching to 'portable' interpreter for GC checks");
        gDvm.executionMode = kExecutionModeInterpPortable;
    }
#endif

    /* Configure group scheduling capabilities */
    if (!access("/dev/cpuctl/tasks", F_OK)) {
        ALOGV("Using kernel group scheduling");
        gDvm.kernelGroupScheduling = 1;
    } else {
        ALOGV("Using kernel scheduler policies");
    }

    /* configure signal handling */
    if (!gDvm.reduceSignals)
        blockSignals();

    /* verify system page size */
    if (sysconf(_SC_PAGESIZE) != SYSTEM_PAGE_SIZE) {
        return StringPrintf("expected page size %d, got %d",
                SYSTEM_PAGE_SIZE, (int) sysconf(_SC_PAGESIZE));
    }

    /* mterp setup */
    ALOGV("Using executionMode %d", gDvm.executionMode);
    dvmCheckAsmConstants();

    /*
     * Initialize components.
     */
    dvmQuasiAtomicsStartup();
    if (!dvmAllocTrackerStartup()) {
        return "dvmAllocTrackerStartup failed";
    }
    if (!dvmGcStartup()) {
        return "dvmGcStartup failed";
    }
    if (!dvmThreadStartup()) {
        return "dvmThreadStartup failed";
    }
    if (!dvmInlineNativeStartup()) {
        return "dvmInlineNativeStartup";
    }
    if (!dvmRegisterMapStartup()) {
        return "dvmRegisterMapStartup failed";
    }
    if (!dvmInstanceofStartup()) {
        return "dvmInstanceofStartup failed";
    }
    if (!dvmClassStartup()) {
        return "dvmClassStartup failed";
    }

    /*
     * At this point, the system is guaranteed to be sufficiently
     * initialized that we can look up classes and class members. This
     * call populates the gDvm instance with all the class and member
     * references that the VM wants to use directly.
     */
    if (!dvmFindRequiredClassesAndMembers()) {
        return "dvmFindRequiredClassesAndMembers failed";
    }

    if (!dvmStringInternStartup()) {
        return "dvmStringInternStartup failed";
    }
    if (!dvmNativeStartup()) {
        return "dvmNativeStartup failed";
    }
    if (!dvmInternalNativeStartup()) {
        return "dvmInternalNativeStartup failed";
    }
    if (!dvmJniStartup()) {
        return "dvmJniStartup failed";
    }
    if (!dvmProfilingStartup()) {
        return "dvmProfilingStartup failed";
    }

    /*
     * Create a table of methods for which we will substitute an "inline"
     * version for performance.
     */
    if (!dvmCreateInlineSubsTable()) {
        return "dvmCreateInlineSubsTable failed";
    }

    /*
     * Miscellaneous class library validation.
     */
    if (!dvmValidateBoxClasses()) {
        return "dvmValidateBoxClasses failed";
    }

    /*
     * Do the last bits of Thread struct initialization we need to allow
     * JNI calls to work.
     */
    if (!dvmPrepMainForJni(pEnv)) {
        return "dvmPrepMainForJni failed";
    }

    /*
     * Explicitly initialize java.lang.Class.  This doesn't happen
     * automatically because it's allocated specially (it's an instance
     * of itself).  Must happen before registration of system natives,
     * which make some calls that throw assertions if the classes they
     * operate on aren't initialized.
     */
    if (!dvmInitClass(gDvm.classJavaLangClass)) {
        return "couldn't initialized java.lang.Class";
    }

    /*
     * Register the system native methods, which are registered through JNI.
     */
    if (!registerSystemNatives(pEnv)) {
        return "couldn't register system natives";
    }

    /*
     * Do some "late" initialization for the memory allocator.  This may
     * allocate storage and initialize classes.
     */
    if (!dvmCreateStockExceptions()) {
        return "dvmCreateStockExceptions failed";
    }

    /*
     * At this point, the VM is in a pretty good state.  Finish prep on
     * the main thread (specifically, create a java.lang.Thread object to go
     * along with our Thread struct).  Note we will probably be executing
     * some interpreted class initializer code in here.
     */
    if (!dvmPrepMainThread()) {
        return "dvmPrepMainThread failed";
    }

    /*
     * Make sure we haven't accumulated any tracked references.  The main
     * thread should be starting with a clean slate.
     */
    if (dvmReferenceTableEntries(&dvmThreadSelf()->internalLocalRefTable) != 0)
    {
        ALOGW("Warning: tracked references remain post-initialization");
        dvmDumpReferenceTable(&dvmThreadSelf()->internalLocalRefTable, "MAIN");
    }

    /* general debugging setup */
    if (!dvmDebuggerStartup()) {
        return "dvmDebuggerStartup failed";
    }

    if (!dvmGcStartupClasses()) {
        return "dvmGcStartupClasses failed";
    }

    /*
     * Init for either zygote mode or non-zygote mode.  The key difference
     * is that we don't start any additional threads in Zygote mode.
     */
    if (gDvm.zygote) {
        if (!initZygote()) {
            return "initZygote failed";
        }
    } else {
        if (!dvmInitAfterZygote()) {
            return "dvmInitAfterZygote failed";
        }
    }


#ifndef NDEBUG
    if (!dvmTestHash())
        ALOGE("dvmTestHash FAILED");
    if (false /*noisy!*/ && !dvmTestIndirectRefTable())
        ALOGE("dvmTestIndirectRefTable FAILED");
#endif

    if (dvmCheckException(dvmThreadSelf())) {
        dvmLogExceptionStackTrace();
        return "Exception pending at end of VM initialization";
    }

    scopedShutdown.disarm();
    return "";
}
/*
 * Prepare a ClassPathEntry struct, which at this point only has a valid
 * filename.  We need to figure out what kind of file it is, and for
 * everything other than directories we need to open it up and see
 * what's inside.
 */
static bool prepareCpe(ClassPathEntry* cpe, bool isBootstrap)
{
    struct stat sb;

    if (stat(cpe->fileName, &sb) < 0) {
        ALOGD("Unable to stat classpath element '%s'", cpe->fileName);
        return false;
    }
    if (S_ISDIR(sb.st_mode)) {
        ALOGE("Directory classpath elements are not supported: %s", cpe->fileName);
        return false;
    }

    char suffix[10];
    getFileNameSuffix(cpe->fileName, suffix, sizeof(suffix));

    if ((strcmp(suffix, "jar") == 0) || (strcmp(suffix, "zip") == 0) ||
            (strcmp(suffix, "apk") == 0)) {
        JarFile* pJarFile = NULL;
        if (dvmJarFileOpen(cpe->fileName, NULL, &pJarFile, isBootstrap) == 0) {
            cpe->kind = kCpeJar;
            cpe->ptr = pJarFile;
            return true;
        }
    } else if (strcmp(suffix, "dex") == 0) {
        RawDexFile* pRawDexFile = NULL;
        if (dvmRawDexFileOpen(cpe->fileName, NULL, &pRawDexFile, isBootstrap) == 0) {
            cpe->kind = kCpeDex;
            cpe->ptr = pRawDexFile;
            return true;
        }
    } else {
        ALOGE("Unknown type suffix '%s'", suffix);
    }

    ALOGD("Unable to process classpath element '%s'", cpe->fileName);
    return false;
}

/*
 * Open a Jar file.  It's okay if it's just a Zip archive without all of
 * the Jar trimmings, but we do insist on finding "classes.dex" inside
 * or an appropriately-named ".odex" file alongside.
 *
 * If "isBootstrap" is not set, the optimizer/verifier regards this DEX as
 * being part of a different class loader.
 */
int dvmJarFileOpen(const char* fileName, const char* odexOutputName,
    JarFile** ppJarFile, bool isBootstrap)
{
    /*
     * TODO: This function has been duplicated and modified to become
     * dvmRawDexFileOpen() in RawDexFile.c. This should be refactored.
     */

    ZipArchive archive;
    DvmDex* pDvmDex = NULL;
    char* cachedName = NULL;
    bool archiveOpen = false;
    bool locked = false;
    int fd = -1;
    int result = -1;

    /* Even if we're not going to look at the archive, we need to
     * open it so we can stuff it into ppJarFile.
     */
    if (dexZipOpenArchive(fileName, &archive) != 0)
        goto bail;
    archiveOpen = true;

    /* If we fork/exec into dexopt, don't let it inherit the archive's fd.
     */
    dvmSetCloseOnExec(dexZipGetArchiveFd(&archive));

    /* First, look for a ".odex" alongside the jar file.  It will
     * have the same name/path except for the extension.
     */
    fd = openAlternateSuffix(fileName, "odex", O_RDONLY, &cachedName);
    if (fd >= 0) {
        ALOGV("Using alternate file (odex) for %s ...", fileName);
        if (!dvmCheckOptHeaderAndDependencies(fd, false, 0, 0, true, true)) {
            ALOGE("%s odex has stale dependencies", fileName);
            free(cachedName);
            cachedName = NULL;
            close(fd);
            fd = -1;
            goto tryArchive;
        } else {
            ALOGV("%s odex has good dependencies", fileName);
            //TODO: make sure that the .odex actually corresponds
            //      to the classes.dex inside the archive (if present).
            //      For typical use there will be no classes.dex.
        }
    } else {
        ZipEntry entry;

tryArchive:
        /*
         * Pre-created .odex absent or stale.  Look inside the jar for a
         * "classes.dex".
         */
        entry = dexZipFindEntry(&archive, kDexInJarName);
        if (entry != NULL) {
            bool newFile = false;

            /*
             * We've found the one we want.  See if there's an up-to-date copy
             * in the cache.
             *
             * On return, "fd" will be seeked just past the "opt" header.
             *
             * If a stale .odex file is present and classes.dex exists in
             * the archive, this will *not* return an fd pointing to the
             * .odex file; the fd will point into dalvik-cache like any
             * other jar.
             */
            if (odexOutputName == NULL) {
                cachedName = dexOptGenerateCacheFileName(fileName,
                                kDexInJarName);
                if (cachedName == NULL)
                    goto bail;
            } else {
                cachedName = strdup(odexOutputName);
            }
            ALOGV("dvmJarFileOpen: Checking cache for %s (%s)",
                fileName, cachedName);
            fd = dvmOpenCachedDexFile(fileName, cachedName,
                    dexGetZipEntryModTime(&archive, entry),
                    dexGetZipEntryCrc32(&archive, entry),
                    isBootstrap, &newFile, /*createIfMissing=*/true);
            if (fd < 0) {
                ALOGI("Unable to open or create cache for %s (%s)",
                    fileName, cachedName);
                goto bail;
            }
            locked = true;

            /*
             * If fd points to a new file (because there was no cached version,
             * or the cached version was stale), generate the optimized DEX.
             * The file descriptor returned is still locked, and is positioned
             * just past the optimization header.
             */
            if (newFile) {
                u8 startWhen, extractWhen, endWhen;
                bool result;
                off_t dexOffset;

                dexOffset = lseek(fd, 0, SEEK_CUR);
                result = (dexOffset > 0);

                if (result) {
                    startWhen = dvmGetRelativeTimeUsec();
                    result = dexZipExtractEntryToFile(&archive, entry, fd) == 0;
                    extractWhen = dvmGetRelativeTimeUsec();
                }
                if (result) {
                    result = dvmOptimizeDexFile(fd, dexOffset,
                                dexGetZipEntryUncompLen(&archive, entry),
                                fileName,
                                dexGetZipEntryModTime(&archive, entry),
                                dexGetZipEntryCrc32(&archive, entry),
                                isBootstrap);
                }

                if (!result) {
                    ALOGE("Unable to extract+optimize DEX from '%s'",
                        fileName);
                    goto bail;
                }

                endWhen = dvmGetRelativeTimeUsec();
                ALOGD("DEX prep '%s': unzip in %dms, rewrite %dms",
                    fileName,
                    (int) (extractWhen - startWhen) / 1000,
                    (int) (endWhen - extractWhen) / 1000);
            }
        } else {
            ALOGI("Zip is good, but no %s inside, and no valid .odex "
                    "file in the same directory", kDexInJarName);
            goto bail;
        }
    }

    /*
     * Map the cached version.  This immediately rewinds the fd, so it
     * doesn't have to be seeked anywhere in particular.
     */
    if (dvmDexFileOpenFromFd(fd, &pDvmDex) != 0) {
        ALOGI("Unable to map %s in %s", kDexInJarName, fileName);
        goto bail;
    }

    if (locked) {
        /* unlock the fd */
        if (!dvmUnlockCachedDexFile(fd)) {
            /* uh oh -- this process needs to exit or we'll wedge the system */
            ALOGE("Unable to unlock DEX file");
            goto bail;
        }
        locked = false;
    }

    ALOGV("Successfully opened '%s' in '%s'", kDexInJarName, fileName);

    *ppJarFile = (JarFile*) calloc(1, sizeof(JarFile));
    (*ppJarFile)->archive = archive;
    (*ppJarFile)->cacheFileName = cachedName;
    (*ppJarFile)->pDvmDex = pDvmDex;
    cachedName = NULL;      // don't free it below
    result = 0;

bail:
    /* clean up, closing the open file */
    if (archiveOpen && result != 0)
        dexZipCloseArchive(&archive);
    free(cachedName);
    if (fd >= 0) {
        if (locked)
            (void) dvmUnlockCachedDexFile(fd);
        close(fd);
    }
    return result;
}
//我们可以看到它会优先判断该jar或者apk是否有odex文件,如果有就用dvmCheckOptHeaderAndDependencies函数检测odex的头
fd = openAlternateSuffix(fileName, "odex", O_RDONLY, &cachedName);
    if (fd >= 0) {
        ALOGV("Using alternate file (odex) for %s ...", fileName);
        if (!dvmCheckOptHeaderAndDependencies(fd, false, 0, 0, true, true)) {
            ALOGE("%s odex has stale dependencies", fileName);
            free(cachedName);
            cachedName = NULL;
            close(fd);
            fd = -1;
            goto tryArchive;
        } else {
            ALOGV("%s odex has good dependencies", fileName);
            //TODO: make sure that the .odex actually corresponds
            //      to the classes.dex inside the archive (if present).
            //      For typical use there will be no classes.dex.
        }
    } else {
        ZipEntry entry;
struct DexOptHeader {
    u1  magic[8];           /* includes version number */

    u4  dexOffset;          /* file offset of DEX header */
    u4  dexLength;
    u4  depsOffset;         /* offset of optimized DEX dependency table */
    u4  depsLength;
    u4  optOffset;          /* file offset of optimized data tables */
    u4  optLength;

    u4  flags;              /* some info flags */
    u4  checksum;           /* adler32 checksum covering deps/opt */

    /* pad for 64-bit alignment if necessary */
};

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 3
支持
分享
最新回复 (4)
雪    币: 756
活跃值: (587)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
前排,膜拜
2016-4-22 22:38
0
雪    币: 6
活跃值: (19)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
谢谢大神分享
2016-4-22 23:15
0
雪    币: 64
活跃值: (67)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
5
建议漏出 做   SO   VM级的加壳 吧,不要 研究dex了。。。。java层的东西商业价值不大。移动里面唯独游戏最具商业价值。其他都是浮云。。。。
2016-4-23 01:00
0
雪    币: 34
活跃值: (50)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
6
妹子你这个提议不错,这篇文章研究的是odex如何绕过VM的有效检测,你说的so在jni部分。回头我看看这部分的实现原理。
2016-4-23 08:18
0
游客
登录 | 注册 方可回帖
返回
//