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

补 JNI 环境的数据

补jni内容分成两部分,数据和代码。

数据主要指各种字段和属性的获取

1
2
3
4
5
6
7
8
9
case "com/meituan/android/common/mtguard/NBridge$SIUACollector->uiAutomatorClickCount()I":{
return 0;
}
case "android/view/Display->getHeight()I":{
return 2160;
}
case "android/view/Display->getWidth()I":{
return 1080;
}

代码主要指各种方法的调用

1
2
3
4
case "java/lang/String->compareToIgnoreCase(Ljava/lang/String;)I":{
String str = vaList.getObjectArg(0).getValue().toString();
return dvmObject.getValue().toString().compareToIgnoreCase(str);
}

第一个方法

通过分析字段/方法的语义和上下文返回合理值。

比如isVpn函数,通过面向 Google 学习,我们意识到它是检测Vpn的函数,以及返回什么值对应什么语义。

1
2
3
4
// 检测 VPN,通过 tun0/ppp0。
case "com/meituan/android/common/mtguard/NBridge$SIUACollector->isVPN()Ljava/lang/String;":{
return new StringObject(vm, "0");
}

第二个方法

ADB命令

比如上篇获取屏幕的宽高,通过wm size命令获取。

1
2
C:\Users\13352>adb shell wm size
Physical size: 1080x2160

第三个办法

自写 demo app 做验证

上篇加速度传感器名字的获取就是通过这个办法

1
2
3
4
5
6
7
8
9
case "android/hardware/Sensor->getName()Ljava/lang/String;":{
int type = (int) dvmObject.getValue();
System.out.println("Sensor getName:"+type);
if(type == 1){
return new StringObject(vm, "ICM20690");
}else {
throw new UnsupportedOperationException(signature);
}
}

JNI 调用很复杂,但复写成 Java 代码逻辑往往很简单。

1
2
3
SensorManager manager= (SensorManager)getSystemService(SENSOR_SERVICE);
Sensor accelSensor = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
Log.e("lilac sensor Name",accelSensor.getName());

第四个办法

Frida Hook 和 Call

Unidbg 补的执行流在真机上也理应执行过,那么 Frida Spwan 模式 Hook 应该能拦截到这种调用并做出打印。比如上篇uiAutomatorClickCount的处理就依赖于 Hook。

1
2
3
4
5
6
7
8
9
@Override
public int callIntMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
switch (signature){
case "com/meituan/android/common/mtguard/NBridge$SIUACollector->uiAutomatorClickCount()I":{
return 0;
}
}
return super.callIntMethodV(vm, dvmObject, signature, vaList);
}

Hook 代码通过Jadx一键生成,然后裹上Java.perform

1
2
3
4
5
6
7
8
9
Java.perform(function() {
let SIUACollector = Java.use("com.meituan.android.common.mtguard.NBridge$SIUACollector");
SIUACollector["uiAutomatorClickCount"].implementation = function () {
console.log('uiAutomatorClickCount is called');
let ret = this.uiAutomatorClickCount();
console.log('uiAutomatorClickCount ret value is ' + ret);
return ret;
};
})

如果 Hook 拦截不到或有其他考虑,那么直接 Call 函数也挺好。

第五个办法是使用 trace

r0tracer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function main() {
Java.perform(function () {
console.Purple("r0tracer begin ... !")
//0. 增加精简模式,就是以彩虹色只显示进出函数。默认是关闭的,注释此行打开精简模式。
//isLite = true;
/*
//以下三种模式,取消注释某一行以开启
*/
//A. 简易trace单个类
traceClass("javax.crypto.Cipher")
//B. 黑白名单trace多个类,第一个参数是白名单(包含关键字),第二个参数是黑名单(不包含的关键字)
// hook("javax.crypto.Cipher", "$");
//C. 报某个类找不到时,将某个类名填写到第三个参数,比如找不到com.roysue.check类。(前两个参数依旧是黑白名单)
// hook("com.roysue.check"," ","com.roysue.check");
})
}

它支持三种模式

一是追踪单个类里的所有域值、参数、调用栈和返回值

二是批量追踪包含某个关键字的所有类,并且可以设置白名单

三是在二的基础上,枚举类加载器,努力找寻某个类

我们可以使用它追踪SIUACollector类里的数据情况。

1
2
traceClass("com.meituan.android.common.mtguard.NBridge$SIUACollector")
// frida -U -f com.dianping.v1 -l r0tracer.js --no-pause

在补 JNI 时,我们并不预先确定样本会调用哪个或哪些类,但大体上可以分为两类,

1 是样本自定义的类库,比如com.meituan.android.common.mtguard.NBridge$SIUACollector

2 是 Android FrameWork 类库,比如android.hardware.Sensor

其他:

jnitrace

jTrace

补 JNI 环境的代码

直接复用 JDK 类库

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

比如对 String 的操作

1
2
3
case "java/lang/String->equalsIgnoreCase(Ljava/lang/String;)Z":{
return dvmObject.getValue().toString().equalsIgnoreCase(vaList.getObjectArg(0).getValue().toString());
}

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

复用样本自定义类库

通过反编译工具(如 JADX、GDA、JEB)获取样本自定义类库的逻辑,并直接嵌入到 Unidbg 环境中。有些 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));
}

高频 Framework 类的自定义实现

对于 JDK 中没有直接实现,但 Android Framework 使用频率较高的类,可以通过自定义逻辑实现基本功能。

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
public class SharedPreferences {
private JSONObject jsonObject;
private final File mFile;

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

private void loadFromDisk() {
if (mFile.exists() && mFile.canRead()) {
try (BufferedInputStream str = new BufferedInputStream(new FileInputStream(mFile), 16 * 1024)) {
readSP(str);
} catch (Exception e) {
System.out.println("Error loading SharedPreferences: " + e);
}
}
}

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

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

private void readSP(InputStream in) {
try (InputStreamReader input = new InputStreamReader(in)) {
char[] buf = new char[16 * 1024];
int len = input.read(buf);
jsonObject = JSON.parseObject(new String(buf, 0, len));
} 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;
}
}

降级方法调用

对于难以处理的方法,可以通过简单数据返回或占位的方式处理,降低复杂性。

简单返回占位对象

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
case "android/content/pm/PackageManager->getInstalledPackages(I)Ljava/util/List;": {
return null; // 返回 null 避免运行时错误
}

通过这几种方法,基本可以覆盖大部分补环境的需求:

  1. 优先复用现有类库,减少不必要的实现工作量。
  2. 样本自定义类库和 Framework 类库,通过反编译工具获取逻辑,并尽量还原代码。
  3. 高频 Framework 类的自定义实现,确保功能可用且灵活扩展。
  4. 降级处理适用于高风险或非关键逻辑,确保程序可继续运行。

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

评论