Unidbg使用指南
- 简介
- 使用
- Unidbg补环境
- 仅含C语言
- C调用 Java
- 实操——车智赢+在unidbg实现执行so中的方法
- 附——关于引用数据类型的转换
- 附——静态注册和动态注册模板
- 静态注册
- 动态注册
现在很多的app使用了so加密,以后会越来越多。爬虫工程师可能会直接逆向app,看java代码,完成java层的算法破解,但是如果遇到so该怎么办呢?可能你会直接破解so,但是真的会有很多爬虫工程师会去破解so吗?有时候我们可以不用破解so,利用很多大佬写好的轮子即可完成so的调用。
说到调用,就有很多方法了,比如用frida+rpc、xposed+andserver、再者就是unicorn+web框架等等,今天要说的并不是这些,而是unidbg,这框架有什么好的地方呢?看看简介。
简介
unidbg是一个Java项目,可以帮助我们去模拟一个安卓或IOS设备,用于去执行so文件中的算法,从而不需要再去逆向他内部的算法。
关于so的解决方法:
- 硬核分析+调试+破解
- frida-rpc
- unidbg
使用
- 在github上开源的项目:unidbg
- 打开项目
由于unidbg项目是由java编写的,所以需要用 Intellij IDEA 打开并操作。
导入项目后我们运行下测试代码,看看我们的环境是否有问题
当我们运行测试代码后能出现sign值说明环境是没问题的。
Unidbg补环境
unidbg在运行so文件时会出现两类情况:
- so算法都基于C语言实现,
- so算法中还会读取Java中成员,
仅含C语言
这种直接基于unidbg来进行so文件即可。
C调用 Java
这种就需要补环境,将C中调用的Java中的功能给补上,这样so中的代码才能正常执行。
所谓的unidbg补环境,其实补的就是这个。
实操——车智赢+在unidbg实现执行so中的方法
-
创建类
-
设备初始化
package com.com.demo; import com.github.unidbg.AndroidEmulator; import com.github.unidbg.Module; import com.github.unidbg.linux.android.AndroidEmulatorBuilder; import com.github.unidbg.linux.android.AndroidResolver; import com.github.unidbg.linux.android.dvm.*; import com.github.unidbg.memory.Memory; import java.io.File; public class che extends AbstractJni{ public static AndroidEmulator emulator; public static Memory memory; public static VM vm; public static DalvikModule dm; public static Module module; che() { // 1.创建设备(32位或64位模拟器), 具体看so文件在哪个目录。 在armeabi-v7a就选择32位 emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.che168.autotradercloud").build(); // 2.获取内存对象(可以操作内存) memory = emulator.getMemory(); // 3.设置安卓sdk版本(只支持19、23) memory.setLibraryResolver(new AndroidResolver(23)); // 4.创建虚拟机(运行安卓代码需要虚拟机,就想运行py代码需要python解释器一样) vm = emulator.createDalvikVM(new File("unidbg-android/apks/che/atc282.apk")); vm.setJni(this); //vm.setVerbose(true); //是否展示调用过程的细节 // 5.加载so文件 DalvikModule dm = vm.loadLibrary(new File("unidbg-android/apks/che/libnative-lib.so"), false); //dm.callJNI_OnLoad(emulator); // 6.dm代表so文件,dm.getModule()得到module对象,基于module对象可以访问so中的成员。 module = dm.getModule(); } public static void main(String[] args) { che obj = new che(); } }
-
执行签名
public void sign() {
// 找到java中native所在的类,并加载
DvmClass CheckSignUtil = vm.resolveClass("com/autohome/ahkit/jni/CheckSignUtil");
// 方法的符号表示
String method = "get3desKey(Landroid/content/Context;)Ljava/lang/String;"; //JNI签名
//导入的包 返回值
// 执行类中的静态成员
StringObject obj = CheckSignUtil.callStaticJniMethodObject(
emulator,
method,
vm.resolveClass("android/content/Context").newObject(null)
);
String keyString = obj.getValue();
System.out.println(keyString);
}
通过上述操作我们就完成了在unidbg实现执行so中的方法。
附——关于引用数据类型的转换
附——静态注册和动态注册模板
静态注册
- 根据函数名将Java代码中的native方法与so中的JNI方法一一对应,当Java层调用so层的函数时,如果发现其上有JNIEXPORT和JNICALL两个宏定义声明时,就会将so层函数链接到对应的native方法上。
- 而native方法和so方法对应规则是:以字符串“Java”为前缀,并且用“_”下划线将包名、类名以及native方法名连接起来就是对应的JNI函数名了。
Java.perform(function () {
var dlsymadd = Module.findExportByName("libdl.so", 'dlsym');
Interceptor.attach(dlsymadd, {
onEnter: function (args) {
this.info = args[1];
}, onLeave: function (retval) {
//那个so文件 module.name
var module = Process.findModuleByAddress(retval);
if (module == null) {
return retval;
}
// native方法
var funcName = this.info.readCString();
if (funcName.indexOf("getHNASignature") !== -1) {
console.log(module.name);
console.log('\t', funcName);
}
return retval;
}
})
});
// Application(identifier="com.rytong.hnair", name="海南航空", pid=14958, parameters={})
// frida -U -f com.rytong.hnair -l static_find_so.js
动态注册
- 在调用System.loadLibrary()时会在so层调用一个名为JNI_OnLoad()的函数,我们可以提供一个函数映射表,再在JNI_Onload()函数中通过JNI中提供的RegisterNatives()方法来注册函数。这样Java就可以通过函数映射表来调用函数,而不必通过函数名来查找对应函数。
var symbols = Module.enumerateSymbolsSync("libart.so");
var addrRegisterNatives = null;
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("RegisterNatives") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0) {
addrRegisterNatives = symbol.address;
console.log("RegisterNatives is at ", symbol.address, symbol.name);
}
}
console.log("addrRegisterNatives=", addrRegisterNatives);
if (addrRegisterNatives != null) {
Interceptor.attach(addrRegisterNatives, {
onEnter: function (args) {
var env = args[0];
var java_class = args[1];
var class_name = Java.vm.tryGetEnv().getClassName(java_class);
// 只有类名为com.bilibili.nativelibrary.LibBili,才打印输出
var taget_class = "com.xunmeng.pinduoduo.secure.DeviceNative";
if (class_name === taget_class) {
console.log("\n[RegisterNatives] method_count:", args[3]);
var methods_ptr = ptr(args[2]);
var method_count = parseInt(args[3]);
for (var i = 0; i < method_count; i++) {
// Java中函数名字的
var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
// 参数和返回值类型
var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
// C中的函数指针
var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));
var name = Memory.readCString(name_ptr); // 读取java中函数名
var sig = Memory.readCString(sig_ptr); // 参数和返回值类型
var find_module = Process.findModuleByAddress(fnPtr_ptr); // 根据C中函数指针获取模块
var offset = ptr(fnPtr_ptr).sub(find_module.base) // fnPtr_ptr - 模块基地址
// console.log("[RegisterNatives] java_class:", class_name);
console.log("name:", name, "sig:", sig, "module_name:", find_module.name, "offset:", offset);
//console.log("name:", name, "module_name:", find_module.name, "offset:", offset);
}
}
}
});
}
// frida -U -f com.xunmeng.pinduoduo -l dynamic_find_so.js