unidbg中参数传递时的类型封装

一、引言

众所周知,在Native层编写代码时,倘若Java层和Native层进行数据传递,则需借助JNI进行类型的转变,它们之间的类型对照表如下:

基本类型对照表

Java Native
Boolean jboolean
Byte jbyte
Char jchar
Short jshort
Integer jint
Long jlong
Float jfloat
Double jdouble

引用类型对照表

同样在unidbg中,也做了几乎完整的等价模拟和映射。

  • 基本类型不进行封装,直接传递即可。

  • 引用类型需要封装,情况如下:

    • jobject 对应于 DvmObject
    • jclass 对应于 DvmClass
    • jstring 对应于 StringObject
    • jarray 对应于 BaseArray
    • jobjectArray 对应于 ArrayObject
    • jbyteArray 对应于 ByteArray
    • jshortArray 对应于 ShortArray
    • jintArray 对应于 IntArray
    • ……

二、unidbg中传参类型的封装

在unidbg中,DvmClass类的所有callxxxJniMethodxxx方法最终都调用callJniMethod方法,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
protected static Number callJniMethod(Emulator<?> emulator, VM vm, DvmClass objectType, DvmObject<?> thisObj, String method, Object...args) {
//根据方法签名找到对应的函数地址
UnidbgPointer fnPtr = objectType.findNativeFunction(emulator, method);
vm.addLocalObject(thisObj);
//添加 JNIEnv、Jobject/Jclass 作为第一、二个参数,这是 JNI 规范的默认要求
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv());
list.add(thisObj.hashCode());
//处理传参
if (args != null) {
for (Object arg : args) {
if (arg instanceof Boolean) {
// Boolean类型,转成1、0
list.add((Boolean) arg ? VM.JNI_TRUE : VM.JNI_FALSE);
continue;
} else if(arg instanceof Hashable) {
// hashable 类型参数
list.add(arg.hashCode()); // dvm object

if(arg instanceof DvmObject) {
// 本身就是DvmObject类型,直接强制转换
vm.addLocalObject((DvmObject<?>) arg);
}
continue;
} else if (arg instanceof DvmAwareObject ||
arg instanceof String ||
arg instanceof byte[] ||
arg instanceof short[] ||
arg instanceof int[] ||
arg instanceof float[] ||
arg instanceof double[] ||
arg instanceof Enum) {
// String、byte[]等类型通过 ProxyDvmObject.createObject 函数进行封装
DvmObject<?> obj = ProxyDvmObject.createObject(vm, arg);
list.add(obj.hashCode());
vm.addLocalObject(obj);
continue;
}

list.add(arg);
}
}
return Module.emulateFunction(emulator, fnPtr.peer, list.toArray());
}

上述代码逻辑中,Boolean 类型会转为 0、1, String、byte[] 等类型,会通过ProxyDvmObject.createObject自动封装为 Unidbg JNI 中对应的类型。

来看createObject函数,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public static DvmObject<?> createObject(VM vm, Object value) {
if (value == null) {
return null;
}
if (value instanceof Class<?>) {//class类型
return getObjectType(vm, (Class<?>) value);
}
if (value instanceof DvmObject) {
return (DvmObject<?>) value;
}

if (value instanceof byte[]) {//byte[] 类型
return new ByteArray(vm, (byte[]) value);
}
if (value instanceof short[]) {//short[] 类型
return new ShortArray(vm, (short[]) value);
}
if (value instanceof int[]) {//int[] 类型
return new IntArray(vm, (int[]) value);
}
if (value instanceof float[]) {//float[] 类型
return new FloatArray(vm, (float[]) value);
}
if (value instanceof double[]) {//double[] 类型
return new DoubleArray(vm, (double[]) value);
}
if (value instanceof String) {//String[] 类型
return new StringObject(vm, (String) value);
}
//不在上述类型的参数,获取参数的类
Class<?> clazz = value.getClass();
if (clazz.isArray()) {//如果获取到的参数的类是个数组,说明参数是一个对象数组
if (clazz.getComponentType().isPrimitive()) {//判断是否是基本类型
throw new UnsupportedOperationException(String.valueOf(value));
}
//转成对象数组,对每个元素进行封装
Object[] array = (Object[]) value;
DvmObject<?>[] dvmArray = new DvmObject[array.length];
for (int i = 0; i < array.length; i++) {
dvmArray[i] = createObject(vm, array[i]);
}
return new ArrayObject(dvmArray);
}

return new ProxyDvmObject(vm, value);
}

