首页 > 其他分享 >ADVMP 三代壳(vmp加固)原理分析(加壳流程)

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

时间:2023-04-14 12:33:05浏览次数:56  
标签:ADVMP value 加壳 append File new cpp sb vmp

开源项目地址

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,value,加壳,append,File,new,cpp,sb,vmp
From: https://www.cnblogs.com/gradyblog/p/17317943.html

相关文章

  • dpt-shell 抽取壳实现原理分析(加壳逻辑)
    开源项目位置(为大佬开源精神点赞)https://github.com/luoyesiqiu/dpt-shell抽取壳分为两个步骤加壳逻辑:一对apk进行解析,将codeItem抽出到一个文件中,并进行nop填充二对抽取后的apk进行加密三注入壳程序相关文件即配置信息执行逻辑:一壳程序执行二壳解密......
  • 使用eval的fromCharCode方法对js代码加壳
    在JavaScript中,使用eval函数可以将字符串作为代码来执行。这个特性可以被用来对JavaScript代码进行加壳以增加代码的安全性和保护知识产权。其中一个常用的方法是通过String.fromCharCode方法来创建一系列的ASCII字符,并将其拼接成一个包含加密代码的字符串。然后再通过eval函数执行......
  • 菜鸟手脱VMP,附上脱壳过程和自己写的脚本,可跨平台
    工作需要要脱一个VMP壳,我是一个从来没接触过脱壳的人。瞬间那种心情遇到的人应该都知道!没办法硬着头皮找教程,7天看完了《天草的壳的世界》尝试脱壳下面是我的脱壳过程希望大牛多多指正!1、准备工具,FEID(查壳工具)、DIE(查壳工具)、LordPE(dump工具)、ImpRec(IAT修复工具)、UIF(IAT修复工具)......
  • 加密与解密之加壳程序
    描述自己实现一个简单的加壳程序,能够对选取程序的代码段进行加密,并添加外壳部分,在运行时还原本文分为三个部分:外壳程序,加壳程序,和用户交互程序外壳程序写到Stub.dll中,方便同加壳程序共享数据,获取原程序的PE文件信息将数据段合并到代码段,方便加壳程序读取并添加到原程序中......
  • 软件加壳之输入表转储
    //EncrpyImport.cpp:定义控制台应用程序的入口点。//#include"stdafx.h"#include<Windows.h>#include<iostream>#include<fstream>#include<ImageHlp.h>usingnamespacestd;#pragmacomment(lib,"imagehlp.lib")......
  • 软件加壳输入表处理-解析
    本篇博文说下PE文件中输入表的格式和具体的使用,以及在软件加壳中的注意事项(本人菜鸟),高手飘过IMAGE_IMPORT_DESCRIPTORSTRUC{unionCharacteristicsDWORDOriginalFirstThunkDWORDendsTimeDateStampDWORDForwardChainDWORDNameD......
  • [C#] 代码混淆和加壳
    目的对比不同的主流保护工具,针对dnSpy反编译出的效果。非混淆代码:usingSystem;usingSystem.ComponentModel;usingSystem.Drawing;usingSystem.Windows.Forms;namespacetest_winform{ //Token:0x02000002RID:2 publicclassForm1:Form { //Token:0......
  • VMProtect完美脱壳过程
    VMProtect完美脱壳过程1.查看程序这是我自己写的一个VB的小程序,长得有点丑,别介意。然后自己加了一个壳,是VMProtectv.1.6x-2.03的壳。接下来我们国际惯例,用PEID,EXEinfoPE查一下壳可以看到是加了VMP的壳的,VMP壳的介绍我会放在帖子的最后哦。2.拉到OD去啦~push......
  • Dnguard旗舰版 在线加壳加密服务
    提供.net程序的在线加密服务用户自己选择加密选项参数,上传要加密软件压缩包,后台加密完成后直接返回下载压缩包。加密工具DNGuard旗舰版(EnterpriseEdition)技术优势......
  • vmp3.5 指令分析
    目录概述结构流程分析vm_entryvm_context_initvm_handlevm_stack_restorevm_exit举例_RTC_CheckEsp()add(1,5c)参考链接概述结构vm_context(vm寄存器)vm_stack(vm栈)vm......