详解如何自定义 Android Dex VMP 保护壳

版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/

前言

Android Dex VMP(Virtual Machine Protection,虚拟机保护)壳是一种常见的应用保护技术,主要用于保护 Android 应用的代码免受反编译和逆向工程的攻击。

VMP 保护壳通过将应用的原始 Dex(Dalvik Executable)文件进行加密、混淆、虚拟化等处理,使得恶意用户无法轻易获取到应用的原始代码和逻辑。

比如,实现一个 Android 下的 Dex VMP 保护壳,用来保护 Kotlin 层 sign 算法,防止被逆向。

假设 sign 算法源码如下:

package com.cyrus.example.vmp

import java.security.MessageDigest
import java.util.Base64

object SignUtil {

    /**
     * 对输入字符串进行签名并返回 Base64 编码后的字符串
     * @param input 要签名的字符串
     * @return Base64 编码后的字符串
     */
    fun sign(input: String): String {
        // 使用 SHA-256 计算摘要
        val digest = MessageDigest.getInstance("SHA-256")
        val hash = digest.digest(input.toByteArray())

        // 使用 Base64 编码
        return Base64.getEncoder().encodeToString(hash)
    }
}

转换为指令流

把 apk 拖入 GDA,找到 sign 方法,右键选择 SmaliJava(F5)

word/media/image1.png

GDA 是一个开源的 Android 逆向分析工具,可反编译 APK、DEX、ODEX、OAT、JAR、AAR 和 CLASS 文件,支持恶意行为检测、隐私泄露检测、漏洞检测、路径解密、打包器识别、变量跟踪、反混淆、python 和 Java 脚本等等…

Show ByteCode

word/media/image2.png

得到字节码和对应的 smali 指令如下:

