开源项目地址

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.ycadvmp_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<Method> 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<? extends MethodParameter> oldParams = value.getParameters();
List<ImmutableMethodParameter> 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<Method> 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<? extends CharSequence> 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,完成(没有重新签名),由用户自行签名

ADVMP 三代壳(vmp加固)原理分析(加壳流程)的更多相关文章

  1. UPX源码分析——加壳篇

    0x00 前言 UPX作为一个跨平台的著名开源压缩壳,随着Android的兴起,许多开发者和公司将其和其变种应用在.so库的加密防护中.虽然针对UPX及其变种的使用和脱壳都有教程可查,但是至少在中文网 ...

  2. android apk 防止反编译技术第一篇-加壳技术

    做android framework方面的工作将近三年的时间了,现在公司让做一下android apk安全方面的研究,于是最近就在网上找大量的资料来学习.现在将最近学习成果做一下整理总结.学习的这些成 ...

  3. 加壳&脱壳 - 前言(4.17更新)

    0x00 闲谈 最近打算学习学习加壳脱壳相关的知识,大致会有以下几个部分 1.upx壳的加壳原理及脱壳方法 --UPX压缩壳的工作原理 --脱upx壳--初试--单步追踪 -- 0x01 参考链接 1 ...

  4. VMP虚拟机加壳的原理学习

    好久没有到博客写文章了,9月份开学有点忙,参加了一个上海的一个CHINA SIG信息比赛,前几天又无锡南京来回跑了几趟,签了阿里巴巴的安全工程师,准备11月以后过去实习,这之前就好好待在学校学习了. ...

  5. Android中的Apk的加固(加壳)原理解析和实现

    一.前言 今天又到周末了,憋了好久又要出博客了,今天来介绍一下Android中的如何对Apk进行加固的原理.现阶段.我们知道Android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个apk, ...

  6. Android中的Apk的加固(加壳)原理解析和实现(转)

    一.前言 今天又到周末了,憋了好久又要出博客了,今天来介绍一下Android中的如何对Apk进行加固的原理.现阶段.我们知道Android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个apk, ...

  7. 【转】Android中的Apk的加固(加壳)原理解析和实现

    一.前言 今天又到周末了,憋了好久又要出博客了,今天来介绍一下Android中的如何对Apk进行加固的原理.现阶段.我们知道Android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个apk, ...

  8. android黑科技系列——Apk的加固(加壳)原理解析和实现

    一.前言 今天又到周末了,憋了好久又要出博客了,今天来介绍一下Android中的如何对Apk进行加固的原理.现阶段.我们知道Android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个apk, ...

  9. Android中对Apk加固(加壳)续篇之---对Native层(so文件)进行加固

    有人说Android程序用Java代码写的,再怎么弄都是不安全的,很容易破解的,现在晚上关于应用加固的技术也很多了,当然这些也可以用于商业发展的,梆梆加密和爱加密就是很好的例子,当然这两家加固的Apk ...

  10. PHP-自动加载原理分析

    说起PHP的自动加载,很多同学可能都会想到各种框架的自动加载功能,PHP规范中的PSR0和PSR4原则,Composer的自动加载功能等等,这些都为我们的开发提供了很大的方便. 那么PHP自动加载的前 ...

随机推荐

  1. [转帖]【VIM】多行缩进空格与删除

    向前或向后缩进一个TAB 按ctrl + v组合键进入Visual Line模式,可使用方向键选择多行: 按<或>,进行向前或向后缩进tab. 缩进n个TAB,按n+<或> 多 ...

  2. [转帖]Python基础之函数(四)

    https://www.jianshu.com/p/168e341fb81c 一.函数定义 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段:比如常用的print(),就是内建函数:通 ...

  3. [转帖]JVM监控及诊断工具-命令行

    https://www.cnblogs.com/xiaojiesir/p/15622372.html 性能指标 停顿时间(响应时间) 提交请求和返回响应之间使用的时间,一般比较关注平均响应时间 常用操 ...

  4. [转帖]Linux后门的几种姿势

      转载自 https://evilanne.github.io/2017/08/26/Linux后门-持续关注/ 在一次渗透中,成功获取某目标几台比较重要的机器,当时只想着获取脱库,结果动静太大被发 ...

  5. 神经网络优化篇:详解为超参数选择合适的范围(Using an appropriate scale to pick hyperparameters)

    为超参数选择合适的范围 假设要选取隐藏单元的数量\(n^{[l]}\),假设,选取的取值范围是从50到100中某点,这种情况下,看到这条从50-100的数轴,可以随机在其取点,这是一个搜索特定超参数的 ...

  6. 文盘Rust -- 如何把配置文件打包到二进制文件里

    ​在实际开发中,经常会遇到各种不同的配置文件.通常,程序运行的各种配置从外部读取,以增强应用配置的灵活性.java 生态中的 springboot 提供了这种设计的典范.springboot 的应用程 ...

  7. 【小测试】玩一玩 VictoriaMetrics 的 force merge

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 我是期望通过备份来建立 VictoriaMetrics 的 ...

  8. 解决VS选择运行“在证书存储区中找不到清单签名证书”

     转:https://www.cnblogs.com/190196539/archive/2011/12/03/2272861.html 解决"在证书存储区中找不到清单签名证书" ...

  9. CentOS使用iptables开放3000端口

    关闭firewall systemctl stop firewalld.service 禁止firewall开机启动 systemctl disable firewalld.service 设置ipt ...

  10. 精进语言模型:探索LLM Training微调与奖励模型技术的新途径

    精进语言模型:探索LLM Training微调与奖励模型技术的新途径 LLMs Trainer 是一个旨在帮助人们从零开始训练大模型的仓库,该仓库最早参考自 Open-Llama,并在其基础上进行扩充 ...