此文章是对本人在刚开始使用时遇到的一些想不通地方的整理,想看详细的教程可以点击链接。感谢大佬的文章,写的真的是通俗易懂!
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只是方便我们处理。
- 解析 Apk 基本信息,减少使用者在补 JNI 环境上的工作量。Unidbg 会解析 Apk 的版本名、版本号、包名、 Apk 签名等信息。如果样本通过 JNI 调用获取这些信息,Unidbg 会替我们做处理。如果没有加载 Apk,这些逻辑就需要我们去补环境,平添了不少工作量。
- 解析和管理 Apk 资源文件,加载 Apk 后可以通过
openAsset获取 APKassets目录下的文件。如果样本通过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)可以声明一个类名是className的DvmClass。
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 等等,在大体上遵循两类处理办法。
- 如果是 JDK 中包含的类库和方法,比如二维数组、字符串数组、HashMap 等等,直接构造然后使用
ProxyDvmObject.createObject(vm, obj);构造出对象。除此之外比如 Okhttp3 之类的第三方类库,导入到本地环境里,也可以使用这个办法。 - 如果是 JDK 中无法包含的类库,比如 Android FrameWork 以及样本自定义的类库,通过
resolveClass(className).newObject处理,就像本节的NativeApi那样处理。