但事实上,createObject除了可以处理 string、array 这样的普通对象,也可以处理 map 或其他对象类型。来看ProxyDvmObject 对象的构造逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
private ProxyDvmObject(VM vm, Object value) {
super(getObjectType(vm, value.getClass()), value);//根据类型创建DvmObject
}
//父类的构造方法
protected DvmObject(DvmClass objectType, T value) {
this(objectType == null ? null : objectType.vm, objectType, value);
}

private DvmObject(BaseVM vm, DvmClass objectType, T value) {
this.vm = vm;
this.objectType = objectType;
this.value = value;
}

接着看getObjectType

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static DvmClass getObjectType(VM vm, Class<?> clazz) {
//获取参数的对应类的父类
Class<?> superClass = clazz.getSuperclass();
//接口类 + 父类
DvmClass[] interfaces = new DvmClass[clazz.getInterfaces().length + (superClass == null ? 0 : 1)];
int i = 0;
if (superClass != null) {
interfaces[i++] = getObjectType(vm, superClass);
}
for (Class<?> cc : clazz.getInterfaces()) {
interfaces[i++] = getObjectType(vm, cc);
}
// 通过resolveClass函数加载对应类
return vm.resolveClass(clazz.getName().replace('.', '/'), interfaces);
}

它会对类的父类以及接口类做解析,嵌套式的getObjectType,最后resolveClass得到的DvmClass可通过newObject方法得到最终的DvmObject,代码如下

1
2
3
public DvmObject<?> newObject(Object value) {
return new DvmObject<>(this, value);
}

来看resolveClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Override
public final DvmClass resolveClass(String className, DvmClass... interfaceClasses) {
//将className中的 . 替换成 /
className = className.replace('.', '/');
//在classMap(存放已加载的class)中查找
int hash = Objects.hash(className);
DvmClass dvmClass = classMap.get(hash);
//如果有接口类
DvmClass superClass = null;
if (interfaceClasses != null && interfaceClasses.length > 0) {
superClass = interfaceClasses[0];
interfaceClasses = Arrays.copyOfRange(interfaceClasses, 1, interfaceClasses.length);
}
//该类从未被加载过,先通过DvmClassFactory类的createClass加载,如果未加载成功,则通过BaseVM类的createClass加载
if (dvmClass == null) {
if (dvmClassFactory != null) {
dvmClass = dvmClassFactory.createClass(this, className, superClass, interfaceClasses);
}
if (dvmClass == null) {
dvmClass = this.createClass(this, className, superClass, interfaceClasses);
}
classMap.put(hash, dvmClass);
}
addGlobalObject(dvmClass);
return dvmClass;
}

它先查找classMap确认是否已加载,如果没有加载,则通过createClass函数加载目标类。

三、类型封装情况分类

根据前几篇文章的实操以及类型封装过程的追踪,将类型封装的情况分为四类:

  1. 基本类型直接传递,int、long、boolean、double 等。

  2. 下面几种对象类型也可以直接传递

    • String

    • byte 数组

    • short 数组

    • int 数组

    • float 数组

    • double 数组

    • Enum 枚举类型

    也可以自己调用new StringObject(vm, str)new ByteArray(vm, value)等。

  3. 如果是 JDK 中包含的类库和方法,比如二维数组、字符串数组、HashMap 等等,直接构造然后使用ProxyDvmObject.createObject(vm, obj);构造出对象。除此之外比如 Okhttp3 之类的第三方类库,导入到本地环境里,也可以使用这个办法。

  4. 如果是 JDK 中无法包含的类库,比如 Android FrameWork 以及样本自定义的类库,通过resolveClass(className).newObject处理,就像本节的NativeApi那样处理。

3.1 类型一

首先看基本类型,即所谓的 byte、short、int、long、double、float、boolean、char,只需要直接传递即可,例如

1
2
3
4
5
6
7
@Override
public int getStaticIntField(BaseVM vm, DvmClass dvmClass, String signature) {
if ("android/content/pm/PackageManager->GET_SIGNATURES:I".equals(signature)) {
return 0x40;
}
throw new UnsupportedOperationException(signature);
}

需要注意的是,在准备参数,发起函数调用时,Long类型的参数必须显式声明,即在整数后面加L,以供 Unidbg 识别和处理。

3.2 类型二

接下来看基本对象——基本类型的包装类、字符串、基本类型数组、对象类型数组等等。

例一

1
2
3
4
java.lang.UnsupportedOperationException: cn/xiaochuankeji/tieba/AppController->getPackageName()Ljava/lang/String;
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:416)
at com.izuiyou.NetWork.callObjectMethodV(NetWork.java:86)
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)

首先获取包名String packageName = vm.getPackageName();,然后下面两种方案任选一种处理为StringObject

1
2
StringObject stringObject1 = (StringObject) ProxyDvmObject.createObject(vm, packageName);
StringObject stringObject2 = new StringObject(vm, packageName);

