了解Unicorn
Unicorn是一个基于QEMU的轻量级、多平台、多架构的CPU模拟器框架,它允许用户在不同平台上模拟不同架构的CPU操作。
Unidbg
unidbg 是一款基于 unicorn 和 dynarmic(ARM的动态重编译器) 的逆向工具,可以模拟执行so文件。
安装项目
在github上下载项目unidbg,可以直接clone到本地
下载好之后用IDEA打开,等待加载依赖(科学上网)
项目结构
在unidbg-android目录包含了特定于 Android 平台的模拟和分析工具,src下有main和test两个目录,main包含主程序的代码,test包含用于测试 Unidbg 功能的测试代码。
在unidbg-api目录里面包含了unidbg提供的API接口。
分析实例
unidbg\unidbg-android\src\test\java\com\bytedance\frameworks\core\encrypt
构造函数里的模拟器介绍
TTEncrypt(boolean logging) {
this.logging = logging;
emulator = AndroidEmulatorBuilder
.for32Bit() //指定模拟器目标平台
.setProcessName("com.qidian.dldl.official") //设置进程名
.addBackendFactory(new Unicorn2Factory(true)) //模拟器的底层执行引擎
.build(); // 创建模拟器实例
// 获取模拟器的内存操作接口
final Memory memory = emulator.getMemory();
// 设置系统库解析器
memory.setLibraryResolver(new AndroidResolver(23));
// 创建一个 Dalvik 虚拟机实例。
vm = emulator.createDalvikVM();
// 设置虚拟机的日志输出模式。
vm.setVerbose(logging);
// 调用虚拟机的 loadLibrary 方法来加载指定的本地库
// 第二个参数 false 表示不自动调用库的 JNI_OnLoad 函数。
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/libttEncrypt.so"), false);
// 手动执行JNI_OnLoad函数
dm.callJNI_OnLoad(emulator);
// 获取加载的库对应的模块对象
module = dm.getModule();
// 解析类TTEncryptUtils
TTEncryptUtils = vm.resolveClass("com/bytedance/frameworks/core/encrypt/TTEncryptUtils");
}
destroy()
void destroy() {
// 调用 IOUtils.close 方法来关闭 emulator 实例。
IOUtils.close(emulator);
if (logging) {
// 在控制台上打印字符串 "destroy"。
System.out.println("destroy");
}
}
ttEncrypt()
这个函数代码比较多,我把关键的部分留下来
byte[] ttEncrypt() {
if (logging) {
...
}
if (logging) {
emulator.attach(DebuggerType.ANDROID_SERVER_V7); // 附加IDA android_server,可输入c命令取消附加继续运行
}
byte[] data = new byte[16];
ByteArray array = TTEncryptUtils.callStaticJniMethodObject(emulator, "ttEncrypt([BI)[B", new ByteArray(vm, data), data.length); // 执行Jni方法
return array.getValue();
}
main()
public static void main(String[] args) throws Exception {
// 创建TTEncrypt 类实例
TTEncrypt test = new TTEncrypt(true);
// 调用 TTEncrypt 实例的 ttEncrypt 方法
byte[] data = test.ttEncrypt();
// 用于打印或以其他方式检查 data 数组的内容。"ttEncrypt" 是一个标签,用于标识这些数据是来自 ttEncrypt 方法的结果。
Inspector.inspect(data, "ttEncrypt");
// 调用 TTEncrypt 实例的 destroy 方法,清理和释放与 emulator 相关的资源。
test.destroy();
}
ttEncrypt函数里的callStaticJniMethodObject
在 Unidbg 框架中,callStaticJniMethodObject 是一个用于调用 Android 应用中静态 JNI 方法的方法。
ByteArray array = TTEncryptUtils.callStaticJniMethodObject(emulator, "ttEncrypt([BI)[B", new ByteArray(vm, data), data.length);
这里面TTEncryptUtils是获取的Java类的DvmClass对象。
callStaticJniMethodObject
方法用于调用该类的静态JNI方法。
emulator
是AndroidEmulator 实例,它提供了执行环境。
ttEncrypt([BI)[B
是 JNI 方法的签名。指定了方法的名称(ttEncrypt),参数类型(一个字节数组 byte[],表示为 [B),I表示整型,和返回类型(一个字节数组 byte[])。
new ByteArray(vm, data)
创建了一个新的 ByteArray 实例,它包装了要加密的数据 data。vm 是虚拟机实例,用于创建和管理 ByteArray 对象。
data.length
是传递给 JNI 方法的第二个参数,它可能是加密方法需要的额外信息,例如数据的长度。
callStaticJniMethodObject
通过符号寻找函数的地址,在动态注册函数中以符号为键寻找函数地址,如果找不到,则按静态注册规则拼接符号,然后寻找函数地址函数名和函数签名