中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁(yè) > news >正文

長(zhǎng)沙定制網(wǎng)站建設(shè)一站式自媒體服務(wù)平臺(tái)

長(zhǎng)沙定制網(wǎng)站建設(shè),一站式自媒體服務(wù)平臺(tái),如何在拼多多開(kāi)網(wǎng)店,長(zhǎng)春餐飲網(wǎng)站建設(shè)版權(quán)歸作者所有,如有轉(zhuǎn)發(fā),請(qǐng)注明文章出處:https://cyrus-studio.github.io/blog/ 前言 Android Dex VMP(Virtual Machine Protection,虛擬機(jī)保護(hù))殼是一種常見(jiàn)的應(yīng)用保護(hù)技術(shù),主要用于保護(hù) And…

版權(quán)歸作者所有,如有轉(zhuǎn)發(fā),請(qǐng)注明文章出處:https://cyrus-studio.github.io/blog/

前言

Android Dex VMP(Virtual Machine Protection,虛擬機(jī)保護(hù))殼是一種常見(jiàn)的應(yīng)用保護(hù)技術(shù),主要用于保護(hù) Android 應(yīng)用的代碼免受反編譯和逆向工程的攻擊。

VMP 保護(hù)殼通過(guò)將應(yīng)用的原始 Dex(Dalvik Executable)文件進(jìn)行加密、混淆、虛擬化等處理,使得惡意用戶無(wú)法輕易獲取到應(yīng)用的原始代碼和邏輯。

比如,實(shí)現(xiàn)一個(gè) Android 下的 Dex VMP 保護(hù)殼,用來(lái)保護(hù) Kotlin 層 sign 算法,防止被逆向。

假設(shè) sign 算法源碼如下:

