进入Unidbg的世界
概述
Unidbg是一个基于Unicorn的逆向工具,可以黑盒调用Android和iOS中的so文件.这使逆向人员无须了解so内部算法原理,只需主动调用so中的函数,传入所需的参数,补全运行所需的环境,即可得到需要的结果.
对于Android逆向来说,Unidbg有以下几个特点:
- 模拟JNI调用的API,因而可以调用JNI_OnLoad函数
- 支持JavaVM和JNIEnv
- 支持模拟系统调用指令
- 支持ARM32和ARM64
- 支持基于Dobby的Inline Hook
- 支持基于xHook的GOT Hook
- Unicorn后端支持简单的控制台调试器、GDB Stub、指令跟踪和内存读写跟踪
- 支持Dynarmic
其官方地址如下:
https://github.com/zhkl0228/unidbg
模拟执行初探
测试Apk
java层:
|
|
Native层:
|
|
符号调用
Unidbg支持两种调用so中函数的方式:符号调用和地址调用.
符号调用的示例代码如下:
|
|
若使用ProxyDvmObject创建调用so的代理对象obj,需保证在Unidbg中新建的测试类和so中的Native方法所在的类一致.
md5方法的返回值为String类型,故应使用callJniMethodObject()方法来调用md5方法,同时使用DvmObject对象来接收该返回值.除了基本类型之外,其余的类型都要返回DvmObject对象.
地址调用
地址调用的示例代码如下:
|
|
在使用符号调用方式时,Unidbg帮助我们完成了很多操作,例如拼接函数名、填充参数等.但在使用地址调用方式时,这些操作需要我们自己来完成.
上述在构造第三个参数需要注意的是,在Unidbg中,当传入参数为非指针和Number类型时,需要将其定义为DvmObject对象并添加到VM中.而对于常用的String类,Unidbg对其做了相应的封装,这里新建了一个StringObject对象来作为第三个参数.
上述在调用so中的函数后获得的返回值为Number类型,这与实际的返回值String类型不相符.在Unidbg中对于基本类型或者布尔类型,可以直接通过Number获取相应的值.而对于Object对象来说,Number中实际存储的是Object对象的hash值,因此需要先通过vm.getObject()方法来获取DvmObject对象,之后即可通过getValue()方法来获取对象中储存的值,并将其强制转换为String类型后输出.
模拟执行
|
|
修补执行环境
测试Apk
java层:
|
|
Native层:
|
|
模拟执行
|
|
使用上述Unidbg代码进行模拟调用时,会报下述错误.
|
|
由于模拟执行的函数中有JNI操作,而不同的so调用的JNI方法有所不同,Unidbg无法为其一一实现,缺失的部分需要我们自己来实现,因此上述提示我们需要设置vm.setJni()来实现缺失的JNI接口部分.
当我们调用了vm.setJni()方法时,再次运行,会报下述错误.
|
|
我们需要重写报错的callVoidMethodV方法,为Unidbg补全缺失的update()方法的具体实现,使之可以继续执行.这个补齐JNI的过程叫做"补环境".
具体的修补代码如下:
|
|
当执行到上述方法时,参数vm为虚拟机,dvmObject为调用该函数的对象,signature为函数的签名,vaList为函数的参数列表.
在执行messageDigest.update()方法时,我们需要先取得messageDigest对象,dvmObject是调用该方法的对象,因此可以通过dvmObject.getValue()方法来获取messageDigest对象.
update()方法的参数为byte[],而在JNI中没有对象的概念.因此Unidbg为非基本类型维护了一个Map引用,通过getIntArg()方法取得Map中的key值,然后通过getObject()方法从VM虚拟机中取得该对象,最后通过getValue()方法取得实际对象的值.
修补完update()方法后继续运行,会出现下述错误.
|
|
具体的修补代码如下:
|
|
步骤和上述大致相同,多了一个将运算得到的结果digest先代理创建为DvmObject对象,然后在添加到VM虚拟机的操作.
所有的非基本类型、包装类型都需要添加到VM虚拟机的Map映射中,否则在JNI中无法找到该引用.最后将运算得到的结果当做函数返回值返回.
上述修补完后,再次运行会报下述错误.
|
|
具体的修补代码如下:
|
|
再次运行,成功输出结果如下:
|
|
Unidbg Hook
Unidbg支持多种Hook框架,如HookZz、Dobby、xHook、whale等.下面我们使用HookZz来完成对64位so程序的Hook
测试Apk
java层:
|
|
Native层:
|
|
HookZz
HookZz的wrap方法只是在函数的头、尾来添加并执行额外的逻辑,并不影响函数原来的代码.示例如下:
|
|
HookZz的replace是替换掉函数,让函数不执行.当第三个参数为true时,表示执行原函数,此时类似于wrap方法.示例如下:
|
|
onCall()方法是在函数执行之前执行,postCall()方法是在函数执行结束后执行.postCall()方法默认是不执行的,需要配置第三个参数enablePostCall为true来启用.
Unidbg Hook完整代码如下:
|
|
Dobby
Dobby的各个方法与接口和HookZz差不多.示例如下:
|
|
xHook
xHook仅能实现符号表的Hook,优点是稳定.示例如下:
|
|
Unidbg Patch
测试Apk
同Unidbg Hook测试Apk
Patch
如果想要在Unidbg中修改程序的逻辑代码,可以利用Patch技术.
下面展示将下述so中的ADD指令修改为SUB指令.
可通过下述网站来讲汇编指令转换为机器码.
Patch代码如下:
|
|
也可以使用Keystone将汇编指令自动转换为机器码.
|
|
完整的Unidbg代码如下:
|
|
Unidbg Debugger
Unidbg还对Hook进行了封装,提供了调试器功能,可以让我们很方便地在控制台进行调试.除了提供Console Debugger,还支持GDB与IDA协助调试.
测试Apk
同Unidbg Hook测试Apk
Debugger
在使用调试器功能时,需要使用默认的Unicorn后端.调用调试器的相关代码如下:
|
|
当attach方法的参数为空时,默认使用Console Debugger
使用Console Debugger进行调试的界面如下:
调试指令如下:
|
|
Unidbg Debugger完整代码如下:
|
|
参考链接
<Unidbg逆向工程 原理与实践>