抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

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

Unidbg 类型封装

根据 Unidbg 类型封装过程的分析,类型封装情况可以分为以下四类:

1. 基本类型直接传递

对于 Java 的基本数据类型,调用时可以直接传递,无需额外封装。这些类型包括:

intlongbooleandouble

Unidbg 会自动将这些基本类型直接映射为 JNI 方法的入参,并传递给底层逻辑。

2. 常见对象类型的直接传递

常见的 Java 对象类型,可以直接作为参数传递,Unidbg 会自动封装为相应的 JNI 类型(如 StringObjectByteArray 等):

  • String(字符串)

  • 基本类型的数组:

    byte[]short[]int[]float[]double[]

  • Enum(枚举类型)

如果需要手动封装这些对象类型,也可以通过相关构造器创建对应的封装对象,例如使用 StringObjectByteArray 等。

3. JDK 标准类库的封装

对于 JDK 中包含的类库(如二维数组、字符串数组、HashMap 等),可以通过 ProxyDvmObject.createObject() 进行封装。

该方法会自动将类库对象转换为 ProxyDvmObject,以便在 JNI 调用中正确传递。此方法支持封装 JDK 标准类库对象以及常见的第三方类库(如 OkHttp 等)。

支持封装的类型

二维数组,字符串数组,集合类(如 HashMap 等)

4. 非 JDK 标准类库的封装

对于无法在 JDK 中包含的类库(如 Android Framework 的组件类或样本自定义的类库),需要通过 resolveClass 动态解析类名 并调用 .newObject 来手动构造封装对象。此类封装方法通常用于处理 Android 系统组件(如 Context)或特定样本中自定义的复杂类。

如果要发起对目标函数的调用,那么查看代码的反编译情况,可以看到清晰的参数类型

1
static native SignedQuery s(SortedMap<String, String> sortedMap);

如果样本通过 JNI 访问 JAVA 方法,那么根据报错确定返回值类型。

例一

1
2
3
4
java.lang.UnsupportedOperationException: com/izuiyou/common/base/BaseApplication->getAppContext()Landroid/content/Context;
at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethodV(AbstractJni.java:503)
at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethodV(AbstractJni.java:437)
at com.github.unidbg.linux.android.dvm.DvmMethod.callStaticObjectMethodV(DvmMethod.java:64)

方法签名中,返回值是Landroid/content/Context;

