查看原文
其他

Dalvik解释器源码到VMP分析

glider菜鸟 看雪学院 2021-03-08

本文为看雪论坛优秀文章

看雪论坛作者ID:glider菜鸟



前言



学习这块的主要目的还是想知道vmp是如何实现的,如何与系统本身的虚拟机配合工作,所以简单的学习了Dalvik的源码并对比分析了数字公司的解释器。笔记结构如下:

1、dalvik解释器分析
  • dalvik解释器解释指令前的准备工作

  • dalvik解释器的模型

  • invoke-super指令实例分析

2、数字壳解释器分析
  • 解释器解释指令前的准备工作

  • 解释器的模型

  • invoke-super指令实例分析



dalvik解释器分析



dalvik解释器解释指令前的准备工作


从外部进入解释器的调用链如下:
dvmCallMethod -> dvmCallMethodV -> dvmInterpret
 
这三个函数是在解释器取指令,选分支之前被调用,主要负责一些准备工作,包括分配虚拟寄存器,放入参数,初始化解释器参数等。其中dvmCallMethod,直接调用了dvmCallMethodV.下面分析下后两个函数。

dvmCallMethodV


dalvik虚拟机是基于寄存器架构的,可想而知,在具体执行函数之前,首先要做的就是分配好虚拟寄存器空间,并且将函数所需的参数,放入虚拟寄存器中。主要流程:

1. 取出函数的简单声明,如onCreate函数的简单声明为:VL

2. 分配虚拟寄存器栈

3. 放入this参数,根据参数类型放入申明中的参数

4. 如果方法是native方法,直接跳转到method->nativeFunc执行

5. 如果方法是java方法,进入dvmInterpret解释执行


void dvmCallMethodV(Thread* self, const Method* method, Object* obj, bool fromJni, JValue* pResult, va_list args)
{
    //取出方法的简要声明
    const char* desc = &(method->shorty[1]); // [0] is the return type.
    int verifyCount = 0;
    ClassObject* clazz;
    u4* ins;
    //访问权限检查,以及分配函数调用栈,在栈中维护了一份虚拟寄存器列表。
    clazz = callPrep(self, method, obj, false);
    if (clazz == NULL)
        return;
 
    /* "ins" for new frame start at frame pointer plus locals */
    //指向第一个参数
    ins = ((u4*)self->interpSave.curFrame) + (method->registersSize - method->insSize);
 
    //放入this指针,到第一个参数。
    /* put "this" pointer into in0 if appropriate */
    if (!dvmIsStaticMethod(method)) {
        *ins++ = (u4) obj;
        verifyCount++;
    }
    //根据后续参数的类型,放入后续参数
    while (*desc != '\0') {
        switch (*(desc++)) {
            case 'D': case 'J': {
                u8 val = va_arg(args, u8);
                memcpy(ins, &val, 8); // EABI prevents direct store
                ins += 2;
                verifyCount += 2;
                break;
            }
            case 'F': {
                /* floats were normalized to doubles; convert back */
                float f = (float) va_arg(args, double);
                *ins++ = dvmFloatToU4(f);
                verifyCount++;
                break;
            }
            case 'L': { /* 'shorty' descr uses L for all refs, incl array */
                void* arg = va_arg(args, void*);
                assert(obj == NULL || dvmIsHeapAddress(obj));
                jobject argObj = reinterpret_cast<jobject>(arg);
                if (fromJni)
                    *ins++ = (u4) dvmDecodeIndirectRef(self, argObj);
                else
                    *ins++ = (u4) argObj;
                verifyCount++;
                break;
            }
            default: {
                /* Z B C S I -- all passed as 32-bit integers */
                *ins++ = va_arg(args, u4);
                verifyCount++;
                break;
            }
        }
    }
    //如果是本地方法,就直接跳转到本地方法,若是java方法,进入解释器,解释执行。
    if (dvmIsNativeMethod(method)) {
        TRACE_METHOD_ENTER(self, method);
        /*
         * Because we leave no space for local variables, "curFrame" points
         * directly at the method arguments.
         */

        (*method->nativeFunc)((u4*)self->interpSave.curFrame, pResult,
                              method, self);
        TRACE_METHOD_EXIT(self, method);
    } else {
        dvmInterpret(self, method, pResult);
    }
    dvmPopFrame(self);
}


