开源项目地址

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. iftop的学习与使用

    iftop的学习与使用 背景 前段时间一直进行netperf 等网络性能验证工具的学习与使用. 监控很多时候采用了 node-exporter + prometheus + grafana来进行观察 ...

  2. [转帖]s3fs - 使用S3FS存储桶目录允许其他用户使用权限

    https://www.coder.work/article/6661505   我在使用S3FS时遇到问题.我正在使用 ubuntu@ip-x-x-x-x:~$ /usr/bin/s3fs --ve ...

  3. [转帖]Kubernetes-15:一文详解Pod、Node调度规则(亲和性、污点、容忍、固定节点)

    https://www.cnblogs.com/v-fan/p/13609124.html Kubernetes Pod调度说明 简介 Scheduler 是 Kubernetes 的调度器,主要任务 ...

  4. Navicat For Redis 的学习与使用

    Navicat For Redis 的学习与使用 背景 周末在家看了几个公众号: 说到Navicat 16.2已经有了 Redis的客户端. 想着前段时间一直在学习Redis, 但是没有GUI的工具, ...

  5. 【转帖】Ethernet 与 Infiniband的网络特性对比

    一.两者定位 以太网(Ethernet): 应用最广泛,是最成熟的网络互联技术,也是整个互联网络大厦的基石,兼容性非常好,可实现不同的系统之间的互连互通 IB(Infiniband): 领域很专,作为 ...

  6. [转帖]20191022-从Jenkins NativeOOM到Java8内存

    我把老掉牙的Jenkins升级了,它跑了几天好好的:后来我有一个python脚本使用JenkinsAPI 0.3.9每隔2.5分钟发送约300余get请求,结果过了3天,它就挂了:当我开两个脚本时,4 ...

  7. Java进程 OOM的多种情况

    Java进程 OOM的多种情况 摘要 OOM 其实有多种: 第一类是JVM原生自发处理的, 这种也分为多种情况. 1. 堆区使用了比较多,并且大部分对象都还有引用, GC不出来可用内存, 这是要给对象 ...

  8. Win10 查看无线局域网的密码

    1. 打开命令行 输入 control 打开控制面板 2. 进入网络和共享中心 3. 打开连接 4. 使用如下进行查看.

  9. Redis labs 的安装

    Install and setup This guide shows how to install Redis Enterprise Software, which includes several ...

  10. unzip 解压缩存在Bug-- 这个方法不行啊

    linux中解压大于4G的zip压缩包(已解决) tar -zxvf 压缩包名.zip