JAVA 类型 签名
boolean Z
byte B
char C
short S
int I
long J
float F
double D
void V
class Lclass;
[ type type[]

根据对应关系,其类型就是android/content/Context

类型一

1. 基本类型的直接传递

基本类型包括:

  • 整数类型byteshortintlong
  • 浮点类型floatdouble
  • 布尔类型boolean
  • 字符类型char

这些类型可以直接传递给 JNI 方法,无需额外的封装操作。Unidbg 会自动识别并处理这些基本类型的参数。

例如,在 AbstractJNI 类中,重写的 getStaticIntField 方法直接返回了一个整型值 0x40

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 类型的显式声明

在参数准备和函数调用时,如果涉及到 long 类型的参数,必须显式声明整数为 Long 类型。这是因为 Unidbg 无法识别未声明的 long 类型参数,并且可能会将其误识别为 int 类型。

正确的传递方式: 在整数值后面添加 L 来显式标记为 Long 类型,例如:

1
callStaticJniMethod(emulator, "nativeFunction(J)V", 1234567890L);

类型二

基本对象的封装与处理

在 Unidbg 环境中,基本对象的封装包括字符串(String)、基本类型数组(如 byte[]int[])、对象数组(如 String[])、以及集合(如 List)。它们都需要通过特定的方法封装为 DVM 对象,供虚拟机正确识别和调用。

String(字符串)

在 Unidbg 中,字符串会被封装为 StringObject

1
java.lang.UnsupportedOperationException: cn/xiaochuankeji/tieba/AppController->getPackageName()Ljava/lang/String;

方法一:通过 StringObject 构造

直接通过 StringObject 构造字符串对象:

1
2
String packageName = vm.getPackageName();
StringObject stringObject = new StringObject(vm, packageName);

方法二:通过 ProxyDvmObject.createObject

使用 ProxyDvmObject.createObject,它会自动检测类型并封装为 StringObject

1
2
String packageName = vm.getPackageName();
return ProxyDvmObject.createObject(vm, packageName);

两者对比

  • **StringObject**:直观且代码明确,适合直接封装字符串。
  • **ProxyDvmObject.createObject**:适合动态场景,可以传入任意类型。

Integer(基本类型的包装类)

1
java.lang.UnsupportedOperationException: java/lang/Integer-><init>(I)V

基本类型(如 int)的包装类(如 Integer)在 Unidbg 中通过 valueOf 方法进行封装。

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

Unidbg 对常用包装类(如 IntegerLongFloat 等)进行了支持,这些包装类位于 com.github.unidbg.linux.android.dvm.wrapper 包下。

基本类型数组

1
java.lang.UnsupportedOperationException: java/net/NetworkInterface->getHardwareAddress()[B

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

方法一:通过 ByteArray

对于基本类型数组(如 byte[]),Unidbg 提供了相应的类(如 ByteArrayIntArray)进行封装。例如:

1
2
3
byte[] byteArray = new byte[]{1, 2, 3};
ByteArray byteArrayObject = new ByteArray(vm, byteArray);
return byteArrayObject;

方法二:通过 ProxyDvmObject.createObject

使用 ProxyDvmObject.createObject,它会自动检测并封装为对应的数组对象:

1
2
byte[] byteArray = new byte[]{1, 2, 3};
return ProxyDvmObject.createObject(vm, byteArray);

com.github.unidbg.linux.android.dvm.array包下有对各种基本类型数组的表示和处理。

对象数组

1
java.lang.UnsupportedOperationException: android/os/Build->SUPPORTED_ABIS:[Ljava/lang/String;

类型是[Ljava/lang/String;

方法一:通过 ArrayObject

Unidbg 提供了 ArrayObject 来表示对象数组。如果是字符串数组,还提供了方便的静态方法 newStringArray 进行构造:

1
2
String[] abis = new String[]{"arm64-v8a", "armeabi-v7a", "armeabi"};
return ArrayObject.newStringArray(vm, abis);

方法二:通过 ProxyDvmObject.createObject

ProxyDvmObject.createObject 可以将 Java 的对象数组自动转换为 ArrayObject

1
2
String[] abis = new String[]{"arm64-v8a", "armeabi-v7a", "armeabi"};
return ProxyDvmObject.createObject(vm, abis);

内部的实现会将数组元素逐一封装为 DvmObject,再组合成 ArrayObject

1
2
3
4
5
6
7
8
9
Class<?> clazz = value.getClass();
if (clazz.isArray()) {
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);
}

对于对象数组类型,用ProxyDvmObject.createObject更省事。

集合

1
java.lang.UnsupportedOperationException: android/hardware/SensorManager->getSensorList(I)Ljava/util/List;

封装为ArrayListObject

对于集合类型(如 List),可以使用 ArrayListObject 封装。

示例:返回传感器列表 以下代码演示了如何封装 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);
}

类型三

在 Unidbg 的模拟环境中,部分 JDK 类的行为无法直接支持或无法解析,例如:

  1. 无法直接通过 newObject 创建实例。
  2. 无法直接返回 Dalvik 层的复杂类型。
  3. 无法确定返回值类型(如 Object 类型)。

这种情况下,我们需要使用 ProxyDvmObject.createObject 来手动创建代理对象,以桥接 Dalvik 层和 Java 层之间的调用。

无法直接创建对象(HashMap 示例)

1
java.lang.UnsupportedOperationException: java/util/HashMap-><init>()V

使用 ProxyDvmObject.createObject 手动创建 HashMap 实例,并返回代理对象。

1
2
3
4
case "java/util/HashMap-><init>()V": {
// 创建一个新的 HashMap 并包装为 ProxyDvmObject
return ProxyDvmObject.createObject(vm, new HashMap<>());
}

返回复杂对象(InputStream 示例)

1
java.lang.UnsupportedOperationException: java/util/zip/ZipFile->getInputStream(Ljava/util/zip/ZipEntry;)Ljava/io/InputStream;

手动调用 Java 方法,并将结果包装成 ProxyDvmObject 返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
case "java/util/zip/ZipFile->getInputStream(Ljava/util/zip/ZipEntry;)Ljava/io/InputStream;": {
// 获取 ZipFile 对象
ZipFile zipFile = (ZipFile) dvmObject.getValue();
try {
// 调用 Java 方法获取 InputStream
InputStream inputStream = zipFile.getInputStream((ZipEntry) vaList.getObjectArg(0).getValue());
// 将 InputStream 包装为 ProxyDvmObject
return ProxyDvmObject.createObject(vm, inputStream);
} catch (IOException e) {
// 打印异常信息
e.printStackTrace();
}
}

返回不确定类型的对象(Map 示例)

1
java.lang.UnsupportedOperationException: java/util/Map->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;

这里只知道是 Object,而不确定具体是字符串还是其他什么类型

手动处理 Map.put() 方法的逻辑,并将返回值包装为 ProxyDvmObject。

1
2
3
4
5
6
7
8
9
10
11
case "java/util/Map->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;": {
// 获取 Map 对象
Map map = (Map) dvmObject.getValue();
// 获取 key 和 value
Object key = varArg.getObjectArg(0).getValue();
Object value = varArg.getObjectArg(1).getValue();
// 调用 Map.put() 方法
Object result = map.put(key, value);
// 返回结果包装为 ProxyDvmObject
return ProxyDvmObject.createObject(vm, result);
}

类型四

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

静态方法调用(FtTelephonyAdapter 示例)

1
java.lang.UnsupportedOperationException: android/telephony/FtTelephonyAdapter->getFtTelephony(Landroid/content/Context;)Landroid/telephony/FtTelephony;

这是因为 FtTelephonyAdapter 是 Android Framework 层的类,Unidbg 无法直接处理。

使用 resolveClass 创建占位类,并返回占位对象。

占位类(Placeholder Class)是一种编程中的设计概念,用于在特定上下文中临时替代实际类,以满足程序的运行需求。它的作用是为未实现的、缺失的或者特定场景中的类提供一个占位符,从而避免程序运行时发生错误或异常。

1
2
3
4
case "android/telephony/FtTelephonyAdapter->getFtTelephony(Landroid/content/Context;)Landroid/telephony/FtTelephony;": {
// 使用 resolveClass 解析 FtTelephony 类,并创建一个占位对象
return vm.resolveClass("android/telephony/FtTelephony").newObject(null);
}

静态方法调用(Class.forName 示例)

1
java.lang.UnsupportedOperationException: java/lang/Class->forName(Ljava/lang/String;)Ljava/lang/Class;

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

通过 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);
}