dvmInterpret


dvmInterpret作为虚拟机的入口,主要做了如下工作:
1. 初始化解释器的执行环境。主要是对解释器的变量进行初始化,如将要执行方法的指针,当前函数栈的指针,程序计数器等。
2. 判断将要执行的方法是否合法
3. JIT环境的设置
4. 根据系统参数选择解释器(Fast解释器或者Portable解释器)


void dvmInterpret(Thread* self, const Method* method, JValue* pResult)
{
    //解释器的状态
    InterpSaveState interpSaveState;
    ExecutionSubModes savedSubModes;
 
#if defined(WITH_JIT)
    double calleeSave[JIT_CALLEE_SAVE_DOUBLE_COUNT];
#endif
 
    //保存之前的解释器状态,并将新的状态和之前的状态连接起来(链表)
    interpSaveState = self->interpSave;
    self->interpSave.prev = &interpSaveState;
    /*
     * Strip out and save any flags that should not be inherited by
     * nested interpreter activation.
     */

    savedSubModes = (ExecutionSubModes)(
              self->interpBreak.ctl.subMode & LOCAL_SUBMODE);
    if (savedSubModes != kSubModeNormal) {
        dvmDisableSubMode(self, savedSubModes);
    }
#if defined(WITH_JIT)
    dvmJitCalleeSave(calleeSave);
#endif
 
#if defined(WITH_TRACKREF_CHECKS)
    self->interpSave.debugTrackedRefStart =
        dvmReferenceTableEntries(&self->internalLocalRefTable);
#endif
    self->debugIsMethodEntry = true;
#if defined(WITH_JIT)
    /* Initialize the state to kJitNot */
    self->jitState = kJitNot;
#endif
 
    /初始化解释器的执行环境
 
    self->interpSave.method = method; //初始化执行的方法
    self->interpSave.curFrame = (u4*) self->interpSave.curFrame; //初始化函数调用栈
    self->interpSave.pc = method->insns; //初始化程序计数器
    //检查方法是否为本地方法
    assert(!dvmIsNativeMethod(method));
    //方法的类是否初始化
    if (method->clazz->status < CLASS_INITIALIZING || method->clazz->status == CLASS_ERROR)
    {
        ALOGE("ERROR: tried to execute code in unprepared class '%s' (%d)",
            method->clazz->descriptor, method->clazz->status);
        dvmDumpThread(self, false);
        dvmAbort();
    }
    // 选择解释器
    typedef void (*Interpreter)(Thread*);
    Interpreter stdInterp;
    if (gDvm.executionMode == kExecutionModeInterpFast)
        stdInterp = dvmMterpStd;
#if defined(WITH_JIT)
    else if (gDvm.executionMode == kExecutionModeJit ||
             gDvm.executionMode == kExecutionModeNcgO0 ||
             gDvm.executionMode == kExecutionModeNcgO1)
        stdInterp = dvmMterpStd;
#endif
    else
        stdInterp = dvmInterpretPortable;
 
    // Call the interpreter
    (*stdInterp)(self);
 
    *pResult = self->interpSave.retval;
 
    /* Restore interpreter state from previous activation */
    self->interpSave = interpSaveState;
#if defined(WITH_JIT)
    dvmJitCalleeRestore(calleeSave);
#endif
    if (savedSubModes != kSubModeNormal) {
        dvmEnableSubMode(self, savedSubModes);
    }
}


dalvik解释器流程分析


dalvik解释器有两种:Fast解释器,Portable解释器。选择分析Portable解释器,因为Portable解释器的可读性更好。在分析前,先看下Portable解释器的模型。

Thread Code技术


实现解释器的一个常见思路如下代码,循环取指令,然后判断指令类型,去相应分支执行,执行完成后,再返回到switch执行下条指令。

while (*ins) {
    switch (*ins) {
        case NOP:
            break;
        case MOV:
            break;
        ......
    }
}


但是当每次执行一条指令,都需要重新判断下条指令类型,然后选择switch分支,这是个昂贵的开销。Dalvik为了解决这个问题,引入了Thread Code技术。

