unidbg常用方法解释

此文章是对本人在刚开始使用时遇到的一些想不通地方的整理,想看详细的教程可以点击链接。感谢大佬的文章,写的真的是通俗易懂!

1. 位数

AndroidEmulatorBuilder.for64Bit()
AndroidEmulatorBuilder.for32Bit()

代表模拟器选择要处理32位so还是64位so。如果设置为 32 位,那么传入的动态库必须是 ARM32,如果设置为 64 位,传入的动态库必须是 ARM64。

2. 进程名

setProcessName("tv.danmaku.bili")

按照样本的真实进程名做设置,否则会带来隐患。在不设置的情况下会返回unidbg,有些app会对进程名做出检测。

3. 后端

emulator = AndroidEmulatorBuilder
                .for64Bit()
                .addBackendFactory(new Unicorn2Factory(true))
                .setProcessName("tv.danmaku.bili")
                .build();

指的是addBackendFactory(new Unicorn2Factory(true)),在不写的情况下默认使用Unicorn为后端,但是在他们两者之间最好是选择Unicorn2,因此加上这段代码就行。

4. 根目录

emulator = AndroidEmulatorBuilder
                .for64Bit()
                .addBackendFactory(new Unicorn2Factory(true))
                // 设置根目录
                .setRootDir(new File("target/rootfs"))
                .setProcessName("tv.danmaku.bili")
                .build();

目标 SO 可能会做文件访问与读写操作时,就应该设置根目录。如果不加以设置,Unidbg 会默认在本机临时目录下创建根目录,这会在将项目迁移到其他电脑上时带来不便。所以我们一般会主动设置根目录,并设置为target/rootfs这个相对路径,使得潜在的文件依赖位于在当前 Unidbg 项目里,方便打包处理和迁移。

5. 多线程

当 Unidbg 飘红报错Out of Memory时,你需要打开 Unidbg 的多线程模式。后端需要设置成unicorn2。

emulator = AndroidEmulatorBuilder.for32Bit()
        .addBackendFactory(new Unicorn2Factory(false))
        .setProcessName("test")
        .build();
// 设置执行多少条指令切换一次线程
emulator.getBackend().registerEmuCountHook(10000);
// 开启线程调度器
emulator.getSyscallHandler().setEnableThreadDispatcher(true);

6. 创建虚拟机

//创建虚拟机
VM dalvikVM = emulator.createDalvikVM((File) null);
//创建虚拟机并指定APK文件
VM dalvikVM = emulator.createDalvikVM(new File("apk file path"));

推荐使用第二个。unidbg并不是安卓模拟器,加载APK只是方便我们处理。

  1. 解析 Apk 基本信息,减少使用者在补 JNI 环境上的工作量。Unidbg 会解析 Apk 的版本名、版本号、包名、 Apk 签名等信息。如果样本通过 JNI 调用获取这些信息,Unidbg 会替我们做处理。如果没有加载 Apk,这些逻辑就需要我们去补环境,平添了不少工作量。
  2. 解析和管理 Apk 资源文件,加载 Apk 后可以通过 openAsset获取 APK assets目录下的文件。如果样本通过AAssetManager_open等函数访问 apk 的assets,Unidbg 会替我们做处理。

7. 加载 SO

// 传入本地so文件路径
DalvikModule dm = vm.loadLibrary(new File(soPath), true);
// 传入so文件名称
DalvikModule dm = vm.loadLibrary("so文件名称", true);

可以发现区别在于第一个参数,前者传入文件后者传入动态库的名字。

后者在使用上近似于 JAVA 的System.loadLibrary(soName),名字要掐头去尾,如 libkwsgmain.so 对应为 kwsgmain。

8. 类

DvmClass BiHelper = vm.resolveClass("com/bilibili/nativelibrary/LibBili");

在java层调用的native方法一般会在某个类下,这就需要先实例化这个类在进行调用。在 Unidbg 中,通过vm.resolveClass(className)可以声明一个类名是classNameDvmClass

BiHelper.newObject(null)

然后通过newObject(null)进行类的实例化

9. 调用

如果返回值是字符串,是对象,就用callJniMethodObject,如果返回值是 int 就用callJniMethodInt,其他的根据提示对应起来即可。

如果调用的方法是静态方法,那么自然不需要 newObject,调用则通过 callStaticJNIMethodXXX 系列函数。

10. 参数传递

  • 基本类型直接传递,int、long、boolean、double 等。
  • 下面几种对象类型也可以直接传递
    • String
    • byte 数组
    • short 数组
    • int 数组
    • float 数组
    • double 数组
    • Enum 枚举类型

除此之外还有许多种可能的参数,比如字符串数组、二维数组、Android Context/Application、HashMap 等等,在大体上遵循两类处理办法。

  1. 如果是 JDK 中包含的类库和方法,比如二维数组、字符串数组、HashMap 等等,直接构造然后使用ProxyDvmObject.createObject(vm, obj);构造出对象。除此之外比如 Okhttp3 之类的第三方类库,导入到本地环境里,也可以使用这个办法。
  2. 如果是 JDK 中无法包含的类库,比如 Android FrameWork 以及样本自定义的类库,通过resolveClass(className).newObject处理,就像本节的NativeApi那样处理。
上一篇
下一篇