ProxyDvmObject.createObject内部做了类型判断,将 String 转为 StringObject。但使用 StringObject 构造更为更直观。

例二

1
2
3
java.lang.UnsupportedOperationException: java/lang/Integer-><init>(I)V
at com.github.unidbg.linux.android.dvm.AbstractJni.newObjectV(AbstractJni.java:789)
at com.demo5.MeiTuan.newObjectV(MeiTuan.java:557)

Integer 类是基本类型int所对应的包装类,Unidbg 对部分包装类做了处理,详见unidbg-android/src/main/java/com/github/unidbg/linux/android/dvm/wrapper目录,其中这些类型都提供了valueOf类方法做包装

1
2
3
4
case "java/lang/Integer-><init>(I)V": {
int i = vaList.getIntArg(0);
return DvmInteger.valueOf(vm, i);
}

例三

1
2
3
java.lang.UnsupportedOperationException: java/net/NetworkInterface->getHardwareAddress()[B
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:921)
at com.demo14.SimpleSign.callObjectMethod(SimpleSign.java:177)

[B即 byte[],在 Unidbg 中对应于ByteArray

1
2
3
case "java/net/NetworkInterface->getHardwareAddress()[B":
byte[] addr = new byte[]{0x64, (byte) 0xBC, 0x0C, 0x65, (byte) 0xAA, 0x1E};
return new ByteArray(vm, addr);

unidbg-android/src/main/java/com/github/unidbg/linux/android/dvm/array目录下有对各种基本类型数组的表示和处理。这里也可以用ProxyDvmObject.createObject,就像StringObject一样,它内部会对应做处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (value instanceof byte[]) {
return new ByteArray(vm, (byte[]) value);
}
if (value instanceof short[]) {
return new ShortArray(vm, (short[]) value);
}
if (value instanceof int[]) {
return new IntArray(vm, (int[]) value);
}
if (value instanceof float[]) {
return new FloatArray(vm, (float[]) value);
}
if (value instanceof double[]) {
return new DoubleArray(vm, (double[]) value);
}

例四

1
2
3
java.lang.UnsupportedOperationException: android/os/Build->SUPPORTED_ABIS:[Ljava/lang/String;
at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:103)
at com.demo5.MeiTuan.getStaticObjectField(MeiTuan.java:629)

类型是[Ljava/lang/String;,它是个字符串数组。在 Unidbg 中,对象数组通过ArrayObject表示,而且还为字符串数组提供了一个方便的处理函数。

1
2
3
4
5
6
7
8
9
10
public static ArrayObject newStringArray(VM vm, String... strings) {
StringObject[] objects = new StringObject[strings.length];
for (int i = 0; i < strings.length; i++) {
String str = strings[i];
if (str != null) {
objects[i] = new StringObject(vm, str);
}
}
return new ArrayObject(objects);
}

使用它可以方便的构造字符串数组

1
2
3
case "android/os/Build->SUPPORTED_ABIS:[Ljava/lang/String;":{
return ArrayObject.newStringArray(vm, "arm64-v8a", "armeabi-v7a", "armeabi");
}

也可以使用ProxyDvmObject.createObject

1
2
3
4
case "android/os/Build->SUPPORTED_ABIS:[Ljava/lang/String;":{
String[] abis = new String[]{"arm64-v8a", "armeabi-v7a", "armeabi"};
return ProxyDvmObject.createObject(vm, abis);
}

它在内部最终也是转换为了ArrayObject。对于对象数组类型,用ProxyDvmObject.createObject更省事。

例五

1
2
3
java.lang.UnsupportedOperationException: android/hardware/SensorManager->getSensorList(I)Ljava/util/List;
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:416)
at com.demo2.Tiny.callObjectMethodV(Tiny.java:334)

这是一个 List,将它转成ArrayListObject

1
2
3
4
5
6
7
8
case "android/hardware/SensorManager->getSensorList(I)Ljava/util/List;":{
int length = 10;
List<DvmObject<?>> SensorList = new ArrayList<>();
for (int i = 0; i < length; i++) {
SensorList.add(vm.resolveClass("android/hardware/Sensor").newObject(i));
}
return new ArrayListObject(vm, SensorList);
}

3.3 类型三

上一种类型,处理起来往往有两方面的选择,而除此之外其余的 JDK 类库,基本只能用ProxyDvmObject.createObject

例一

1
2
3
java.lang.UnsupportedOperationException: java/util/HashMap-><init>()V
at com.github.unidbg.linux.android.dvm.AbstractJni.newObject(AbstractJni.java:741)
at com.demo4.TBSecurity.newObject(TBSecurity.java:392)

HashMap 对象

1
2
3
case "java/util/HashMap-><init>()V":{
return ProxyDvmObject.createObject(vm, new HashMap<>());
}

例二

1
2
3
java.lang.UnsupportedOperationException: java/util/zip/ZipFile->getInputStream(Ljava/util/zip/ZipEntry;)Ljava/io/InputStream;
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:416)
at com.demo7.TDJNI.callObjectMethodV(TDJNI.java:625)