package com.cyrus.example.vmpimport java.security.MessageDigest
import java.util.Base64object SignUtil {/*** 對(duì)輸入字符串進(jìn)行簽名并返回 Base64 編碼后的字符串* @param input 要簽名的字符串* @return Base64 編碼后的字符串*/fun sign(input: String): String {// 使用 SHA-256 計(jì)算摘要val digest = MessageDigest.getInstance("SHA-256")val hash = digest.digest(input.toByteArray())// 使用 Base64 編碼return Base64.getEncoder().encodeToString(hash)}
}

轉(zhuǎn)換為指令流

把 apk 拖入 GDA,找到 sign 方法,右鍵選擇 SmaliJava(F5)

word/media/image1.png

GDA 是一個(gè)開(kāi)源的 Android 逆向分析工具,可反編譯 APK、DEX、ODEX、OAT、JAR、AAR 和 CLASS 文件,支持惡意行為檢測(cè)、隱私泄露檢測(cè)、漏洞檢測(cè)、路徑解密、打包器識(shí)別、變量跟蹤、反混淆、python 和 Java 腳本等等…

  • GDA 下載地址:http://www.gda.wiki:9090/

  • GDA 項(xiàng)目地址:https://github.com/charles2gan/GDA-android-reversing-Tool

Show ByteCode

word/media/image2.png

得到字節(jié)碼和對(duì)應(yīng)的 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

構(gòu)建虛擬機(jī)解釋器

解釋器的任務(wù)是執(zhí)行這些虛擬機(jī)指令。我們需要寫(xiě)一個(gè)虛擬機(jī),它能夠按照虛擬指令集中的指令依次執(zhí)行操作。

創(chuàng)建 cpp 文件,定義一個(gè) JNI 方法 execute,接收字節(jié)碼數(shù)組和字符串參數(shù),每個(gè)字節(jié)碼指令會(huì)被映射為我們定義的虛擬指令。

#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;// 獲取字節(jié)碼數(shù)組的長(zhǎng)度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;  // 程序計(jì)數(shù)器try {// 執(zhí)行字節(jié)碼中的指令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 來(lái)定義一個(gè)可以存儲(chǔ)多種類型的寄存器值。

// 定義支持的寄存器類型(比如 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 引入的一個(gè)模板類,用于表示一個(gè)可以存儲(chǔ)多種類型中的一種的類型。它類似于聯(lián)合體(union),但是比聯(lián)合體更安全,因?yàn)樗梢悦鞔_地跟蹤當(dāng)前存儲(chǔ)的是哪一種類型。

定義寄存器個(gè)數(shù)和寄存器數(shù)組

// 定義寄存器數(shù)量
constexpr size_t NUM_REGISTERS = 10;// 定義寄存器數(shù)組
RegisterValue registers[NUM_REGISTERS];

寫(xiě)寄存器

// 存儲(chǔ)不同類型的值到寄存器
template <typename T>
void setRegisterValue(uint8_t reg, T value) {// 通過(guò)模板將類型 T 存儲(chǔ)到寄存器registers[reg] = value;
}

讀寄存器

// 根據(jù)類型從寄存器讀取對(duì)應(yīng)的值
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 jfloat.");}} else if (paramType == "D") {  // double 類型if (std::holds_alternative<jdouble>(val)) {result.d = std::get<jdouble>(val);} else {throw std::runtime_error("Type mismatch: Expected jdouble.");}} else if (paramType == "Z") {  // boolean 類型if (std::holds_alternative<jboolean>(val)) {result.z = std::get<jboolean>(val);} else {throw std::runtime_error("Type mismatch: Expected jboolean.");}} else if (paramType == "B") {  // byte 類型if (std::holds_alternative<jbyte>(val)) {result.b = std::get<jbyte>(val);} else {throw std::runtime_error("Type mismatch: Expected jbyte.");}} else if (paramType == "S") {  // short 類型if (std::holds_alternative<jshort>(val)) {result.s = std::get<jshort>(val);} else {throw std::runtime_error("Type mismatch: Expected jshort.");}} else if (paramType == "Ljava/lang/String;") {  // String 類型if (std::holds_alternative<jstring>(val)) {result.l = std::get<jstring>(val);} else {throw std::runtime_error("Type mismatch: Expected jstring.");}} else if (paramType[0] == 'L') {  // jobject 類型(以 L 開(kāi)頭)if (std::holds_alternative<jstring>(val)) {result.l = std::get<jstring>(val);} else if (std::holds_alternative<jobject>(val)) {result.l = std::get<jobject>(val);} else {throw std::runtime_error("Type mismatch: Expected jobject.");}} else if (paramType[0] == '[') {  // 數(shù)組類型// 處理數(shù)組類型,判斷是基礎(chǔ)類型數(shù)組還是對(duì)象數(shù)組if (paramType == "[I") {  // jintArray 類型if (std::holds_alternative<jintArray>(val)) {result.l = std::get<jintArray>(val);  // jvalue 直接存儲(chǔ)數(shù)組} else {throw std::runtime_error("Type mismatch: Expected jintArray.");}} else if (paramType == "[J") {  // jlongArray 類型if (std::holds_alternative<jlongArray>(val)) {result.l = std::get<jlongArray>(val);} else {throw std::runtime_error("Type mismatch: Expected jlongArray.");}} else if (paramType == "[F") {  // jfloatArray 類型if (std::holds_alternative<jfloatArray>(val)) {result.l = std::get<jfloatArray>(val);} else {throw std::runtime_error("Type mismatch: Expected jfloatArray.");}} else if (paramType == "[D") {  // jdoubleArray 類型if (std::holds_alternative<jdoubleArray>(val)) {result.l = std::get<jdoubleArray>(val);} else {throw std::runtime_error("Type mismatch: Expected jdoubleArray.");}} else if (paramType == "[Z") {  // jbooleanArray 類型if (std::holds_alternative<jbooleanArray>(val)) {result.l = std::get<jbooleanArray>(val);} else {throw std::runtime_error("Type mismatch: Expected jbooleanArray.");}} else if (paramType == "[B") {  // jbyteArray 類型if (std::holds_alternative<jbyteArray>(val)) {result.l = std::get<jbyteArray>(val);} else {throw std::runtime_error("Type mismatch: Expected jbyteArray.");}} else if (paramType == "[S") {  // jshortArray 類型if (std::holds_alternative<jshortArray>(val)) {result.l = std::get<jshortArray>(val);} else {throw std::runtime_error("Type mismatch: Expected jshortArray.");}} else if (paramType == "[Ljava/lang/String;") {  // String[] 類型if (std::holds_alternative<jobjectArray>(val)) {result.l = std::get<jobjectArray>(val);} else {throw std::runtime_error("Type mismatch: Expected String array.");}} else if (paramType[0] == '[' && paramType[1] == 'L') {  // jobject[] 類型(數(shù)組的元素為對(duì)象)if (std::holds_alternative<jobjectArray>(val)) {result.l = std::get<jobjectArray>(val);} else {throw std::runtime_error("Type mismatch: Expected jobject array.");}} else {throw std::runtime_error("Unsupported array type.");}} else {throw std::runtime_error("Unsupported parameter type.");}return result;
}

模擬字符串常量池

由于指令中用到字符串,所有需要模擬一個(gè)字符串常量池去實(shí)現(xiàn)指令中字符串的引用。

在 dex 文件中,字符串常量池(string_ids)是一個(gè)數(shù)組,其中每個(gè)條目存儲(chǔ)一個(gè)字符串的偏移量,這個(gè)偏移量指向 dex 文件中 string_data 區(qū)域。

word/media/image3.png

這里簡(jiǎn)單通過(guò)字符串索引和字符串做關(guān)聯(lián),代碼實(shí)現(xiàn)如下:

// 模擬字符串常量池
std::unordered_map <uint32_t, std::string> stringPool = {{0x004e00, "input"},{0x002c00, "SHA-256"},{0x024a00, "getBytes\\(...\\)"},{0x034400, "encodeToString\\(...\\)"},
};

指令解析執(zhí)行

虛擬機(jī)接收到字節(jié)指令流,經(jīng)過(guò)解析操作碼并分發(fā)到各指令執(zhí)行函數(shù)。接下來(lái)實(shí)現(xiàn)指令執(zhí)行函數(shù)。

1. const-string

該指令將一個(gè)預(yù)定義的字符串常量加載到指定的寄存器中。例如:

const-string v0, "Hello, World!"

這條指令的作用是將字符串 “Hello, World!” 加載到寄存器 v0 中。

指令結(jié)構(gòu)

const-string v0, “input” 的字節(jié)碼為:

1A 00 4E 00

結(jié)構(gòu)解釋:

  • 1A (操作碼): 表示 const-string 指令。

  • 00 (目標(biāo)寄存器 v0): 表示字符串將存儲(chǔ)到寄存器 v0 中。

  • 4E 00 (字符串索引 0x004E): 表示字符串在字符串常量池中的位置。

具體代碼實(shí)現(xiàn)

// 處理 const-string 指令
void handleConstString(JNIEnv *env, const uint8_t *bytecode, size_t &pc) {uint8_t opcode = bytecode[pc];if (opcode != CONST_STRING_OPCODE) {  // 檢查是否為 const-string 指令throw std::runtime_error("Unexpected opcode");}// 獲取目標(biāo)寄存器索引 reg 和字符串索引uint8_t reg = bytecode[pc + 1];  // 目標(biāo)寄存器// 讀取字符串索引(第 2、3、4 字節(jié))uint32_t stringIndex = (bytecode[pc + 1] << 16) | (bytecode[pc + 2] << 8) | bytecode[pc + 3];// 從字符串常量池獲取字符串const std::string &value = stringPool[stringIndex];// 創(chuàng)建 jstring 并將其存儲(chǔ)到目標(biāo)寄存器jstring str = env->NewStringUTF(value.c_str());registers[reg] = str;// 更新程序計(jì)數(shù)器pc += 4;  // const-string 指令占用 4 字節(jié)
}

2. invoke-static

invoke-static 指令用于執(zhí)行類的靜態(tài)方法。例如:

invoke-static {v5, v0}, Lkotlin/jvm/internal/Intrinsics;->checkNotNullParameter(Ljava/lang/Object;Ljava/lang/String;)V

各部分的解釋:

  • invoke-static:這是調(diào)用靜態(tài)方法的指令

  • {v5, v0}:這是方法調(diào)用時(shí)傳遞的參數(shù)寄存器

  • Lkotlin/jvm/internal/Intrinsics;:目標(biāo)類的名稱。

  • ->checkNotNullParameter:這是要調(diào)用的靜態(tài)方法的名稱

  • (Ljava/lang/Object;Ljava/lang/String;):這是方法的參數(shù)簽名

  • V:表示方法的返回類型是 void。

指令結(jié)構(gòu)

一個(gè)標(biāo)準(zhǔn)的 invoke-static 字節(jié)碼指令通常如下所示(6個(gè)字節(jié)):

71 <reg_count> <method_index> <reg> 00操作碼 (1 字節(jié)) | 寄存器數(shù)量 (1 字節(jié)) | 方法索引 (2 字節(jié)) | 目標(biāo)寄存器 (1 字節(jié)) | 填充字節(jié),指令對(duì)齊 (1 字節(jié))
  • 71:操作碼,表示 invoke-static。

  • <reg_count>:寄存器數(shù)量,參數(shù)個(gè)數(shù)。

  • <method_index>:目標(biāo)方法在方法表中的索引。

  • :目標(biāo)寄存器,表示要將傳參存儲(chǔ)到的寄存器。

  • 00:填充字節(jié),指令對(duì)齊

實(shí)現(xiàn) invoke 指令,需要根據(jù)指令中的 method index 從 dex 中找到 method,然后通過(guò) jni 接口發(fā)起調(diào)用。

word/media/image4.png

具體代碼實(shí)現(xiàn)

// 解析并執(zhí)行 invoke-static 指令
void handleInvokeStatic(JNIEnv *env, const uint8_t *bytecode, size_t &pc) {uint8_t opcode = bytecode[pc];if (opcode != INVOKE_STATIC_OPCODE) {  // 檢查是否為 invoke-staticthrow std::runtime_error("Unexpected opcode for invoke-static");}// 第 5 個(gè)字節(jié)表示了要使用的寄存器uint8_t reg1 = bytecode[pc + 4] & 0xF;         // 低4位表示第一個(gè)寄存器uint8_t reg2 = (bytecode[pc + 4] >> 4) & 0xF;  // 高4位表示第二個(gè)寄存器// 讀取方法索引(第 2、3、4 字節(jié))uint32_t methodIndex = (bytecode[pc + 1] << 16) | (bytecode[pc + 2] << 8) | bytecode[pc + 3];// 類名和方法信息std::string className;std::string methodName;std::string methodSignature;// 根據(jù) methodIndex 來(lái)解析并設(shè)置類名、方法名、簽名switch (methodIndex) {case 0x202000:  // checkNotNullParameterclassName = "kotlin/jvm/internal/Intrinsics";methodName = "checkNotNullParameter";methodSignature = "(Ljava/lang/Object;Ljava/lang/String;)V";break;case 0x101c00:  // getInstance (MessageDigest)className = "java/security/MessageDigest";methodName = "getInstance";methodSignature = "(Ljava/lang/String;)Ljava/security/MessageDigest;";break;case 0x201f00:  // checkNotNullExpressionValueclassName = "kotlin/jvm/internal/Intrinsics";methodName = "checkNotNullExpressionValue";methodSignature = "(Ljava/lang/Object;Ljava/lang/String;)V";break;case 0x001e00:  // getEncoder (Base64)className = "java/util/Base64";methodName = "getEncoder";methodSignature = "()Ljava/util/Base64$Encoder;";break;default:throw std::runtime_error("Unknown method index");}// 獲取目標(biāo)類jclass targetClass = env->FindClass(className.c_str());if (targetClass == nullptr) {throw std::runtime_error("Class not found: " + className);}// 獲取方法 IDjmethodID methodID = env->GetStaticMethodID(targetClass, methodName.c_str(), methodSignature.c_str());if (methodID == nullptr) {throw std::runtime_error("Method not found: " + methodName);}// 解析方法簽名,得到參數(shù)個(gè)數(shù)和返回值類型std::vector<std::string> paramTypes;std::string returnType;parseMethodSignature(methodSignature, paramTypes, returnType);int paramCount = paramTypes.size();// 動(dòng)態(tài)獲取參數(shù)uint8_t reg_list[] = {reg1, reg2};std::vector <jstring> params(paramCount);for (size_t i = 0; i < paramCount; ++i) {// 獲取寄存器中的值并轉(zhuǎn)化為 JNI 參數(shù)jvalue value = getRegisterAsJValue(reg_list[i], paramTypes[i]);params[i] = static_cast<jstring>(value.l);}// 更新程序計(jì)數(shù)器pc += 6;  // invoke-static 指令占用 6 字節(jié)// 調(diào)用靜態(tài)方法// 根據(jù)返回值類型決定調(diào)用方式if (returnType == "V") {  // void 返回值if (paramCount == 0) {env->CallStaticVoidMethod(targetClass, methodID);  // 無(wú)參數(shù)} else if (paramCount == 1) {env->CallStaticVoidMethod(targetClass, methodID, params[0]);} else {env->CallStaticVoidMethod(targetClass, methodID, params[0], params[1]);}} else if (returnType == "Z") {  // boolean 返回值jboolean boolResult;if (paramCount == 0) {boolResult = env->CallStaticBooleanMethod(targetClass, methodID);  // 無(wú)參數(shù)} else if (paramCount == 1) {boolResult = env->CallStaticBooleanMethod(targetClass, methodID, params[0]);} else {boolResult = env->CallStaticBooleanMethod(targetClass, methodID, params[0], params[1]);}// move-resulthandleMoveResultObject(env, bytecode, pc, boolResult);} else if (returnType == "B") {  // byte 返回值jbyte byteResult;if (paramCount == 0) {byteResult = env->CallStaticByteMethod(targetClass, methodID);  // 無(wú)參數(shù)} else if (paramCount == 1) {byteResult = env->CallStaticByteMethod(targetClass, methodID, params[0]);} else {byteResult = env->CallStaticByteMethod(targetClass, methodID, params[0], params[1]);}// move-resulthandleMoveResultObject(env, bytecode, pc, byteResult);} else if (returnType == "S") {  // short 返回值jshort shortResult;if (paramCount == 0) {shortResult = env->CallStaticShortMethod(targetClass, methodID);  // 無(wú)參數(shù)} else if (paramCount == 1) {shortResult = env->CallStaticShortMethod(targetClass, methodID, params[0]);} else {shortResult = env->CallStaticShortMethod(targetClass, methodID, params[0], params[1]);}// move-resulthandleMoveResultObject(env, bytecode, pc, shortResult);} else if (returnType == "I") {  // int 返回值jint intResult;if (paramCount == 0) {intResult = env->CallStaticIntMethod(targetClass, methodID);  // 無(wú)參數(shù)} else if (paramCount == 1) {intResult = env->CallStaticIntMethod(targetClass, methodID, params[0]);} else {intResult = env->CallStaticIntMethod(targetClass, methodID, params[0], params[1]);}// move-resulthandleMoveResultObject(env, bytecode, pc, intResult);} else if (returnType == "J") {  // long 返回值jlong longResult;if (paramCount == 0) {longResult = env->CallStaticLongMethod(targetClass, methodID);  // 無(wú)參數(shù)} else if (paramCount == 1) {longResult = env->CallStaticLongMethod(targetClass, methodID, params[0]);} else {longResult = env->CallStaticLongMethod(targetClass, methodID, params[0], params[1]);}// move-resulthandleMoveResultObject(env, bytecode, pc, longResult);} else if (returnType == "F") {  // float 返回值jfloat floatResult;if (paramCount == 0) {floatResult = env->CallStaticFloatMethod(targetClass, methodID);  // 無(wú)參數(shù)} else if (paramCount == 1) {floatResult = env->CallStaticFloatMethod(targetClass, methodID, params[0]);} else {floatResult = env->CallStaticFloatMethod(targetClass, methodID, params[0], params[1]);}// move-resulthandleMoveResultObject(env, bytecode, pc, floatResult);} else if (returnType == "D") {  // double 返回值jdouble doubleResult;if (paramCount == 0) {doubleResult = env->CallStaticDoubleMethod(targetClass, methodID);  // 無(wú)參數(shù)} else if (paramCount == 1) {doubleResult = env->CallStaticDoubleMethod(targetClass, methodID, params[0]);} else {doubleResult = env->CallStaticDoubleMethod(targetClass, methodID, params[0], params[1]);}// move-resulthandleMoveResultObject(env, bytecode, pc, doubleResult);} else if (returnType[0] == 'L') {  // 對(duì)象返回值jobject objResult;if (paramCount == 0) {objResult = env->CallStaticObjectMethod(targetClass, methodID);  // 無(wú)參數(shù)} else if (paramCount == 1) {objResult = env->CallStaticObjectMethod(targetClass, methodID, params[0]);} else {objResult = env->CallStaticObjectMethod(targetClass, methodID, params[0], params[1]);}// 處理返回的對(duì)象if (objResult) {if(returnType == "Ljava/lang/String;"){jstring strResult = static_cast<jstring>(objResult);handleMoveResultObject(env, bytecode, pc, strResult);}else{handleMoveResultObject(env, bytecode, pc, objResult);}}} else {throw std::runtime_error("Unsupported return type: " + returnType);}
}

3. move-result-object

move-result-object 用于從方法調(diào)用的結(jié)果中將對(duì)象類型的返回值移動(dòng)到指定的寄存器中。例如:

move-result-object v0

解釋:

  • move-result-object:這條指令的作用是將最近一次方法調(diào)用的返回結(jié)果移動(dòng)到指定的寄存器中。

  • v0:指定目標(biāo)寄存器,返回的對(duì)象會(huì)被存儲(chǔ)在 v0 寄存器中。

指令結(jié)構(gòu)

一個(gè)標(biāo)準(zhǔn)的 move-result-object 字節(jié)碼指令通常如下所示(2個(gè)字節(jié)):

0c <reg>操作碼 (1 字節(jié))  | 目標(biāo)寄存器 (1 字節(jié))  

具體代碼實(shí)現(xiàn)

// move-result-object
template <typename T>
void handleMoveResultObject(JNIEnv *env, const uint8_t *bytecode, size_t &pc, T result) {uint8_t opcode = bytecode[pc];if (opcode == MOVE_RESULT_OBJECT_OPCODE) {uint8_t reg = bytecode[pc + 1];  // 目標(biāo)寄存器setRegisterValue(reg, result);// 更新程序計(jì)數(shù)器pc += 2;  // move-result-object 指令占用 2 字節(jié)}
}

4. sget-object

sget-object 是一條靜態(tài)字段讀取指令。它用于從一個(gè)類的靜態(tài)字段中獲取一個(gè)引用類型(對(duì)象)的值,并存儲(chǔ)到指定的寄存器中。

例如:

sget-object v1, Lkotlin/text/Charsets;->UTF_8:Ljava/nio/charset/Charset;

解釋:

  • sget-object:表示從類的靜態(tài)字段中獲取對(duì)象類型的值。

  • v1:目標(biāo)寄存器,指令執(zhí)行后,字段值(一個(gè)對(duì)象)會(huì)被存儲(chǔ)在 v1 寄存器中。

  • Lkotlin/text/Charsets;:目標(biāo)類的名稱。

  • ->UTF_8:表示靜態(tài)字段 UTF_8。

  • :Ljava/nio/charset/Charset;:字段的類型描述符,表示該字段的類型是 java.nio.charset.Charset。

指令結(jié)構(gòu)

一個(gè)標(biāo)準(zhǔn)的 sget-object 字節(jié)碼指令通常如下所示(4個(gè)字節(jié)):

62 <reg> <field_index>操作碼 (1 字節(jié))  | 目標(biāo)寄存器 (1 字節(jié))  | 字段索引 (2 字節(jié))  

具體代碼實(shí)現(xiàn)

// 解析和執(zhí)行 sget-object 指令
void handleSgetObject(JNIEnv *env, const uint8_t *bytecode, size_t &pc) {uint8_t opcode = bytecode[pc];if (opcode != SGET_OBJECT_OPCODE) {  // 檢查是否為 sget-objectthrow std::runtime_error("Unexpected opcode for sget-object");}// 解析指令uint8_t reg = bytecode[pc + 1];          // 目標(biāo)寄存器uint16_t fieldIndex = (bytecode[pc + 2] << 8) | bytecode[pc + 3]; // 字段索引// 類名和方法信息std::string className;std::string fieldName;std::string fieldType;// 解析每條指令,依據(jù)方法的不同來(lái)設(shè)置類名、方法名、簽名switch (fieldIndex) {case 0x0900:  // Lkotlin/text/Charsets;->UTF_8:Ljava/nio/charset/Charset;className = "kotlin/text/Charsets";fieldName = "UTF_8";fieldType = "Ljava/nio/charset/Charset;"; // 字段類型為 Charsetbreak;default:throw std::runtime_error("Unknown field index");}// 1. 獲取 Java 類jclass clazz = env->FindClass(className.c_str());if (clazz == nullptr) {LOGI("Failed to find class %s", className.c_str());return;}// 2. 獲取靜態(tài)字段的 Field IDjfieldID fieldID = env->GetStaticFieldID(clazz, fieldName.c_str(), fieldType.c_str());if (fieldID == nullptr) {LOGI("Failed to get field ID for %s", fieldName.c_str());return;}// 3. 獲取靜態(tài)字段的值jobject field = env->GetStaticObjectField(clazz, fieldID);if (field == nullptr) {LOGI("%s field is null", fieldName.c_str());return;}// 保存到目標(biāo)寄存器setRegisterValue(reg, field);// 更新程序計(jì)數(shù)器pc += 4; // sget-object 指令占用 4 字節(jié)
}

5. invoke-virtual

invoke-virtual 指令會(huì)調(diào)用指定對(duì)象的實(shí)例方法。例如

invoke-virtual {v5, v1}, Ljava/lang/String;->getBytes(Ljava/nio/charset/Charset;)[B

解釋:

  • invoke-virtual:表示調(diào)用對(duì)象的實(shí)例方法。

  • {v5, v1}:傳遞給目標(biāo)方法的參數(shù)寄存器。這里,v5 和 v1 寄存器的值會(huì)作為參數(shù)傳遞給方法。

  • Ljava/lang/String;:目標(biāo)類的名稱。

  • ->getBytes:目標(biāo)方法的名稱。

  • (Ljava/nio/charset/Charset;):方法的參數(shù)簽名。

  • [B:方法的返回類型簽名,表示該方法返回一個(gè)字節(jié)數(shù)組。

指令結(jié)構(gòu)

一個(gè)標(biāo)準(zhǔn)的 invoke-virtual 字節(jié)碼指令通常如下所示(6個(gè)字節(jié)):

6e <reg_count> <method_index> <reg> 00操作碼 (1 字節(jié)) | 寄存器數(shù)量 (1 字節(jié)) | 方法索引 (2 字節(jié)) | 目標(biāo)寄存器 (1 字節(jié)) | 填充字節(jié),指令對(duì)齊 (1 字節(jié))
  • 6e:操作碼,表示 invoke-static。

  • <reg_count>:寄存器數(shù)量,參數(shù)個(gè)數(shù)。

  • <method_index>:目標(biāo)方法在方法表中的索引。

  • :目標(biāo)寄存器,表示要將傳參存儲(chǔ)到的寄存器。

  • 00:填充字節(jié),指令對(duì)齊

具體代碼實(shí)現(xiàn)

// invoke-virtual 指令
void handleInvokeVirtual(JNIEnv* env, const uint8_t* bytecode, size_t& pc) {// 解析指令uint8_t opcode = bytecode[pc];  // 獲取操作碼if (opcode != INVOKE_VIRTUAL_OPCODE) {  // 確保是 invoke-virtual 操作碼throw std::runtime_error("Expected invoke-virtual opcode");}// 獲取寄存器數(shù)量uint8_t regCount = (bytecode[pc + 1] >> 4) & 0xF;// 第 5 個(gè)字節(jié)表示了要使用的寄存器uint8_t reg1 = bytecode[pc + 4] & 0xF;         // 低4位表示第一個(gè)寄存器uint8_t reg2 = (bytecode[pc + 4] >> 4) & 0xF;  // 高4位表示第二個(gè)寄存器// 讀取方法索引(第 2、3、4 字節(jié))uint32_t methodIndex = (bytecode[pc + 1] << 16) | (bytecode[pc + 2] << 8) | bytecode[pc + 3];// 類名和方法信息std::string className;std::string methodName;std::string methodSignature;// 根據(jù) methodIndex 來(lái)解析并設(shè)置類名、方法名、簽名switch (methodIndex) {case 0x201600:  // Ljava/lang/String;->getBytes(Ljava/nio/charset/Charset;)[BclassName = "java/lang/String";methodName = "getBytes";methodSignature = "(Ljava/nio/charset/Charset;)[B";break;case 0x201b00:  // Ljava/security/MessageDigest;->digest([B)[BclassName = "java/security/MessageDigest";methodName = "digest";methodSignature = "([B)[B";break;case 0x201d00:  // Ljava/util/Base64$Encoder;->encodeToString([B)Ljava/lang/String;className = "java/util/Base64$Encoder";methodName = "encodeToString";methodSignature = "([B)Ljava/lang/String;";break;default:throw std::runtime_error("Unknown method index: " + std::to_string(methodIndex));}// 查找類和方法jclass clazz = env->FindClass(className.c_str());if (!clazz) {throw std::runtime_error("Class not found: " + className);}// 獲取方法 IDjmethodID methodID = env->GetMethodID(clazz, methodName.c_str(), methodSignature.c_str());if (!methodID) {throw std::runtime_error("Method not found: " + methodName);}// 解析方法簽名,得到參數(shù)個(gè)數(shù)和返回值類型std::vector<std::string> paramTypes;std::string returnType;parseMethodSignature(methodSignature, paramTypes, returnType);int paramCount = paramTypes.size();// 目標(biāo)對(duì)象的類型std::stringstream ss;ss << "L" << className << ";";std::string classType = ss.str();// 獲取目標(biāo)對(duì)象(寄存器中的第一個(gè)參數(shù),通常是方法的目標(biāo)對(duì)象)jobject targetObject = getRegisterAsJValue(reg1, classType).l;// 參數(shù)std::vector <jvalue> params(paramCount);if(paramCount > 0){params[0] = getRegisterAsJValue(reg2, paramTypes[0]);}// 更新程序計(jì)數(shù)器pc += 6;// 檢查返回值的類型,并調(diào)用適當(dāng)?shù)姆椒╥f (returnType == "V") {  // 如果沒(méi)有返回值 (void 方法)// 調(diào)用 void 方法env->CallVoidMethodA(targetObject, methodID, params.data());} else if (returnType == "[B") {  // 如果返回值是 byte 數(shù)組jbyteArray result = (jbyteArray) env->CallObjectMethodA(targetObject, methodID, params.data());// 處理返回的 byte 數(shù)組if (result) {handleMoveResultObject(env, bytecode, pc, result);}} else if (returnType[0] == 'L') {  // 如果返回值是對(duì)象jobject objResult = env->CallObjectMethodA(targetObject, methodID, params.data());// 處理返回的對(duì)象if (objResult) {if(returnType == "Ljava/lang/String;"){jstring strResult = static_cast<jstring>(objResult);handleMoveResultObject(env, bytecode, pc, strResult);}else{handleMoveResultObject(env, bytecode, pc, objResult);}}} else if (returnType == "I") {  // 如果返回值是 intjint result = env->CallIntMethodA(targetObject, methodID, params.data());// 處理返回的 inthandleMoveResultObject(env, bytecode, pc, result);} else if (returnType == "Z") {  // 如果返回值是 booleanjboolean result = env->CallBooleanMethodA(targetObject, methodID, params.data());// 處理返回的 booleanhandleMoveResultObject(env, bytecode, pc, result);} else if (returnType == "D") {  // 如果返回值是 doublejdouble result = env->CallDoubleMethodA(targetObject, methodID, params.data());// 處理返回的 doublehandleMoveResultObject(env, bytecode, pc, result);} else if (returnType == "F") {  // 如果返回值是 floatjfloat result = env->CallFloatMethodA(targetObject, methodID, params.data());// 處理返回的 floathandleMoveResultObject(env, bytecode, pc, result);} else {throw std::runtime_error("Unsupported return type in method: " + returnType);}
}

6. return-object

這條指令通常用于結(jié)束一個(gè)方法的執(zhí)行,并將指定寄存器中的對(duì)象作為返回值返回給調(diào)用者。

例如:

return-object v2

解釋:

  • return-object:表示方法執(zhí)行結(jié)束時(shí),返回一個(gè)對(duì)象類型的值。

  • v2:表示返回的對(duì)象存儲(chǔ)在寄存器 v2 中。執(zhí)行這條指令時(shí),寄存器 v2 中的對(duì)象將作為方法的返回值。

指令結(jié)構(gòu)

一個(gè)標(biāo)準(zhǔn)的 return-object 字節(jié)碼指令通常如下所示(2個(gè)字節(jié)):

11 <reg>操作碼 (1 字節(jié))  | 目標(biāo)寄存器 (1 字節(jié))  

具體代碼實(shí)現(xiàn)

// return-object
void handleReturnResultObject(JNIEnv *env, const uint8_t *bytecode, size_t &pc) {uint8_t opcode = bytecode[pc];if (opcode == RETURN_OBJECT_OPCODE) {uint8_t reg = bytecode[pc + 1];  // 目標(biāo)寄存器// 把目標(biāo)寄存器中的值設(shè)置到 v0 寄存器setRegisterValue(0, registers[reg]);// 更新程序計(jì)數(shù)器pc += 2;}
}

注冊(cè)解析器

在 kotlin 層中定義 VMP 入口方法 execute

package com.cyrus.example.vmpclass SimpleVMP {companion object {// 加載本地庫(kù)init {System.loadLibrary("vmp-lib")}// 定義靜態(tài)方法 execute@JvmStaticexternal fun execute(bytecode: ByteArray, input: String): String}
}

在 JNI_Onload 中調(diào)用 RegisterNatives 方法動(dòng)態(tài)注冊(cè) C++ 中的 execute 方法到 com/cyrus/example/vmp/SimpleVMP

// 定義方法簽名
static JNINativeMethod gMethods[] = {{"execute", "([BLjava/lang/String;)Ljava/lang/String;", (void*)execute}
};// JNI_OnLoad 動(dòng)態(tài)注冊(cè)方法
extern "C" JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) {JNIEnv *env = nullptr;if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {return JNI_ERR;}jclass clazz = env->FindClass("com/cyrus/example/vmp/SimpleVMP");if (clazz == nullptr) {return JNI_ERR; // 類未找到}// 注冊(cè)所有本地方法jint result = env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0]));if (result != JNI_OK) {return JNI_ERR; // 注冊(cè)失敗}return JNI_VERSION_1_6;
}

測(cè)試

把 sign 方法的調(diào)用改為通過(guò) VMP 執(zhí)行 sign 算法計(jì)算 input 參數(shù)的加密結(jié)果。

// 參數(shù)
val input = "example"// 模擬 smali 指令的字節(jié)流
val bytecode = byteArrayOf(0x1A, 0x00, 0x4E, 0x00, // const-string v0, "input"0x71, 0x20, 0x20, 0x00, 0x05, 0x00, // invoke-static{v5, v0}, checkNotNullParameter0x1A, 0x00, 0x2C, 0x00, // const-string v0, "SHA-256"0x71, 0x10, 0x1C, 0x00, 0x00, 0x00, // invoke-static{v0}, getInstance0x0C, 0x00, // move-result-object v00x62, 0x01, 0x09, 0x00, // sget-object v1, UTF_80x6E, 0x20, 0x16, 0x00, 0x15, 0x00, // invoke-virtual{v5, v1}, getBytes0x0C, 0x01, // move-result-object v10x6E, 0x20, 0x1B, 0x00, 0x10, 0x00, // invoke-virtual{v0, v1}, digest0x0C, 0x01, // move-result-object v10x71, 0x00, 0x1E, 0x00, 0x00, 0x00, // invoke-static{}, getEncoder0x0C, 0x02, // move-result-object v20x6E, 0x20, 0x1D, 0x00, 0x12, 0x00, // invoke-virtual{v2, v1}, encodeToString0x0C, 0x02, // move-result-object v20x11, 0x02  // return-object v2
)// 通過(guò) VMP 解析器執(zhí)行指令流
val result = SimpleVMP.execute(bytecode, input)// 顯示 Toast
Toast.makeText(this, result, Toast.LENGTH_SHORT).show()

通過(guò) VMP 執(zhí)行結(jié)果如下:

word/media/image5.png

和原來(lái)算法對(duì)比結(jié)果是一樣的。

word/media/image6.png

安全性增強(qiáng)

  1. 指令流加密:比如使用 AES 加密指令流,在運(yùn)行時(shí)解密執(zhí)行。

  2. 動(dòng)態(tài)加載:使用 dex 動(dòng)態(tài)加載虛擬機(jī)和指令流。

  3. 多態(tài)指令集:每次保護(hù)代碼時(shí)動(dòng)態(tài)生成不同的指令集,防止通過(guò)固定指令集逆向。

  4. 反調(diào)試檢測(cè):檢測(cè)調(diào)試器附加、內(nèi)存修改或運(yùn)行環(huán)境,防止虛擬機(jī)被分析。

優(yōu)點(diǎn)與局限

優(yōu)點(diǎn)

  • 提高逆向難度:通過(guò)指令集和虛擬機(jī)隱藏關(guān)鍵邏輯。

  • 動(dòng)態(tài)保護(hù):運(yùn)行時(shí)加載和執(zhí)行,防止靜態(tài)分析。

局限

  • 性能開(kāi)銷:解釋執(zhí)行比原生代碼慢。

  • 開(kāi)發(fā)成本:需要設(shè)計(jì)和實(shí)現(xiàn)虛擬機(jī)框架。

通過(guò)上述方法,可以實(shí)現(xiàn)一個(gè)基本的自定義 Android 虛擬機(jī)保護(hù),并根據(jù)需要逐步增強(qiáng)安全性。

源碼

完整源碼:https://github.com/CYRUS-STUDIO/AndroidExample

http://www.risenshineclean.com/news/22480.html

相關(guān)文章:

  • 怎樣做汽車(chē)之家視頻網(wǎng)站游戲推廣員如何推廣引流
  • 高古樓網(wǎng)站 做窗子網(wǎng)站制作的基本流程
  • 怎么做公司網(wǎng)站推廣免費(fèi)正規(guī)的接單平臺(tái)
  • 西寧企業(yè)做網(wǎng)站互聯(lián)網(wǎng)公司有哪些
  • 剛開(kāi)始做寫(xiě)手上什么網(wǎng)站seo大全
  • 企業(yè)網(wǎng)站建立網(wǎng)絡(luò)虛擬社區(qū)時(shí)對(duì)于企業(yè)成品短視頻網(wǎng)站源碼搭建
  • 織夢(mèng)手機(jī)網(wǎng)站模板刪除注冊(cè)城鄉(xiāng)規(guī)劃師含金量
  • 網(wǎng)站開(kāi)發(fā)時(shí)怎么隱藏文字上海關(guān)鍵詞排名優(yōu)化怎樣
  • 美國(guó)人做的古文字網(wǎng)站亞馬遜免費(fèi)的關(guān)鍵詞工具
  • 靈山招聘網(wǎng)靈山英才網(wǎng)做靈山專業(yè)的招聘網(wǎng)站百度一下點(diǎn)擊搜索
  • 騰訊服務(wù)器做網(wǎng)站龍崗網(wǎng)站設(shè)計(jì)
  • 網(wǎng)站建設(shè)策劃書(shū)范文案例網(wǎng)絡(luò)優(yōu)化工程師騙局
  • 不收費(fèi)的小說(shuō)網(wǎng)站排名百度推廣非企代理
  • git 網(wǎng)站開(kāi)發(fā)應(yīng)用有哪些網(wǎng)絡(luò)營(yíng)銷公司
  • 蘇州做網(wǎng)站推廣常州網(wǎng)站推廣
  • 東莞網(wǎng)站建站公司seo網(wǎng)絡(luò)推廣是干嘛的
  • 怎么做網(wǎng)站步驟重慶seo論壇
  • 敦煌做網(wǎng)站的公司電話營(yíng)銷模式
  • 深圳網(wǎng)站建設(shè)哪家好附子seo
  • 新電商平臺(tái)seo整站優(yōu)化吧
  • wordpress 插件被墻免費(fèi)seo工具
  • 蘇州疫情最新政策seo外鏈資源
  • 淄博張店網(wǎng)站排名優(yōu)化怎么自己做一個(gè)網(wǎng)頁(yè)
  • 鷹潭做網(wǎng)站自己怎么做網(wǎng)站優(yōu)化
  • 南昌網(wǎng)站seo技術(shù)廠家如何做公司網(wǎng)站推廣
  • dedecms建手機(jī)網(wǎng)站游戲推廣是什么工作
  • 北京本地服務(wù)信息網(wǎng)西安seo站內(nèi)優(yōu)化
  • 永興縣網(wǎng)站建設(shè)公司哪家好湖人最新排名最新排名
  • 網(wǎng)站建設(shè)怎么記賬手機(jī)關(guān)鍵詞點(diǎn)擊排名軟件
  • 網(wǎng)站建設(shè)相關(guān)資料整理的重要性seo快排技術(shù)教程