从案例中学习unidbg的使用(三)

一、引言

目标 APK:right573.apk

目标方法:com.izuiyou.network.NetCrypto.sign

目标方法实现:libnet_crypto.so

本篇将开始讨论补环境。

二、任务描述

jadx反编译apk,得到目标函数 sign 代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.izuiyou.network;

...

public class NetCrypto {
public static ChangeQuickRedirect changeQuickRedirect;

static {
we5.a(ContextProvider.get(), "net_crypto");
native_init();
}

...

public static native String sign(String str, byte[] bArr);
}

这里使用 Frida 附加目标进程做主动调用,代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function callSign(){
Java.perform(function() {
function stringToBytes(str) {
var javaString = Java.use('java.lang.String');
return javaString.$new(str).getBytes();
}

let NetCrypto = Java.use("com.izuiyou.network.NetCrypto");
let arg1 = "hello world";
let arg2 = "V I 50";
let ret = NetCrypto.sign(arg1, stringToBytes(arg2));
console.log("ret:"+ret);
})
}

输出是 v2-b94195d5f3c2ad3a876f13346fa283a0,本篇的目标是使用 Unidbg 复现对 sign 的调用。

三、初始化

在 Unidbg 的 unidbg-android/src/test/java 下新建包和类。

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
public class NetCrypto extends AbstractJni {

private final AndroidEmulator emulator;
private final DvmClass cNetCrypto;
private final VM vm;
private final Memory memory;

public NetCrypto(){
emulator = AndroidEmulatorBuilder.for32Bit()
.addBackendFactory(new Unicorn2Factory(true))
.setProcessName("cn.xiaochuankeji.tieba").build();

memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));

vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/izuiyou/right573.apk"));
vm.setJni(this);
vm.setVerbose(true);

/* 如果出现load dependency libandroid.so failed
则通过下面代码加载 libandroid.so 虚拟模块, 但需早于目标 SO 加载的时机*/
// new AndroidModule(emulator, vm).register(memory);

DalvikModule dm = vm.loadLibrary("net_crypto", true);
cNetCrypto = vm.resolveClass("com/izuiyou/network/NetCrypto");

dm.callJNI_OnLoad(emulator);
}

public String sign(){
String arg1 = "hello world";
byte[] arg2 = "V I 50".getBytes(StandardCharsets.UTF_8);
return cNetCrypto.callStaticJniMethodObject(emulator, "sign(Ljava/lang/String;[B)Ljava/lang/String;", arg1, arg2)
.getValue()
.toString();
}

public static void main(String[] args) {
NetCrypto netCrypto = new NetCrypto();
System.out.println("call sign result: " + netCrypto.sign());
}
}

样本 lib 目录下只包含armeabi-v7a的动态库文件,因此这里只能选择 32 位,运行代码发现首行日志提示,libnet_crypto.so 试图加载 libandroid.so 这个依赖库,但没找到。

1
[16:54:19 395]  INFO [com.github.unidbg.linux.AndroidElfLoader] (AndroidElfLoader:481) - libnet_crypto.so load dependency libandroid.so failed

这里加载失败的 libandroid.so 就是一个系统库,但 Unidbg 并没有在 /src/main/resources/sdkxx/lib 目录下实现它,而是将其实现为虚拟模块。这是因为并非所有的 SO 都可以被 Unidbg 完备的加载和处理。以当前的 libandroid.so 为例,它依赖于相当多的类库,其中许多和 Android 系统、软硬件、大量的系统调用紧密相关。这使得 Unidbg 很难对它们做妥善、完备的处理,因为 Unidbg 并不是真正完善健全的模拟器。除了 libandroid.so 外,libart.so、libjnigraphics.so 等库都因为这个原因没法完成加载。因此 Unidbg 只默认加载了依赖最少,使用又最多的一批库函数。

对于 libandroid.so 这样的系统库出现频率很高,Unidbg 提供了一种叫做虚拟模块的机制,提供对这些 SO 一部分函数的模拟实现。目前 Unidbg 提供了 libandroid.so、libjnigraphics.so、libmediandk.so 三个库的虚拟模块。使用它们相当简单,比如此处缺少 libandroid.so,只需要在早于目标 SO 加载的时机,添下面这行代码即可。

1
new AndroidModule(emulator, vm).register(memory);;

五、补 JNI 环境

运行代码,报错如下

