免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!
内容参考于:图灵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);}}
补自定义环境在下一节中