从实践中学习unidbg使用(五)

  1. 从实践中学习unidbg使用(五)
    1. 一、引言
    2. 二、概述
    3. 三、类型一
    4. 四、类型二
    5. 五、类型三
    6. 六、类型四
    7. 七、尾声
    8. 八、参考

从实践中学习unidbg使用(五)

一、引言

本篇讨论 Unidbg 中补 JNI 环境的 “形式”。

二、概述

在前文中给出了如下规则,但它看起来很干瘪,本篇就是要让它们更 “饱满”。

  • 基本类型直接传递;
  • 字符串、字节数组等基本对象直接传递,其内部会做封装,也可以自己调用 new StringObject(vm, str)、**new ByteArray(vm, value)**等;
  • JDK 标准对象,如 HashMap、JSONObject 等,使用 ProxyDvmObject.createObject(vm, value) 处理;
  • 非 JDK 标准库对象,如 Android Context、SharedPreference 等,使用 vm.resolveClass(vm, className).newObject(value) 处理。

可以看到,主要基于数据类型做不同的处理。

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

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

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

  • 例一
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

  • 例二
java.lang.UnsupportedOperationException: cn/xiaochuankeji/tieba/AppController->getPackageManager()Landroid/content/pm/PackageManager;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:416)
	at com.izuiyou.NetWork.callObjectMethodV(NetWork.java:79)

同理,类型是 android/content/pm/PackageManager

  • 例三
java.lang.UnsupportedOperationException: android/os/Process->myPid()I
	at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticIntMethodV(AbstractJni.java:211)

I 即 int,对于基本类型,也可以根据使用的 JNI 方法做判断,比如这里是 callStaticIntMethodV,其中的 Int

  • 例四
java.lang.UnsupportedOperationException: android/os/Debug->isDebuggerConnected()Z
	at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticBooleanMethodV(AbstractJni.java:191)

Z 即 boolean,或者根据 callStaticBooleanMethodV 里的 Boolean 确认。

三、类型一

首先看基本类型,即所谓的 byte、short、int、long、double、float、boolean、char,只需要直接传递即可。以 AbstractJNI 为例,可以找到很多的例子,比如下面直接返回 0x40,至于值是如何来的,这不是本篇的重点,本篇只讨论补环境的 ”形式“ ,补环境的 ”内容” 则是后文的重点。

@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 识别和处理,而不能搞隐式转换那套。

四、类型二

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

  • 例一
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 packgeName = vm.getPackageName();,然后下面两种方案任选一种处理为 StringObject

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

ProxyDvmObject.createObject 内部做了类型判断,将 String 转为 StringObject。

if (value instanceof String) {
    return new StringObject(vm, (String) value);
}

使用哪一种看个人喜好,我个人认为使用 StringObject构造更好,因为更直观。

  • 例二
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 对部分包装类做了处理,详见 com.github.unidbg.linux.android.dvm.wrapper

image-20250809172742114

其中都提供了 valueOf 类方法做包装

case "java/lang/Integer-><init>(I)V": {
    int i = vaList.getIntArg(0);
    return DvmInteger.valueOf(vm, i);
}
  • 例三
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

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

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

这里也可以用 ProxyDvmObject.createObject,就像 StringObject 一样,它内部会做对应处理。

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);
}
  • 例四
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;

Java类型 签名
class Lclass;
[ type type[]

对应上面的表,它是个对象数组,更具体的说是字符串数组。

在 Unidbg 中,对象数组通过 ArrayObject 表示,而且还为字符串数组提供了一个方便的处理函数。

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

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

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

也可以使用 ProxyDvmObject.createObject

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

它在内部最终也是转换为了 ArrayObject

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

对于对象数组类型,我个人感觉用 ProxyDvmObject.createObject 更省事。

  • 例五
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

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

五、类型三

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

  • 例一
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 对象

case "java/util/HashMap-><init>()V":{
    return ProxyDvmObject.createObject(vm, new HashMap<>());
}
  • 例二
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 对象

case "java/util/zip/ZipFile->getInputStream(Ljava/util/zip/ZipEntry;)Ljava/io/InputStream;":{
    ZipFile zipFile = (ZipFile) dvmObject.getValue();
    try {
        return ProxyDvmObject.createObject(vm, zipFile.getInputStream((ZipEntry) vaList.getObjectArg(0).getValue()));
    } catch (IOException e) {
        e.printStackTrace();
    }
}
  • 例三
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 是好选择。

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

六、类型四

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

在 AbstractJNI 中可以看到大量的例子:

  • 例一
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 占位。

case "android/telephony/FtTelephonyAdapter->getFtTelephony(Landroid/content/Context;)Landroid/telephony/FtTelephony;":{
    return vm.resolveClass("android/telephony/FtTelephony").newObject(null);
}
  • 例二
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 是好办法。

case "java/lang/Class->forName(Ljava/lang/String;)Ljava/lang/Class;":{
    String className = vaList.getObjectArg(0).getValue().toString();
    System.out.println("Class->forName:"+className);
    return vm.resolveClass(className);
}
  • 例三
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 框架层的类库,只能做占位处理。

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,以及把初始化的内容传递到之后的函数中去。

七、尾声

本篇讨论了补 JNI 环境在 “形式” 上的一般规则,需要注意并非没有特例,我们会在具体文章中做更多讨论。

八、参考

Unidbg 的基本使用(五)


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1621925986@qq.com

💰

×

Help us with donation