ADVMP 三代壳(vmp加固)原理分析(执行流程)

发布时间 2023-04-14 13:12:15作者: 明月照江江

由于在加壳时插入了System.loadLibrary("advmp");,看一下JNI_OnLoad

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env = NULL;

    if (vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK) {
        return JNI_ERR;
    }

    // 注册本地方法。
    registerFunctions(env);

    // 获得apk路径。
    gAdvmp.apkPath = GetAppPath(env);
    MY_LOG_INFO("apk path:%s", gAdvmp.apkPath);

    // 释放yc文件。
    gAdvmp.ycSize = ReleaseYcFile(gAdvmp.apkPath, &gAdvmp.ycData);
    if (0 == gAdvmp.ycSize) {
        MY_LOG_WARNING("release Yc file fail!");
        goto _ret;
    }

    // 解析yc文件。
    gAdvmp.ycFile = new YcFile;
    if (!gAdvmp.ycFile->parse(gAdvmp.ycData, gAdvmp.ycSize)) {
        MY_LOG_WARNING("parse Yc file fail.");
        goto _ret;
    }

_ret:
    return JNI_VERSION_1_4;
}
在这里解析了yc文件,并保存在了内存中(gAdvmp.ycFile)

看一下MainActivity

public class MainActivity extends Activity {
	
	public static final String TAG = "debug";
	
	private Button mbtnTest;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		nativeLog();
		
		mbtnTest = (Button) findViewById(R.id.btnTest);
		mbtnTest.setOnClickListener(new View.OnClickListener() {
			
			@Override
			public void onClick(View v) {
				int result = separatorTest(2);
				Log.i(TAG, "separatorTest result:" + result);
			}
		});
	}
	
	private native int separatorTest(int value);
	
	private native static void nativeLog();
	
	static {
		System.loadLibrary("advmp");
	}
	
}

壳已经将separatorTest 转化为了native方法了

当执行时就会执行到相应的native方法(也是之前壳生成的cpp代码)

jint separatorTest(JNIEnv* env, jobject thiz, jint value) {
    MY_LOG_INFO("separatorTest - value=%d", value);
    jvalue result = BWdvmInterpretPortable(gAdvmp.ycFile->GetSeparatorData(0), env, thiz, value);
    return result.i;
}

关键就是BWdvmInterpretPortable,这个函数实现的转发,看一下它的实现, 它在InterpC.cpp,这是一个自定义指令解释器的实现

InterpC.cpp::BWdvmInterpretPortable

jvalue BWdvmInterpretPortable(const SeparatorData* separatorData, JNIEnv* env, jobject thiz, ...) {
    jvalue* params = NULL; // 参数数组。
    jvalue retval;  // 返回值。

    const u2* pc;   // 程序计数器。
    u4 fp[65535];   // 寄存器数组。
    u2 inst;        // 当前指令。
    u2 vsrc1, vsrc2, vdst;      // usually used for register indexes

    unsigned int startIndex;

    // 处理参数。
    va_list args;
    va_start(args, thiz); 
    params = getParams(separatorData, args);
    va_end(args);

    // 获得参数寄存器个数。    
    size_t paramRegCount = getParamRegCount(separatorData);

    // 设置参数寄存器的值。
    if (isStaticMethod(separatorData)) {
        startIndex = separatorData->registerSize - separatorData->paramSize;
    } else {
        startIndex = separatorData->registerSize - separatorData->paramSize;
        fp[startIndex++] = (u4)thiz;
    }
    for (int i = startIndex, j = 0; j < separatorData->paramSize; j++ ) {
        if ('D' == separatorData->paramShortDesc.str[i] || 'J' == separatorData->paramShortDesc.str[i]) {
            fp[i++] = params[j].j & 0xFFFFFFFF;
            fp[i++] = (params[j].j >> 32) & 0xFFFFFFFF;
        } else {
            fp[i++] = params[j].i;
        }
    }

    pc = separatorData->insts;

    /* static computed goto table */
    DEFINE_GOTO_TABLE(handlerTable);

    // 抓取第一条指令。
    FINISH(0);

/*--- start of opcodes ---*/

/* File: c/OP_NOP.cpp */
HANDLE_OPCODE(OP_NOP)
    FINISH(1);
OP_END

这里通过数组来模拟寄存器, 通过int指针来模拟pc寄存器,完成各个指令的运算,比如

HANDLE_OPCODE(OP_MOVE /*vA, vB*/)
    vdst = INST_A(inst);
    vsrc1 = INST_B(inst);
    MY_LOG_VERBOSE("|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

HANDLE_OPCODE(OP_MOVE /*vA, vB*/),生成的是一个goto用的标签,
在FINISH中会有一个goto来实现跳转

# define FINISH(_offset) {                                                  \
        ADJUST_PC(_offset);                                                 \
        inst = FETCH(0);                                                    \
        /*if (self->interpBreak.ctl.subMode) {*/                                \
            /*dvmCheckBefore(pc, fp, self);*/                                   \
        /*}*/                                                                   \
        goto *handlerTable[INST_INST(inst)];                                \
    }

通过这种方式来完成多行指令的顺序执行
知道遇到RETURN指令结束执行,返回返回值

HANDLE_OPCODE(OP_RETURN /*vAA*/)
    vsrc1 = INST_AA(inst);
    MY_LOG_VERBOSE("|return%s v%d",
        (INST_INST(inst) == OP_RETURN) ? "" : "-object", vsrc1);
    retval.i = GET_REGISTER(vsrc1);
    /*GOTO_returnFromMethod();*/
    GOTO_bail();
OP_END
.....

bail:
    if (NULL != params) {
        delete[] params;
    }
    MY_LOG_INFO("|-- Leaving interpreter loop");
    return retval;
执行原理结论:
  1. JNI_OnLoad 读取yc文件,获取指令
  2. native 中执行BWdvmInterpretPortable 主要入参为从yc中获得的separatorData
  3. 通过自定义解释器逐行指令指令,并返回返回值

ps: 这个开源项目提供的是一种思路,不可用于商用, 仅支持计算用指令的解释, 引用类的指令解释未实现