构造方法调用(IntentFilter 示例)

1
java.lang.UnsupportedOperationException: android/content/IntentFilter-><init>(Ljava/lang/String;)V

IntentFilter 是 Android Framework 层的类,通常用于广播接收器的过滤条件。

通过 resolveClass 创建占位类,并传递参数用于初始化。

1
2
3
4
5
6
7
case "android/content/IntentFilter-><init>(Ljava/lang/String;)V": {
// 获取传入的 IntentFilter 参数
String intent = vaList.getObjectArg(0).getValue().toString();
System.out.println("IntentFilter: " + intent);
// 使用 resolveClass 创建 IntentFilter 类,并传入参数
return vm.resolveClass("android/content/IntentFilter").newObject(intent);
}

传递参数(intent)是因为样本可能初始化了多个 IntentFilter,需要区分不同实例。后续方法调用时,可以通过 intent 参数还原初始化状态。

resolveClassProxyDvmObject 的区别

特性 resolveClass ProxyDvmObject
用途 用于解析 Android Framework 层类并创建占位类。 用于包装 Java 对象,桥接 Dalvik 和 Java 层。
适用场景 Android Framework 类(如 IntentFilter)。 JDK 类(如 HashMapInputStream)。
返回类型 DvmClassDvmObject ProxyDvmObject(包装的代理对象)。
使用复杂度 较低,直接调用 resolveClass 方法即可。 较高,需要自定义方法逻辑并包装对象。

评论