InputStream 对象

1
2
3
4
5
6
7
8
9
case "java/util/zip/ZipFile->getInputStream(Ljava/util/zip/ZipEntry;)Ljava/io/InputStream;":{
ZipFile zipFile = (ZipFile) dvmObject.getValue();
ZipEntry entry = (ZipEntry) vaList.getObjectArg(0).getValue()
try {
return ProxyDvmObject.createObject(vm, zipFile.getInputStream(entry));
} catch (IOException e) {
e.printStackTrace();
}
}

例三

1
2
3
java.lang.UnsupportedOperationException: java/util/Map->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:922)
at com.Bili.NativeLibrary.callObjectMethod(NativeLibrary.java:88)

这里只知道是 Object,而不确定具体是字符串还是其他什么类型,使用ProxyDvmObject.createObject是好选择。

1
2
3
4
5
6
case "java/util/Map->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;":{
Map map = (Map) dvmObject.getValue();
Object key = varArg.getObjectArg(0).getValue();
Object value = varArg.getObjectArg(1).getValue();
return ProxyDvmObject.createObject(vm, map.put(key, value));
}

3.4 类型四

对于 Android FrameWork 类库中的对象和类,使用resolveClass创建对应的DvmClassDvmObject比较好。

例一

1
2
3
java.lang.UnsupportedOperationException: android/telephony/FtTelephonyAdapter->getFtTelephony(Landroid/content/Context;)Landroid/telephony/FtTelephony;
at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethodV(AbstractJni.java:503)
at com.demo7.TDJNI.callStaticObjectMethodV(TDJNI.java:763)

这显然是一个 Android 里的类库,没法在 Unidbg 中实际处理,只能用resolveClass占位。

1
2
3
4
case "android/telephony/FtTelephonyAdapter->getFtTelephony(Landroid/content/Context;)Landroid/telephony/FtTelephony;":{
//只需要满足函数的返回值类型即可,具体值等后面报错了再视具体情况解决
return vm.resolveClass("android/telephony/FtTelephony").newObject(null);
}

例二

1
2
3
java.lang.UnsupportedOperationException: java/lang/Class->forName(Ljava/lang/String;)Ljava/lang/Class;
at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethodV(AbstractJni.java:503)
at com.demo7.TDJNI.callStaticObjectMethodV(TDJNI.java:763)

Class.forName(className)用于加载类,我们不确定所加载的类是样本自定义的类、Android 框架层类库,还是 JDK 中的标准类库,这种情况里使用resolveClass是好办法。

1
2
3
4
5
6
7
case "java/lang/Class->forName(Ljava/lang/String;)Ljava/lang/Class;":{
//获取入参
String className = vaList.getObjectArg(0).getValue().toString();
System.out.println("Class->forName:"+className);
//使用resolveClass加载对应类
return vm.resolveClass(className);
}

例三

1
2
3
java.lang.UnsupportedOperationException: android/content/IntentFilter-><init>(Ljava/lang/String;)V
at com.github.unidbg.linux.android.dvm.AbstractJni.newObjectV(AbstractJni.java:791)
at com.demo7.TDJNI.newObjectV(TDJNI.java:826)

这里要初始化IntentFilter,同样的 Android 框架层的类库,只能做占位处理。

1
2
3
4
5
6
7
case "android/content/IntentFilter-><init>(Ljava/lang/String;)V":{
//获取入参
String intent = vaList.getObjectArg(0).getValue().toString();
System.out.println("IntentFilter:"+intent);
//占位处理
return vm.resolveClass("android/content/IntentFilter").newObject(intent);
}

可以注意到,这里的newObject没有传null,而是把参数传递了进去,这是因为样本初始化了多个IntentFilter,需要一个办法分辨不同的IntentFilter,以及把初始化的内容传递到之后的函数中去。

四、参考

Unidbg 的基本使用(四)

[Unidbg 的基本使用(五)](https://www.yuque.com/lilac-2hqvv/xdwlsg/hiiaukxvr2zuz5mb?# 《Unidbg 的基本使用(五)》)


unidbg中参数传递时的类型封装
http://example.com/2024/12/05/Unidbg模拟执行/unidbg中参数传递时的类型封装/
作者
gla2xy
发布于
2024年12月5日
许可协议