【Android 逆向】整体加固脱壳 ( DEX 优化流程分析 | dvmDexFileOpenPartial | dexFileParse | 脱壳点 | 获取 dex 文件在内存中的首地址 )

简介: 【Android 逆向】整体加固脱壳 ( DEX 优化流程分析 | dvmDexFileOpenPartial | dexFileParse | 脱壳点 | 获取 dex 文件在内存中的首地址 )

文章目录

前言

一、DexPrepare.cpp 中 rewriteDex() 方法分析

二、DvmDex.cpp 中 dvmDexFileOpenPartial() 方法分析 ( 脱壳点 )

三、DexFile.cpp 中 dexFileParse() 方法分析 ( 脱壳点 )

前言

上一篇博客 【Android 逆向】整体加固脱壳 ( DEX 优化流程分析 | DexPrepare.cpp 中 dvmContinueOptimizati() 函数分析 ) 中 , 分析了 DexPrepare.cpp 中 dvmContinueOptimizati() 方法 , 在其中调用了 rewriteDex() 方法 , 重写 DEX 文件 ;


本篇博客继续分析 DexPrepare.cpp 中 rewriteDex() 方法 ;






一、DexPrepare.cpp 中 rewriteDex() 方法分析


第一个参数 u1* addr 是加载到内存中的 dex 文件的首地址 ;


第二个参数 int len 是内存中的 dex 文件的字节长度 ;


static bool rewriteDex(u1* addr, int len, bool doVerify, bool doOpt,
    DexClassLookup** ppClassLookup, DvmDex** ppDvmDex)


dvmDexFileOpenPartial 函数是 脱壳点 函数 , 通过该函数定位脱壳点 , 然后进行脱壳操作 ;


/*
  * 既然可以直接读取DEX文件,那么创建一个DexFile结构
  * 为了它。
  */
    if (dvmDexFileOpenPartial(addr, len, &pDvmDex) != 0) {
        ALOGE("Unable to create DexFile");
        goto bail;
    }



DexPrepare.cpp 中 rewriteDex() 方法源码 :


/*
 * 对内存映射的DEX文件执行就地重写。
 * 
 * 如果这是从短期子进程(dexopt)调用的,我们可以
 * 疯狂地加载类和分配内存。当天气好的时候
 * 调用以准备字节数组中提供的类,我们可能需要
 * 要保守一点。
 * 
 * 如果“ppClassLookup”为非空,则为指向新分配的
 * DexClassLookup将在成功时返回。
 * 
 * 如果“ppDvmDex”为非空,则将创建新分配的DvmDex结构
 * 成功返回。
 */
static bool rewriteDex(u1* addr, int len, bool doVerify, bool doOpt,
    DexClassLookup** ppClassLookup, DvmDex** ppDvmDex)
{
    DexClassLookup* pClassLookup = NULL;
    u8 prepWhen, loadWhen, verifyOptWhen;
    DvmDex* pDvmDex = NULL;
    bool result = false;
    const char* msgStr = "???";
    /* 如果索引的字节顺序错误,请立即交换它 */
    if (dexSwapAndVerify(addr, len) != 0)
        goto bail;
  /*
  * 既然可以直接读取DEX文件,那么创建一个DexFile结构
  * 为了它。
  */
    if (dvmDexFileOpenPartial(addr, len, &pDvmDex) != 0) {
        ALOGE("Unable to create DexFile");
        goto bail;
    }
  /*
  * 创建类查找表。这最终将被追加
  * 直到最后。奥德克斯。
  * 
  * 我们从DexFile创建一个临时链接,以便
  * 类加载,如下所示。
  */
    pClassLookup = dexCreateClassLookup(pDvmDex->pDexFile);
    if (pClassLookup == NULL)
        goto bail;
    pDvmDex->pDexFile->pClassLookup = pClassLookup;
  /*
  * 如果我们不打算验证或优化这些类,
  * 加载它们没有任何价值,所以尽早退出。
  */
    if (!doVerify && !doOpt) {
        result = true;
        goto bail;
    }
    prepWhen = dvmGetRelativeTimeUsec();
  /*
  * 加载在此DEX文件中找到的所有类。如果它们无法加载
  * 由于某些原因,它们不会得到验证(这是应该的)。
  */
    if (!loadAllClasses(pDvmDex))
        goto bail;
    loadWhen = dvmGetRelativeTimeUsec();
  /*
  * 创建字节码优化器使用的数据结构。
  * 我们需要在几个类中查找方法,因此这可能会导致
  * 一点类加载。我们通常在VM初始化期间执行此操作,但是
  * 对于dexopt on core。jar操作的顺序变得有点棘手,
  * 所以我们把它推迟到这里。
  */
    if (!dvmCreateInlineSubsTable())
        goto bail;
  /*
  * 验证并优化DEX文件(命令行)中的所有类
  * (如果允许的话)。
  * 
  * 这是最大的努力,所以dexopt真的没有办法
  * 在这一点上失败。
  */
    verifyAndOptimizeClasses(pDvmDex->pDexFile, doVerify, doOpt);
    verifyOptWhen = dvmGetRelativeTimeUsec();
    if (doVerify && doOpt)
        msgStr = "verify+opt";
    else if (doVerify)
        msgStr = "verify";
    else if (doOpt)
        msgStr = "opt";
    ALOGD("DexOpt: load %dms, %s %dms, %d bytes",
        (int) (loadWhen - prepWhen) / 1000,
        msgStr,
        (int) (verifyOptWhen - loadWhen) / 1000,
        gDvm.pBootLoaderAlloc->curOffset);
    result = true;
bail:
    /*
     * 成功后,归还来电者要求的物品。
     */
    if (pDvmDex != NULL) {
        /* break link between the two */
        pDvmDex->pDexFile->pClassLookup = NULL;
    }
    if (ppDvmDex == NULL || !result) {
        dvmDexFileFree(pDvmDex);
    } else {
        *ppDvmDex = pDvmDex;
    }
    if (ppClassLookup == NULL || !result) {
        free(pClassLookup);
    } else {
        *ppClassLookup = pClassLookup;
    }
    return result;
}


