跳转至

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 对应的 appcpp 的内容大致如下:

image-20220512173836654

也可以在 ida 中反编译 so 文件后查看 init_array 的信息:将 so 文件拖入 ida 中分析,ctrl + s 查看 init_array 中有两个函数:go into myConstructor1go 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:00000340o
.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函数

直接执行 unidbgJNI_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 函数

我们先调用在 idaExports 中可以查看到的 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);

评论

回到页面顶部