zoukankan      html  css  js  c++  java
  • 安卓 dex 通用脱壳技术研究(一)

    注:以下4篇博文中,部分图片引用自DexHunter作者zyqqyz在slide.pptx中的图片,版本归原作者所有;

    0x01 背景介绍

    安卓 APP 的保护一般分为下列几个方面:

    1. JAVA/C代码混淆

    2. dex文件加壳

    3. .so文件加壳

    4. 反动态调试技术

    其中混淆和加壳是为了防止对应用的静态分析;代码混淆会增加攻击者的时间成本, 但并不能从根本上解决应用被逆向的问题;而加壳技术一旦被破解,其优势更是荡然无存;反调试用来对抗对APP的动态分析;

    昨天看雪zyqqyz同学发了一个Android应用程序通用自动脱壳方法:DexHunter,详见Github;通过定制Dalvik虚拟机实现,并对目前国内6款主流加固产品的进行了测试,效果良好;下面我们会讲解Dalvik解释器实现原理,并分析DexHunter代码实现;

    目前来看,要对抗这种脱壳方法,最好的办法应该是APP启动时hook被修改的几处函数,并还原之;对开发者来说,应当将涉及资产、创新的关键代码放入Native层实现,并通过.so加壳和反调试进行保护;

    上次与梆梆的交流,他们提到梆梆3.0正在研发类似PC上的VMP保护壳技术,实现APK保护;但个人认为要在兼容性方面做的工作实在太多,短时间内估计很难实现了;

    下面开始,分3个方面介绍通过定制Dalvik的通用脱壳方案:1.Dalvik 解释器原理分析,2.DexHunter代码分析,3.测试

    0x02 Dalvik 解释器原理分析

    解释器是Dalvik虚拟机的执行引擎,它负责解释执行Dalvik字节码。在字节码加载完毕后,Dalvik虚拟机调用解释器开始取指解释字节码,解释器跳转到解释程序处执行。目前安卓解释器有两种,Portable和Fast解释器,分别使用C和汇编实现;优势分别是兼容性和性能,具体使用哪个可以自己来指定,因此本着简单的原则,我们分析并使用Portable解释器;

    获取字节码并分析与解释执行是Dalvik虚拟机解释器的主要工作。Dalvik虚拟机的入口函数是vm/interp下的dvmInterpret函数;外部通过调用dvmInterpret函数进入解释器执行,其流程为dvmCallMethod->dvmCallMethodV->dvmInterpret。

    在外部函数调用解释器以后,解释器执行的主要流程有以下几个步骤。

    1. 初始化解释器执行环境

    2. 根据系统参数,选择使用Portable或Fast解释器

    3. 跳转到相应解释器执行

    4. 取指及指令检查

    5. 执行字节码对应程序段

    dvmInterpret函数作为解释器的入口函数,主要完成整个流程的前三部分,执行流程如下:

    由于前三部分与我们定制Dalvik无关,这里不做详细介绍;

    根据解释器的功能,可以想像的到,最简单的模型就是一个大的switch语句,对每条指令进行判断,然后case到相应的代码进行解释,解释完成后又回到switch顶部,如下:

    while (insn) {
        switch (insn) {
            case NOP:
                break;
            case MOV:
                do something;
                break;
            ...
            case OP:
                do something;
                break;
            default:
                break;
        }
        取指;
    }

    然而当解释完成一条指令后,再重新判断指令类型是个昂贵的开销。因为对于每条指令,都将从switch顶部开始判断,也就是从NOP指令开始判断,直到找到相应的指令为止,这使得解释器的执行效率十分低下。

    这类问题的解决方法就是空间换时间,Dalvik就采用了这个思路;它为每条指令分配一个对应的标签(Label),标签标示的是该指令解释程序的开始,每条指令的解释程序末尾,有取指动作,可以取下一条要执行指令;Dalvik具体使用GCC的Threaded Code技术来实现,它使用了一个静态的标签数组,用来存储各个字节码解释程序对应的标签地址,其具体以一个宏来定义:

    dalvik/libdex/DexOpcodes.h

    /*
     * Macro used to generate a computed goto table for use in implementing
     * an interpreter in C.
     */
    #define DEFINE_GOTO_TABLE(_name) 
        static const void* _name[kNumPackedOpcodes] = {                      
            /* BEGIN(libdex-goto-table); GENERATED AUTOMATICALLY BY opcode-gen */ 
            H(OP_NOP),                                                            
            H(OP_MOVE),                                                           
            H(OP_MOVE_FROM16),                                                    
            H(OP_MOVE_16),                                                        
            H(OP_MOVE_WIDE),                                                      
            ...
        }

    下面看下H宏实现:

    #define    H(_op)    &&op_##_op

    那如何根据指令得到相应的Label地址呢?Dalvik中使用了索引号:

    enum Opcode {
        // BEGIN(libdex-opcode-enum); GENERATED AUTOMATICALLY BY opcode-gen
        OP_NOP                          = 0x00,
        OP_MOVE                         = 0x01,
        OP_MOVE_FROM16                  = 0x02,
        OP_MOVE_16                      = 0x03,
        OP_MOVE_WIDE                    = 0x04,
        OP_MOVE_WIDE_FROM16             = 0x05,
        OP_MOVE_WIDE_16                 = 0x06,
        OP_MOVE_OBJECT                  = 0x07,
        ....
    }

    因此整个执行流程就是:取指令->取索引号->取Label得到解释程序地址->执行指令,并取下一条指令。

    上面分析了解释器的基本模型,下面看Dalvik Portable的执行流程。其解析流程如图:

    首先进行相关变量的声明,保存当前正在解释的方法curMethod、程序计数器pc、栈桢指针fp、当前指令inst、指令译码的相关部分包括保存寄存器值vsrc1,vsrc2,vdst、设置方法调用指针methodToCall等。

    通过DEFINE_GOTO_TABLE(handlerTable)宏进行GOTO Label的绑定,获取并拷贝self->interpSave里保存的当前状态,包括方法method、程序计数器pc、堆栈帧curFrame、返回值retval、要分析的Dex文件的类对象信息curMethod->clazz->pDvmDex等已声明的变量。其代码如下:

    dalvik/vm/mterp/out/InterpC-portable.cpp

        /* copy state in */
        curMethod = self->interpSave.method;
        pc = self->interpSave.pc;
        fp = self->interpSave.curFrame;
        retval = self->interpSave.retval;   /* only need for kInterpEntryReturn? */
        methodClassDex = curMethod->clazz->pDvmDex;

    最后通过FINISH(0)来取得第一条指令开始执行字节码解析。

    在Dalvik Portable中,解释程序是由一系列宏控制,以对应的Label来表示,以NOP操作为例,其定义如下:

    dalvik/vm/mterp/out/InterpC-portable.cpp

    /*--- start of opcodes ---*/
    /* File: c/OP_NOP.cpp */
    HANDLE_OPCODE(OP_NOP)
        FINISH(1);
    OP_END
    /* File: c/OP_MOVE.cpp */
    HANDLE_OPCODE(OP_MOVE /*vA, vB*/)
        vdst = INST_A(inst);
        vsrc1 = INST_B(inst);
        ILOGV("|move%s v%d,v%d %s(v%d=0x%08x)",
            (INST_INST(inst) == OP_MOVE) ? "" : "-object", vdst, vsrc1,
            kSpacing, vdst, GET_REGISTER(vsrc1));
        SET_REGISTER(vdst, GET_REGISTER(vsrc1));
        FINISH(1);
    OP_END
    /* File: c/OP_MOVE_FROM16.cpp */
    HANDLE_OPCODE(OP_MOVE_FROM16 /*vAA, vBBBB*/)
        vdst = INST_AA(inst);
        vsrc1 = FETCH(1);
        ILOGV("|move%s/from16 v%d,v%d %s(v%d=0x%08x)",
            (INST_INST(inst) == OP_MOVE_FROM16) ? "" : "-object", vdst, vsrc1,
            kSpacing, vdst, GET_REGISTER(vsrc1));
        SET_REGISTER(vdst, GET_REGISTER(vsrc1));
        FINISH(2);
    OP_END

    HANDLE_OPCODE(OP_NOP)表示对应的是OP_NOP操作,紧接其后的是解释程序的具体实现。到OP_END结束。而在Portable中,所以有解释程序都由C语言编写。NOP操作中的HANDLE_OPCODE、FINISH和OP_END都是宏定义。其中HANDLE_OPCODE和OP_END是成对出现的,OP_END什么也不做:

    #define    OP_END

    所以

    HANDLE_OPCODE(OP_NOP)
        FINISH(1);
    OP_END

    可以翻译为:

    op_OP_NOP:
        FINISH(1);

    对于NOP指令,其完成的工作就是什么也不做。因此,对应的解释程序就是直接取下一条将要执行的指令,也就是FINISH(1)所完成的工作。在FINISH()宏里,虚拟机获取下一条指令,并从指令中提取操作码号,根据该操作码号到指令解释程序查找表中得到相应的标签,然后跳转到该处理程序执行。其定义如下:

    # define FINISH(_offset) {                                                  
            ADJUST_PC(_offset);                                                 
            inst = FETCH(0);                                                    
            if (self->interpBreak.ctl.subMode) {                                
                dvmCheckBefore(pc, fp, self);                                   
            }                                                                   
            goto *handlerTable[INST_INST(inst)];                                
        }
  • 相关阅读:
    Yii Model中添加默认搜索条件
    Yii CModel中rules验证 获取错误信息
    Yii CModel中rules验证规则[转]
    json转jsonscheme在线转换工具地址
    git的基本使用
    python笔记(二)-打印、注释、输入、变量命名
    python笔记(一)-pycharm的简单使用
    python自动化-操作excel方法的封装
    python基础学习-excel的读写
    Robot Framework中处理时间控件的选择
  • 原文地址:https://www.cnblogs.com/gm-201705/p/9901535.html
Copyright © 2011-2022 走看看