Unidbg使用指南

现在很多的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

使用

  1. 在github上开源的项目:unidbg
    在这里插入图片描述
  2. 打开项目
    由于unidbg项目是由java编写的,所以需要用 Intellij IDEA 打开并操作。
    导入项目后我们运行下测试代码,看看我们的环境是否有问题
    在这里插入图片描述
    当我们运行测试代码后能出现sign值说明环境是没问题的。

Unidbg补环境

unidbg在运行so文件时会出现两类情况:

  • so算法都基于C语言实现,
  • so算法中还会读取Java中成员,

仅含C语言

这种直接基于unidbg来进行so文件即可。

在这里插入图片描述

C调用 Java

这种就需要补环境,将C中调用的Java中的功能给补上,这样so中的代码才能正常执行。
所谓的unidbg补环境,其实补的就是这个。

在这里插入图片描述

实操——车智赢+在unidbg实现执行so中的方法

在这里插入图片描述
在这里插入图片描述

  1. 创建类
    在这里插入图片描述

  2. 设备初始化
    在这里插入图片描述

    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();
        }
    }
    
  3. 执行签名

    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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值