Unidbg使用指南
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.namevar 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