免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!
内容参考于:图灵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();}}
点击下图红框可以运行
运行之后的效果图: