开源项目地址

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. Redis lua脚本简要学习

    Redis lua脚本简要学习 背景 上周督促客户从Windows平台升级到了Linux平台. redis一周相安无事. 但是这周一突然又出现了卡断和慢的情况. 只能继续进行分析. 分析思路 现场日志 ...

  2. 【转帖】You can now run a GPT-3-level AI model on your laptop, phone, and Raspberry Pi

    https://arstechnica.com/information-technology/2023/03/you-can-now-run-a-gpt-3-level-ai-model-on-you ...

  3. 【转帖】JVM 内存模型与垃圾回收

    文章目录 1. JVM内存模型 1.1. 程序计数器 (线程私有) 1.2. Java 虚拟机栈 (线程私有) 1.3. 本地方法栈 (线程私有) 1.4. Java 堆 (线程共享) 1.5. 方法 ...

  4. Python学习之七_input和print

    Python学习之七_input和print 摘要 python3 之后 函数必须带 () 了 因为我开始学习的比较晚, 所以准备Python3开始学起 前面主要是模仿别人的代码进行学习 后续慢慢学习 ...

  5. 【转帖】Seccomp、BPF与容器安全

    语音阅读2022-06-30 20:26 本文详细介绍了关于seccomp的相关概念,包括seccomp的发展历史.Seccomp BPF的实现原理已经与seccomp相关的一些工具等.此外,通过实例 ...

  6. Stress-ng 的简单学习

    背景 想研究一下国产和不同架构,不通型号CPU的算力 也作为后续生产交付的基线准备. 学习各种不同工具进行简要测试. 安装 git clone https://github.com/ColinIanK ...

  7. WinSCP和xftp 从Windows 上传到linux服务器时出现中文乱码的解决方案

    1. 日常工作中有需求从Windows的办公机器将文件上传到linux服务器上面进行使用 中文经常出现乱码, 需要处理一下. 这里面主要用到了两个工具 WinSCP还有xftp 两个的原理都是一样的 ...

  8. 六个节点三主三从Redis集群最简单搭建方法

    背景 一个服务器上面的三主三从的界面太low,容易出问题. 为了验证高可用, 我这边使用六台机器进行了三主三从的搭建. 模仿开源版的一键搭建集群的脚本进行使用,感觉非常简单,这里简单进行一下总结. 环 ...

  9. locust+python性能测试库

    一.简介 locust官网介绍:Locust 是一个用于 HTTP 和其他协议的开源性能/负载测试工具.其对开发人员友好的方法允许您在常规 Python 代码中定义测试.Locust测试可以从命令行运 ...

  10. React中兄弟组件通信和组件跨级传递Context的使用

    React兄弟组件之间的通信 Child2组件需要去更改Child1组件中的数据. 因为Child1和Child2是兄弟组件 所以数据和事件都放在最进的父级组件中去 兄弟组件通信的简单使用 impor ...