跳转至

unidbg 加载 so 并调用 so 中函数

1. 前提准备

本文前需要以下准备:

unidbg 也一直在维护和更新中,一切请以 unidbg 官网最新 api 为准。

1.1. 环境配置

将上步骤的 jdk 等环境配置即可,然后 git 克隆 unidbg 的项目,会自动下载依赖。如果能跑通 unidbg 中的 看雪 和 Github 等示例即可。都很简单,不再赘述!

1.2. unidbg 新建项目

根据目标 app 的包名信息创建项目

那么,为什么要以目标 app 的包名创建项目呢?

简单来说,就是 unidbg 在符号调用 so 的函数时需要包名信息来确定要调用的方法所在的 java 类。

image-20220513215426540

2. 调用过程

unidbg 执行 so 函数可以使用符号调用或地址调用,以下会使用这两类方式进行演示:

2.1.加载 so,并调用 init 以及 init_array 中的函数

init 对应的 app 中 cpp 的内容大致如下:

image-20220512173836654

也可以在 ida 中反编译 so 文件后查看 init_array 的信息:将 so 文件拖入 ida 中分析,ctrl + s 查看 init_array 中有两个函数:go into myConstructor1 和 go into myConstructor2

image-20220512213107767

go into myConstructor1 如下:

image-20220512213329461

go into myConstructor2 如下:

image-20220512213302245

unidbg 在加载 so 时就完成了 init 的相关调用

对应 uniodbg 的输出信息为:

image-20220513214317360

2.2. 调用 so 中的普通函数

2.2.1. 调用 add(int, int) 函数:

image-20220512215256347

对应的导出函数名为 _Z3addii,具体如下:

.text:0000098C
.text:0000098C ; =============== S U B R O U T I N E ====================================
.text:0000098C
.text:0000098C
.text:0000098C ; _DWORD __fastcall add(int, int)
.text:0000098C                 EXPORT _Z3addii
.text:0000098C _Z3addii                                ; CODE XREF: add(int,int)+8↑j
.text:0000098C                                         ; DATA XREF: LOAD:00000370↑o ...
.text:0000098C
.text:0000098C var_14          = -0x14
.text:0000098C var_10          = -0x10
.text:0000098C var_C           = -0xC
.text:0000098C var_8           = -8
.text:0000098C var_4           = -4
.text:0000098C                                         ; DATA XREF: LOAD:00000370↑o ...

则调用 add(int, int) 函数的示例为:

1
2
3
4
5
6
// 先注册此方法所在的 java 类
DvmObject obj = ProxyDvmObject.createObject(vm, this);

// _Z3addii 调用 so 函数
Number add_result = module.callFunction(emulator, "_Z3addii", 1, 2);
System.out.println("call add(int,int) result is ==> " + add_result.intValue());
1
2
3
// 符号调用 so 方法
Number result=unicorn08module.callFunction(emulator,"_Z3addii",1,2)[0];
System.out.println(result.intValue());

2.2.2. 调用 add_six(int,int,int,int,int,int) 函数

ida 查看对应的导出函数名为 _Z7add_sixiiiiii,具体如下:

.text:000009A8 ; =============== S U B R O U T I N E ==============================
.text:000009A8
.text:000009A8 ; Attributes: bp-based frame
.text:000009A8
.text:000009A8 ; _DWORD __fastcall add_six(int, int, int, int, int, int)
.text:000009A8                 EXPORT _Z7add_sixiiiiii
.text:000009A8 _Z7add_sixiiiiii                        ; DATA XREF: LOAD:00000340↑o
.text:000009A8
.text:000009A8 var_3C          = -0x3C
.text:000009A8 var_38          = -0x38
.text:000009A8 var_34          = -0x34
.text:000009A8 var_30          = -0x30
.text:000009A8 var_2C          = -0x2C
.text:000009A8 var_28          = -0x28
.text:000009A8 var_24          = -0x24
.text:000009A8 var_20          = -0x20
.text:000009A8 var_1C          = -0x1C
.text:000009A8 var_18          = -0x18
.text:000009A8 var_14          = -0x14
.text:000009A8 arg_0           =  8
.text:000009A8 arg_4           =  0xC
.text:000009A8

则调用 add_six(int,int,int,int,int,int) 函数的示例为:

1
2
3
//_Z7add_sixiiiiii
Number add_six_result = module.callFunction(emulator,"_Z7add_sixiiiiii",1, 2, 3, 4, 5, 6);
System.out.println("call add_six(int,int,int,int,int,int) result is ==> " + add_six_result.intValue());
1
2
3
//_Z7add_sixiiiiii
result = unicorn08module.callFunction(emulator, "_Z7add_sixiiiiii", 1, 2, 3, 4, 5, 6)[0];
System.out.println(result.intValue());

