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

2025-11-22 19:19:55

开源项目地址

https://github.com/chago/ADVMP

vmp 加固可以说时各大加固厂商的拳头产品了,这个开源项目虽然不是十分完善,让我们可以一览vmp加固的原理,是十分好的学习资源

vmp 全称: virtual machine protect , 本质是将原来smali对应的代码转化为自定义的代码,然后通过自定义的解释器进行解释和执行

ADVMP 实现了 基本计算相关指令的解释和执行,而一些调用 ,引用 framework 相关api的部分没有实现,但也可以一窥究竟了

源码目录说明

AdvmpTest:测试用的项目。

base:Java项目。里面是一些工具类代码。

control-centre:Java项目。控制加固流程。

separator:Java项目。抽离方法指令,然后将抽离的指令按照自定义格式输出,并同时输出C文件。

template/jni:C代码。里面包含了解释器的代码。

ycformat:自定义的文件格式,用于保存抽取出来指令等数据。

加壳流程分析

control-centre 的 EntryPoint 是加固流程的入口

public static void main(String[] args) {

log.info("------ 进入控制中心 ------");

try {

......

ControlCentre controlCentre = new ControlCentre(opt);

log.info("开始加固。");

if (controlCentre.shell()) {

//log.info

}

} catch (ParseException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

log.info("------ 离开控制中心 ------");

}

主要是执行controlCentre.shell() 这里是加壳主流程

public boolean shell() {

boolean bRet = false;

try {

// 1. 找到所谓的第一个类,比如Application 或者MainActivity

TypeDescription classDesc = AndroidManifestHelper.findFirstClass(new File(mApkUnpackDir, "AndroidManifest.xml"));

//2. 找到第一个类的clinit方法,在当中插入System.loadLibrary指令

InstructionInsert01 instructionInsert01 = new InstructionInsert01(new File(mApkUnpackDir, "classes.dex"), classDesc);

instructionInsert01.insert();

// 3. 运行抽离器。将codeItem 抽出,转换,打包 生成yc文件

runSeparator();

// 4. 从template目录中拷贝jni文件。

copyJniFiles();

// 5. 更新jni文件的内容。

updateJniFiles();

// 6. 编译native代码。

buildNative();

// 7. 将libs目录重命名为lib。

mOpt.libDir = new File(mOpt.jniDir.getParentFile(), "lib");

new File(mOpt.jniDir.getParentFile(), "libs").renameTo(mOpt.libDir);

// 8. 移动yc文件。

File assetsDir = new File(mApkUnpackDir, "assets");

if (!assetsDir.exists()) {

assetsDir.mkdir();

}

File newYcFile = new File(assetsDir, "classes.yc");

Files.move(mOpt.outYcFile.toPath(), newYcFile.toPath());

// 9. 移动classes.dex文件。

Utils.copyFile(new File(mOpt.outYcFile.getParent(), "classes.dex").getAbsolutePath(), new File(mApkUnpackDir, "classes.dex").getAbsolutePath());

// 10. 拷贝lib目录。

Utils.copyFolder(mOpt.libDir.getAbsolutePath(), mApkUnpackDir.getAbsolutePath() + File.separator + "lib");

// 11. 打包

String name = mOpt.apkFile.getName();

name = name.substring(0, name.lastIndexOf('.'));

File outApkFile = new File(mOpt.outDir, name + ".shelled.apk");

ZipHelper.doZip(mApkUnpackDir.getAbsolutePath(), outApkFile.getAbsolutePath());

bRet = true;

} catch (IOException e) {

e.printStackTrace();

} catch (Exception e) {

e.printStackTrace();

}

return bRet;

}

看一下比较核心的 3.运行抽离器

private boolean runSeparator() throws IOException {

SeparatorOption opt = new SeparatorOption();

opt.dexFile = new File(mApkUnpackDir, "classes.dex");

File outDir = new File(mOpt.workspace, "separator");

opt.outDexFile = new File(outDir, "classes.dex");

opt.outYcFile = mOpt.outYcFile = new File(outDir, "classes.yc");

opt.outCPFile = mOpt.outYcCPFile = new File(outDir, "advmp_separator.cpp");

Separator separator = new Separator(opt);

return separator.run();

}

核心流程:是为了生成classes.yc 和advmp_separator.cpp

classes.yc 是一个按格式规则写入的文件,类似之前的二代壳,但这里多了一部指令替换,逆向人员拿到这个文件写入也反编译不出来,(还需要拿到对照表将指令还原才可以)

advmp_separator.cpp 是一个生成的cpp模板代码文件(可以看出壳的本质还是借助生成CPP,然后加入到native源码中打包生成so完成的)

看一下 separator.run()

public boolean run() {

boolean bRet = false;

// 1. 重新生成dex(重要)。

DexFile newDexFile = mDexRewriter.rewriteDexFile(mDexFile);

try {

// 2. 将新dex输出到文件。

DexFileFactory.writeDexFile(mOpt.outDexFile.getAbsolutePath(), newDexFile);

// 3.写Yc文件。

writeYcFile();

// 4.写C文件。

writeCFile();

bRet = true;

} catch (IOException e) {

e.printStackTrace();

}

return bRet;

}

这个mDexRewriter 就很精髓,利用的是dexlib2的能力,对dex中的方法进行了重写

@Nonnull

@Override

public Rewriter getMethodRewriter(Rewriters rewriters) {

return new MethodRewriter(rewriters) {

@Nonnull

@Override

public Method rewrite(@Nonnull Method value) {

if (mConfigHelper.isValid(value)) {

mSeparatedMethod.add(value);

// 抽取代码。

YcFormat.SeparatorData separatorData = new YcFormat.SeparatorData();

separatorData.methodIndex = mSeparatorData.size();

separatorData.accessFlag = value.getAccessFlags();

separatorData.paramSize = value.getParameters().size();

separatorData.registerSize = value.getImplementation().getRegisterCount();

separatorData.paramShortDesc = new StringItem();

separatorData.paramShortDesc.str = MethodHelper.genParamsShortDesc(value).getBytes();

separatorData.paramShortDesc.size = separatorData.paramShortDesc.str.length;

separatorData.insts = MethodHelper.getInstructions((DexBackedMethod) value);

separatorData.instSize = separatorData.insts.length;

separatorData.size = 4 + 4 + 4 + 4 + 4 + separatorData.paramShortDesc.size + 4 + (separatorData.instSize * 2) + 4;

mSeparatorData.add(separatorData);

// 下面这么做的目的是要把方法的name删除,否则生成的dex安装的时候会有这个错误:INSTALL_FAILED_DEXOPT。

List oldParams = value.getParameters();

List newParams = new ArrayList<>();

for (MethodParameter mp : oldParams) {

newParams.add(new ImmutableMethodParameter(mp.getType(), mp.getAnnotations(), null));

}

// 生成一个新的方法。

return new ImmutableMethod(value.getDefiningClass(), value.getName(), newParams, value.getReturnType(), value.getAccessFlags() | AccessFlags.NATIVE.getValue(), value.getAnnotations(), null);

}

return super.rewrite(value);

}

};

}

重写过程中生成了YcFormat(用于生成Yc文件)和mSeparatedMethod(一个Method对象列表)

最后返回了一个空方法

new ImmutableMethod(value.getDefiningClass(), value.getName(), newParams, value.getReturnType(), value.getAccessFlags() | AccessFlags.NATIVE.getValue(), value.getAnnotations(), null);

实现dex中方法代码的抽离

然后调用writeYcFile() 对YcFormat对象进行解析和写入文件(和二代壳类似)

然后调用 writeCFile() (关键)

private void writeCFile() throws IOException {

SeparatorCWriter separatorCWriter = new SeparatorCWriter(mOpt.outCPFile, mSeparatedMethod);

separatorCWriter.write();

}

separatorCWriter.write

public void write() throws IOException {

try (BufferedWriter fileWriter = new BufferedWriter(new FileWriter(mOutFile))) {

int index = 0;

for (Method method : mSeparatedMethod) {

String definingClass = method.getDefiningClass();

if (classes.containsKey(definingClass)) {

classes.get(definingClass).add(method);

} else {

List ms = new ArrayList<>();

ms.add(method);

classes.put(definingClass, ms);

}

writeMethod(index, method, fileWriter);

index++;

}

write_registerNatives(fileWriter);

fileWriter.write("void registerFunctions(JNIEnv* env) {");

fileWriter.newLine();

for (String registerNativesName : registerNativesNames) {

fileWriter.write(String.format("if (!%s(env)) { MY_LOG_ERROR(\"register method fail.\"); return; }", registerNativesName));

fileWriter.newLine();

}

fileWriter.newLine();

fileWriter.write("}");

fileWriter.newLine();

}

}

这里就是想解析mSeparatedMethod(方法列表),生成一个动态注册的模板代码

private void writeMethod(int index, Method method, BufferedWriter fileWriter) throws IOException {

StringBuffer sb = new StringBuffer();

sb.append(MethodHelper.genTypeInNative(method));

sb.append(" ");

sb.append(method.getName());

sb.append(" (");

sb.append(MethodHelper.genParamTypeListInNative(method));

sb.append(") {");

fileWriter.write(sb.toString());

fileWriter.newLine();

sb.delete(0, sb.length());

sb.append("jvalue result = BWdvmInterpretPortable(gAdvmp.ycFile->GetSeparatorData(");

sb.append(index);

sb.append("), env, thiz");

List params = method.getParameterTypes();

for (int i = 0; i < params.size(); i++) {

sb.append(", ");

sb.append(MethodHelper.paramNames[i]);

}

sb.append(");");

fileWriter.write(sb.toString());

fileWriter.newLine();

sb.delete(0, sb.length());

sb.append("return ");

char cType = method.getReturnType().charAt(0);

switch (cType) {

case 'Z':

sb.append("result.z");

break;

case 'B':

sb.append("result.b");

break;

case 'S':

sb.append("result.s");

break;

case 'C':

sb.append("result.c");

break;

case 'I':

sb.append("result.i");

break;

case 'J':

sb.append("result.j");

break;

case 'F':

sb.append("result.f");

break;

case 'D':

sb.append("result.d");

break;

case 'L':

sb.append("result.l");

break;

case '[':

sb.append("result.l");

break;

}

sb.append(";}");

fileWriter.write(sb.toString());

fileWriter.newLine();

}

每个java侧对应的native方法,都由BWdvmInterpretPortable进行转发执行,这个方法十分关键,会转发给自定义解释器进行执行

到这里 抽取步骤就完成了,

然后是构建生成so的步骤,即ControlCenter shell的后续

// 从template目录中拷贝jni文件。

copyJniFiles();

// 更新jni文件的内容。

updateJniFiles();

// 编译native代码。

buildNative();

copyJniFiles和buildNative都是常规操作, 关键是updateJniFiles,这里有对模板代码进一步的更新

private void updateJniFiles() throws IOException {

File file;

File tmpFile;

StringBuffer sb = new StringBuffer();

// 更新avmp.cpp文件中的内容。

try (BufferedReader reader = new BufferedReader(new FileReader(mOpt.outYcCPFile))) {

String line = null;

while (null != (line = reader.readLine())) {

sb.append(line);

sb.append(System.getProperty("line.separator"));

}

}

file = new File(mOpt.jniDir.getAbsolutePath() + File.separator + "advmpc" + File.separator + "avmp.cpp");

tmpFile = new File(mOpt.jniDir.getAbsolutePath() + File.separator + "advmpc" + File.separator + "avmp.cpp" + ".tmp");

try (BufferedReader reader = new BufferedReader(new FileReader(file));

BufferedWriter writer = new BufferedWriter(new FileWriter(tmpFile))) {

String line = null;

while (null != (line = reader.readLine())) {

if ("#ifdef _AVMP_DEBUG_".equals(line)) {

writer.write("#if 0");

writer.newLine();

} else if ("//+${replaceAll}".equals(line)) {

writer.write(sb.toString());

} else {

writer.write(line);

writer.newLine();

}

}

}

file.delete();

tmpFile.renameTo(file);

sb.delete(0, sb.length());

}

这里是将advmp_separator.cpp 的代码和advmp.cpp 的代码合并,生成新的advmp.cpp,看一下编译选项

template/jni/Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := advmp

LOCAL_SRC_FILES := ioapi.c \

unzip.c \

Globals.cpp \

avmp.cpp \ # 我们生成的代码文件

BitConvert.cpp \

InterpC.cpp \

io.cpp \

Utils.cpp \

YcFile.cpp

LOCAL_SRC_FILES += DexOpcodes.cpp \

Exception.cpp

LOCAL_LDLIBS := -llog -lz

include $(BUILD_SHARED_LIBRARY)

最后构建生成 advmp.so

最后调用ZipHelper.doZip 重新打包成apk,完成(没有重新签名),由用户自行签名

辐射四怎么设置中文?辐射四设置中文方法
深圳通优惠政策是什么?