1
2
3
4
5
6
JNIEnv->GetStaticMethodID(com/izuiyou/common/base/BaseApplication.getAppContext()Landroid/content/Context;) => 0x2157b33c was called from RX@0x1204da57[libnet_crypto.so]0x4da57
[20:30:08 237] WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=-452987556, svcNumber=0x170, PC=unidbg@0xfffe0794, LR=RX@0x1204db2f[libnet_crypto.so]0x4db2f, syscall=null
java.lang.UnsupportedOperationException: com/izuiyou/common/base/BaseApplication->getAppContext()Landroid/content/Context;
at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethodV(AbstractJni.java:504)
at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethodV(AbstractJni.java:438)
at com.github.unidbg.linux.android.dvm.DvmMethod.callStaticObjectMethodV(DvmMethod.java:59)

Unidbg 通过主动抛出异常来提醒我们补 JNI 环境。

5.1 分析异常

对于报错的这第一部分,有意义的是LR,它即 JNI 跳板函数的返回地址,对应于样本发起 JNI 调用的位置。

1
[08:21:02 080]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:532) - handleInterrupt intno=2, NR=-1073744548, svcNumber=0x170, PC=unidbg@0xfffe0794, LR=RX@0x4004db2f[libnet_crypto.so]0x4db2f, syscall=null

第二部分是其余部分

1
2
3
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)

首先是 Unidbg 无法处理的函数其签名com/izuiyou/common/base/BaseApplication->getAppContext()Landroid/content/Context;,这个格式很清晰,即com/izuiyou/common/base/BaseApplication类的getAppContext方法。

再看它的调用栈,来自于callStaticObjectMethodV这个 JNI 方法。接下来讨论为什么要补 JNI 环境。

5.2 补环境的原因

为什么要补 JNI 环境?

Unidbg 通过构造JNIEnvJavaVM结构,辅之以SVC跳板函数,实现了对JNI的拦截和接管。

但到底如何去模拟 JNI 函数,这是问题的核心。Unidbg 实现了一套 JNI 处理逻辑,当遇到 JNI 调用时,如果是FindClassNewGlobalRefGetObjectClassGetMethodIDGetStringLengthGetStringCharsReleaseStringChars等等函数,它的可以自洽处理,不需要使用者介入。

那么补 JNI 环境的需求在哪里?在于如果是callStaticObjectMethodcallObjectMethodVgetObjectFieldgetStaticIntField等 JNI 函数,这些函数对样本的 Java 层数据做访问,Unidbg 既未运行 Dex,更没运行 Apk,因为需要借助读者,去填充这些外部信息。

举个例子,下面是一个小 demo。通过 JNI 访问了样本的com.example.jnidemo.Utils类里的静态字段country

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_jnidemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
jclass clz = env->FindClass("com/example/jnidemo/Utils");
jfieldID jfieldId = env->GetStaticFieldID(clz, "country", "Ljava/lang/String;");
jstring ret = static_cast<jstring>(env->GetStaticObjectField(clz, jfieldId));
const char* c_str = env->GetStringUTFChars(ret, nullptr);
return env->NewStringUTF(c_str);
}

Utils 类的代码如下

1
2
3
4
5
package com.example.jnidemo;

public class Utils {
public static String country = "China";
}

Unidbg 只模拟执行 Native 层,自然无法无法感知和获取到 Utils 类以及其中的 country 字段,所以必须交由使用者自行处理,比如在JADX中反编译样本,找到 Utils 类并静态分析代码,获悉country字段的值,比如直接 Frida Hook 查看等等。

因为担心用户意识不到补 JNI 的需求,所以通过抛出报错和打印堆栈予以提醒。

需要注意,并不是所有基于 JNI 发起的函数调用、字段访问都必须由用户处理,我们发现,其中有部分可以被预处理。举一些例子

  • Android PackageManager 类里的 GET_SIGNATURES 静态字段 ,它的值固定是 64
  • String 类的 getBytes 方法
  • List 实现类的 size 方法

为了减少用户的补 JNI 负担,所以 Unidbg 预处理了数百个常见的 JNI 函数调用和字段访问,逻辑位于src/main/java/com/github/unidbg/linux/android/dvm/AbstractJni.java类里。

比如callBooleanMethodV,调用返回布尔值的实例方法,预处理了如下方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Override
public boolean callBooleanMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
switch (signature) {
case "java/util/Enumeration->hasMoreElements()Z":
return ((Enumeration) dvmObject).hasMoreElements();
case "java/util/ArrayList->isEmpty()Z":
return ((ArrayListObject) dvmObject).isEmpty();
case "java/util/Iterator->hasNext()Z":
Object iterator = dvmObject.getValue();
if (iterator instanceof Iterator) {
return ((Iterator<?>) iterator).hasNext();
}
case "java/lang/String->startsWith(Ljava/lang/String;)Z":{
String str = (String) dvmObject.getValue();
StringObject prefix = vaList.getObjectArg(0);
return str.startsWith(prefix.value);
}
}

throw new UnsupportedOperationException(signature);
}

比如getObjectField,访问对象类型的实例字段,做了如下处理。

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
@Override
public DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
if ("android/content/pm/PackageInfo->signatures:[Landroid/content/pm/Signature;".equals(signature) &&
dvmObject instanceof PackageInfo) {
PackageInfo packageInfo = (PackageInfo) dvmObject;
if (packageInfo.getPackageName().equals(vm.getPackageName())) {
CertificateMeta[] metas = vm.getSignatures();
if (metas != null) {
Signature[] signatures = new Signature[metas.length];
for (int i = 0; i < metas.length; i++) {
signatures[i] = new Signature(vm, metas[i]);
}
return new ArrayObject(signatures);
}
}
}
if ("android/content/pm/PackageInfo->versionName:Ljava/lang/String;".equals(signature) &&
dvmObject instanceof PackageInfo) {
PackageInfo packageInfo = (PackageInfo) dvmObject;
if (packageInfo.getPackageName().equals(vm.getPackageName())) {
String versionName = vm.getVersionName();
if (versionName != null) {
return new StringObject(vm, versionName);
}
}
}

throw new UnsupportedOperationException(signature);
}

这两个调用是获取 Apk 签名信息以及版本信息,Unidbg 之所以能处理,依赖我们传入的 APK,解析出了这些信息。

在 AbstractJNI 中,下面四类处理为主

  • App 基本信息与签名
  • JDK 加密解密与数字签名
  • 字符串和容器类型
  • Android FrameWork 类库

第一类就比如上面的两个,但不止于此,加载和解析 APK 让 Unidbg 获取了大量的信息。

比如处理获取包名

1
2
3
4
5
6
7
8
9
10
@Override
public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
if ("android/app/ActivityThread->currentPackageName()Ljava/lang/String;".equals(signature)) {
String packageName = vm.getPackageName();
if (packageName != null) {
return new StringObject(vm, packageName);
}
}
throw new UnsupportedOperationException(signature);
}

第二类是 JAVA 加解密和数字签名相关的方法,它主要服务于 Apk 签名校验,出现频率很高。

比如

1
2
3
4
5
6
7
8
9
case "java/security/KeyFactory->getInstance(Ljava/lang/String;)Ljava/security/KeyFactory;":{
StringObject algorithm = vaList.getObjectArg(0);
assert algorithm != null;
try {
return dvmClass.newObject(KeyFactory.getInstance(algorithm.value));
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}

比如

1
2
3
4
5
6
7
case "javax/crypto/spec/SecretKeySpec-><init>([BLjava/lang/String;)V":{
byte[] key = (byte[]) vaList.getObjectArg(0).value;
StringObject algorithm = vaList.getObjectArg(1);
assert algorithm != null;
SecretKeySpec secretKeySpec = new SecretKeySpec(key, algorithm.value);
return dvmClass.newObject(secretKeySpec);
}

第三类是对基本类型的包装类、字符串、容器类型的处理。

比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public DvmObject<?> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
switch (signature) {
case "java/lang/String-><init>([B)V":
ByteArray array = varArg.getObjectArg(0);
return new StringObject(vm, new String(array.getValue()));
case "java/lang/String-><init>([BLjava/lang/String;)V":
array = varArg.getObjectArg(0);
StringObject string = varArg.getObjectArg(1);
try {
return new StringObject(vm, new String(array.getValue(), string.getValue()));
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
}
}

throw new UnsupportedOperationException(signature);
}

比如

1
2
3
4
5
6
7
8
9
10
11
12
case "java/lang/Integer->intValue()I": {
DvmInteger integer = (DvmInteger) dvmObject;
return integer.value;
}
case "java/util/List->size()I": {
List<?> list = (List<?>) dvmObject.getValue();
return list.size();
}
case "java/util/Map->size()I":{
Map<?, ?> map = (Map<?, ?>) dvmObject.getValue();
return map.size();
}

第四类是依赖于 Android FrameWork 的类库,它们没法在普通的 JAVA 环境里良好处理,因此需要做一些粗糙的模拟或占位。

比如 Android 系统服务

1
2
3
4
5
case "android/app/Application->getSystemService(Ljava/lang/String;)Ljava/lang/Object;": {
StringObject serviceName = vaList.getObjectArg(0);
assert serviceName != null;
return new SystemService(vm, serviceName.getValue());
}

SystemService 的实现如下,就是大量的简单占位,调用其中的方法实际获取数据时,还是要使用者来补。

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.github.unidbg.linux.android.dvm.api;

import com.github.unidbg.arm.backend.BackendException;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.DvmObject;
import com.github.unidbg.linux.android.dvm.VM;

public class SystemService extends DvmObject<String> {

public static final String WIFI_SERVICE = "wifi";
public static final String CONNECTIVITY_SERVICE = "connectivity";
public static final String TELEPHONY_SERVICE = "phone";
public static final String ACCESSIBILITY_SERVICE = "accessibility";
public static final String KEYGUARD_SERVICE = "keyguard";
public static final String ACTIVITY_SERVICE = "activity";
public static final String SENSOR_SERVICE = "sensor";
public static final String INPUT_METHOD_SERVICE = "input_method";
public static final String LOCATION_SERVICE = "location";
public static final String WINDOW_SERVICE = "window";
public static final String UI_MODE_SERVICE = "uimode";
public static final String DISPLAY_SERVICE = "display";
public static final String AUDIO_SERVICE = "audio";

public SystemService(VM vm, String serviceName) {
super(getObjectType(vm, serviceName), serviceName);
}

private static DvmClass getObjectType(VM vm, String serviceName) {
switch (serviceName) {
case TELEPHONY_SERVICE:
return vm.resolveClass("android/telephony/TelephonyManager");
case WIFI_SERVICE:
return vm.resolveClass("android/net/wifi/WifiManager");
case CONNECTIVITY_SERVICE:
return vm.resolveClass("android/net/ConnectivityManager");
case ACCESSIBILITY_SERVICE:
return vm.resolveClass("android/view/accessibility/AccessibilityManager");
case KEYGUARD_SERVICE:
return vm.resolveClass("android/app/KeyguardManager");
case ACTIVITY_SERVICE:
return vm.resolveClass("android/os/BinderProxy"); // android/app/ActivityManager
case SENSOR_SERVICE:
return vm.resolveClass("android/hardware/SensorManager");
case INPUT_METHOD_SERVICE:
return vm.resolveClass("android/view/inputmethod/InputMethodManager");
case LOCATION_SERVICE:
return vm.resolveClass("android/location/LocationManager");
case WINDOW_SERVICE:
return vm.resolveClass("android/view/WindowManager");
case UI_MODE_SERVICE:
return vm.resolveClass("android/app/UiModeManager");
case DISPLAY_SERVICE:
return vm.resolveClass("android/hardware/display/DisplayManager");
case AUDIO_SERVICE:
return vm.resolveClass("android/media/AudioManager");
default:
throw new BackendException("service failed: " + serviceName);
}
}

}

关于 AbstractJNI 可以做两点总结。

  1. 确实能让我们免于一些补环境的苦恼
  2. 相较于所有可能的 JNI 访问,它只是杯水车薪,补 JNI 环境这个步骤不可避免。

5.3 基本规范

回到最初的报错上

1
2
3
4
5
6
7
8
9
10
11
JNIEnv->FindClass(com/izuiyou/common/base/BaseApplication) was called from RX@0x4004da21[libnet_crypto.so]0x4da21
JNIEnv->GetStaticMethodID(com/izuiyou/common/base/BaseApplication.getAppContext()Landroid/content/Context;) => 0x2157b33c was called from RX@0x4004da57[libnet_crypto.so]0x4da57
[08:21:02 080] WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:532) - handleInterrupt intno=2, NR=-1073744548, svcNumber=0x170, PC=unidbg@0xfffe0794, LR=RX@0x4004db2f[libnet_crypto.so]0x4db2f, syscall=null
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)
at com.github.unidbg.linux.android.dvm.DalvikVM$113.handle(DalvikVM.java:1810)
at com.github.unidbg.linux.ARM32SyscallHandler.hook(ARM32SyscallHandler.java:131)
at com.github.unidbg.arm.backend.Unicorn2Backend$11.hook(Unicorn2Backend.java:347)
at com.github.unidbg.arm.backend.unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:109)

函数签名是com/izuiyou/common/base/BaseApplication->getAppContext()Landroid/content/Context;根据抛出的异常,它来自callStaticObjectMethodV调用。

如何构造和补 Context 是一个特殊问题,下面这个构造方式在绝大多数情况下可行。

1
DvmObject<?> context = vm.resolveClass("android/app/Application", vm.resolveClass("android/content/ContextWrapper", vm.resolveClass("android/content/Context"))).newObject(null);

这么做的话,调用sign函数的结果会与Frida Hook的结果不一致!!!

应该使用如下方法构造Context。

1
DvmObject<?> context = vm.resolveClass("cn/xiaochuankeji/tieba/AppController").newObject(null);

接下来补环境,我们可以在 AbstractJNI 的 callStaticObjectMethodV 里添加对这个方法的处理,如下所示。

1
2
3
4
5
6
7
8
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature){
case "com/izuiyou/common/base/BaseApplication->getAppContext()Landroid/content/Context;": {
return vm.resolveClass("cn/xiaochuankeji/tieba/AppController").newObject(null);
}
}
return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
}

5.4 开始补环境

继续运行,遇到了新的 JNI 补环境。

1
2
3
4
[21:01:49 894]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=-452987532, svcNumber=0x11f, PC=unidbg@0xfffe0284, LR=RX@0x1204d563[libnet_crypto.so]0x4d563, syscall=null
java.lang.UnsupportedOperationException: cn/xiaochuankeji/tieba/AppController->getPackageManager()Landroid/content/pm/PackageManager;
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)

获取包管理器如何返回?AbstractJNI 里有可供参考的代码。

重写,参考 AbstractJNI 去补 PackageManager。

1
2
3
4
5
6
7
8
9
@Override
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
switch (signature){
case "cn/xiaochuankeji/tieba/AppController->getPackageManager()Landroid/content/pm/PackageManager;":{
return vm.resolveClass("android/content/pm/PackageManager")newObject(signature);
}
}
return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

继续运行,报错如下

1
2
3
[21:04:39 169]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=-452987532, svcNumber=0x11f, PC=unidbg@0xfffe0284, LR=RX@0x1204d563[libnet_crypto.so]0x4d563, syscall=null
java.lang.UnsupportedOperationException: cn/xiaochuankeji/tieba/AppController->getPackageName()Ljava/lang/String;
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)

获取包名,AbstractJNI 里同样有这个处理逻辑。

直接照抄。

继续运行,报错如下

1
2
3
[21:09:54 046]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=-452987588, svcNumber=0x11f, PC=unidbg@0xfffe0284, LR=RX@0x1204d563[libnet_crypto.so]0x4d563, syscall=null
java.lang.UnsupportedOperationException: cn/xiaochuankeji/tieba/AppController->getClass()Ljava/lang/Class;
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)

同样在 AbstractJNI 里能找到 getClass 的处理逻辑

照抄。

继续运行,报错如下

1
2
3
[21:13:01 871]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=-452987588, svcNumber=0x11f, PC=unidbg@0xfffe0284, LR=RX@0x1204d563[libnet_crypto.so]0x4d563, syscall=null
java.lang.UnsupportedOperationException: java/lang/Class->getSimpleName()Ljava/lang/String;
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)

AbstractJNI 里找不到getSimpleName的处理,但是通过搜索发现,该函数是在获取类的简写名,比如android.content.Context简写名就是ContextDvmClassgetClassName可以获取类名,我们要截取末尾的名字。

1
2
3
4
5
case "java/lang/Class->getSimpleName()Ljava/lang/String;":{
String className = ((DvmClass) dvmObject).getClassName();
String[] name = className.split("/");
return new StringObject(vm, name[name.length - 1]);
}

继续运行,报错如下

1
2
3
[21:14:39 057]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=-452988524, svcNumber=0x11f, PC=unidbg@0xfffe0284, LR=RX@0x1204d563[libnet_crypto.so]0x4d563, syscall=null
java.lang.UnsupportedOperationException: cn/xiaochuankeji/tieba/AppController->getFilesDir()Ljava/io/File;
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)

进行占位处理即可。

1
2
3
case "cn/xiaochuankeji/tieba/AppController->getFilesDir()Ljava/io/File;":{
return vm.resolveClass("java/io/File").newObject(null);
}

然而这样做并没有修不好,运行会报错,提示创建的File为空。

1
2
3
[21:16:56 017]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=-452988524, svcNumber=0x11f, PC=unidbg@0xfffe0284, LR=RX@0x1204d563[libnet_crypto.so]0x4d563, syscall=null
java.lang.NullPointerException: Cannot invoke "java.io.File.getAbsolutePath()" because "file" is null
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:307)

需进行如下处理(原因尚不可知)。

1
2
3
case "cn/xiaochuankeji/tieba/AppController->getFilesDir()Ljava/io/File;":{
return vm.resolveClass("java/io/File").newObject("/data/data/cn.xiaochuankeji.tieba/files");
}

继续运行,报错如下

1
2
3
4
5
JNIEnv->GetMethodID(java/io/File.getAbsolutePath()Ljava/lang/String;) => 0xb4553f34 was called from RX@0x1205af99[libnet_crypto.so]0x5af99
[21:18:39 172] WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=-452988524, svcNumber=0x11f, PC=unidbg@0xfffe0284, LR=RX@0x1204d563[libnet_crypto.so]0x4d563, syscall=null
java.lang.ClassCastException: class java.lang.String cannot be cast to class java.io.File (java.lang.String and java.io.File are in module java.base of loader 'bootstrap')
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:306)
at com.izuiyou.network.NetCrypto.callObjectMethodV(NetCrypto.java:96)

提示 getAbsolutePath 方法处有问题,无法将 String类 转成 File 类,需进行如下处理。

1
2
3
case "java/io/File->getAbsolutePath()Ljava/lang/String;":{
return new StringObject(vm, dvmObject.getValue().toString());
}

继续运行,报错如下

1
2
3
4
5
6
7
8
JNIEnv->GetStaticMethodID(android/os/Debug.isDebuggerConnected()Z) => 0xd74c7fb4 was called from RX@0x40050c1f[libnet_crypto.so]0x50c1f
[11:04:59 936] WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:532) - handleInterrupt intno=2, NR=-1073744460, svcNumber=0x173, PC=unidbg@0xfffe07c4, LR=RX@0x40050cf7[libnet_crypto.so]0x50cf7, syscall=null
java.lang.UnsupportedOperationException: android/os/Debug->isDebuggerConnected()Z
at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticBooleanMethodV(AbstractJni.java:191)
at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticBooleanMethodV(AbstractJni.java:186)
at com.github.unidbg.linux.android.dvm.DvmMethod.callStaticBooleanMethodV(DvmMethod.java:179)
at com.github.unidbg.linux.android.dvm.DalvikVM$116.handle(DalvikVM.java:1884)
at com.github.unidbg.linux.ARM32SyscallHandler.hook(ARM32SyscallHandler.java:131)

检测调试信息,理应返回 false 防止被检测到。

1
2
3
4
5
6
7
8
9
@Override
public boolean callStaticBooleanMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature){
case "android/os/Debug->isDebuggerConnected()Z":{
return false;
}
}
return super.callStaticBooleanMethodV(vm, dvmClass, signature, vaList);
}

继续运行,报错如下

1
2
3
4
5
6
JNIEnv->GetStaticMethodID(android/os/Process.myPid()I) => 0xfb198f3e was called from RX@0x1205b3b5[libnet_crypto.so]0x5b3b5
[21:25:00 006] WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=-452987428, svcNumber=0x17e, PC=unidbg@0xfffe0874, LR=RX@0x1205b4a7[libnet_crypto.so]0x5b4a7, syscall=null
java.lang.UnsupportedOperationException: android/os/Process->myPid()I
at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticIntMethodV(AbstractJni.java:211)
at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticIntMethodV(AbstractJni.java:206)
at com.github.unidbg.linux.android.dvm.DvmMethod.callStaticIntMethodV(DvmMethod.java:159)

补 myPid 函数,返回 Unidbg 所生成的 PID。

1
2
3
4
5
6
7
8
9
@Override
public int callStaticIntMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature){
case "android/os/Process->myPid()I":{
return emulator.getPid();
}
}
return super.callStaticIntMethodV(vm, dvmClass, signature, vaList);
}

运行测试,得到结果,符合我们的预期。

1
call s result:v2-b94195d5f3c2ad3a876f13346fa283a0

六、参考

补库函数(五)

https://www.yuque.com/lilac-2hqvv/xdwlsg/bmf8lm68ffvhrfu0?#%20%E3%80%8AUnidbg%20%E7%9A%84%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8%EF%BC%88%E4%B8%89%EF%BC%89%E3%80%8B


从案例中学习unidbg的使用(三)
http://example.com/2024/12/05/Unidbg模拟执行/从案例中学习unidbg的使用(三)/
作者
gla2xy
发布于
2024年12月5日
许可协议