介绍
Unidbg是一个轻量级模拟器,支持对Android Native函数的模拟执行。
Unidbg是一个基于Maven构建的 JAVA 项目,可在 Github 下载源代码,然后在 IDEA/VScode 等 IDE 里打开,其依赖下载完成后,测试运行。
初始化环境
1 | import com.github.unidbg.AndroidEmulator; |
加载动态链接库
1 | DalvikModule loadLibrary(String libname, boolean forceCallInit); |
1 | DalvikModule loadLibrary(File elfFile, boolean forceCallInit); |
获取类和对象
类
resolveClass()
方法签名
1 | DvmClass resolveClass(String className, DvmClass... interfaceClasses); |
示例
1 | DvmClass myClass = vm.resolveClass("com/example/MyClass"); |
对象
DvmClass.newObject()
通过类获取对象
1 | DvmObject myobject = myClass.newObject(null); |
ProxyDvmObject.createObject()
方法签名
1 | DvmObject<?> createObject(VM vm, Object value) |
示例
1 | DvmObject myobject = ProxyDvmObject.createObject(vm,value) |
调用方法
调用静态方法与实例方法的区别
静态方法
通过类直接调用,不需要实例对象。
实例方法
需要通过类的实例对象调用。
调用静态方法
根据返回值使用合适的调用方法。
调用实例方法
根据返回值使用合适的调用方法。
通过module调用so中方法
通过符号名调用
1 | Number callFunction(Emulator<?> emulator, String symbolName, Object... args); |
通过地址调用
1 | Number callFunction(Emulator<?> emulator, long offset, Object... args); |
参数传递
基本类型的参数传递
Java 中的基本类型(如byte
、char
、short
、 int
、long
、float
、double
、boolean
等)可以直接作为参数传递。
String类型也可以直接作为参数传递(不建议,有时候可能导致错误)。
基本对象的参数传递
这里的基本对象指的是基本类型的包装类、字符串、基本类型数组,对象类型数组等
字符串
可以用如下方式处理StringObject
1 | StringObject stringObj = vm.resolveClass("java/lang/String").newObject("Hello World"); // 这样写太冗长了,不建议 |
字节数组
1 | ByteArray byteArray = new ByteArray(vm, new byte[]{0x64, (byte) 0xBC, 0x0C, 0x65, (byte) 0xAA, 0x1E}); |
字符串数组
1 | ArrayObject arrayObject = ArrayObject.newStringArray(vm, "Hello", "World"); |
List
1 | int length = 10; |
对象参数
对象类型的参数都可以用ProxyDvmObject.createObject()
来处理
HashMap 对象
1 | ProxyDvmObject.createObject(vm, new HashMap<>()); |
Android里的类和对象
对于Android里的类和对象,用vm.resolveClass()
来处理比较好。
获取模块的基地址
1 | Module module = emulator.getMemory().getModule("target.so"); |
补环境
系统调用
系统调用是由svc
软中断发起的,依赖与svc
后面的数字
1 | svc imm |
在系统调用的调用约定里,imm
无实际意义,而且默认使用0
,svc 0
,Unidbg可以根据imm
是否为0来确认模拟的是JNI函数调用还是系统调用。
JNI调用
Unidbg构造了JNIEnv
和JavaVM
这两个JNI中的关键结构,凭借系统调用的处理模块来实现对JNI的代理,在svc imm
及bx lr
这部分,有意义的是lr
,它是svc
的返回地址,对应样本发起JNI调用的位置。
补环境的原因
对于对样本的Java层数据做访问的一系列JNI函数,由于Unidbg未运行Dex以及Apk,就需要自己去填充外部信息。
为了减少补环境的负担,Unidbg预处理了常见的JNI函数调用和字段访问,逻辑位于src/main/java/com/github/unidbg/linux/android/dvm/AbstractJni.java
类里。
日志打印
Unidbg 中使用 Apache 的开源项目 log4j 和 commons-logging 处理日志。
1 | import org.apache.log4j.Level; |
如果希望输出所有模块的日志,可以修改unidbg-android/src/test/resources/log4j.properties
文件,将 unidbg 的日志等级从 INFO 改为 DEBUG。
1 | # 修改前 |
虚拟机日志
除了常规日志,Unidbg 还有另一套日志输出,主要打印 JNI 、Syscall 调用相关的内容。它和常规日志的输出有重叠,但内容更详细一些。
通过 vm.setVerbose 开启或关闭它。
1 | // 设置是否打印以 JNI 为主的虚拟机调用细节 |
断点调试
1 | BreakPoint addBreakPoint(long address); |
示例
1 | // 在地址 0x1000 处设置断点,并绑定一个自定义回调 |
命令
1 | c: continue |
Trace
traceCode()
1 | TraceHook traceCode(); |
用法示例
1 | emulator.traceCode(module.base, module.base + module.size); |
trace的时机
如果想 trace 从某个函数开始的执行流,那就让 traceCode 早于它执行即可。
想trace 从 JNI_OnLoad 开始的目标 SO 执行流,在
dm.callJNI_OnLoad(emulator);
前添加 trace。如下
1
2emulator.traceCode(module.base, module.base + module.size);
dm.callJNI_OnLoad(emulator);如果想到更早的时机开启追踪,即追踪 init_proc、init_array 这些初始化函数的执行情况,那就需要将 traceCode 放到 loadLibrary 之前调用,代码直接提前到前面是不行的,因为此时还没有取到
module
对象,自然也没有什么module.base
和module.size
,这时候最正确的处理办法是使用模块监听器,在模块加载的第一时间开始 trace 。如下
1
2
3
4
5
6
7
8
9
10memory.addModuleListener(new ModuleListener() {
public void onLoaded(Emulator<?> emulator, Module module) {
if(module.name.equals("lib.so")){
emulator.traceCode(module.base, module.base+module.size);
}
}
});
DalvikModule dm = vm.loadLibrary("xxx", true);
dm.callJNI_OnLoad(emulator);
AndroidEmulator
1 | AndroidEmulator emulator; |
打包及Python调用
路径问题
这里我在unidbg
的根目录下创建了一个文件夹apks
,在这里面放apk文件,在java文件里写apk路径的时候可以直接apks/.../example.apk
,就可以了。
打包
点击菜单中的File
->Project Structure
,然后在Project Settings
里点击Artifacts
,依次点击如下内容
然后会弹出如下的窗口,在Main Class里选择要打包的类,勾选下图中的选项,并指定META-INF/MANIFEST.MF目录
然后点击OK->Apply->OK
Build
菜单栏点击Build->Build Artifacts
->unidbg:jar
->Build
完成之后会在unidbg
根目录下生成一个out
文件夹,打包的文件就在unidbg_jar
里,如下。
但是这个文件夹里没有apk文件,还需要手动把apk文件及其目录一起移动到这里面
直接把apks目录赋值到unidbg_jar
里,如下
终端运行
进入到unidbg_jar
目录下,打开终端,执行命令,就可以了
1 | java -jar unidbg.jar |
Python调用
使用subprocess
调用
1 | import subprocess |