简单的说就是在执行函数之前,建立一个分发表GOTO_TABLE,每条指令在表中有一个对应条目,条目里存放的就是处理该条指令的handler地址。

比如invoke-super指令,它的opcode为6f,那么处理该条指令的handler地址就是:GOTO_TABLE[6f].那么在每条指令的解释程序末尾,都可以加上取指动作,然后goto到下条指令的handler。

dvmInterpretPortable源码分析


dvmInterpretPortable是Portable型虚拟机的具体实现,流程如下:

1. 初始化一些关于虚拟机执行环境的变量

2. 初始化分发表

3. FINISH(0)开始执行指令


void dvmInterpretPortable(Thread* self)
{
 
    DvmDex* methodClassDex; // curMethod->clazz->pDvmDex
    JValue retval;
 
    //一些核心的状态
    const Method* curMethod; // 要执行的方法
    const u2* pc; // 指令计数器
    u4* fp; // 函数栈指针
    u2 inst; // 当前指令
    /* instruction decoding */
    u4 ref; // 用来表示类的引用
    u2 vsrc1, vsrc2, vdst; // 寄存器索引
    /* method call setup */
    const Method* methodToCall;
    bool methodCallRange;
 
    //建立分发表
    DEFINE_GOTO_TABLE(handlerTable);
 
    //初始化上面定义的变量
    curMethod = self->interpSave.method;
    pc = self->interpSave.pc;
    fp = self->interpSave.curFrame;
    retval = self->interpSave.retval; /* only need for kInterpEntryReturn? */
    methodClassDex = curMethod->clazz->pDvmDex;
 
    if (self->interpBreak.ctl.subMode != 0) {
        TRACE_METHOD_ENTER(self, curMethod);
        self->debugIsMethodEntry = true; // Always true on startup
    }
 
    methodToCall = (const Method*) -1;
 
    //取出第一条指令,并且执行
    FINISH(0); /* fetch and execute first instruction */
 
//下面就是定义了每条指令的处理分支。
//NOP指令的处理程序:什么都不做,然后处理下条指令
HANDLE_OPCODE(OP_NOP)
    FINISH(1);
OP_END
.....


invoke-super指令实例分析


invoke-super这条指令的handler如下:

#define GOTO_invoke(_target, _methodCallRange) \
    do { \
        methodCallRange = _methodCallRange; \
        goto _target; \
    } while(false)
 
HANDLE_OPCODE(OP_INVOKE_SUPER /*vB, {vD, vE, vF, vG, vA}, meth@CCCC*/)
    GOTO_invoke(invokeSuper, false);
OP_END


invokeSuper这个标签定义如下:

//invoke-super位描述符如下:A|G|op BBBB F|E|D|C
//methodCallRange depending on whether this is a "/range" instruction.
GOTO_TARGET(invokeSuper, bool methodCallRange)
    {
        Method* baseMethod;
        u2 thisReg;
 
        EXPORT_PC();
        //取出AG的值
        vsrc1 = INST_AA(inst);
        //要调用的method索引
        ref = FETCH(1);
        //要作为参数的寄存器的索引
        vdst = FETCH(2);
 
        //取出this寄存器的索引,比如thisReg为3的话,表示第三个寄存器,放的是this参数。
        if (methodCallRange) {
            ILOGV("|invoke-super-range args=%d @0x%04x {regs=v%d-v%d}",
                vsrc1, ref, vdst, vdst+vsrc1-1);
            thisReg = vdst;
        } else {
            ILOGV("|invoke-super args=%d @0x%04x {regs=0x%04x %x}",
                vsrc1 >> 4, ref, vdst, vsrc1 & 0x0f);
            thisReg = vdst & 0x0f;
        }
 
        //检查this 是否为空
        if (!checkForNull((Object*) GET_REGISTER(thisReg)))
            GOTO_exceptionThrown();
 
        //解析要调用的方法
        baseMethod = dvmDexGetResolvedMethod(methodClassDex, ref);
        if (baseMethod == NULL) {
            baseMethod = dvmResolveMethod(curMethod->clazz, ref,METHOD_VIRTUAL);
            if (baseMethod == NULL) {
                ILOGV("+ unknown method or access denied");
                GOTO_exceptionThrown();
            }
        }
 
        if (baseMethod->methodIndex >= curMethod->clazz->super->vtableCount) {
            /*
             * Method does not exist in the superclass. Could happen if
             * superclass gets updated.
             */

            dvmThrowNoSuchMethodError(baseMethod->name);
            GOTO_exceptionThrown();
        }
        methodToCall = curMethod->clazz->super->vtable[baseMethod->methodIndex];
 
#if 0
        if (dvmIsAbstractMethod(methodToCall)) {
            dvmThrowAbstractMethodError("abstract method not implemented");
            GOTO_exceptionThrown();
        }
#else
        assert(!dvmIsAbstractMethod(methodToCall) ||
            methodToCall->nativeFunc != NULL);
#endif
        LOGVV("+++ base=%s.%s super-virtual=%s.%s",
            baseMethod->clazz->descriptor, baseMethod->name,
            methodToCall->clazz->descriptor, methodToCall->name);
        assert(methodToCall != NULL);
        //调用方法
        GOTO_invokeMethod(methodCallRange, methodToCall, vsrc1, vdst);
    }
GOTO_TARGET_END


解析完要调用的方法后,跳转到invokeMethod结构来执行函数调用,invokeMethod为要调用的函数创建虚拟寄存器栈,新的寄存器栈和之前的栈是由重叠的。

然后重新设置解释器执行环境的参数,调用FINISH(0)执行函数:

GOTO_TARGET(invokeMethod, bool methodCallRange, const Method* _methodToCall, u2 count, u2 regs)
{
        //节选
        if (!dvmIsNativeMethod(methodToCall)) {
            /*
             * "Call" interpreted code. Reposition the PC, update the
             * frame pointer and other local state, and continue.
             */

            curMethod = methodToCall; //设置要调用的方法
            self->interpSave.method = curMethod;
            methodClassDex = curMethod->clazz->pDvmDex;
            pc = methodToCall->insns; //重置pc到要调用的方法
            fp = newFp;
            self->interpSave.curFrame = fp;
#ifdef EASY_GDB
            debugSaveArea = SAVEAREA_FROM_FP(newFp);
#endif
            self->debugIsMethodEntry = true; // profiling, debugging
            ILOGD("> pc <-- %s.%s %s", curMethod->clazz->descriptor,
                curMethod->name, curMethod->shorty);
            DUMP_REGS(curMethod, fp, true); // show input args
            FINISH(0); // jump to method start
        }



数字壳解释器分析



数字壳解释执行前的准备工作


进入解释器的流程为onCreate->sub_D930->sub_3FE5C->sub_3FF5C。sub_3FF5C真正的解释器入口,sub_D930和sub_3FE5C负责执行前的准备工作。这部分准备工作和dalvik解释器的准备工作类似。

sub_D930


sub_D930分为两部分,调用sub_66BD4之前为第一部分,之后为第二部分。这两部分主要做的事情如下:


1. 第一部分
(1) jni的一些初始化工作,FindClass,GetMethodID之类的工作
(2)利用java.lang.Thread.getStackTrace获取到调用当前方法的类的类名以及函数名
2. 第二部分
(1)调用sub_66BD4获取一些全局信息,以及待解释函数的信息
(2)构建解释器的虚拟寄存器栈
(3)解析待解释函数的简单声明,将函数参数放入虚拟寄存器


主要分析第二部分,首先引入一些数据结构,这类数据结构是动态分析出来的,有些字段的含义还不清楚标记为unkonw。

struct GlobalInfo {
    DexInfo *dex_info;
    vector<MethodInfo*> method_list;
};
 
struct DexInfo {
    void *dexBase; //指向内存中dex的基址
    int unknow[12];
    void *dexHeader; //指向dex header,用来解析dex的。
    .....
    optable[];
};
 
struct MethodInfo {
    int dex_type_id; //该方法在dex文件中DexTypeId列表的索引
    int dex_class_def_id; // 该方法所在类在dex文件中DexClassDef列表索引
    int unknow;
    int codeoff; //该方法的DexCode结构距离dex头的偏移。
};
 
struct StackInfo {
    JNIEnv * jni_env;
    int registerSize; //函数所需寄存器数量
    DexCode *dexCode; //指向DexCode结构
    int unkonw;
    void *registerSpace; // 放入原始参数, malloc(4 * registerSize)
    void *registerSpace2; // 新建一个object引用上面的参数, malloc(4 * registerSize)
    int unkonw2;
    char key; //解密指令的key
    char origin_key; //计算解密指令key所需的一个数据
};


sub_66BD4返回的是指向GlobalInfo结构的指针。这个全局信息里面包含了dex有关的信息和待解释函数的信息。有了这个信息就可以构建解释器所需的虚拟寄存器栈,完成准备工作。

int __fastcall sub_D930(unsigned int a1, JNIEnv *a2, _DWORD *a3)
{
  //节选
  //获取全局信息
  global_info = sub_66BD4(v109, &v114);
  if ( v114 & 1 )
    j_j_j__ZdlPv(*(v47 + 5));
  if ( global_info && (v52 = *(global_info + 4), (*(global_info + 8) - v52) >> 2 > v4) )
  {
    v53 = v52 + 4 * v4;
    a2c = v3;
    method_info = *v53;
    dexInfo = *global_info;
    DexCode = (**global_info + *(*v53 + 12)); // DexCode
    ((*v3)->PushLocalFrame)();
    //创建并初始化StackInfo结构
    stackinfo = malloc_1(0x20u); // 创建StackInfo结构
    v56 = *DexCode;
    *stackinfo = a2c; // stackinfo->jni_env
    *(stackinfo + 4) = v56; // stackinfo->registerSize
    *(stackinfo + 8) = DexCode; // stackinfo->dexCode
    *(stackinfo + 12) = 0;
    v57 = 4 * v56;
    v58 = malloc_0(4 * v56); // 创建虚拟寄存器栈
    *(stackinfo + 16) = v58;
    memset(v58, v57, 0);
    v59 = malloc_0(v57); // 创建虚拟寄存器栈
    *(stackinfo + 20) = v59;
    memset(v59, v57, 0);
    *(stackinfo + 29) = 0;
    *(stackinfo + 28) = 0;
    v60 = sub_3E268();
    v61 = DexCode;
    *(stackinfo + 28) = *DexCode ^ *(v60 + 24) ^ *(method_info + 4) ^ *method_info; // stackinfo->key
    *(stackinfo + 29) = *(v60 + 24);
}


通过创建并初始化StackInfo结构,就完成了虚拟寄存器栈的创建,可以看到这里分配了两个虚拟寄存器栈。

后面调试发现主要使用的是第二个虚拟寄存器栈。猜测这两个虚拟寄存器栈和dalvik拥有两个虚拟寄存器栈一样的原因一样,是一个用来执行native方法,一个执行java方法。
 
创建完虚拟寄存器栈的下一步工作就是放入函数参数。

int __fastcall sub_D930(unsigned int a1, JNIEnv *a2, _DWORD *a3)
{
    //节选
    v107 = stackinfo;
    proto = (*dexInfo
           + *(*dexInfo
             + *(dexInfo[4] + 60)
             + 4 * *(*dexInfo + *(dexInfo[4] + 76) + 12 * *(*dexInfo + *(dexInfo[4] + 92) + 8 * *(method_info + 4) + 2))));
    do
      v63 = proto++; // 方法的简单声明: VL
    while ( *v63 < 0 );
    v64 = j_j_strlen(proto);
    v99 = v64;
    v65 = *v61 - v61[1]; // 函数内部使用寄存器个数:DexCode.registerSize - DexCode.insSize
    v105 = v65 - 1;
    v66 = v95 + 1;
    v67 = v107;
    if ( !*(method_info + 8) )
    {
      v105 = v65;
      v68 = 4 * v65;
      v69 = *(*(v107 + 20) + v68); // 函数的第一个参数 thisReg
      v96 = *v95;
      //节选
      v111 = v68;
      v73 = (*(v67 + 20) + v68);
      v74 = *v73;
      if ( *v73 && !*(v74 + 4) )
      {
        *(v74 + 4) = 1;
        v78 = v111;
        v77 = v96;
        **(*(v67 + 20) + v111) = v96;
      }
      else
      {
        v75 = v64;
        v76 = malloc_1(8u);                     // 重新创建了一个MainActivity object,并放入第二个register space位置。
        *v76 = 0;
        *(v76 + 4) = 0;
        v77 = v96;
        *v76 = v96;
        *(v76 + 4) = 1;
        *v73 = v76;
        v67 = v107;
        v64 = v75;
        v78 = v111;
      }
      *(*(v67 + 16) + v78) = v77;               // 直接将Activity实例,放入第一个register space的相应位置、
    }
    v108 = v67;
    if ( v64 >
= 2 ) // 处理除了this之外的其他参数。
    {
      v79 = 1;
      v112 = 1;
      do
      {
        v80 = v63[v79++ + 1];
        if ( v80 > 89 )
        {
          if ( v80 == 90 )
          {
            *(*(v67 + 16) + 4 * (v112++ + v105)) = *v66;
            ++v66;
          }
        }
        else
        {
          v81 = v80 - 66;
          if ( v81 <= 0x11 )
            JUMPOUT(__CS__, *(&off_E308 + v81) + 58120);// 调用jni->
newGlobalRef,构造将传递给onCreate的参数
        }
      }
      while ( v79 < v64 );
    }
 
}


这里是从dex文件中提取出函数的简要声明,onCreate的简单声明为VL,然后根据声明,放入参数。首先放入this参数,然后根据后续参数的类型,将参数放入相应位置。和dalvik虚拟机流程类似。

sub_3FE5C


sub_D930完成了虚拟寄存器栈的构建并放入参数后,调用了sub_3FE5C。sub_3FE5C主要负责初始化解释器的一些状态,主要是InterpState结构。

struct InterpState {
    void *pc;
    char key;
    DexInfo *dex_info;
};
 
LOAD:0003FF18                 BL              malloc_1                  ; 创建InterpState结构
LOAD:0003FF1C                 PUSH            {R4}
LOAD:0003FF1E                 POP             {R1}
LOAD:0003FF20                 PUSH            {R0}
LOAD:0003FF22                 POP             {R4}
LOAD:0003FF24                 STR             R4, [SP,#0x74+var_24]
LOAD:0003FF26                 LDRB            R0, [R1,#0x1C]
LOAD:0003FF28                 ADDS            R6, #0x10
LOAD:0003FF2A                 STR             R6, [SP,#0x74+var_2C]
LOAD:0003FF2C                 STR             R6, [R4]                  ; InterpState->pc
LOAD:0003FF2E                 STRB            R0, [R4,#4]               ; InterpState->key
LOAD:0003FF30                 STR             R5, [R4,#8]               ; InterpState->dex_info


数字壳解释器模型


sub_D930和sub_3FE5C完成了解释器的准备工作。sub_3FF5C负责解释执行。数字壳的解释器模型就是上面提到的那种最直观的模型,循环取指令,然后判断指令类型,去相应分支执行,执行完成后,再返回到switch执行下条指令。

//R4寄存器存放的是InterpState结构
LOAD:0003FF5C LDRB R1, [R4,#4] ; 取出key,InterpState->key
LOAD:0003FF5E MOVS R6, #0x31 ; '1'
LOAD:0003FF60 PUSH {R1}
LOAD:0003FF62 POP {R2}
LOAD:0003FF64 ANDS R2, R6
LOAD:0003FF66 LDR R0, loc_402C8
LOAD:0003FF68 PUSH {R0}
LOAD:0003FF6A POP {R3}
LOAD:0003FF6C BICS R3, R1
LOAD:0003FF6E ORRS R3, R2
LOAD:0003FF70 MOVS R2, #0xEF00
LOAD:0003FF74 LSLS R1, R1, #8
LOAD:0003FF76 ANDS R2, R1
LOAD:0003FF78 BICS R0, R1
LOAD:0003FF7A ORRS R0, R2
LOAD:0003FF7C EORS R0, R3
LOAD:0003FF7E LDR R1, [R4] ; 取出pc, Interpstate->pc
LOAD:0003FF80 LDRH R4, [R1] ; 取出指令
 
//执行完invoke-super后
LOAD:000463EC loc_463EC
LOAD:000463EC
LOAD:000463EC LDR R0, [R4]
LOAD:000463EE ADDS R0, #6
LOAD:000463F0 STR R0, [R4] ; InterpState->pc = InterpState->pc + 6
LOAD:000463F2 BL loc_3FF5C ; 跳转到解释器开头,执行下一条指令。


数字壳invoke-super指令分析


invoke-super指令包含了函数调用的过程,可以看到dalvik解释器虚拟寄存器栈是比较复杂的,设计很多数据结构。目前为止数字壳的相关结构中,并未发现类似的结构。

所以想分析下数字壳的解释器是如何处理函数调用的。调试分析后发现,数字壳的解释器其实并未实现真正的函数调用,它是通过调用jni中的CallVoidMethod方法来实现函数调用。
 
处理invoke-super的handler为sub_4878C,流程如下:
1. 提取invoke-super指令中包含的MethodID,从dex中获取到DexMethodId结构,获取函数名。
2. 从DexMethodId中获取ClassId
3. 利用获取的ClassId可以获取到类名,利用jni->FindClass获取类
4. 利用jni->GetMethod获取函数
5. 准备函数参数
6. 利用jni->CallVoidMehtod调用函数,实现invoke-method

int  sub_4878C(int a1, bool methodCallRange, JNIEnv *a3, DexInfo *a4, StackInfo *a5, InterpState *a6, int opNumber, void *a8)
{
  //节选
  a1a = v8;
  dex_base = *v8;
  dex_header = *(a1a + 16);
  key = *(a6 + 4); // 取出key
  v11 = (key << 8) | key;
  DexMethodId = dex_base + *(dex_header + 92) + 8 * (*(*a6 + 2) ^ v11);
  v13 = dex_base + *(dex_header + 60);
  methodName = (dex_base + *(v13 + 4 * *(DexMethodId + 4))); //获取函数名
  do
    v15 = *methodName++ < 0;
  while ( v15 );
  DexMethodId2 = (dex_base + *(dex_header + 92) + 8 * (*(*a6 + 2) ^ ((key << 8) | key)));
  proto = (dex_base
         + *(v13
           + 4 * *(dex_base + *(dex_header + 68) + 4 * *(dex_base + *(dex_header + 76) + 12 * *(DexMethodId + 2) + 4))));
  do
    v17 = *proto++; //获取函数简单声明
  while ( v17 < 0 );
  v18 = *(*a6 + 4);
  if ( v52 == 1 )
    thisReg = v18 ^ ((key << 8) | key); //this参数
  else
    thisReg = (v18 ^ key) & 0xF;
  v20 = 0;
  if ( v51 )
  {
    v21 = *(*(a5 + 20) + 4 * thisReg);
    if ( !v21 || (v20 = *v21) == 0 )
    {
      v30 = ((*v53)->FindClass)(v53, "java/lang/NullPointerException");
      .....
    }
  }
  arg0 = v20; // this object
  v62 = v53;
  v63 = 0;
  if ( ((*v53)->ExceptionCheck)(v53) )
    v63 = 0;
  if ( v51 != 2 && v51 != 4 )
  {
    sub_610BC(a1a, v53, *DexMethodId2); // 获取到classid所指定的类
    return sub_48AE2(v32, v33, v34, v35, a5); // 利用jni->CallVoidMethod调用父类方法
  }
 
 
//48EAA: 构建invoke-super除this外的参数
//49770:调用jni->CallVoidMethod方法


数字壳比较复杂,通过分析可以学到很多东西,比如各种反调试,linker,适配很多版本的动态加载,解释器等,感谢数字公司提供的免费加固。

笔记如果有错误,欢迎指正,也希望大佬们可以交流下其他vmp的实现思路。





- End -







看雪ID:glider菜鸟

https://bbs.pediy.com/user-657393.htm 


*本文由看雪论坛  glider菜鸟  原创,转载请注明来自看雪社区






推荐文章++++

CVE-2017-13258 Android 蓝牙BNEP漏洞分析

Java Socket抓包的一个实现方式

Fuzz 工具 WinAFL 的使用感受—对 FreeImage 图片解析库进行模糊测试

从shellcode学习到缓冲区溢出实战

Ubuntu 18.04.3 LTS 编译安卓6.0.0_r1





进阶安全圈,不得不读的一本书










“阅读原文”一起来充电吧!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存