源码路径 : /dalvik/vm/analysis/DexPrepare.cpp






二、DvmDex.cpp 中 dvmDexFileOpenPartial() 方法分析 ( 脱壳点 )


该函数中的 参数 const void* addr 是 dex 文件在内存中的起始地址 ;


在调用的 dexFileParse 函数中 , 也可以获取到 dex 文件在内存中的首地址 ;



DvmDex.cpp 中 dvmDexFileOpenPartial() 方法源码 :


/*
 * 为“部分”DEX创建DexFile结构。这是一个在
 * 被优化的过程。优化标头未完成
 * 我们没有任何辅助数据表,所以我们必须这样做
 * 初始化过程略有不同。
 * 
 * 错误时返回非零。
 */
int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex)
{
    DvmDex* pDvmDex;
    DexFile* pDexFile;
    int parseFlags = kDexParseDefault;
    int result = -1;
    /* -- 文件不完整,尚未计算新校验和
    if (gDvm.verifyDexChecksum)
        parseFlags |= kDexParseVerifyChecksum;
    */
    pDexFile = dexFileParse((u1*)addr, len, parseFlags);
    if (pDexFile == NULL) {
        ALOGE("DEX parse failed");
        goto bail;
    }
    pDvmDex = allocateAuxStructures(pDexFile);
    if (pDvmDex == NULL) {
        dexFileFree(pDexFile);
        goto bail;
    }
    pDvmDex->isMappedReadOnly = false;
    *ppDvmDex = pDvmDex;
    result = 0;
bail:
    return result;
}


源码路径 : /dalvik/vm/DvmDex.cpp






三、DexFile.cpp 中 dexFileParse() 方法分析 ( 脱壳点 )


/*
 * 解析优化或未优化的。存储在内存中的dex文件。这是
 * 在字节排序和结构对齐修复后调用。
 * 
 * 成功后,返回新分配的文件。
 */
