[Unidbg 的基本使用(五)](https://www.yuque.com/lilac-2hqvv/xdwlsg/hiiaukxvr2zuz5mb?# 《Unidbg 的基本使用(五)》)学!
Unidbg 类型封装
根据 Unidbg 类型封装过程的分析,类型封装情况可以分为以下四类:
1. 基本类型直接传递
对于 Java 的基本数据类型,调用时可以直接传递,无需额外封装。这些类型包括:
int,long,boolean,double
Unidbg 会自动将这些基本类型直接映射为 JNI 方法的入参,并传递给底层逻辑。
2. 常见对象类型的直接传递
常见的 Java 对象类型,可以直接作为参数传递,Unidbg 会自动封装为相应的 JNI 类型(如 StringObject、ByteArray 等):
- String(字符串)
- 基本类型的数组: - byte[],- short[],- int[],- float[],- double[]
- Enum(枚举类型)
如果需要手动封装这些对象类型,也可以通过相关构造器创建对应的封装对象,例如使用 StringObject 或 ByteArray 等。
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 | java.lang.UnsupportedOperationException: com/izuiyou/common/base/BaseApplication->getAppContext()Landroid/content/Context; | 
方法签名中,返回值是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. 基本类型的直接传递
基本类型包括:
- 整数类型:byte、short、int、long
- 浮点类型:float、double
- 布尔类型:boolean
- 字符类型:char
这些类型可以直接传递给 JNI 方法,无需额外的封装操作。Unidbg 会自动识别并处理这些基本类型的参数。
例如,在 AbstractJNI 类中,重写的 getStaticIntField 方法直接返回了一个整型值 0x40:
| 1 | 
 | 
注意 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 | String packageName = vm.getPackageName(); | 
方法二:通过 ProxyDvmObject.createObject
使用 ProxyDvmObject.createObject,它会自动检测类型并封装为 StringObject:
| 1 | String packageName = vm.getPackageName(); | 
两者对比:
- **StringObject**:直观且代码明确,适合直接封装字符串。
- **ProxyDvmObject.createObject**:适合动态场景,可以传入任意类型。
Integer(基本类型的包装类)
| 1 | java.lang.UnsupportedOperationException: java/lang/Integer-><init>(I)V | 
基本类型(如 int)的包装类(如 Integer)在 Unidbg 中通过 valueOf 方法进行封装。
| 1 | case "java/lang/Integer-><init>(I)V": { | 
Unidbg 对常用包装类(如 Integer、Long、Float 等)进行了支持,这些包装类位于 com.github.unidbg.linux.android.dvm.wrapper 包下。
基本类型数组
| 1 | java.lang.UnsupportedOperationException: java/net/NetworkInterface->getHardwareAddress()[B | 
[B即 byte[],在 Unidbg 中对应于ByteArray
方法一:通过 ByteArray 类
对于基本类型数组(如 byte[]),Unidbg 提供了相应的类(如 ByteArray、IntArray)进行封装。例如:
| 1 | byte[] byteArray = new byte[]{1, 2, 3}; | 
方法二:通过 ProxyDvmObject.createObject
使用 ProxyDvmObject.createObject,它会自动检测并封装为对应的数组对象:
| 1 | byte[] byteArray = new byte[]{1, 2, 3}; | 
在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 | String[] abis = new String[]{"arm64-v8a", "armeabi-v7a", "armeabi"}; | 
方法二:通过 ProxyDvmObject.createObject
ProxyDvmObject.createObject 可以将 Java 的对象数组自动转换为 ArrayObject:
| 1 | String[] abis = new String[]{"arm64-v8a", "armeabi-v7a", "armeabi"}; | 
内部的实现会将数组元素逐一封装为 DvmObject,再组合成 ArrayObject:
| 1 | Class<?> clazz = value.getClass(); | 
对于对象数组类型,用ProxyDvmObject.createObject更省事。
集合
| 1 | java.lang.UnsupportedOperationException: android/hardware/SensorManager->getSensorList(I)Ljava/util/List; | 
封装为ArrayListObject
对于集合类型(如 List),可以使用 ArrayListObject 封装。
示例:返回传感器列表 以下代码演示了如何封装 List 对象为 ArrayListObject:
| 1 | case "android/hardware/SensorManager->getSensorList(I)Ljava/util/List;": { | 
类型三
在 Unidbg 的模拟环境中,部分 JDK 类的行为无法直接支持或无法解析,例如:
- 无法直接通过 newObject创建实例。
- 无法直接返回 Dalvik 层的复杂类型。
- 无法确定返回值类型(如 Object类型)。
这种情况下,我们需要使用 ProxyDvmObject.createObject 来手动创建代理对象,以桥接 Dalvik 层和 Java 层之间的调用。
无法直接创建对象(HashMap 示例)
| 1 | java.lang.UnsupportedOperationException: java/util/HashMap-><init>()V | 
使用 ProxyDvmObject.createObject 手动创建 HashMap 实例,并返回代理对象。
| 1 | case "java/util/HashMap-><init>()V": { | 
返回复杂对象(InputStream 示例)
| 1 | java.lang.UnsupportedOperationException: java/util/zip/ZipFile->getInputStream(Ljava/util/zip/ZipEntry;)Ljava/io/InputStream; | 
手动调用 Java 方法,并将结果包装成 ProxyDvmObject 返回。
| 1 | case "java/util/zip/ZipFile->getInputStream(Ljava/util/zip/ZipEntry;)Ljava/io/InputStream;": { | 
返回不确定类型的对象(Map 示例)
| 1 | java.lang.UnsupportedOperationException: java/util/Map->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; | 
这里只知道是 Object,而不确定具体是字符串还是其他什么类型
手动处理 Map.put() 方法的逻辑,并将返回值包装为 ProxyDvmObject。
| 1 | case "java/util/Map->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;": { | 
类型四
对于 Android FrameWork 类库中的对象和类,使用resolveClass创建对应的DvmClass和DvmObject比较好。
静态方法调用(FtTelephonyAdapter 示例)
| 1 | java.lang.UnsupportedOperationException: android/telephony/FtTelephonyAdapter->getFtTelephony(Landroid/content/Context;)Landroid/telephony/FtTelephony; | 
这是因为 FtTelephonyAdapter 是 Android Framework 层的类,Unidbg 无法直接处理。
使用 resolveClass 创建占位类,并返回占位对象。
占位类(Placeholder Class)是一种编程中的设计概念,用于在特定上下文中临时替代实际类,以满足程序的运行需求。它的作用是为未实现的、缺失的或者特定场景中的类提供一个占位符,从而避免程序运行时发生错误或异常。
| 1 | case "android/telephony/FtTelephonyAdapter->getFtTelephony(Landroid/content/Context;)Landroid/telephony/FtTelephony;": { | 
静态方法调用(Class.forName 示例)
| 1 | java.lang.UnsupportedOperationException: java/lang/Class->forName(Ljava/lang/String;)Ljava/lang/Class; | 
Class.forName(className)用于加载类,我们不确定所加载的类是样本自定义的类、Android 框架层类库,还是 JDK 中的标准类库,这种情况里使用resolveClass是好办法。
通过 resolveClass 动态解析类名并返回占位对象。
| 1 | case "java/lang/Class->forName(Ljava/lang/String;)Ljava/lang/Class;": { | 
构造方法调用(IntentFilter 示例)
| 1 | java.lang.UnsupportedOperationException: android/content/IntentFilter-><init>(Ljava/lang/String;)V | 
IntentFilter 是 Android Framework 层的类,通常用于广播接收器的过滤条件。
通过 resolveClass 创建占位类,并传递参数用于初始化。
| 1 | case "android/content/IntentFilter-><init>(Ljava/lang/String;)V": { | 
传递参数(intent)是因为样本可能初始化了多个 IntentFilter,需要区分不同实例。后续方法调用时,可以通过 intent 参数还原初始化状态。
resolveClass 与 ProxyDvmObject 的区别
| 特性 | resolveClass | ProxyDvmObject | 
|---|---|---|
| 用途 | 用于解析 Android Framework 层类并创建占位类。 | 用于包装 Java 对象,桥接 Dalvik 和 Java 层。 | 
| 适用场景 | Android Framework 类(如 IntentFilter)。 | JDK 类(如 HashMap、InputStream)。 | 
| 返回类型 | DvmClass和DvmObject。 | ProxyDvmObject(包装的代理对象)。 | 
| 使用复杂度 | 较低,直接调用 resolveClass方法即可。 | 较高,需要自定义方法逻辑并包装对象。 | 
 
          
          
          
         
    
    
       
     
          
         
          
        