2.2.3. 调用 getstringlength(char const*) 函数

getstringlength(char const*) 对应的导出函数为 _Z15getstringlengthPKc,至于怎么得到这个结论不再演示。

则调用示例为:

1
2
3
4
5
6
7
8
9
// _Z15getstringlengthPKc 函数运行示例

// getstringlength 准备参数
List<Object> p_args = new ArrayList<>();
p_args.add("this is pointer_function param");

// 执行
Number p_result = module.callFunction(emulator, 0x0A26 + 1, p_args.toArray());
System.out.println("p_function result is ==>" + p_result.intValue());
// _Z15getstringlengthPKc
// 函数参数中为指针,先申请内存空间
MemoryBlock block1 = memory.malloc(10);
// str1_ptr 为内存地址
UnidbgPointer str1_ptr = block1.getPointer();
str1_ptr.write("hello".getBytes());
// 读取,其中 getBackend 可认为是 unidbg 引擎的封装
String content = ARM.readCString(emulator.getBackend(),str1_ptr.peer);
System.out.println(str1_ptr.toString() + "---" + content);
result = unicorn08module.callFunction(emulator, "_Z15getstringlengthPKc", new PointerNumber(str1_ptr))[0];
Number r0value = emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_R0);
System.out.println(result.intValue() + "----" + r0value);

2.2.4. 调用 getstringlength2(char const*,char const*) 函数

这个函数就比 2.2.2 多个同是指针的参数,老版 unidbg 记得申请内存地址两次即可,新版也只要添加个同样的参数而已,不再赘述。

3. 调用 JNI_OnLoad函数

直接执行 unidbg 的 JNI_OnLoad 函数就可以了

// unidbg 会提前做很多事情,可以传入 app 包
vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/example_binaries/myprac/app-debug.apk"));
// 这个会输出 unidbg 的调试信息
vm.setVerbose(true);
// 加载 so 文件,第二个参数为是否初始化
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/myprac/libnative-lib.so"), false);

// 操作 so 的句柄
module = dm.getModule();
// 执行 JNI_OnLoad 函数
vm.callJNI_OnLoad(emulator, module);    
1
2
3
4
// JNI_OnLoad 会得到注册的函数

// 调用 jni_OnLoad 函数,第二个参数就是加载的 so
vm.callJNI_OnLoad(emulator, unicorn08module);

执行结果为:

image-20220514014701286

4. 调用 jni 函数

根据 3 的执行结果,可以知道 b35 地址对应的为 stringFromJNI2 函数信息。在 ida 中按快捷键 G 输入 b35 跳转到对应代码内容。

4.1. 调用静态注册的 stringFromJNI1 函数

我们先调用在 ida 的 Exports 中可以查看到的 stringFromJNI1 函数,ida 中查看代码为:

image-20220513230508497

unidbg 调用示例如下:

1
2
3
4
5
6
7
8
// 目标函数所需的参数
String data = "this is a param";

// 符号调用
DvmObject dvmObject = obj.callJniMethodObject(emulator,"stringFromJNI1(Ljava/lang/String;)Ljava/lang/String;", data);
String result = (String) dvmObject.getValue();

System.out.println("[symbol] Call the stringFromJNI1 function result is ==> " + result);
// 调用 jni 函数,对于动态注册的 jni 函数必须在完成地址的绑定才能调用

// 先注册此方法所在的 java 类
DvmClass MainActivity_dvmclass = vm.resolveClass("com/example/unicorncourse08/MainActivity");
DvmObject resultobj = MainActivity_dvmclass.callStaticJniMethodObject(emulator,"stringFromJNI1(Ljava/lang/String;)Ljava/lang/String;","helloworld");
System.out.println("resultobj:" + resultobj);

// 标准写法: new StringObject,虽然会自动转变类型
resultobj = MainActivity_dvmclass.callStaticJniMethodObject(emulator,"stringFromJNI1(Ljava/lang/String;)Ljava/lang/String;",new StringObject(vm, "hellokanxue"));
System.out.println("resultobj:" + resultobj);

4.2. 调用动态注册的 stringFromJNI2 函数

在 ida 中按快捷键 G 输入 b35 跳转到对应代码内容如下:

image-20220514014951619

在上面和下面的 unidbg 日志可知,动态注册的函数名称为 stringFromJNI2:

则调用示例为:

1
2
3
4
5
6
// 新旧版一致不再分两类了

// 符号调用
dvmObject = obj.callJniMethodObject(emulator,"stringFromJNI2(Ljava/lang/String;)Ljava/lang/String;", "this is getstringlength2 param");
result = (String) dvmObject.getValue();
System.out.println("[symbol] Call the stringFromJNI2 function result is ==> " + result);

评论

回到页面顶部