長(zhǎng)沙定制網(wǎng)站建設(shè)一站式自媒體服務(wù)平臺(tái)
版權(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)
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
得到字節(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 ¶mType) {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ū)域。
這里簡(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)用。
具體代碼實(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é)果如下:
和原來(lái)算法對(duì)比結(jié)果是一樣的。
安全性增強(qiáng)
-
指令流加密:比如使用 AES 加密指令流,在運(yùn)行時(shí)解密執(zhí)行。
-
動(dòng)態(tài)加載:使用 dex 動(dòng)態(tài)加載虛擬機(jī)和指令流。
-
多態(tài)指令集:每次保護(hù)代碼時(shí)動(dòng)態(tài)生成不同的指令集,防止通過(guò)固定指令集逆向。
-
反調(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