免責聲明:內容僅供學習參考,請合法利用知識,禁止進行違法犯罪活動!
內容參考于:圖靈Python學院
工具下載:
鏈接:https://pan.baidu.com/s/1bb8NhJc9eTuLzQr39lF55Q?pwd=zy89
提取碼:zy89
復制這段內容后打開百度網盤手機App,操作更方便哦
上一個內容:42.安卓逆向2-補環境-unidbg安裝和簡單使用
開始之前先說明一下,unidbg是一個系統虛擬環境,它并不會去找某個app,它只是模擬一個運行環境,unidbg用到app的類、包名全部是假的(并不是我們使用反編譯工具看到的那樣,unidbg它只是通過類名、方法去構建一個假的類,然后去調用so文件),unidbg它只是模擬的安卓系統(模型安卓系統標準庫函數),如果so文件或java代碼依賴了開發者自定義的邏輯,unidbg是沒辦法模擬的,需要我們自己手動處理
然后先設置一下ida的目錄顯示,如下圖把壓縮空的中間軟件包取消勾選
首先創建一個目錄(或者叫包,包和目錄是一個東西),如下圖紅框的目錄,在com路面選擇新加軟件包
目錄名mmmm,輸入完按回車
按完回車目錄就創建好了,如下圖紅框
然后再創建一個java類(這里又創建了一個叫dac的目錄,在它里面創建java類)
如下圖java類名,輸入完按回車
然后就創建好了
然后開始寫unidbg的代碼,首先是初始結構和模擬器
package com.mmmm.dac;// 導入Unidbg框架的核心類
// AndroidEmulator:Android模擬器的核心類,用于模擬Android運行環境
import com.github.unidbg.AndroidEmulator;
// Module:用于操作加載的SO文件(動態鏈接庫)
import com.github.unidbg.Module;
// AndroidEmulatorBuilder:模擬器構建器,用于創建不同配置的模擬器
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
// AndroidResolver:用于解析Android系統庫,模擬系統庫調用
import com.github.unidbg.linux.android.AndroidResolver;
// AbstractJni:JNI抽象類,用于處理SO中的JNI調用(如Java方法調用)
import com.github.unidbg.linux.android.dvm.AbstractJni;
// DalvikModule:Dalvik虛擬機中的模塊類,用于加載和處理SO文件
import com.github.unidbg.linux.android.dvm.DalvikModule;
// VM:Dalvik虛擬機類,模擬Android的Java虛擬機環境
import com.github.unidbg.linux.android.dvm.VM;
// Memory:內存操作接口,用于管理模擬器的內存
import com.github.unidbg.memory.Memory;// 用于文件操作的Java標準類
import java.io.File;/*** DcTest類:繼承自AbstractJni,用于測試和分析目標SO文件* 作用:模擬Android環境,加載指定的SO文件,并獲取其中的函數地址等信息*/
public class DcTest extends AbstractJni {// 成員變量聲明// 模擬器實例:整個模擬環境的核心private final AndroidEmulator emulator;// 虛擬機實例:模擬Android的Java虛擬機private final VM vm;// 模塊實例:代表加載的SO文件,用于操作其中的函數和符號private final Module module;/*** 構造方法:初始化模擬器、虛擬機和加載SO文件* 當創建DcTest對象時,會自動執行這些初始化操作*/public DcTest(){// 1. 創建Android模擬器實例// for64Bit():指定創建64位模擬器(如果目標SO是32位,需改為for32Bit())// setProcessName():設置模擬的進程名,通常設為目標SO所在的APP包名// build():完成模擬器構建emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.xxx.news").build();// 2. 獲取模擬器的內存操作接口// 內存接口用于管理模擬器的內存分配、庫解析等final Memory memory = emulator.getMemory();// 3. 設置系統庫解析器// AndroidResolver(23):指定模擬的Android系統版本為API 23(Android 6.0)// 作用:當SO調用系統庫(如libc.so)時,模擬器能正確解析并模擬這些調用memory.setLibraryResolver(new AndroidResolver(23));// 4. 創建Dalvik虛擬機(Android的Java虛擬機)vm = emulator.createDalvikVM();// 5. 設置JNI處理器// 將當前類(DcTest)作為JNI調用的處理器// 當SO中調用JNI函數(如調用Java方法)時,會由當前類處理vm.setJni(this);// 6. 加載目標SO文件// 第一個參數:SO文件的路徑(這里是相對路徑,實際使用時需確保文件存在)// 第二個參數:false表示不自動調用JNI_OnLoad(后續會手動調用)// DalvikModule:用于在Dalvik虛擬機中管理SO文件DalvikModule dm = vm.loadLibrary(new File("utils/dc1127/libwtf.so"), false);// 7. 手動調用SO中的JNI_OnLoad函數// JNI_OnLoad是SO被加載時的初始化函數,通常用于注冊JNI方法// 動態注冊的JNI方法需要調用此函數才會生效,靜態注冊可以省略dm.callJNI_OnLoad(emulator);// 8. 獲取Module實例// Module是操作SO文件的主要接口,通過它可以查找函數、獲取基地址等module = dm.getModule();// 9. 打印SO文件的基地址// 基地址是SO加載到內存中的起始地址,函數的實際地址=基地址+偏移量System.out.println("SO文件基地址:" + module.base);// 10. 查找并打印指定函數的地址(C++函數,經過名稱修飾)// _ZN3MD56updateEPKhj:是C++函數MD5::update(const unsigned char*, unsigned int)的名稱修飾后的結果// findSymbolByName:通過函數名查找符號// getAddress():獲取函數在內存中的地址int address = (int)module.findSymbolByName("_ZN3MD56updateEPKhj").getAddress();System.out.println("MD5::update函數地址(十六進制):" + Long.toHexString(address));// 11. 查找并打印Java native方法對應的C函數地址// Java_cn_thecover_lib_common_manager_SignManager_getSign:// 是Java類cn.thecover.lib.common.manager.SignManager中的getSign()本地方法對應的C函數名// 這是JNI靜態注冊的命名規則:Java_包名_類名_方法名int funaddr = (int)module.findSymbolByName("Java_cn_thecover_lib_common_manager_SignManager_getSign").getAddress();System.out.println("getSign函數地址(十六進制):" + Long.toHexString(funaddr));}
}
然后為了方便引入so文件,這里創建一個目錄專門用來存放so文件,如下圖鼠標右擊根目錄,選擇新建目錄
然后輸入utils,然后按回車
然后在utils目錄再創建一個目錄,這個目錄是為了區分不同app的so文件(比如兩個app一個叫測試1一個叫測試2,然后創建一個測1目錄,然后測試1里用到的so文件全部都放到測1目錄里,然后創建一個測2目錄,用來存放測試2app中使用的so文件)
如下圖的目錄和文件
如下圖代碼中的使用
如下圖上圖紅框的so文件中存在getSign方法
然后可以得到一個類路徑,如下圖紅框
然后使用unidbg獲取這個SignManager類(可以理解為讓unidbg使用cn/thecover/lib/common/manager/SignManager這個類路徑創建一個虛擬的類),這里用的smail語法
vm.resolveClass("cn/thecover/lib/common/manager/SignManager");
獲取方法名,首先要把下圖紅框的代碼轉成smail代碼
如下圖利用大模型轉
# 聲明一個公共靜態 native 方法
.method public static native getSign(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
.end method
然后復制下圖紅框的代碼
然后unidbg代碼
//要調用的方法
String method = "getSign(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;";// 調用方法時傳的參數
String 參數1 = "1";
String 參數2 = "2";
String 參數3 = "3";//調用方法和獲取返回值
StringObject value = dvmClass.callStaticJniMethodObject(emulator, method, 參數1, 參數2, 參數3);
調用方法的完整代碼
/*** 調用目標Java類的靜態native方法getSign,并返回結果* 功能:通過Unidbg模擬調用SO中實現的getSign方法,傳遞三個字符串參數并獲取返回值*/
public String getSign(){// 1. 加載并獲取目標Java類的虛擬表示(DvmClass)// vm.resolveClass:讓Unidbg的虛擬機(vm)查找并加載指定的Java類// 參數是類的全限定名(用斜杠分隔),對應真實Java類:cn.thecover.lib.common.manager.SignManager// 返回的DvmClass對象相當于這個類在虛擬環境中的"代言人",通過它可以操作這個類的靜態方法DvmClass dvmClass = vm.resolveClass("cn/thecover/lib/common/manager/SignManager");// 2. 定義要調用的方法簽名(方法的"身份證")// 格式:方法名(參數類型列表)返回值類型// 這里的簽名對應Java方法:public static native String getSign(String, String, String)// 解析:// - Ljava/lang/String; 表示參數類型為String(Smali語法,所有引用類型都用這種格式)// - 三個Ljava/lang/String; 對應三個String參數// - 最后的Ljava/lang/String; 表示返回值為StringString method = "getSign(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;";// 3. 準備調用方法時需要傳遞的參數String 參數1 = "1"; // 第一個字符串參數,實際使用中可能是具體業務數據(如時間戳、設備ID等)String 參數2 = "2"; // 第二個字符串參數,可能是加密鹽值、用戶ID等String 參數3 = "3"; // 第三個字符串參數,可能是隨機數、簽名類型等// 4. 調用目標類的靜態native方法// dvmClass.callStaticJniMethodObject:通過虛擬類對象調用靜態方法// 參數說明:// - emulator:當前的Android模擬器實例(提供運行環境)// - method:上面定義的方法簽名(指定要調用的具體方法)// - 參數1/2/3:傳遞給方法的實際參數// 返回值:StringObject(Unidbg中對String的包裝類,包含方法調用的結果)StringObject value = dvmClass.callStaticJniMethodObject(emulator, method, 參數1, 參數2, 參數3);// 5. 提取返回結果并返回// value.getValue():將Unidbg的StringObject轉換為Java原生Stringreturn value.getValue();
}
運行代碼
package com.mmmm.dac;// 導入Unidbg框架的核心類
// AndroidEmulator:Android模擬器的核心類,用于模擬Android運行環境
import com.github.unidbg.AndroidEmulator;
// Module:用于操作加載的SO文件(動態鏈接庫)
import com.github.unidbg.Module;
// AndroidEmulatorBuilder:模擬器構建器,用于創建不同配置的模擬器
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
// AndroidResolver:用于解析Android系統庫,模擬系統庫調用
import com.github.unidbg.linux.android.AndroidResolver;
// AbstractJni:JNI抽象類,用于處理SO中的JNI調用(如Java方法調用)
import com.github.unidbg.linux.android.dvm.*;
// DalvikModule:Dalvik虛擬機中的模塊類,用于加載和處理SO文件
// VM:Dalvik虛擬機類,模擬Android的Java虛擬機環境
// Memory:內存操作接口,用于管理模擬器的內存
import com.github.unidbg.memory.Memory;// 用于文件操作的Java標準類
import java.io.File;/*** DcTest類:繼承自AbstractJni,用于測試和分析目標SO文件* 作用:模擬Android環境,加載指定的SO文件,并獲取其中的函數地址等信息*/
public class DcTest extends AbstractJni {// 成員變量聲明// 模擬器實例:整個模擬環境的核心private final AndroidEmulator emulator;// 虛擬機實例:模擬Android的Java虛擬機private final VM vm;// 模塊實例:代表加載的SO文件,用于操作其中的函數和符號private final Module module;/*** 構造方法:初始化模擬器、虛擬機和加載SO文件* 當創建DcTest對象時,會自動執行這些初始化操作*/public DcTest(){// 1. 創建Android模擬器實例// for64Bit():指定創建64位模擬器(如果目標SO是32位,需改為for32Bit())// setProcessName():設置模擬的進程名,通常設為目標SO所在的APP包名// build():完成模擬器構建emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.xxx.news").build();// 2. 獲取模擬器的內存操作接口// 內存接口用于管理模擬器的內存分配、庫解析等final Memory memory = emulator.getMemory();// 3. 設置系統庫解析器// AndroidResolver(23):指定模擬的Android系統版本為API 23(Android 6.0)// 作用:當SO調用系統庫(如libc.so)時,模擬器能正確解析并模擬這些調用memory.setLibraryResolver(new AndroidResolver(23));// 4. 創建Dalvik虛擬機(Android的Java虛擬機)vm = emulator.createDalvikVM();// 5. 設置JNI處理器// 將當前類(DcTest)作為JNI調用的處理器// 當SO中調用JNI函數(如調用Java方法)時,會由當前類處理vm.setJni(this);// 6. 加載目標SO文件// 第一個參數:SO文件的路徑(這里是相對路徑,實際使用時需確保文件存在)// 第二個參數:false表示不自動調用JNI_OnLoad(后續會手動調用)// DalvikModule:用于在Dalvik虛擬機中管理SO文件DalvikModule dm = vm.loadLibrary(new File("utils/dcs/libwtf.so"), false);// 7. 手動調用SO中的JNI_OnLoad函數// JNI_OnLoad是SO被加載時的初始化函數,通常用于注冊JNI方法// 動態注冊的JNI方法需要調用此函數才會生效,靜態注冊可以省略dm.callJNI_OnLoad(emulator);// 8. 獲取Module實例// Module是操作SO文件的主要接口,通過它可以查找函數、獲取基地址等module = dm.getModule();// 9. 打印SO文件的基地址// 基地址是SO加載到內存中的起始地址,函數的實際地址=基地址+偏移量System.out.println("SO文件基地址:" + module.base);// 10. 查找并打印指定函數的地址(C++函數,經過名稱修飾)// _ZN3MD56updateEPKhj:是C++函數MD5::update(const unsigned char*, unsigned int)的名稱修飾后的結果// findSymbolByName:通過函數名查找符號// getAddress():獲取函數在內存中的地址int address = (int)module.findSymbolByName("_ZN3MD56updateEPKhj").getAddress();System.out.println("MD5::update函數地址(十六進制):" + Long.toHexString(address));// 11. 查找并打印Java native方法對應的C函數地址// Java_cn_thecover_lib_common_manager_SignManager_getSign:// 是Java類cn.thecover.lib.common.manager.SignManager中的getSign()本地方法對應的C函數名// 這是JNI靜態注冊的命名規則:Java_包名_類名_方法名int funaddr = (int)module.findSymbolByName("Java_cn_thecover_lib_common_manager_SignManager_getSign").getAddress();System.out.println("getSign函數地址(十六進制):" + Long.toHexString(funaddr));}/*** 調用目標Java類的靜態native方法getSign,并返回結果* 功能:通過Unidbg模擬調用SO中實現的getSign方法,傳遞三個字符串參數并獲取返回值*/public String getSign(){// 1. 加載并獲取目標Java類的虛擬表示(DvmClass)// vm.resolveClass:讓Unidbg的虛擬機(vm)查找并加載指定的Java類// 參數是類的全限定名(用斜杠分隔),對應真實Java類:cn.thecover.lib.common.manager.SignManager// 返回的DvmClass對象相當于這個類在虛擬環境中的"代言人",通過它可以操作這個類的靜態方法DvmClass dvmClass = vm.resolveClass("cn/thecover/lib/common/manager/SignManager");// 2. 定義要調用的方法簽名(方法的"身份證")// 格式:方法名(參數類型列表)返回值類型// 這里的簽名對應Java方法:public static native String getSign(String, String, String)// 解析:// - Ljava/lang/String; 表示參數類型為String(Smali語法,所有引用類型都用這種格式)// - 三個Ljava/lang/String; 對應三個String參數// - 最后的Ljava/lang/String; 表示返回值為StringString method = "getSign(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;";// 3. 準備調用方法時需要傳遞的參數String 參數1 = "1"; // 第一個字符串參數,實際使用中可能是具體業務數據(如時間戳、設備ID等)String 參數2 = "2"; // 第二個字符串參數,可能是加密鹽值、用戶ID等String 參數3 = "3"; // 第三個字符串參數,可能是隨機數、簽名類型等// 4. 調用目標類的靜態native方法// dvmClass.callStaticJniMethodObject:通過虛擬類對象調用靜態方法// 參數說明:// - emulator:當前的Android模擬器實例(提供運行環境)// - method:上面定義的方法簽名(指定要調用的具體方法)// - 參數1/2/3:傳遞給方法的實際參數// 返回值:StringObject(Unidbg中對String的包裝類,包含方法調用的結果)StringObject value = dvmClass.callStaticJniMethodObject(emulator, method, 參數1, 參數2, 參數3);// 5. 提取返回結果并返回// value.getValue():將Unidbg的StringObject轉換為Java原生Stringreturn value.getValue();}public static void main(String[] args) {DcTest dcTest = new DcTest();String sign = dcTest.getSign();System.out.println(sign);}}
運行后會報錯,這個錯誤是調用 getSign(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; so方法,它里面又調用了cn/thecover/lib/common/utils/LogShutDown類里的getAppSign方法,這個getAppSign方法找不到就會出下圖中的錯誤
如下圖還有一個Please.vm.setJni,這個與上方的錯誤一樣,都是so方法中調用了java方法,然后找不到調用的java方法就報錯了
這就需要補環境,再補環境之前,還有一個東西,上方是通過smail語法去調用的方法,接下來寫通過so文件的方法地址來調用方法,如下圖使用ida打開so文件,接下來就通過調用MD5的update方法做實例
上圖紅框并不是真正的函數名,真正的函數名是_ZN3MD56updateEPKhj
函數的地址43834
通過下圖紅框的代碼也可以獲取_ZN3MD56updateEPKhj方法地址
獲取java方法的地址
使用地址調用方法
/*** 通過Unidbg調用原生函數獲取簽名結果* 功能:調用指定地址的原生函數,傳入三個字符串參數,返回函數處理后的簽名字符串*/
public String getSignAdd(){// 定義要調用的原生函數在目標模塊中的內存地址(偏移量)// 0x45B48是通過逆向分析(如IDA、Ghidra)得到的函數地址long functionAddress = 0x45B48;/** 準備JNI環境相關對象* JNI(Java Native Interface)是Java與原生代碼交互的接口*/// 獲取JNI環境指針(JNIEnv*),這是調用任何JNI函數的第一個參數Pointer jniEnv = vm.getJNIEnv();// 創建三個字符串對象作為函數參數// StringObject是Unidbg中用于表示Java字符串的包裝類StringObject data1 = new StringObject(vm, "1"); // 第一個字符串參數值為"1"StringObject data2 = new StringObject(vm, "2"); // 第二個字符串參數值為"2"StringObject data3 = new StringObject(vm, "3"); // 第三個字符串參數值為"3"// 構建函數調用的參數列表List<Object> args = new ArrayList<>();// 添加第一個參數:JNI環境指針(JNIEnv*),這是JNI函數的標準第一個參數args.add(jniEnv);// 添加后續參數:將字符串對象轉換為DVM本地引用// vm.addLocalObject()會將對象添加到Dalvik虛擬機的本地引用表,返回引用ID// 原生函數通過這個引用ID可以訪問到對應的Java對象args.add(vm.addLocalObject(data1));args.add(vm.addLocalObject(data2));args.add(vm.addLocalObject(data3));// 調用目標原生函數// module.callFunction():通過模塊調用指定地址的函數// 參數說明:模擬器實例、函數地址、參數數組// 返回值:原生函數的返回結果(這里是一個對象引用ID)Number numbers = module.callFunction(emulator, functionAddress, args.toArray());// 將返回的引用ID轉換為DVM中的對象// 原生函數返回的是Java對象引用,需要通過vm.getObject()獲取實際對象DvmObject<?> object = vm.getObject(numbers.intValue());// 從DVM對象中提取字符串值// 假設原生函數返回的是String類型對象,通過getValue()獲取其字符串內容String value = (String) object.getValue();// 返回獲取到的簽名結果return value;
}
完整代碼
package com.mmmm.dac;// 導入Unidbg框架的核心類
// AndroidEmulator:Android模擬器的核心類,用于模擬Android運行環境
import com.github.unidbg.AndroidEmulator;
// Module:用于操作加載的SO文件(動態鏈接庫)
import com.github.unidbg.Module;
// AndroidEmulatorBuilder:模擬器構建器,用于創建不同配置的模擬器
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.arm.context.Arm64RegisterContext;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
// AndroidResolver:用于解析Android系統庫,模擬系統庫調用
import com.github.unidbg.linux.android.AndroidResolver;
// AbstractJni:JNI抽象類,用于處理SO中的JNI調用(如Java方法調用)
import com.github.unidbg.linux.android.dvm.*;
// DalvikModule:Dalvik虛擬機中的模塊類,用于加載和處理SO文件
// VM:Dalvik虛擬機類,模擬Android的Java虛擬機環境
// Memory:內存操作接口,用于管理模擬器的內存
import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.pointer.UnidbgPointer;
import com.sun.jna.Pointer;// 用于文件操作的Java標準類
import java.io.File;
import java.util.ArrayList;
import java.util.List;/*** DcTest類:繼承自AbstractJni,用于測試和分析目標SO文件* 作用:模擬Android環境,加載指定的SO文件,并獲取其中的函數地址等信息*/
public class DcTest extends AbstractJni {// 成員變量聲明// 模擬器實例:整個模擬環境的核心private final AndroidEmulator emulator;// 虛擬機實例:模擬Android的Java虛擬機private final VM vm;// 模塊實例:代表加載的SO文件,用于操作其中的函數和符號private final Module module;/*** 構造方法:初始化模擬器、虛擬機和加載SO文件* 當創建DcTest對象時,會自動執行這些初始化操作*/public DcTest(){// 1. 創建Android模擬器實例// for64Bit():指定創建64位模擬器(如果目標SO是32位,需改為for32Bit())// setProcessName():設置模擬的進程名,通常設為目標SO所在的APP包名// build():完成模擬器構建emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.xxx.news").build();// 2. 獲取模擬器的內存操作接口// 內存接口用于管理模擬器的內存分配、庫解析等final Memory memory = emulator.getMemory();// 3. 設置系統庫解析器// AndroidResolver(23):指定模擬的Android系統版本為API 23(Android 6.0)// 作用:當SO調用系統庫(如libc.so)時,模擬器能正確解析并模擬這些調用memory.setLibraryResolver(new AndroidResolver(23));// 4. 創建Dalvik虛擬機(Android的Java虛擬機)vm = emulator.createDalvikVM();// 5. 設置JNI處理器// 將當前類(DcTest)作為JNI調用的處理器// 當SO中調用JNI函數(如調用Java方法)時,會由當前類處理vm.setJni(this);// 6. 加載目標SO文件// 第一個參數:SO文件的路徑(這里是相對路徑,實際使用時需確保文件存在)// 第二個參數:false表示不自動調用JNI_OnLoad(后續會手動調用)// DalvikModule:用于在Dalvik虛擬機中管理SO文件DalvikModule dm = vm.loadLibrary(new File("utils/dcs/libwtf.so"), false);// 7. 手動調用SO中的JNI_OnLoad函數// JNI_OnLoad是SO被加載時的初始化函數,通常用于注冊JNI方法// 動態注冊的JNI方法需要調用此函數才會生效,靜態注冊可以省略dm.callJNI_OnLoad(emulator);// 8. 獲取Module實例// Module是操作SO文件的主要接口,通過它可以查找函數、獲取基地址等module = dm.getModule();// 9. 打印SO文件的基地址// 基地址是SO加載到內存中的起始地址,函數的實際地址=基地址+偏移量System.out.println("SO文件基地址:" + module.base);// 10. 查找并打印指定函數的地址(C++函數,經過名稱修飾)// _ZN3MD56updateEPKhj:是C++函數MD5::update(const unsigned char*, unsigned int)的名稱修飾后的結果// findSymbolByName:通過函數名查找符號// getAddress():獲取函數在內存中的地址int address = (int)module.findSymbolByName("_ZN3MD56updateEPKhj").getAddress();System.out.println("MD5::update函數地址(十六進制):" + Long.toHexString(address));// 11. 查找并打印Java native方法對應的C函數地址// Java_cn_thecover_lib_common_manager_SignManager_getSign:// 是Java類cn.thecover.lib.common.manager.SignManager中的getSign()本地方法對應的C函數名// 這是JNI靜態注冊的命名規則:Java_包名_類名_方法名int funaddr = (int)module.findSymbolByName("Java_cn_thecover_lib_common_manager_SignManager_getSign").getAddress();System.out.println("getSign函數地址(十六進制):" + Long.toHexString(funaddr));}/*** 調用目標Java類的靜態native方法getSign,并返回結果* 功能:通過Unidbg模擬調用SO中實現的getSign方法,傳遞三個字符串參數并獲取返回值*/public String getSign(){// 1. 加載并獲取目標Java類的虛擬表示(DvmClass)// vm.resolveClass:讓Unidbg的虛擬機(vm)查找并加載指定的Java類// 參數是類的全限定名(用斜杠分隔),對應真實Java類:cn.thecover.lib.common.manager.SignManager// 返回的DvmClass對象相當于這個類在虛擬環境中的"代言人",通過它可以操作這個類的靜態方法DvmClass dvmClass = vm.resolveClass("cn/thecover/lib/common/manager/SignManager");// 2. 定義要調用的方法簽名(方法的"身份證")// 格式:方法名(參數類型列表)返回值類型// 這里的簽名對應Java方法:public static native String getSign(String, String, String)// 解析:// - Ljava/lang/String; 表示參數類型為String(Smali語法,所有引用類型都用這種格式)// - 三個Ljava/lang/String; 對應三個String參數// - 最后的Ljava/lang/String; 表示返回值為StringString method = "getSign(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;";// 3. 準備調用方法時需要傳遞的參數String 參數1 = "1"; // 第一個字符串參數,實際使用中可能是具體業務數據(如時間戳、設備ID等)String 參數2 = "2"; // 第二個字符串參數,可能是加密鹽值、用戶ID等String 參數3 = "3"; // 第三個字符串參數,可能是隨機數、簽名類型等// 4. 調用目標類的靜態native方法// dvmClass.callStaticJniMethodObject:通過虛擬類對象調用靜態方法// 參數說明:// - emulator:當前的Android模擬器實例(提供運行環境)// - method:上面定義的方法簽名(指定要調用的具體方法)// - 參數1/2/3:傳遞給方法的實際參數// 返回值:StringObject(Unidbg中對String的包裝類,包含方法調用的結果)StringObject value = dvmClass.callStaticJniMethodObject(emulator, method, 參數1, 參數2, 參數3);// 5. 提取返回結果并返回// value.getValue():將Unidbg的StringObject轉換為Java原生Stringreturn value.getValue();}/*** 通過Unidbg調用原生函數獲取簽名結果* 功能:調用指定地址的原生函數,傳入三個字符串參數,返回函數處理后的簽名字符串*/public String getSignAdd(){// 定義要調用的原生函數在目標模塊中的內存地址(偏移量)// 0x45B48是通過逆向分析(如IDA、Ghidra)得到的函數地址long functionAddress = 0x45B48;/** 準備JNI環境相關對象* JNI(Java Native Interface)是Java與原生代碼交互的接口*/// 獲取JNI環境指針(JNIEnv*),這是調用任何JNI函數的第一個參數Pointer jniEnv = vm.getJNIEnv();// 創建三個字符串對象作為函數參數// StringObject是Unidbg中用于表示Java字符串的包裝類StringObject data1 = new StringObject(vm, "1"); // 第一個字符串參數值為"1"StringObject data2 = new StringObject(vm, "2"); // 第二個字符串參數值為"2"StringObject data3 = new StringObject(vm, "3"); // 第三個字符串參數值為"3"// 構建函數調用的參數列表List<Object> args = new ArrayList<>();// 添加第一個參數:JNI環境指針(JNIEnv*),這是JNI函數的標準第一個參數args.add(jniEnv);// 添加后續參數:將字符串對象轉換為DVM本地引用// vm.addLocalObject()會將對象添加到Dalvik虛擬機的本地引用表,返回引用ID// 原生函數通過這個引用ID可以訪問到對應的Java對象args.add(vm.addLocalObject(data1));args.add(vm.addLocalObject(data2));args.add(vm.addLocalObject(data3));// 調用目標原生函數// module.callFunction():通過模塊調用指定地址的函數// 參數說明:模擬器實例、函數地址、參數數組// 返回值:原生函數的返回結果(這里是一個對象引用ID)Number numbers = module.callFunction(emulator, functionAddress, args.toArray());// 將返回的引用ID轉換為DVM中的對象// 原生函數返回的是Java對象引用,需要通過vm.getObject()獲取實際對象DvmObject<?> object = vm.getObject(numbers.intValue());// 從DVM對象中提取字符串值// 假設原生函數返回的是String類型對象,通過getValue()獲取其字符串內容String value = (String) object.getValue();// 返回獲取到的簽名結果return value;}public static void main(String[] args) {DcTest dcTest = new DcTest();String sign = dcTest.getSignAdd();System.out.println(sign);}}
補自定義環境在下一節中