1a004e00            | const-string v0, "input"
712020000500        | invoke-static{v5, v0}, Lkotlin/jvm/internal/Intrinsics;->checkNotNullParameter(Ljava/lang/Object;Ljava/lang/String;)V
1a002c00            | const-string v0, "SHA-256"
71101c000000        | invoke-static{v0}, Ljava/security/MessageDigest;->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;
0c00                | move-result-object v0
62010900            | sget-object v1, Lkotlin/text/Charsets;->UTF_8:Ljava/nio/charset/Charset;
6e2016001500        | invoke-virtual{v5, v1}, Ljava/lang/String;->getBytes(Ljava/nio/charset/Charset;)[B
0c01                | move-result-object v1
1a024a00            | const-string v2, "getBytes\(...\)"
71201f002100        | invoke-static{v1, v2}, Lkotlin/jvm/internal/Intrinsics;->checkNotNullExpressionValue(Ljava/lang/Object;Ljava/lang/String;)V
6e201b001000        | invoke-virtual{v0, v1}, Ljava/security/MessageDigest;->digest([B)[B
0c01                | move-result-object v1
71001e000000        | invoke-static{}, Ljava/util/Base64;->getEncoder()Ljava/util/Base64$Encoder;
0c02                | move-result-object v2
6e201d001200        | invoke-virtual{v2, v1}, Ljava/util/Base64$Encoder;->encodeToString([B)Ljava/lang/String;
0c02                | move-result-object v2
1a034400            | const-string v3, "encodeToString\(...\)"
71201f003200        | invoke-static{v2, v3}, Lkotlin/jvm/internal/Intrinsics;->checkNotNullExpressionValue(Ljava/lang/Object;Ljava/lang/String;)V
1102                | return-object v2

构建虚拟机解释器

解释器的任务是执行这些虚拟机指令。我们需要写一个虚拟机,它能够按照虚拟指令集中的指令依次执行操作。

创建 cpp 文件,定义一个 JNI 方法 execute,接收字节码数组和字符串参数,每个字节码指令会被映射为我们定义的虚拟指令。

#define CONST_STRING_OPCODE 0x1A  // const-string 操作码
#define INVOKE_STATIC_OPCODE 0x71  // invoke-static 操作码
#define MOVE_RESULT_OBJECT_OPCODE 0x0c  // move-result-object 操作码
#define SGET_OBJECT_OPCODE 0x62  // sget-object 操作码
#define INVOKE_VIRTUAL_OPCODE 0x6e  // invoke-virtual 操作码
#define RETURN_OBJECT_OPCODE 0x11  // return-object 操作码


jstring execute(JNIEnv *env, jobject thiz, jbyteArray bytecodeArray, jstring input) {

    // 传参存到 v5 寄存器
    registers[5] = input;

    // 获取字节码数组的长度
    jsize length = env->GetArrayLength(bytecodeArray);
    std::vector <uint8_t> bytecode(length);
    env->GetByteArrayRegion(bytecodeArray, 0, length, reinterpret_cast<jbyte *>(bytecode.data()));

    size_t pc = 0;  // 程序计数器
    try {
        // 执行字节码中的指令
        while (pc < bytecode.size()) {
            uint8_t opcode = bytecode[pc];

            switch (opcode) {
                case CONST_STRING_OPCODE:
                    handleConstString(env, bytecode.data(), pc);
                    break;
                case INVOKE_STATIC_OPCODE:
                    handleInvokeStatic(env, bytecode.data(), pc);
                    break;
                case SGET_OBJECT_OPCODE:
                    handleSgetObject(env, bytecode.data(), pc);
                    break;
                case INVOKE_VIRTUAL_OPCODE:
                    handleInvokeVirtual(env, bytecode.data(), pc);
                    break;
                case RETURN_OBJECT_OPCODE:
                    handleReturnResultObject(env, bytecode.data(), pc);
                    break;
                default:
                    throw std::runtime_error("Unknown opcode encountered");
            }
        }

        if (std::holds_alternative<jstring>(registers[0])) {
            jstring result = std::get<jstring>(registers[0]);   // 返回寄存器 v0 的值
            // 清空寄存器
            std::fill(std::begin(registers), std::end(registers), nullptr);
            return result;
        }
    } catch (const std::exception &e) {
        env->ThrowNew(env->FindClass("java/lang/RuntimeException"), e.what());
    }

    // 清空寄存器
    std::fill(std::begin(registers), std::end(registers), nullptr);
    return nullptr;
}

模拟寄存器

使用 std::variant 来定义一个可以存储多种类型的寄存器值。

// 定义支持的寄存器类型(比如 jstring、jboolean、jobject 等等)
using RegisterValue = std::variant<
        jstring,
        jboolean,
        jbyte,
        jshort,
        jint,
        jlong,
        jfloat,
        jdouble,
        jobject,
        jbyteArray,
        jintArray,
        jlongArray,
        jfloatArray,
        jdoubleArray,
        jbooleanArray,
        jshortArray,
        jobjectArray,
        std::nullptr_t
>;

std::variant 是 C++17 引入的一个模板类,用于表示一个可以存储多种类型中的一种的类型。它类似于联合体(union),但是比联合体更安全,因为它可以明确地跟踪当前存储的是哪一种类型。

定义寄存器个数和寄存器数组

// 定义寄存器数量
constexpr size_t NUM_REGISTERS = 10;

// 定义寄存器数组
RegisterValue registers[NUM_REGISTERS];

写寄存器

// 存储不同类型的值到寄存器
template <typename T>
void setRegisterValue(uint8_t reg, T value) {
    // 通过模板将类型 T 存储到寄存器
    registers[reg] = value;
}

读寄存器

// 根据类型从寄存器读取对应的值
jvalue getRegisterAsJValue(int regIdx, const std::string &paramType) {
    const RegisterValue &val = registers[regIdx];
    jvalue result;

    if (paramType == "I") {  // int 类型
        if (std::holds_alternative<jint>(val)) {
            result.i = std::get<jint>(val);
        } else {
            throw std::runtime_error("Type mismatch: Expected jint.");
        }
    } else if (paramType == "J") {  // long 类型
        if (std::holds_alternative<jlong>(val)) {
            result.j = std::get<jlong>(val);
        } else {
            throw std::runtime_error("Type mismatch: Expected jlong.");
        }
    } else if (paramType == "F") {  // float 类型
        if (std::holds_alternative<jfloat>(val)) {
            result.f = std::get<jfloat>(val);
        } else {
            throw std::runtime_error("Type mismatch: Expected jfloa
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值