免責聲明:內容僅供學習參考,請合法利用知識,禁止進行違法犯罪活動!
內容參考于:圖靈Python學院
工具下載:
鏈接:https://pan.baidu.com/s/1bb8NhJc9eTuLzQr39lF55Q?pwd=zy89
提取碼:zy89
復制這段內容后打開百度網盤手機App,操作更方便哦
上一個內容:41.安卓逆向2-frida hook技術-過firda檢測(五)-利用ida分析app的so文件中frida檢測函數過檢測
unidbg可以實現單獨跑某個so文件,簡單說,Unidbg 就是一個 “讓隱藏代碼顯形的工具”—— 不管這些代碼藏在安卓還是蘋果系統里,不管有沒有加密,它都能搭一個 “假環境” 讓代碼運行起來,還能幫我們看清代碼的每一步操作(它支持斷點)。
下載:
https://github.com/zhkl0228/unidbg,打開網址后點擊下圖紅框
![]()
然后點擊下圖紅框進行下載
![]()
它是采用java語言和maven項目管理工具,所以就需要安裝java和maven,這倆放到百度網盤了(jdk下載很麻煩,之前很簡單,maven網絡不好沒法下),然后下圖紅框repository有點特殊,repository里面是java語言用到的一些代碼(別人寫好的代碼,給我用的),repository要使用maven加載,加載方式是修改maven的文件,后面寫了,jdk-8u202-windows-x64的安裝有點特殊后面寫了,其它的沒特殊的了,正常安裝就可以(最好把下面的內容都看完了再安裝)
![]()
下載完需要對maven進行設置,下載完maven解壓好,然后找到下圖紅框的文件
![]()
打開上圖紅框的文件,然后找到下圖紅框的內容
![]()
把上圖紅框中了目錄替換成,下圖紅框 repository 解壓位置就可以了
![]()
jdk安裝注意下圖紅框,要點一下,這個jdk就是java語言
![]()
下圖紅框的兩個,都根據上圖點一下,然后點擊下一步,不點的話安裝的不全
![]()
然后設置環境變量
![]()
然后找到Path
![]()
然后點擊新建
![]()
把java的bin目錄和maven的bin目錄添加進去,如下圖
![]()
然后點擊新建
![]()
創建一個JAVA_HOME,這個目錄是jdk安裝目錄
![]()
jdk安裝目錄如下圖
![]()
到這環境就配好了,打開控制臺使用,mvn -v查看maven的版本,java -version查看java版本,如果都正常就說明搞好了
![]()
寫代碼的工具使用 IntelliJ IDEA,也放到百度網盤了,安裝沒啥要注意的,可以百度idea安裝會有一大堆教學
![]()
unidbg導入idea中,點擊打開
然后找到unidbg解壓的目錄,這個目錄不要有中文,unidbg對中文支持的不好(有中文運行會報錯)
然后點擊信任
然后打開設置
然后設置maven,idea默認不采用我們的maven,所以要設置一下,注意第一次進入idea,它可能會下載一個maven,直接點擊叉號取消下載(它下載完會把我們設置的maven覆蓋,需要重新設置maven)
然后來到pom文件,點擊同步,然后等待同步結束就可以了,這個過程有點慢
然后打開代碼,如下圖會有紅色的錯誤,這是缺少jdk
然后再設置一下JDK,點擊項目結構
選擇我們上方安裝的jdk
點擊完確定就可以了,下方的SignUtil.java是unidbg作者給我們的實例
代碼是從下圖紅框開始執行的(main方法開始執行)
然后直接全選,復制給ai大模型,讓它解釋,這里用的豆包
被ai解釋過的代碼
package com.anjuke.mobile.sign; // 包名:類似文件夾,用于區分不同的類// 導入Unidbg框架的核心類:這些是別人寫好的工具,我們直接拿來用
import com.github.unidbg.AndroidEmulator; // 安卓模擬器核心類:模擬安卓手機的"大腦"
import com.github.unidbg.linux.android.AndroidEmulatorBuilder; // 模擬器建造者:用于"組裝"一個模擬器
import com.github.unidbg.linux.android.AndroidResolver; // 安卓系統庫解析器:告訴模擬器去哪里找系統文件
import com.github.unidbg.linux.android.dvm.DalvikModule; // Dalvik模塊:用于加載SO文件(類似打開一個程序)
import com.github.unidbg.linux.android.dvm.DvmClass; // 虛擬類:對應SO文件里的Java類
import com.github.unidbg.linux.android.dvm.StringObject; // 虛擬字符串:Unidbg里的字符串(和Java字符串略有不同)
import com.github.unidbg.linux.android.dvm.VM; // 虛擬機:模擬安卓的Java運行環境(類似手機里的Java引擎)
import com.github.unidbg.linux.android.dvm.jni.ProxyClassFactory; // JNI代理工廠:處理Java和C語言的交互
import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject; // 代理對象:把Java對象轉換成C語言能識別的格式
import com.github.unidbg.memory.Memory; // 內存管理:模擬手機的內存(存數據的地方)import java.io.File; // 文件操作類:用于找到SO文件的位置
import java.io.IOException; // 異常類:處理文件操作可能出現的錯誤(比如文件找不到)
import java.nio.charset.StandardCharsets; // 字符集:用于把字符串轉換成字節(計算機能看懂的0101)
import java.util.HashMap; // 哈希表:一種鍵值對數據結構(類似字典,方便查找)
import java.util.Map; // 映射接口:規定了哈希表等數據結構的基本操作/*** 簽名工具類:專門用來調用SO文件里的簽名方法* 背景:很多APP的簽名算法藏在SO文件里(C/C++寫的),直接看不懂,* 所以用Unidbg模擬手機環境,調用這個SO文件,拿到簽名結果*/
public class SignUtil {// 安卓模擬器實例:聲明一個模擬器變量(就像說"我要準備一個手機")// final表示這個變量一旦賦值就不能改了(防止被誤操作)private final AndroidEmulator emulator;// SO庫中的簽名工具類:對應SO文件里的SignUtil類(就像找到了程序里的一個功能模塊)private final DvmClass cSignUtil;// Dalvik虛擬機實例:安卓系統的Java虛擬機(手機里運行Java代碼的核心)private final VM vm;/*** 構造方法:創建對象時自動執行的代碼(相當于"開機初始化")* 作用:啟動模擬器、加載系統環境、準備好SO文件*/public SignUtil() {// 1. 創建32位安卓模擬器// AndroidEmulatorBuilder.for32Bit():選擇32位模式(因為很多手機SO是32位的)// setProcessName("com.anjuke.android.app"):告訴SO文件"我是安居客APP進程"// (有些SO會檢查調用者是不是合法APP,填對進程名才能通過檢查)// .build():完成建造,得到一個可用的模擬器emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.anjuke.android.app").build();// 2. 獲取模擬器的內存管理器(相當于拿到手機的"內存條"控制權)Memory memory = emulator.getMemory();// 3. 設置系統庫解析器,指定安卓版本為API 23(即Android 6.0)// 為什么要指定版本?因為不同安卓版本的系統文件不一樣,SO可能依賴特定版本的功能// 比如有些老SO只能在安卓6.0上運行,用高版本會出錯memory.setLibraryResolver(new AndroidResolver(23));// 4. 創建Dalvik虛擬機(相當于在模擬器里啟動Java運行環境)vm = emulator.createDalvikVM();// 5. 設置代理類工廠:處理JNI調用(SO是C寫的,Java調用C需要通過JNI接口)// 這個工廠會自動模擬一些JNI的基礎功能,讓SO能正常調用Java方法vm.setDvmClassFactory(new ProxyClassFactory());// 6. 關閉詳細日志:如果設為true,會打印很多調試信息(初學者可能看得眼花繚亂)vm.setVerbose(false);// 7. 加載目標SO文件(我們要調用的簽名算法就在這個文件里)// 參數1:SO文件的路徑(這里是示例路徑,你需要改成自己的SO文件位置)// 參數2:false表示不自動調用SO的初始化方法(后面我們會手動調用,更靈活)DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/armeabi-v7a/libsignutil.so"), false);// 8. 從SO文件中找到我們需要的SignUtil類// 參數是類的完整路徑(類似"文件夾/文件名"),必須和SO里定義的一致cSignUtil = vm.resolveClass("com/anjuke/mobile/sign/SignUtil");// 9. 手動調用SO的JNI_OnLoad方法(SO的初始化函數)// 相當于告訴SO:"環境準備好了,你可以初始化自己的資源了"dm.callJNI_OnLoad(emulator);}/*** 銷毀模擬器資源* 為什么需要這個?因為模擬器會占用電腦的內存和CPU,用完不關掉會浪費資源* @throws IOException 處理關閉時可能出現的錯誤(比如資源被占用)*/public void destroy() throws IOException {emulator.close(); // 關閉模擬器,釋放所有資源}/*** 調用SO庫中的getSign0方法(核心功能)* 相當于"按下SO里的簽名按鈕,傳入參數,得到結果"** @param p1 第一個參數(字符串類型)* @param p2 第二個參數(字符串類型)* @param map 第三個參數(鍵是字符串,值是字節數組)* @param p3 第四個參數(字符串類型)* @param i 第五個參數(整數類型)* @return 生成的簽名字符串*/public String getSign0(String p1, String p2, Map<String, byte[]> map, String p3, int i) {// 方法簽名:描述方法的參數類型和返回值類型(C語言需要明確知道這些才能正確調用)// 格式解讀:(參數類型)返回值類型// Ljava/lang/String; → 字符串類型(L開頭,;結尾,中間是類路徑)// Ljava/util/Map; → Map類型// I → 整數類型// 整個簽名表示:接收4個字符串和1個整數,返回一個字符串String methodSign = "getSign0(Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;I)Ljava/lang/String;";// 調用SO中SignUtil類的靜態JNI方法// 步驟分解:// 1. cSignUtil:我們要調用的類// 2. callStaticJniMethodObject:調用靜態的JNI方法,返回一個對象(這里是字符串)// 3. 參數列表:// - emulator:用哪個模擬器運行// - methodSign:調用哪個方法(通過簽名確定)// - p1,p2:直接傳字符串(Unidbg會自動轉換成SO能識別的格式)// - ProxyDvmObject.createObject(vm, map):把Java的Map轉換成SO能識別的格式// - p3,i:后續參數StringObject obj = cSignUtil.callStaticJniMethodObject(emulator, methodSign, p1, p2, ProxyDvmObject.createObject(vm, map), p3, i);// 把Unidbg的StringObject轉換成普通Java字符串,返回給調用者return obj.getValue();}/*** 簽名方法(對外提供的"接口")* 作用:接收用戶傳來的普通參數,處理后調用getSign0* synchronized:保證多線程調用時不會混亂(同一時間只有一個線程用這個方法)** @param paramMap 鍵值對參數(用戶傳的是String→String,需要轉成String→byte[])* 其他參數和getSign0一樣* @return 簽名結果*/private synchronized String sign(String p1, String p2, Map<String, String> paramMap, String p3, int i) {// 創建一個新的Map,把String值轉換成字節數組// 為什么要轉?因為SO里的方法可能要求值是字節數組(計算機底層存儲數據的形式)Map<String, byte[]> map = new HashMap<>();// 遍歷用戶傳的paramMap,逐個轉換for (String key : paramMap.keySet()) {// getBytes(StandardCharsets.UTF_8):把字符串按UTF-8編碼轉換成字節數組// (就像把中文翻譯成二進制,讓計算機能看懂)map.put(key, paramMap.get(key).getBytes(StandardCharsets.UTF_8));}// 調用實際和SO交互的方法,返回結果return getSign0(p1, p2, map, p3, i);}/*** 主方法:程序的入口(相當于"測試按鈕")* 作用:演示如何使用上面的代碼生成簽名*/public static void main(String[] args) throws Exception {// 1. 準備測試參數:模擬實際使用時的參數// 創建一個Map,里面放兩個鍵值對(a→b,b→b)Map<String, String> paramMap = new HashMap<String, String>() {{put("a", "b");put("b", "b");}};// 其他參數String p1 = "aa"; // 第一個參數示例String p2 = "bb"; // 第二個參數示例String p3 = "cc"; // 第四個參數示例int i = 10; // 第五個參數示例// 2. 創建簽名工具實例(這一步會執行上面的構造方法,啟動模擬器、加載SO)SignUtil signUtil = new SignUtil();// 3. 調用簽名方法,傳入參數,得到簽名結果String sign = signUtil.sign(p1, p2, paramMap, p3, i);// 4. 打印簽名結果(在控制臺顯示生成的簽名)System.out.println("sign=" + sign);// 5. 用完后銷毀資源(釋放內存,好習慣)signUtil.destroy();}}
點擊下圖紅框可以運行
運行之后的效果圖: