总结unidbg补JNI环境时的代码逻辑

一、引言

fork from [Unidbg 的基本使用(八)](https://www.yuque.com/lilac-2hqvv/xdwlsg/td2t5cvohdedb878?# 《Unidbg 的基本使用(八)》)。

二、处理一

对于 JDK 里包含的类库,可以直接复现其方法逻辑。

比如对 SimpleDateFormat 的操作

1
2
3
4
5
6
case "java/text/SimpleDateFormat-><init>(Ljava/lang/String;Ljava/util/Locale;)V":{
String pattern = vaList.getObjectArg(0).getValue().toString();
Locale locale = (Locale) vaList.getObjectArg(1).getValue();
simpleDateFormat = new SimpleDateFormat(pattern, locale);
return;
}

比如对 StringBuilder 的操作

1
2
3
4
case "java/lang/StringBuilder->append(Ljava/lang/String;)Ljava/lang/StringBuilder;":{
String str = vaList.getObjectArg(0).getValue().toString();
return ProxyDvmObject.createObject(vm, ((StringBuilder) dvmObject.getValue()).append(str));
}

比如 okhttp3,可以 maven 引入然后正常使用

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
case "okhttp3/Headers->name(I)Ljava/lang/String;":{
Headers headers = (Headers) dvmObject.getValue();
return new StringObject(vm, headers.name(vaList.getIntArg(0)));
}
case "okhttp3/Headers->value(I)Ljava/lang/String;":{
Headers headers = (Headers) dvmObject.getValue();
return new StringObject(vm, headers.value(vaList.getIntArg(0)));
}
case "okio/Buffer->clone()Lokio/Buffer;":{
Buffer buffer = (Buffer) dvmObject.getValue();
return vm.resolveClass("okio/Buffer").newObject(buffer.clone());
}
case "okhttp3/Request->newBuilder()Lokhttp3/Request$Builder;": {
Request request = (Request) dvmObject.getValue();
return vm.resolveClass("okhttp3/Request$Builder").newObject(request.newBuilder());
}
case "okhttp3/Request$Builder->header(Ljava/lang/String;Ljava/lang/String;)Lokhttp3/Request$Builder;": {
Request.Builder builder = (Request.Builder) dvmObject.getValue();
builder.header(vaList.getObjectArg(0).getValue().toString(), vaList.getObjectArg(1).getValue().toString());
return dvmObject;
}
case "okhttp3/Request$Builder->build()Lokhttp3/Request;": {
Request.Builder builder = (Request.Builder) dvmObject.getValue();
request = builder.build();
return vm.resolveClass("okhttp3/Request").newObject(request);
}

三、处理二

样本自定义的类库,可以在JADXGDAJEB等工具中反编译,然后把代码逻辑拷贝到 Unidbg 环境里,前文我们就这么处理过 ContainerUtils、SignedQuery,尤其是各种工具类和方法容器。

有些 Android FrameWork 层的类库也同样可以拷贝过来

比如 TextUtils 里的方法

1
2
3
4
case "android/text/TextUtils->isEmpty(Ljava/lang/CharSequence;)Z":{
String str = vaList.getObjectArg(0).getValue().toString();
return str == null || str.length() == 0;
}

比如 Android 的 Base64 工具类,可以直接抠出来。

1
2
3
4
5
case "android/util/Base64->decode(Ljava/lang/String;I)[B":{
String arg = vaList.getObjectArg(0).getValue().toString();
int flag = vaList.getIntArg(1);
return new ByteArray(vm, Base64.decode(arg, flag));
}

四、处理三

有些 Android FrameWork 类库,使用频率很高,JDK 里又没有其对应,我们就需要复写其语义,实现其基本功能。

比如 SharedPreferences,我们最好写一个小的 demo,实现 Android 中对 SP 的创建、打开、读取、修改、保存这些基本功能。

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
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import java.io.*;

public class SharedPreferences {
private JSONObject jsonObject;
private final File mFile;

SharedPreferences(File file) {
mFile = file;
jsonObject = null;
loadFromDisk();
}

private void loadFromDisk() {
if (mFile.exists() && !mFile.canRead()) {
System.out.println("Attempt to read preferences file " + mFile + " without permission");
}
BufferedInputStream str;
try {
str = new BufferedInputStream(new FileInputStream(mFile), 16 * 1024);
readSP(str);
} catch (Exception e) {
System.out.println("getSharedPreferences:" + e);
}
}

public String getString(String key, String defValue) {
String v = (String)jsonObject.get(key);
return v != null ? v : defValue;
}

public void putString(String key, String value){
jsonObject.put(key, value);
}

private void readSP(InputStream in){
char[] buf = new char[16 * 1024];
InputStreamReader input;
try {
input = new InputStreamReader(in);
int len =input.read(buf);
String text =new String(buf,0, len);
jsonObject = JSON.parseObject(text);
} catch (IOException e) {
e.printStackTrace();
}
}
}

以及根据样本所操作的属性和方法,构建对应的类去描述它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class AccessibilityServiceInfo {
// 指定您希望服务处理的无障碍事件所属的应用的软件包名称。如果省略此参数,则无障碍服务会被视为可用于处理任何应用的无障碍事件。
// 即当前无障碍服务作用于哪些app
public String[] packageNames;
// 当前无障碍服务的名称
public String name;
// 当前无障碍服务所属App的标签名
public CharSequence label;
// 当前无障碍服务所属App的包名
public String packageName;

public AccessibilityServiceInfo(String packageName, String name, CharSequence label, String[] packageNames){
this.packageName = packageName;
this.name = name;
this.label = label;
this.packageNames = packageNames;
}
}

五、处理四

如果其他方法处理不了或者很麻烦,我们会将方法调用”降级“为简单的数据返回或占位或返回 null,这么做有风险,但总比没法继续执行好。

比如

1
2
3
4
5
6
7
8
9
10
11
12
case "android/bluetooth/BluetoothAdapter->getDefaultAdapter()Landroid/bluetooth/BluetoothAdapter;":{
return dvmClass.newObject(signature);
}
case "android/telephony/SubscriptionManager->from(Landroid/content/Context;)Landroid/telephony/SubscriptionManager;":{
return dvmClass.newObject(signature);
}
case "android/net/Uri->parse(Ljava/lang/String;)Landroid/net/Uri;":{
String key = vaList.getObjectArg(0).getValue().toString();
if(key.equals("content://telephony/siminfo")){
return dvmClass.newObject(key);
}
}

比如

1
2
3
4
5
// 获取packageName信息,后续对比是否有多开助手类app
// https://blog.csdn.net/qq_32227681/article/details/110563688
case "android/content/pm/PackageManager->getInstalledPackages(I)Ljava/util/List;":{
return null;
}

六、尾声

总体上,补 JNI 环境的代码逻辑方面,就这么几种处理办法。

这里提供大量的案例可供参考,当不知道如何补 JNI 环境时可以去检索和参考。

百度网盘链接:https://pan.baidu.com/s/1G0U8frKK3dotY2cryztFjQ 提取码:6666


总结unidbg补JNI环境时的代码逻辑
http://example.com/2024/12/05/Unidbg模拟执行/总结unidbg补JNI环境时的代码逻辑/
作者
gla2xy
发布于
2024年12月5日
许可协议