DexFile* dexFileParse(const u1* data, size_t length, int flags)
{
    DexFile* pDexFile = NULL;
    const DexHeader* pHeader;
    const u1* magic;
    int result = -1;
    if (length < sizeof(DexHeader)) {
        ALOGE("too short to be a valid .dex");
        goto bail;      /* bad file format */
    }
    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) {
        magic = data;
        if (memcmp(magic+4, DEX_OPT_MAGIC_VERS, 4) != 0) {
            ALOGE("bad opt version (0x%02x %02x %02x %02x)",
                 magic[4], magic[5], magic[6], magic[7]);
            goto bail;
        }
        pDexFile->pOptHeader = (const DexOptHeader*) data;
        ALOGV("Good opt header, DEX offset is %d, flags=0x%02x",
            pDexFile->pOptHeader->dexOffset, pDexFile->pOptHeader->flags);
        /* parse the optimized dex file tables */
        if (!dexParseOptData(data, length, pDexFile))
            goto bail;
        /* ignore the opt header and appended data from here on out */
        data += pDexFile->pOptHeader->dexOffset;
        length -= pDexFile->pOptHeader->dexOffset;
        if (pDexFile->pOptHeader->dexLength > length) {
            ALOGE("File truncated? stored len=%d, rem len=%d",
                pDexFile->pOptHeader->dexLength, (int) length);
            goto bail;
        }
        length = pDexFile->pOptHeader->dexLength;
    }
    dexFileSetupBasicPointers(pDexFile, data);
    pHeader = pDexFile->pHeader;
    if (!dexHasValidMagic(pHeader)) {
        goto bail;
    }
  /*
  * 验证校验和。这相当快,但确实需要
  * 触摸DEX文件中的每个字节。基本校验和在
  * 字节交换和索引优化。
  */
    if (flags & kDexParseVerifyChecksum) {
        u4 adler = dexComputeChecksum(pHeader);
        if (adler != pHeader->checksum) {
            ALOGE("ERROR: bad checksum (%08x vs %08x)",
                adler, pHeader->checksum);
            if (!(flags & kDexParseContinueOnError))
                goto bail;
        } else {
            ALOGV("+++ adler32 checksum (%08x) verified", adler);
        }
        const DexOptHeader* pOptHeader = pDexFile->pOptHeader;
        if (pOptHeader != NULL) {
            adler = dexComputeOptChecksum(pOptHeader);
            if (adler != pOptHeader->checksum) {
                ALOGE("ERROR: bad opt checksum (%08x vs %08x)",
                    adler, pOptHeader->checksum);
                if (!(flags & kDexParseContinueOnError))
                    goto bail;
            } else {
                ALOGV("+++ adler32 opt checksum (%08x) verified", adler);
            }
        }
    }
  /*
  * 验证SHA-1摘要。(通常我们不想这样做--
  * 摘要用于唯一标识原始DEX文件,以及
  * 无法在索引被字节交换后计算以进行验证
  * (并进行了优化。)
  */
    if (kVerifySignature) {
        unsigned char sha1Digest[kSHA1DigestLen];
        const int nonSum = sizeof(pHeader->magic) + sizeof(pHeader->checksum) +
                            kSHA1DigestLen;
        dexComputeSHA1Digest(data + nonSum, length - nonSum, sha1Digest);
        if (memcmp(sha1Digest, pHeader->signature, kSHA1DigestLen) != 0) {
            char tmpBuf1[kSHA1DigestOutputLen];
            char tmpBuf2[kSHA1DigestOutputLen];
            ALOGE("ERROR: bad SHA1 digest (%s vs %s)",
                dexSHA1DigestToStr(sha1Digest, tmpBuf1),
                dexSHA1DigestToStr(pHeader->signature, tmpBuf2));
            if (!(flags & kDexParseContinueOnError))
                goto bail;
        } else {
            ALOGV("+++ sha1 digest verified");
        }
    }
    if (pHeader->fileSize != length) {
        ALOGE("ERROR: stored file size (%d) != expected (%d)",
            (int) pHeader->fileSize, (int) length);
        if (!(flags & kDexParseContinueOnError))
            goto bail;
    }
    if (pHeader->classDefsSize == 0) {
        ALOGE("ERROR: DEX file has no classes in it, failing");
        goto bail;
    }
    /*
     * Success!
     */
    result = 0;
bail:
    if (result != 0 && pDexFile != NULL) {
        dexFileFree(pDexFile);
        pDexFile = NULL;
    }
    return pDexFile;
}


目录
相关文章
|
29天前
|
搜索推荐 Android开发 iOS开发
安卓与iOS系统的用户界面设计对比分析
本文通过对安卓和iOS两大操作系统的用户界面设计进行对比分析,探讨它们在设计理念、交互方式、视觉风格等方面的差异及各自特点,旨在帮助读者更好地理解和评估不同系统的用户体验。
20 1
|
2月前
|
Android开发 数据安全/隐私保护 iOS开发
安卓与iOS系统的发展趋势与比较分析
【2月更文挑战第6天】 在移动互联网时代,安卓和iOS系统作为两大主流移动操作系统,各自呈现出不同的发展趋势。本文将从技术角度出发,对安卓和iOS系统的发展方向、特点及未来趋势进行比较分析,以期为读者提供更深入的了解和思考。
35 4
|
3月前
|
监控 Android开发 C语言
深度解读Android崩溃日志案例分析2:tombstone日志
深度解读Android崩溃日志案例分析2:tombstone日志
83 0
|
3月前
|
数据安全/隐私保护 Android开发
2023安卓逆向 -- 某合伙apk登录加密分析
2023安卓逆向 -- 某合伙apk登录加密分析
26 0
|
2月前
|
网络协议 算法 Android开发
安卓逆向 -- 实战某峰窝APP(动态分析)
安卓逆向 -- 实战某峰窝APP(动态分析)
31 4
|
2月前
|
安全 搜索推荐 Android开发
Android 与 iOS 的比较分析
【2月更文挑战第5天】 Android 和 iOS 是目前市场上两种最流行的移动操作系统,它们都拥有自己的特点和优势。本文将会分别从操作系统设计、应用生态、安全性等方面对这两种操作系统进行比较和分析,希望能够帮助读者更好地选择适合自己的移动设备。
|
2月前
|
Android开发
安卓逆向 -- Hook多个dex文件
安卓逆向 -- Hook多个dex文件
19 1
|
3月前
|
安全 算法 JavaScript
安卓逆向 -- 关键代码定位与分析技术
安卓逆向 -- 关键代码定位与分析技术
42 0
|
3月前
|
算法 安全 Android开发
安卓逆向 -- Frida Hook某车_sign算法分析
安卓逆向 -- Frida Hook某车_sign算法分析
76 0
|
3月前
|
安全 Android开发 数据安全/隐私保护
安卓逆向 -- SO文件逆向分析
安卓逆向 -- SO文件逆向分析
20 0

热门文章

最新文章