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

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

一、引言

本篇样本详见参考

二、任务介绍

Jadx 反编译 APK,找到 com.meituan.android.common.mtguard.NBridge 类,这是 MTGuard 安全 SDK 的一个 Java 桥接层。其中有一个叫 SIUACollector 的内部类,里面有以下这些 Native 方法。

private native String getEnvironmentInfo();

private native String getEnvironmentInfoExtra();

private native String getExternalEquipmentInfo();

private native String getHWEquipmentInfo();

private native String getHWProperty();

private native String getHWStatus();

private native String getLocationInfo();

private native String getPlatformInfo();

private native String getUserAction();

public native String startCollection();

是 “安全信息与用户行为采集器” ,收集 硬件信息、检查 网络信息、获取 应用信息电池状态 等。

请在 Unidbg 中依次执行这十个函数,本篇的重点是巩固 Unidbg 补环境的形式,以及学习 Unidbg 补环境的内容。

三、初始化

首先搭一个基本架子

package com.test4;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.memory.Memory;

import java.io.File;

public class TEST4 extends AbstractJni {
    private final AndroidEmulator emulator;
    private final DvmClass SIUACollector;
    private final VM vm;

    public TEST4() {
        emulator = AndroidEmulatorBuilder
                .for32Bit()
                .addBackendFactory(new Unicorn2Factory(true))
                .setProcessName("com.dianping.v1")
                .build();
        Memory memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));
        vm = emulator.createDalvikVM(new File("E:\\AndroidTools\\unidbg\\unidbg-android\\src\\test\\java\\com\\test4\\dazhongdianping.apk"));
        vm.setJni(this);
        vm.setVerbose(true);
        DalvikModule dm = vm.loadLibrary("mtguard", true);
        SIUACollector = vm.resolveClass("com/meituan/android/common/mtguard/NBridge$SIUACollector");
        dm.callJNI_OnLoad(emulator);

    }
    
    public static void main(String[] args) {
        TEST4 test4 = new TEST4();
    }
    
}

内部类和外部类之间用过 $ 分割,这是 Java 中固定的表示法。

运行看到报错

[10:57:05 459]  INFO [com.github.unidbg.linux.AndroidElfLoader] (AndroidElfLoader:481) - libmtguard.so load dependency libandroid.so failed
[10:57:05 472]  INFO [com.github.unidbg.linux.AndroidElfLoader] (AndroidElfLoader:481) - libmtguard.so load dependency libjnigraphics.so failed
JNIEnv->FindClass(com/meituan/android/common/mtguard/NBridge) was called from RX@0x1200545d[libmtguard.so]0x545d
JNIEnv->RegisterNatives(com/meituan/android/common/mtguard/NBridge, RW@0x120d0004[libmtguard.so]0xd0004, 1) was called from RX@0x120054c7[libmtguard.so]0x54c7
RegisterNative(com/meituan/android/common/mtguard/NBridge, main(I[Ljava/lang/Object;)[Ljava/lang/Object;, RX@0x1205c589[libmtguard.so]0x5c589)
JNIEnv->FindClass(com/meituan/android/common/mtguard/NBridge$SIUACollector) was called from RX@0x12005591[libmtguard.so]0x5591
JNIEnv->RegisterNatives(com/meituan/android/common/mtguard/NBridge$SIUACollector, RW@0x122d205c, 10) was called from RX@0x120055c7[libmtguard.so]0x55c7
RegisterNative(com/meituan/android/common/mtguard/NBridge$SIUACollector, getHWProperty()Ljava/lang/String;, RX@0x1200a931[libmtguard.so]0xa931)
RegisterNative(com/meituan/android/common/mtguard/NBridge$SIUACollector, getEnvironmentInfoExtra()Ljava/lang/String;, RX@0x12007111[libmtguard.so]0x7111)
RegisterNative(com/meituan/android/common/mtguard/NBridge$SIUACollector, getEnvironmentInfo()Ljava/lang/String;, RX@0x12006df9[libmtguard.so]0x6df9)
RegisterNative(com/meituan/android/common/mtguard/NBridge$SIUACollector, getHWStatus()Ljava/lang/String;, RX@0x1201a86d[libmtguard.so]0x1a86d)
RegisterNative(com/meituan/android/common/mtguard/NBridge$SIUACollector, getHWEquipmentInfo()Ljava/lang/String;, RX@0x12028841[libmtguard.so]0x28841)
RegisterNative(com/meituan/android/common/mtguard/NBridge$SIUACollector, getExternalEquipmentInfo()Ljava/lang/String;, RX@0x120353f5[libmtguard.so]0x353f5)
RegisterNative(com/meituan/android/common/mtguard/NBridge$SIUACollector, getUserAction()Ljava/lang/String;, RX@0x12040159[libmtguard.so]0x40159)
RegisterNative(com/meituan/android/common/mtguard/NBridge$SIUACollector, getPlatformInfo()Ljava/lang/String;, RX@0x120464e1[libmtguard.so]0x464e1)
RegisterNative(com/meituan/android/common/mtguard/NBridge$SIUACollector, getLocationInfo()Ljava/lang/String;, RX@0x1204f909[libmtguard.so]0x4f909)
RegisterNative(com/meituan/android/common/mtguard/NBridge$SIUACollector, startCollection()Ljava/lang/String;, RX@0x1205ab41[libmtguard.so]0x5ab41)

加载 libandroid.so、libjnigraphics.so 失败,前面我们提过它俩的虚拟模块。

public TEST4() {
        emulator = AndroidEmulatorBuilder
                .for32Bit()
                .addBackendFactory(new Unicorn2Factory(true))
                .setProcessName("com.dianping.v1")
                .build();
        Memory memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));
        vm = emulator.createDalvikVM(new File("E:\\AndroidTools\\unidbg\\unidbg-android\\src\\test\\java\\com\\test4\\dazhongdianping.apk"));
        vm.setJni(this);
        vm.setVerbose(true);
        
        // 使用 libandroid.so 的虚拟模块
        new AndroidModule(emulator, vm).register(memory);
        // 使用 libjnigraphics.so 的虚拟模块
        new JniGraphics(emulator, vm).register(memory);
        
        DalvikModule dm = vm.loadLibrary("mtguard", true);
        SIUACollector = vm.resolveClass("com/meituan/android/common/mtguard/NBridge$SIUACollector");
        dm.callJNI_OnLoad(emulator);

    }

再次运行就不会报依赖库缺少的错误了,Unidbg 目前一共实现有以下几个虚拟模块。
image-20250810110108107

四、getEnvironmentInfo

这十个函数都是实例方法,为了免于重复 newObject,将 SIUACollector 处理为 DvmObject。同时,添加对文件访问的拦截处理,样本可能有文件操作。

package com.test4;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.virtualmodule.android.AndroidModule;
import com.github.unidbg.virtualmodule.android.JniGraphics;

import java.io.File;

public class TEST4 extends AbstractJni implements IOResolver {
    private final AndroidEmulator emulator;
    private final DvmObject<?> SIUACollector;
    private final VM vm;

    public TEST4() {
        emulator = AndroidEmulatorBuilder
                .for32Bit()
                .addBackendFactory(new Unicorn2Factory(true))
                .setProcessName("com.dianping.v1")
                .build();
        Memory memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));
        vm = emulator.createDalvikVM(new File("E:\\AndroidTools\\unidbg\\unidbg-android\\src\\test\\java\\com\\test4\\dazhongdianping.apk"));
        vm.setJni(this);
        vm.setVerbose(true);

        emulator.getSyscallHandler().addIOResolver(this);

        // 使用 libandroid.so 的虚拟模块
        new AndroidModule(emulator, vm).register(memory);
        // 使用 libjnigraphics.so 的虚拟模块
        new JniGraphics(emulator, vm).register(memory);

        DalvikModule dm = vm.loadLibrary("mtguard", true);
        SIUACollector = vm.resolveClass("com/meituan/android/common/mtguard/NBridge$SIUACollector").newObject(null);
        dm.callJNI_OnLoad(emulator);

    }

    @Override
    public FileResult resolve(Emulator emulator, String pathname, int oflags) {
        System.out.println("lilac open: " + pathname);
        return null;
    }

    public static void main(String[] args) {
        TEST4 test4 = new TEST4();
        System.out.println("[*] getEnvironmentInfo:" + test4.getEnvironmentInfo());
    }


}

发起对 getEnvironmentInfo 的调用

    public static void main(String[] args) {
        TEST4 test4 = new TEST4();
        System.out.println("[*] getEnvironmentInfo:" + test4.getEnvironmentInfo());
    }


    public String getEnvironmentInfo() {
        String result = SIUACollector.callJniMethodObject(emulator, "getEnvironmentInfo()Ljava/lang/String;").getValue().toString();
        return result;
    }

运行发现直接得出了结果,无需补环境。

JNIEnv->NewStringUTF("0|0|0|-|0|") was called from RX@0x12006f5f[libmtguard.so]0x6f5f
JNIEnv->NewStringUTF("0|0|0|-|0|") was called from RX@0x12006ef7[libmtguard.so]0x6ef7
[*] getEnvironmentInfo:0|0|0|-|0|

五、getEnvironmentInfoExtra

发起调用

    public static void main(String[] args) {
        TEST4 test4 = new TEST4();
        System.out.println("[*] getEnvironmentInfo:" + test4.getEnvironmentInfo());
        System.out.println("[*] getEvironmentInfoExtra:" + test4.getEvironmentInfoExtra());
    }


    public String getEnvironmentInfo() {
        String result = SIUACollector.callJniMethodObject(emulator, "getEnvironmentInfo()Ljava/lang/String;").getValue().toString();
        return result;
    }
    public String getEvironmentInfoExtra() {
        String result = SIUACollector.callJniMethodObject(emulator, "getEnvironmentInfoExtra()Ljava/lang/String;").getValue().toString();
        return result;
    }

运行发现报错,需要开始补环境了

image-20250810113618928

分配一个 StringBuilder 对象。对 allocObject 这个 JNI 调用感到陌生,上网查了下,它用于分配对象,我们可以在 AbstractJNI 中得到参考。

    @Override
    public DvmObject<?> allocObject(BaseVM vm, DvmClass dvmClass, String signature) {
        if ("java/util/HashMap->allocObject".equals(signature)) {
            return dvmClass.newObject(new HashMap<>());
        }

        throw new UnsupportedOperationException(signature);
    }

同理处理

    @Override
    public DvmObject<?> allocObject(BaseVM vm, DvmClass dvmClass, String signature) {
        switch (signature) {
            case "java/lang/StringBuilder->allocObject": {
                return dvmClass.newObject(new StringBuilder());
            }
        }
        return super.allocObject(vm, dvmClass, signature);
    }

但是按照前文所讨论的规范,StringBuilder 是 JDK 中的类库,使用 ProxyDvmObject.createObject 更好。

    @Override
    public DvmObject<?> allocObject(BaseVM vm, DvmClass dvmClass, String signature) {
        switch (signature) {
            case "java/lang/StringBuilder->allocObject": {
                return ProxyDvmObject.createObject(vm, new StringBuilder());
            }
        }
        return super.allocObject(vm, dvmClass, signature);
    }

继续运行,报错
image-20250810114335805

刚分配的 StringBuilder 对象,这里在做初始化,什么都不必处理。

    @Override
    public void callVoidMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature) {
            case "java/lang/StringBuilder-><init>()V": {
                return;
            }
        }
        super.callVoidMethodV(vm, dvmObject, signature, vaList);
    }

其实 Unidbg 在 allocObject 这个 JNI 方法上的处理逻辑不太好,这里还没体现出来,后面再说。

继续运行和报错

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->getEnvironmentInfo()Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

这个方法就是我们上一个补的 getEnvironmentInof,你可能觉得像下面这样处理就好了

@Override
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature){
        case "com/meituan/android/common/mtguard/NBridge$SIUACollector->getEnvironmentInfo()Ljava/lang/String;":{
            return new StringObject(vm, getEnvironmentInfo());
        }
    }
    return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

但事实上这是行不通的,Native 方法里调用另一个 Native 方法,这属于嵌套调用,Unidbg 暂不支持这么做,所以这里手动填入先前得到的结果,

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature) {
            case "com/meituan/android/common/mtguard/NBridge$SIUACollector->getEnvironmentInfo()Ljava/lang/String;": {
                return new StringObject(vm, "0|0|0|-|0|");
            }
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }

继续运行和报错

java.lang.UnsupportedOperationException: java/lang/StringBuilder->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:97)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

按照语义补 append 方法

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature) {
            case "com/meituan/android/common/mtguard/NBridge$SIUACollector->getEnvironmentInfo()Ljava/lang/String;": {
                return new StringObject(vm, "0|0|0|-|0|");
            }
            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));
            }
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }

继续运行和报错

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->isVPN()Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:101)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

看起来是用于检测 VPN,Jadx 中看看它的实现,样本采用了自家的热修复技术imgAndroid热更新方案 Robust,代码看起来不太舒服。

        private String isVPN() {
            Object[] objArr = new Object[0];
            ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
            return PatchProxy.isSupport(objArr, this, changeQuickRedirect2, false, "b6362e66d9b10061bc6c2e73cafc0cc8", RobustBitConfig.DEFAULT_VALUE) ? (String) PatchProxy.accessDispatch(objArr, this, changeQuickRedirect2, false, "b6362e66d9b10061bc6c2e73cafc0cc8") : EnvInfoWorker.isVPN();
        }

末尾的 EnvInfoWorker.isVPN() 是其实现函数。

public static synchronized String isVPN() {
    synchronized (EnvInfoWorker.class) {
        Object[] objArr = new Object[0];
        ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
        if (PatchProxy.isSupport(objArr, null, changeQuickRedirect2, true, "b5d91cd54a8036812d448963f1a976c9", RobustBitConfig.DEFAULT_VALUE)) {
            return (String) PatchProxy.accessDispatch(objArr, null, changeQuickRedirect2, true, "b5d91cd54a8036812d448963f1a976c9");
        }
        Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
        if (networkInterfaces == null) {
            return "0";
        }
        Iterator it = Collections.list(networkInterfaces).iterator();
        while (it.hasNext()) {
            NetworkInterface networkInterface = (NetworkInterface) it.next();
            if (networkInterface.isUp()) {
                if (networkInterface.getInterfaceAddresses().size() != 0 && "tun0".equals(networkInterface.getName())) {
                    return "1";
                }
            }
            if ("ppp0".equals(networkInterface.getName())) {
                return "1";
            }
        }
        return "0";
    }
}

问了下 chatGPT,当客户端运行 VPN 时,会创建 tun0ppp0 节点,所以出现带有明显特征的网络接口名称,就可以认定是使用了 VPN 协议进行通信。示例代码如下

private void isDeviceInVPN() {
    try {
        Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
        while (networkInterfaces.hasMoreElements()) {
            String name = networkInterfaces.nextElement().getName();
            if (name.equals("tun0") || name.equals("ppp0")) {
                stop();
            }
        }
    } catch (SocketException e) {
        e.printStackTrace();
    }
}

和样本此处进行对照,可以发现返回 0 是没检测到,返回 1 就是检测到了。

读者可能想图方便,使用 Frida 在真实设备上 Hook 或 Call 这个函数,直接获得结果。但这么做有弊端,如果你真机使用了 VPN,那么就会返回 1.

            case "com/meituan/android/common/mtguard/NBridge$SIUACollector->isVPN()Ljava/lang/String;": {
                return new StringObject(vm, "0");
            }

继续运行报错

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->mContext:Landroid/content/Context;
	at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:171)
	at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:141)
	at com.github.unidbg.linux.android.dvm.DvmField.getObjectField(DvmField.java:126)
	at com.github.unidbg.linux.android.dvm.DalvikVM$92.handle(DalvikVM.java:1408)

Context 是一个相当特殊的对象,前面遇到过它,这里我们先用最朴素的方法去处理它——resolveClass 占位。

    @Override
    public DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
        switch (signature) {
            case "com/meituan/android/common/mtguard/NBridge$SIUACollector->mContext:Landroid/content/Context;": {
                return vm.resolveClass("android/content/Context").newObject(null);
            }
        }
        return super.getObjectField(vm, dvmObject, signature);
    }

继续运行报错

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->brightness(Landroid/content/Context;)Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:104)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

回到 Jadx 中找到对应函数

        private String brightness(Context context) {
            Object[] objArr = {context};
            ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
            return PatchProxy.isSupport(objArr, this, changeQuickRedirect2, false, "e3c10305f195ff2aac2fd606a6235fb2", RobustBitConfig.DEFAULT_VALUE) ? (String) PatchProxy.accessDispatch(objArr, this, changeQuickRedirect2, false, "e3c10305f195ff2aac2fd606a6235fb2") : DeviceInfoWorker.brightness(context);
        }
    public static String brightness(Context context) {
        Object[] objArr = {context};
        ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
        if (PatchProxy.isSupport(objArr, null, changeQuickRedirect2, true, "af80a1c664fdf95866b7caabf24ab495", RobustBitConfig.DEFAULT_VALUE)) {
            return (String) PatchProxy.accessDispatch(objArr, null, changeQuickRedirect2, true, "af80a1c664fdf95866b7caabf24ab495");
        }
        try {
            return StringUtils.toString(Settings.System.getInt(context.getContentResolver(), "screen_brightness") / 255.0f);
        } catch (Throwable th) {
            c.a(th);
            return StringUtils.toString(0.0f);
        }
    }

从函数名和基本逻辑上看,似乎是获取屏幕亮度,Google 搜索 getContentResolver + screen_brightness

,找到如下代码

/**
 * 获取系统默认屏幕亮度值 屏幕亮度值范围(0-255)
 * **/
private int getScreenBrightness(Context context) {
    ContentResolver contentResolver = context.getContentResolver();
    int defVal = 0;
    return Settings.System.getInt(contentResolver,
            Settings.System.SCREEN_BRIGHTNESS, defVal);
}

样本中返回的是 0 - 1 之间的一个数,然后转字符串,我这里选择 0.8

            case "com/meituan/android/common/mtguard/NBridge$SIUACollector->brightness(Landroid/content/Context;)Ljava/lang/String;": {
                return new StringObject(vm, "0.8");
            }

继续运行

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->systemVolume(Landroid/content/Context;)Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:107)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

也还是一样回到 Jadx 中查看样本的伪代码,这里是获取音量

        private String systemVolume(Context context) {
            Object[] objArr = {context};
            ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
            return PatchProxy.isSupport(objArr, this, changeQuickRedirect2, false, "25bed8eb997e1fc4de8fea79d40481a3", RobustBitConfig.DEFAULT_VALUE) ? (String) PatchProxy.accessDispatch(objArr, this, changeQuickRedirect2, false, "25bed8eb997e1fc4de8fea79d40481a3") : DeviceInfoWorker.systemVolume(context);
        }
    public static String systemVolume(Context context) {
        Object[] objArr = {context};
        ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
        if (PatchProxy.isSupport(objArr, null, changeQuickRedirect2, true, "94db8653238995da6df94fea33fbbf79", RobustBitConfig.DEFAULT_VALUE)) {
            return (String) PatchProxy.accessDispatch(objArr, null, changeQuickRedirect2, true, "94db8653238995da6df94fea33fbbf79");
        }
        try {
            AudioManager audioManager = (AudioManager) context.getSystemService("audio");
            return StringUtils.toString((audioManager.getStreamVolume(1) * 100) / audioManager.getStreamMaxVolume(1));
        } catch (Throwable th) {
            c.a(th);
            return "";
        }
    }

直接返回 0,也就是静音

            case "com/meituan/android/common/mtguard/NBridge$SIUACollector->systemVolume(Landroid/content/Context;)Ljava/lang/String;": {
                return new StringObject(vm, "0");
            }

继续运行,报错

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->isAccessibilityEnable()Z
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:625)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:603)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callBooleanMethodA(DvmMethod.java:124)

这次是 isAccessibilityEnable ,也就是所谓的无障碍服务。无障碍服务的本意是帮助残疾人士、老人、小孩等,但长久以来被用于自动化控制,比如抢单,因此无障碍服务是风险检测、环境检测、反欺诈检测的重要一环。

    @Override
    public boolean callBooleanMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature) {
            case "com/meituan/android/common/mtguard/NBridge$SIUACollector->isAccessibilityEnable()Z": {
                return false;
            }
        }
        return super.callBooleanMethodV(vm, dvmObject, signature, vaList);
    }

继续运行报错

java.lang.UnsupportedOperationException: java/lang/String->valueOf(I)Ljava/lang/String;
	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.callStaticObjectMethodA(DvmMethod.java:64)

这个没什么问题,也是直接参照语义直接补就好了

    @Override
    public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature) {
            case "java/lang/String->valueOf(I)Ljava/lang/String;": {
                return new StringObject(vm, String.valueOf(vaList.getIntArg(0)));
            }
        }
        return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
    }

继续运行报错

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->uiAutomatorClickCount()I
	at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethodV(AbstractJni.java:563)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethodV(AbstractJni.java:529)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callIntMethodA(DvmMethod.java:134)

uiAutomatorClickCount 的具体实现如下

    public static int uiAutomatorClickCount() {
        Object[] objArr = new Object[0];
        ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
        if (PatchProxy.isSupport(objArr, null, changeQuickRedirect2, true, "7e55945fa3fd311f57ffc508c34fa364", RobustBitConfig.DEFAULT_VALUE)) {
            return ((Integer) PatchProxy.accessDispatch(objArr, null, changeQuickRedirect2, true, "7e55945fa3fd311f57ffc508c34fa364")).intValue();
        }
        if (verify()) {
            return mAdapter.uiAutomatorCount();
        }
        return 0;
    }

这个类的主要作用是返回 UI Automator 点击次数,可以Frida Hook 看一下

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

结果如下

uiAutomatorClickCount ret value is 0

那么就可以补环境了

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

继续运行报错

java.lang.UnsupportedOperationException: java/lang/StringBuilder->toString()Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:110)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

补环境

case "java/lang/StringBuilder->toString()Ljava/lang/String;":{
    return new StringObject(vm, dvmObject.getValue().toString());
}

运行,终于跑通了

[*] getEvironmentInfoExtra:0|0|0|-|0|0|0.8|0|0|0|

这个结果和上一个结果一样,有很多 0,看起来就像是收集信息,然后使用 | 作为分割并拼接起来。

六、getExtrarnalEquipmentInfo

新增对它的call

    public static void main(String[] args) {
        TEST4 test4 = new TEST4();
        System.out.println("[*] getEnvironmentInfo:" + test4.getEnvironmentInfo());
        System.out.println("[*] getEvironmentInfoExtra:" + test4.getEnvironmentInfoExtra());
        System.out.println("[*] getExtrernalEquipmentInfo:" + test4.getExternalEquipmentInfo());
    }


    public String getEnvironmentInfo() {
        String result = SIUACollector.callJniMethodObject(emulator, "getEnvironmentInfo()Ljava/lang/String;").getValue().toString();
        return result;
    }
    public String getEnvironmentInfoExtra() {
        String result = SIUACollector.callJniMethodObject(emulator, "getEnvironmentInfoExtra()Ljava/lang/String;").getValue().toString();
        return result;
    }

    public String getExternalEquipmentInfo() {
        String result = SIUACollector.callJniMethodObject(emulator, "getExternalEquipmentInfo()Ljava/lang/String;").getValue().toString();
        return result;
    }

运行,报错

java.lang.UnsupportedOperationException: android/content/Context->getApplicationContext()Landroid/content/Context;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:210)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

简单占位

case "android/content/Context->getApplicationContext()Landroid/content/Context;":{
    return vm.resolveClass("android/content/Context").newObject(null);
}

运行,继续报错

java.lang.UnsupportedOperationException: android/content/Context->getSystemService(Ljava/lang/String;)Ljava/lang/Object;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:210)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

系统服务是 Android 所提供的一项重要服务,在 JNI 补环境中出现频率相当高,Unidbg 对它做了专门处理,可以参考 AbstractJni,其中有如下片段

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

抄一下

case "android/content/Context->getSystemService(Ljava/lang/String;)Ljava/lang/Object;":{
    StringObject serviceName = vaList.getObjectArg(0);
    assert serviceName != null;
    return new SystemService(vm, serviceName.getValue());
}

继续运行,报错

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->isPermissionGranted(Ljava/lang/String;Landroid/content/Context;)Z
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:625)
	at com.test4.TEST4.callBooleanMethodV(TEST4.java:230)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:603)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callBooleanMethodA(DvmMethod.java:124)

isPermissionGranted 具体什么作用?根据函数名、函数实现可以看出,就是大致用来检测 App 是否有某项权限。

        private boolean isPermissionGranted(String str, Context context) {
            Object[] objArr = {str, context};
            ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
            return PatchProxy.isSupport(objArr, this, changeQuickRedirect2, false, "a0fa204a6b6694d5ab23ad161583721d", RobustBitConfig.DEFAULT_VALUE) ? ((Boolean) PatchProxy.accessDispatch(objArr, this, changeQuickRedirect2, false, "a0fa204a6b6694d5ab23ad161583721d")).booleanValue() : Permissions.isPermissionGranted(str, context);
        }
    public static boolean isPermissionGranted(String str, Context context) {
        Object[] objArr = {str, context};
        ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
        if (PatchProxy.isSupport(objArr, null, changeQuickRedirect2, true, "44db3326d9c0bbe25993cff8771e9a68", RobustBitConfig.DEFAULT_VALUE)) {
            return ((Boolean) PatchProxy.accessDispatch(objArr, null, changeQuickRedirect2, true, "44db3326d9c0bbe25993cff8771e9a68")).booleanValue();
        }
        try {
            return Build.VERSION.SDK_INT >= 23 ? context.getApplicationContext().getApplicationInfo().targetSdkVersion >= 23 ? context.checkSelfPermission(str) == 0 : PermissionChecker.a(context, str) == 0 : PermissionChecker.a(context, str) == 0;
        } catch (Throwable th) {
            c.a(th);
            DFPLog.error(th);
            return false;
        }
    }

打印一下当前调用的参数。

case "com/meituan/android/common/mtguard/NBridge$SIUACollector->isPermissionGranted(Ljava/lang/String;Landroid/content/Context;)Z":{
    String permissionName = vaList.getObjectArg(0).getValue().toString();
    System.out.println("[!] check permission:"+permissionName);
}

运行查看输出

[!] check permission: android.permission.READ_PHONE_STATE

这个权限很明显了,就是 “允许访问电话状态权限”,直接返回 True

case "com/meituan/android/common/mtguard/NBridge$SIUACollector->isPermissionGranted(Ljava/lang/String;Landroid/content/Context;)Z":{
    String permissionName = vaList.getObjectArg(0).getValue().toString();
    System.out.println("check permission:"+permissionName);
    if(permissionName.equals("android.permission.READ_PHONE_STATE")){
        return true;
    }else {
        throw new UnsupportedOperationException(signature);
    }
}

继续运行

java.lang.UnsupportedOperationException: android/os/Build$VERSION->SDK_INT:I
	at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticIntField(AbstractJni.java:136)
	at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticIntField(AbstractJni.java:128)
	at com.github.unidbg.linux.android.dvm.DvmField.getStaticIntField(DvmField.java:121)
	at com.github.unidbg.linux.android.dvm.DalvikVM$147.handle(DalvikVM.java:2358)

这里是在获取 SDK 的版本,直接返回目前常见的 Android 15,即 35

    @Override
    public int getStaticIntField(BaseVM vm, DvmClass dvmClass, String signature) {
        switch (signature) {
            case "android/os/Build$VERSION->SDK_INT:I": {
                return 35;
            }
        }
        return super.getStaticIntField(vm, dvmClass, signature);
    }

继续运行

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->checkBuildAttribute(Ljava/lang/String;)Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:208)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

先打印一下参数,看看是啥

case "com/meituan/android/common/mtguard/NBridge$SIUACollector->checkBuildAttribute(Ljava/lang/String;)Ljava/lang/String;": {
                String arg = vaList.getObjectArg(0).getValue().toString();
                System.out.println("[!] checkBuildAttribute: " + arg);
            }

运行,但是这个参数不知道是啥

[!] checkBuildAttribute: -

所以还是 Jadx 看看伪代码吧

        private String checkBuildAttribute(String str) {
            Object[] objArr = {str};
            ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
            return PatchProxy.isSupport(objArr, this, changeQuickRedirect2, false, "3b097946787cf593049f918e4d2f8a4e", RobustBitConfig.DEFAULT_VALUE) ? (String) PatchProxy.accessDispatch(objArr, this, changeQuickRedirect2, false, "3b097946787cf593049f918e4d2f8a4e") : (TextUtils.isEmpty(str) || str.equalsIgnoreCase("unknown")) ? CommonConstant.Symbol.MINUS : str;
        }

前面就是常规的热修复判断,后面就是做简单的字符串校验,若字符串为空或是 "unknown",返回 "-";否则返回字符串本身。

补环境

case "com/meituan/android/common/mtguard/NBridge$SIUACollector->checkBuildAttribute(Ljava/lang/String;)Ljava/lang/String;":{
    String arg = vaList.getObjectArg(0).getValue().toString();
    System.out.println("checkBuildAttribute:"+arg);
    return new StringObject(vm, arg.isEmpty() || arg.equalsIgnoreCase("unknown") ? "-" : arg);
}

继续运行

java.lang.UnsupportedOperationException: android/view/WindowManager->getDefaultDisplay()Landroid/view/Display;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:213)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

这是一个 Android 对象,按照前面所说的补环境原则直接处理。

case "android/view/WindowManager->getDefaultDisplay()Landroid/view/Display;":{
    return vm.resolveClass("android/view/Display").newObject(null);
}

继续运行

java.lang.UnsupportedOperationException: android/view/Display->getHeight()I
	at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethodV(AbstractJni.java:563)
	at com.test4.TEST4.callIntMethodV(TEST4.java:273)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethodV(AbstractJni.java:529)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callIntMethodA(DvmMethod.java:134)

又是 Android FrameWork 层的 API,拷打了下 chatGPT,它和 getWidth

一起用于获取屏幕的宽高。对于这类基本设备信息,完全可以通过 ADB 获取。

└─Δ adb shell wm size
Physical size: 1080x2400

补环境

case "android/view/Display->getHeight()I":{
    return 2400;
}

继续运行

java.lang.UnsupportedOperationException: java/lang/StringBuilder->append(I)Ljava/lang/StringBuilder;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:416)
	at com.dianping.NBridge.callObjectMethodV(NBridge.java:139)

补环境

            case "java/lang/StringBuilder->append(I)Ljava/lang/StringBuilder;": {
                int value = vaList.getIntArg(0); // 直接取 int
                ((StringBuilder) dvmObject.getValue()).append(value); // 自动转成字符串拼接
                return dvmObject; // append 返回 this
            }

继续补环境

case "java/lang/StringBuilder->append(C)Ljava/lang/StringBuilder;":{
    return ProxyDvmObject.createObject(vm, ((StringBuilder) dvmObject.getValue()).append((char)vaList.getIntArg(0)));
}

遇到新报错,刚刚补过高,现在补的是宽

java.lang.UnsupportedOperationException: android/view/Display->getWidth()I
	at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethodV(AbstractJni.java:563)
	at com.test4.TEST4.callIntMethodV(TEST4.java:279)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethodV(AbstractJni.java:529)

依旧是根据上面 adb 获取的值进行补

case "android/view/Display->getWidth()I":{
    return 1080;
}

运行报错

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->getTotalInternalMemorySize()Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:219)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

getTotalInternalMemorySize 这个函数名就很清晰直白,再看看代码逻辑验证一下

        private String getTotalInternalMemorySize() {
            Object[] objArr = new Object[0];
            ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
            if (PatchProxy.isSupport(objArr, this, changeQuickRedirect2, false, "838e88ccfd19d138e498f2d967b7d987", RobustBitConfig.DEFAULT_VALUE)) {
                return (String) PatchProxy.accessDispatch(objArr, this, changeQuickRedirect2, false, "838e88ccfd19d138e498f2d967b7d987");
            }
            StatFs statFs = new StatFs(Environment.getDataDirectory().getPath());
            return fileSize(statFs.getBlockCount() * statFs.getBlockSize());
        }

这个逻辑就很简单,Environment.getDataDirectory() 就是 data 文件夹,它是存储的根目录。StatFs 底层对应于 stat 结构体

struct stat {
    dev_t     st_dev;     /* ID of device containing file */
    ino_t     st_ino;     /* inode number */
    mode_t    st_mode;    /* protection */
    nlink_t   st_nlink;   /* number of hard links */
    uid_t     st_uid;     /* user ID of owner */
    gid_t     st_gid;     /* group ID of owner */
    dev_t     st_rdev;    /* device ID (if special file) */
    off_t     st_size;    /* total size, in bytes */
    blksize_t st_blksize; /* blocksize for file system I/O */
    blkcnt_t  st_blocks;  /* number of 512B blocks allocated */
    time_t    st_atime;   /* time of last access */
    time_t    st_mtime;   /* time of last modification */
    time_t    st_ctime;   /* time of last status change */
};

这里是 st_blksize * st_blocks,返回 data 文件夹的实际大小,单位是 Bit。在 Native 代码实现也经常是这个逻辑。最后看 fileSize 函数的实现,它将很大的比特值转为更为直观和合适的 MBGB

        private String fileSize(long j) {
            Object[] objArr = {new Long(j)};
            ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
            if (PatchProxy.isSupport(objArr, this, changeQuickRedirect2, false, "6a69a740a4e2f5966b7d134666227a92", RobustBitConfig.DEFAULT_VALUE)) {
                return (String) PatchProxy.accessDispatch(objArr, this, changeQuickRedirect2, false, "6a69a740a4e2f5966b7d134666227a92");
            }
            StringBuilder sb = new StringBuilder();
            String str = "";
            if (j >= 1024) {
                str = "KB";
                j /= 1024;
                if (j >= 1024) {
                    str = "MB";
                    j /= 1024;
                    if (j >= 1024) {
                        str = "GB";
                        j /= 1024;
                    }
                }
            }
            sb.append(j);
            sb.append(str);
            return sb.toString();
        }

这里直接通过 Frida Call 获取这个值。

function callSIUACollector() {
  setTimeout(function () {
    Java.perform(function () {
      const ActivityThread = Java.use("android.app.ActivityThread");
      let context = ActivityThread.currentApplication().getApplicationContext();
      const ContextClass = Java.use("android.content.Context");
      const ctx = Java.cast(context, ContextClass);

      let SIUACollector = Java.use(
        "com.meituan.android.common.mtguard.NBridge$SIUACollector"
      );
      // 传context创建实例
      let instance = SIUACollector.$new(ctx);

      let result = instance.getTotalInternalMemorySize();
      console.log("ret: " + result);
    });
  }, 3000);
}
callSIUACollector();

我的测试机跑下来是 226GB

case "com/meituan/android/common/mtguard/NBridge$SIUACollector->getTotalInternalMemorySize()Ljava/lang/String;":{
    return new StringObject(vm, "226GB");
}

运行,报错

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->getTotalExternalMemorySize()Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:222)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

那么看看 getTotalExternalMemorySize 具体实现


        private String getTotalExternalMemorySize() {
            Object[] objArr = new Object[0];
            ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
            if (PatchProxy.isSupport(objArr, this, changeQuickRedirect2, false, "522b1633753f28d5eb161a6c3ff3faa1", RobustBitConfig.DEFAULT_VALUE)) {
                return (String) PatchProxy.accessDispatch(objArr, this, changeQuickRedirect2, false, "522b1633753f28d5eb161a6c3ff3faa1");
            }
            try {
                if (!Environment.getExternalStorageState().equals("mounted")) {
                    return "";
                }
                StatFs statFs = new StatFs(Environment.getExternalStorageDirectory().getPath());
                return fileSize(statFs.getBlockCount() * statFs.getBlockSize());
            } catch (Exception e) {
                c.a(e);
                MTGuardLog.error(e);
                return "";
            }
        }

这段代码实现了 获取外部存储总容量 的功能,Environment.getExternalStorageDirectory().getPath() 获取外部存储的路径,同样的采取 Frida call 一下

function callSIUACollector() {
  setTimeout(function () {
    Java.perform(function () {
      const ActivityThread = Java.use("android.app.ActivityThread");
      let context = ActivityThread.currentApplication().getApplicationContext();
      const ContextClass = Java.use("android.content.Context");
      const ctx = Java.cast(context, ContextClass);

      let SIUACollector = Java.use(
        "com.meituan.android.common.mtguard.NBridge$SIUACollector"
      );
      // 传context创建实例
      let instance = SIUACollector.$new(ctx);

      let result = instance.getTotalExternalMemorySize();
      console.log("ret: " + result);
    });
  }, 3000);
}
callSIUACollector();

结果如下

ret: 226GB

同样是 226GB

case "com/meituan/android/common/mtguard/NBridge$SIUACollector->getTotalExternalMemorySize()Ljava/lang/String;":{
    return new StringObject(vm, "226GB");
}

继续运行

[!] check permission: android.permission.ACCESS_WIFI_STATE
[11:18:19 847]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=-452987096, svcNumber=0x123, PC=unidbg@0xfffe02c4, LR=RX@0x1203d9a3[libmtguard.so]0x3d9a3, syscall=null
java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->isPermissionGranted(Ljava/lang/String;Landroid/content/Context;)Z
	at com.test4.TEST4.callBooleanMethodV(TEST4.java:251)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:603)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callBooleanMethodA(DvmMethod.java:124)

这是在确认是否有访问 WiFi 状态的权限,直接选择返回 True。

case "com/meituan/android/common/mtguard/NBridge$SIUACollector->isPermissionGranted(Ljava/lang/String;Landroid/content/Context;)Z":{
    String permissionName = vaList.getObjectArg(0).getValue().toString();
    System.out.println("check permission:"+permissionName);
    if(permissionName.equals("android.permission.READ_PHONE_STATE")){
        return true;
    }else if(permissionName.equals("android.permission.ACCESS_WIFI_STATE")){
        return true;
    }
    else {
        throw new UnsupportedOperationException(signature);
    }
}

继续运行

java.lang.UnsupportedOperationException: android/text/TextUtils->isEmpty(Ljava/lang/CharSequence;)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:184)

这是 Android FrameWork 中处理字符串的工具类,这种工具函数往往不会很复杂,在 AOSP 中找一下它的实现。(但是目前 Google 官方好像关闭了它的访问)

image-20250813112300853

找到具体实现

public static boolean isEmpty(CharSequence str) {
    return str == null || str.length() == 0;
}

复写一下

@Override
public boolean callStaticBooleanMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
    switch (signature){
        case "android/text/TextUtils->isEmpty(Ljava/lang/CharSequence;)Z":{
            String str = vaList.getObjectArg(0).getValue().toString();
            return str == null || str.length() == 0;
        }
    }
    return super.callStaticBooleanMethodV(vm, dvmClass, signature, vaList);
}

继续运行,报错

java.lang.UnsupportedOperationException: android/telephony/TelephonyManager->getSimOperator()Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:225)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

又是 Android FrameWork 里的函数,Google 找篇文章看下,比如这篇,得知这是在获取运行商相关的信息。

同样可以通过 ADB 获取得到相关信息

└─Δ adb shell
ares:/ $ getprop | grep sim
[gsm.sim.num.simlock]: []
[gsm.sim.preiccid_0]: []
[gsm.sim.preiccid_1]: []
[gsm.sim.state]: [ABSENT,ABSENT]
[gsm.sim1.num.simlock]: []
[gsm.sim1.type]: []
[gsm.sim2.type]: []
[persist.log.tag.IsimFileHandler]: [I]
[persist.log.tag.IsimRecords]: [I]
[persist.log.tag.MtkCsimFH]: [I]
[persist.log.tag.MtkIsimFH]: [I]
[persist.log.tag.MtkUsimFH]: [I]
[persist.log.tag.VsimAdaptor]: [I]
[persist.radio.multisim.config]: [dsds]
[persist.vendor.radio.msimmode]: [dsds]
[persist.vendor.radio.sim.mode]: [3]
[persist.vendor.radio.simswitch]: [1]
[ril.csim.mlpl_mspl_ver0]: [,]
[ril.csim.mlpl_mspl_ver1]: [,]
[ro.mtk_perf_simple_start_win]: [1]
[ro.vendor.mtk_external_sim_only_slots]: [0]
[ro.vendor.mtk_external_sim_support]: [1]
[ro.vendor.mtk_sim_card_onoff]: [3]
[ro.vendor.radio.max.multisim]: [dsds]
[ro.vendor.sim_me_lock_mode]: [3]
[vendor.gsm.external.sim.connected]: [0]
[vendor.gsm.external.sim.timeout]: [13,13]
[vendor.gsm.modem.vsim.capability]: [2,2]
[vendor.gsm.sim.extended.format1]: [0]
[vendor.gsm.sim.extended.format2]: [0]
[vendor.gsm.sim.retry.pin1]: []
[vendor.gsm.sim.retry.pin1.2]: []
[vendor.gsm.sim.retry.pin2]: []
[vendor.gsm.sim.retry.pin2.2]: []
[vendor.gsm.sim.retry.puk1]: []
[vendor.gsm.sim.retry.puk1.2]: []
[vendor.gsm.sim.retry.puk2]: []
[vendor.gsm.sim.retry.puk2.2]: []
[vendor.gsm.sim.slot.lock.card.valid]: [2]
[vendor.gsm.sim.slot.lock.card.valid.2]: [2]
[vendor.gsm.sim.slot.lock.policy]: [3]
[vendor.gsm.sim.slot.lock.service.capability]: [4]
[vendor.gsm.sim.slot.lock.service.capability.2]: [4]
[vendor.gsm.sim.slot.lock.state]: [0]
[vendor.ril.sim.onoff.support]: [1]
[vendor.ril.sim.uicc.applications.enable.state]: [0]
[vendor.ril.sim.uicc.applications.enable.state.2]: [0]
[vendor.ril.simswitch.no_reset_support]: [1]
[vendor.ril.simswitch.tpluswsupport]: [1]

但是我的测试机上没插卡,也看不太懂,直接试一下返回一个空字符串,不行再伪造一个。

而如果测试机是有卡的话,是可以采集到值的,返回的值是由 MCC + MNC 组成。

MCC由国际电信联盟 ITU 在全世界范围内统一分配和管理,唯一识别移动用户所属的国家,共3位,中国为460。

MNC用于识别移动用户所归属的移动通信网,2~3位。中国移动系统使用 00、02、04、07,中国联通系统使用 01、06、09,中国电信使用 03、05、11,中国铁通系统使用 20 。

case "android/telephony/TelephonyManager->getSimOperator()Ljava/lang/String;":{
    return new StringObject(vm, "");
}

继续运行,发现还真过了,然后报错

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->getAccessSubType()Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:226)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)
	at com.github.unidbg.linux.android.dvm.DalvikVM$33.handle(DalvikVM.java:578)

Jadx 中看看伪代码实现


        @SuppressLint({"MissingPermission"})
        private String getAccessSubType() {
            Object[] objArr = new Object[0];
            ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
            if (PatchProxy.isSupport(objArr, this, changeQuickRedirect2, false, "4e473c1fae47c0bf65e6f819cd3eaf7c", RobustBitConfig.DEFAULT_VALUE)) {
                return (String) PatchProxy.accessDispatch(objArr, this, changeQuickRedirect2, false, "4e473c1fae47c0bf65e6f819cd3eaf7c");
            }
            Context context = this.mContext;
            if (context == null) {
                return StringUtil.NULL;
            }
            if (!isPermissionGranted("android.permission.ACCESS_NETWORK_STATE", context)) {
                return CommonConstant.Symbol.MINUS;
            }
            ConnectivityManager connectivityManager = (ConnectivityManager) this.mContext.getApplicationContext().getSystemService("connectivity");
            NetworkInfo activeNetworkInfo = connectivityManager != null ? connectivityManager.getActiveNetworkInfo() : null;
            if (activeNetworkInfo == null) {
                return StringUtil.NULL;
            }
            if (activeNetworkInfo.getType() == 1) {
                return "wifi";
            }
            if (activeNetworkInfo.getType() != 0) {
                return "";
            }
            int subtype = activeNetworkInfo.getSubtype();
            return (subtype == 4 || subtype == 1 || subtype == 2) ? "2G" : (subtype == 3 || subtype == 8 || subtype == 6 || subtype == 5 || subtype == 12) ? "3G" : subtype == 13 ? "4G" : "";
        }

看起来是在获取网络类型

//检测当前的网络模式,可以是:wifi/2G/3G/4G
case "com/meituan/android/common/mtguard/NBridge$SIUACollector->getAccessSubType()Ljava/lang/String;":{
    return new StringObject(vm, "wifi");
}

继续运行,得到结果

[*] getExternalEquipmentInfo:-|-|-|2400421080|226GB|226GB|AQmac|-|wifi|

七、getHWEquipmentInfo

首先先初始化发起调用看看报什么错

public String getHWEquipmentInfo(){
    String result = SIUACollector.callJniMethodObject(emulator, "getHWEquipmentInfo()Ljava/lang/String;").getValue().toString();
    return result;
}

报错

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->getCpuInfoType()Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:140)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)

Jadx 中查看对应方法的伪代码

private String getCpuInfoType(){
       Closeable uCloseable;
       Throwable throwable;
       String str1;
       int i = 0;
       Object[] objArray = new Object[i];
       ChangeQuickRedirect changeQuickR = NBridge$SIUACollector.changeQuickRedirect;
       if (PatchProxy.isSupport(objArray, this, changeQuickR, false, "e8c17f92755628eaeec9e7e53ee668d7", 0x4000000000000000)) {
          return PatchProxy.accessDispatch(objArray, this, changeQuickR, i, "e8c17f92755628eaeec9e7e53ee668d7");
       }
       String str = "";
       int i1 = 0;
       try{
          super(new InputStreamReader(new FileInputStream("/proc/cpuinfo")));
          try{
             while ((str1 = this.readLine()) != null) {
                if (str1.startsWith("Processor")) {
                   str = "arm";
                   break ;
                }else if(str1.startsWith("model name")){
                   str = "x86";
                   break ;
                }
             }
             this.close();
          label_005e :
             this.safeClose(uCloseable);
             return str;
          }catch(java.lang.Exception e1){
          }
          c.a(throwable);
          MTGuardLog.error(throwable);
          goto label_005e ;
       }catch(java.lang.Exception e2){
          uCloseable = i1;
          throwable = e2;
       }
    }

根据方法名和代码逻辑可知,该方法就是读取 /proc/cpuinfo 文件,根据内容判断 CPU 架构类型(arm/x86),继续补环境

// CPU 架构
case "com/meituan/android/common/mtguard/NBridge$SIUACollector->getCpuInfoType()Ljava/lang/String;":{
    return new StringObject(vm, "arm");
}

运行,报错

java.lang.UnsupportedOperationException: java/io/BufferedReader->allocObject
	at com.github.unidbg.linux.android.dvm.AbstractJni.allocObject(AbstractJni.java:812)
	at com.test4.TEST4.allocObject(TEST4.java:93)
	at com.github.unidbg.linux.android.dvm.DvmClass.allocObject(DvmClass.java:74)
	at com.github.unidbg.linux.android.dvm.DalvikVM$24.handle(DalvikVM.java:365)

前面也遇到过一个 allocObject,你可能想如法炮制

case "java/io/BufferedReader->allocObject":{
    return ProxyDvmObject.createObject(vm, new BufferedReader());
}

但最好不要这么做了,这是有隐患的处理方法。BufferedReader 没有无参构造函数,它所需要的初始化参数在 allocObject 阶段并没有展露出来,在后续真正初始化的时候才有值。

这里直接用空的 dvmObject 占位,这里的 dvmClass 等价于 vm.resolveClass(“java/io/BufferedReader”)

    @Override
    public DvmObject<?> allocObject(BaseVM vm, DvmClass dvmClass, String signature) {
        switch (signature) {
            case "java/lang/StringBuilder->allocObject": {
                return ProxyDvmObject.createObject(vm, new StringBuilder());
            }
            case "java/io/BufferedReader->allocObject": {
                return dvmClass.newObject(null);
            }
        }
        return super.allocObject(vm, dvmClass, signature);
    }

继续运行

java.lang.UnsupportedOperationException: java/io/InputStreamReader->allocObject
	at com.github.unidbg.linux.android.dvm.AbstractJni.allocObject(AbstractJni.java:812)
	at com.test4.TEST4.allocObject(TEST4.java:96)
	at com.github.unidbg.linux.android.dvm.DvmClass.allocObject(DvmClass.java:74)

是 InputStreamReader,同样占位处理

case "java/io/InputStreamReader->allocObject":{
    return dvmClass.newObject(signature);
}

运行报错

java.lang.UnsupportedOperationException: java/io/FileInputStream->allocObject
	at com.github.unidbg.linux.android.dvm.AbstractJni.allocObject(AbstractJni.java:812)
	at com.test4.TEST4.allocObject(TEST4.java:102)
	at com.github.unidbg.linux.android.dvm.DvmClass.allocObject(DvmClass.java:74)

FileInputStream,同理

case "java/io/FileInputStream->allocObject":{
    return dvmClass.newObject(signature);
}

继续运行

java.lang.UnsupportedOperationException: java/io/FileInputStream-><init>(Ljava/lang/String;)V
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:1007)
	at com.test4.TEST4.callVoidMethodV(TEST4.java:115)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:990)

终于到了调用其 init 函数做初始化的逻辑,因为 callVoid 系列 API 没有返回值,它的操作基于原先的占位 dvmObject,因为我们没法对它重新复制,所以我声明一个全局变量处理它以及后面的逻辑,应该说挺麻烦的。

public class TEST4 extends AbstractJni implements IOResolver {
    private final AndroidEmulator emulator;
    private final DvmObject<?> SIUACollector;
    private final VM vm;
    public FileInputStream fileInputStream;

然后补具体逻辑

case "java/io/FileInputStream-><init>(Ljava/lang/String;)V":{
    String name = vaList.getObjectArg(0).getValue().toString();
    System.out.println("FileInputStream:"+name);
    try {
        fileInputStream = new FileInputStream(name);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
    return;
}

运行发现有问题

FileInputStream: /proc/cpuinfo
JNIEnv->CallVoidMethodA(java.io.FileInputStream@2ddc8ecb, <init>("/proc/cpuinfo")) was called from RX@0x12030391[libmtguard.so]0x30391
JNIEnv->GetMethodID(java/io/InputStreamReader.<init>(Ljava/io/InputStream;)V) => 0x692aa2f2 was called from RX@0x12006369[libmtguard.so]0x6369
[14:33:20 104]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=-452987096, svcNumber=0x13b, PC=unidbg@0xfffe0444, LR=RX@0x12029ed7[libmtguard.so]0x29ed7, syscall=null
java.lang.UnsupportedOperationException: java/io/InputStreamReader-><init>(Ljava/io/InputStream;)V
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:1007)
	at com.test4.TEST4.callVoidMethodV(TEST4.java:128)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:990)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callVoidMethodA(DvmMethod.java:234)
	at com.github.unidbg.linux.android.dvm.DalvikVM$60.handle(DalvikVM.java:1076)
java.io.FileNotFoundException: \proc\cpuinfo (系统找不到指定的路径。)
	at java.base/java.io.FileInputStream.open0(Native Method)
	at java.base/java.io.FileInputStream.open(FileInputStream.java:219)

程序在访问 /proc/cpuinfo 文件,首先将 /proc/cpuinfo 文件拖到本地

└─Δ adb pull /proc/cpuinfo xxx\unidbg\unidbg-android\src\test\java\com\test4
* daemon not running; starting now at tcp:5037
* daemon started successfully
/proc/cpuinfo: 1 file pulled, 0 skipped. 0.2 MB/s (1919 bytes in 0.011s)

然后做路径重定位

case "java/io/FileInputStream-><init>(Ljava/lang/String;)V":{
    String name = vaList.getObjectArg(0).getValue().toString();
    if(name.equals("/proc/cpuinfo")){
        name = "unidbg-android/src/test/java/com/test4/cpuinfo";
    }
    System.out.println("FileInputStream:"+name);
    try {
        fileInputStream = new FileInputStream(name);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
    return;
}

继续运行

java.lang.UnsupportedOperationException: java/io/InputStreamReader-><init>(Ljava/io/InputStream;)V
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:1007)
	at com.test4.TEST4.callVoidMethodV(TEST4.java:131)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:990)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callVoidMethodA(DvmMethod.java:234)

同理处理为全局变量

public class TEST4 extends AbstractJni implements IOResolver {
    private final AndroidEmulator emulator;
    private final DvmObject<?> SIUACollector;
    private final VM vm;
    public FileInputStream fileInputStream;
    public InputStreamReader inputStreamReader;

补方法逻辑

case "java/io/InputStreamReader-><init>(Ljava/io/InputStream;)V":{
    inputStreamReader = new InputStreamReader(fileInputStream);
    return;
}

这个 InputStream 参数就是我们之前定义的全局变量 InputStream,所以拿过来用。

继续运行

java.lang.UnsupportedOperationException: java/io/BufferedReader-><init>(Ljava/io/Reader;)V
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:1007)
	at com.test4.TEST4.callVoidMethodV(TEST4.java:137)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:990)

这三个其实就是一连套的…

public class TEST4 extends AbstractJni implements IOResolver {
    private final AndroidEmulator emulator;
    private final DvmObject<?> SIUACollector;
    private final VM vm;
    public FileInputStream fileInputStream;
    public InputStreamReader inputStreamReader;
    public BufferedReader bufferedReader;

处理

case "java/io/BufferedReader-><init>(Ljava/io/Reader;)V":{
    bufferedReader = new BufferedReader(inputStreamReader);
    return;
}

继续运行

java.lang.UnsupportedOperationException: java/io/BufferedReader->readLine()Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:180)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)

似乎回归到了我们更熟悉的逻辑里了

case "java/io/BufferedReader->readLine()Ljava/lang/String;":{
    String oneline;
    try {
        oneline = bufferedReader.readLine();
        if(oneline != null){
            return new StringObject(vm, oneline);
        }else {
            return null;
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

继续运行

java.lang.UnsupportedOperationException: java/lang/String->compareToIgnoreCase(Ljava/lang/String;)I
	at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethodV(AbstractJni.java:563)
	at com.test4.TEST4.callIntMethodV(TEST4.java:234)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethodV(AbstractJni.java:529)

JDK 方法,正常补即可

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

继续运行

java.lang.UnsupportedOperationException: java/lang/String->lastIndexOf(I)I
	at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethodV(AbstractJni.java:563)
	at com.test4.TEST4.callIntMethodV(TEST4.java:238)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethodV(AbstractJni.java:529)

依旧是 JDK 方法

case "java/lang/String->lastIndexOf(I)I":{
    return dvmObject.getValue().toString().lastIndexOf(vaList.getIntArg(0));
}

继续运行和补环境

java.lang.UnsupportedOperationException: java/lang/String->substring(I)Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:194)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)

case "java/lang/String->substring(I)Ljava/lang/String;":{
    return new StringObject(vm, dvmObject.getValue().toString().substring(vaList.getIntArg(0)));
}

报错

java.lang.UnsupportedOperationException: java/lang/StringBuilder->append(I)Ljava/lang/StringBuilder;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:197)

补环境

            case "java/lang/StringBuilder->append(I)Ljava/lang/StringBuilder;": {
                int value = vaList.getIntArg(0); // 直接取 int
                ((StringBuilder) dvmObject.getValue()).append(value); // 自动转成字符串拼接
                return dvmObject; // append 返回 this
            }

继续运行报错

java.lang.UnsupportedOperationException: java/io/BufferedReader->close()V
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:1007)
	at com.test4.TEST4.callVoidMethodV(TEST4.java:139)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:990)

补环境

case "java/io/BufferedReader->close()V":{
    try {
        bufferedReader.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return;
}

继续运行

java.lang.UnsupportedOperationException: android/hardware/SensorManager->getDefaultSensor(I)Landroid/hardware/Sensor;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:229)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

传感器一向是收集信息的重点,处理它需要更多的耐心。

首先是这个 API 的原型

Sensor getDefaultSensor(int type)

它会从传感器服务获取指定类型的传感器,有相当多的类型,详见 Android 源码

public static final int TYPE_ACCELEROMETER = 1;
public static final int TYPE_ORIENTATION = 3;
public static final int TYPE_POSE_6DOF = 28;
public static final int TYPE_PRESSURE = 6;
public static final int TYPE_PROXIMITY = 8;
public static final int TYPE_RELATIVE_HUMIDITY = 12;
public static final int TYPE_ROTATION_VECTOR = 11;
public static final int TYPE_SIGNIFICANT_MOTION = 17;
public static final int TYPE_STATIONARY_DETECT = 29;
public static final int TYPE_STEP_COUNTER = 19;
public static final int TYPE_STEP_DETECTOR = 18;
public static final int TYPE_GRAVITY = 9;

打印参数,看看样本在获取哪类传感器

case "android/hardware/SensorManager->getDefaultSensor(I)Landroid/hardware/Sensor;":{
    int type = vaList.getIntArg(0);
    System.out.println("Sensor type:"+type);
}

运行

Sensor type:1

1 对应于加速度传感器,下面做常规补环境,将 type 设为 DvmObject 的值,因为担心样本会获取多种传感器,所以提前处理,便于区分。

case "android/hardware/SensorManager->getDefaultSensor(I)Landroid/hardware/Sensor;":{
    int type = vaList.getIntArg(0);
    return vm.resolveClass("android/hardware/Sensor").newObject(type);
}

继续运行

java.lang.UnsupportedOperationException: android/hardware/Sensor->getName()Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:234)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

这里是在获取传感器的名字

读者可以写一个 Android Java 层 demo,来理解这里的逻辑,在 JNI 中看起来有点懵,但 Java 复写其实相当简单

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

在我的测试机上返回 ACCELEROMETER

收集传感器的信息,可以一定程度上阻止 “改机” ,国内主要品牌、主要型号 所对应的传感器信息都可以查到,如果你修改了 Android 设备信息,比如从 pixel 改成 红米 K40,那么对应的,各类传感器信息也要改。如果不协调同步地改,就会有破绽。

继续补环境

case "android/hardware/Sensor->getName()Ljava/lang/String;":{
    int type = (Integer) dvmObject.getValue();
    System.out.println("Sensor type: " + type);
    if(type == 1){
        return new StringObject(vm, "ACCELEROMETER");
    }else {
        throw new UnsupportedOperationException(signature);
    }
}

运行

java.lang.UnsupportedOperationException: android/hardware/Sensor->getVendor()Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:244)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

传感器有一系列可供获取的属性。

  • getMaximumRange() 最大取值范围
  • getName() 设备名称
  • getPower() 功率
  • getResolution() 精度
  • getType() 传感器类型
  • getVentor() 设备供应商
  • getVersions() 设备版本号

这里是在获取设备供应商信息,在 Android demo 中如法炮制

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

运行demo,看下日志就知道了
我的测试机返回的是,lsm6dsoe_acc,是 STMicroelectronics (ST) 生产的 MEMS 传感器。继续补环境

case "android/hardware/Sensor->getVendor()Ljava/lang/String;":{
    int type = (int) dvmObject.getValue();
    System.out.println("Sensor getVendor:" + type);
    if(type == 1){
        return new StringObject(vm, "lsm6dsoe_acc");
    }else {
        throw new UnsupportedOperationException(signature);
    }
}

继续运行

[!] Sensor type(getName): 9
[15:14:41 209]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=-452987096, svcNumber=0x120, PC=unidbg@0xfffe0294, LR=RX@0x1202ad4f[libmtguard.so]0x2ad4f, syscall=null
java.lang.UnsupportedOperationException: android/hardware/Sensor->getName()Ljava/lang/String;
	at com.test4.TEST4.callObjectMethodV(TEST4.java:240)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

可以看到,它又获得了另一种传感器,9 是 TYPE_GRAVITY 即重力传感器。将 Android demo 稍加修改

SensorManager manager= (SensorManager)getSystemService(SENSOR_SERVICE);
Sensor accelSensor = manager.getDefaultSensor(Sensor.TYPE_GRAVITY);
Log.e("lilac sensor Name",accelSensor.getName());
Log.e("lilac sensor Vendor",accelSensor.getVendor());

传感器名为 GRAVITY,vendor 是 MTK,继续完善补环境

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, "ACCELEROMETER");
    }else if(type == 9){
        return new StringObject(vm, "GRAVITY");
    }
    else {
        throw new UnsupportedOperationException(signature);
    }
}
case "android/hardware/Sensor->getVendor()Ljava/lang/String;":{
    int type = (int) dvmObject.getValue();
    System.out.println("Sensor getVendor:"+type);
    if(type == 1){
        return new StringObject(vm, "lsm6dsoe_acc");
    }else if(type == 9){
        return new StringObject(vm, "MTK");
    }
    else {
        throw new UnsupportedOperationException(signature);
    }
}

继续运行

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->safeClose(Ljava/io/Closeable;)V
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:1007)
	at com.test4.TEST4.callVoidMethodV(TEST4.java:147)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:990)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callVoidMethodA(DvmMethod.java:234)

在 Jadx 中查看对应的反编译代码

private void safeClose(Closeable closeable) {
    Object[] objArr = {closeable};
    ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
    if (PatchProxy.isSupport(objArr, this, changeQuickRedirect2, false, "148f9159176b748328c0fe05c9308265", RobustBitConfig.DEFAULT_VALUE)) {
        PatchProxy.accessDispatch(objArr, this, changeQuickRedirect2, false, "148f9159176b748328c0fe05c9308265");
    } else {
        MTGUtils.safeClose(closeable);
    }
}

看起来是处理完之后的资源关闭操作,简单处理即可

case "com/meituan/android/common/mtguard/NBridge$SIUACollector->safeClose(Ljava/io/Closeable;)V":{
    return;
}

继续运行,发现已经跑完了

[*] getHWEquipmentInfo:-|MT6893Z/CZA|8|ACCELEROMETER|lsm6dsoe_acc|GYROSCOPE|MTK|

八、getHWProperty

发起调用

public String getHWProperty(){
    String result = SIUACollector.callJniMethodObject(emulator, "getHWProperty()Ljava/lang/String;").getValue().toString();
    return result;
}

新一轮的补环境

java.lang.UnsupportedOperationException: android/os/Build->BOARD:Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:103)
	at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:53)
	at com.github.unidbg.linux.android.dvm.DvmField.getStaticObjectField(DvmField.java:106)

对于 Build 下的数据,同样可以写 demo 看,这个办法灵活好用,或者使用 ADB 也行。这里就直接使用 adb 指令了

└─Δ adb shell
ares:/ $ getprop | grep board
[ro.board.first_api_level]: [30]
[ro.board.platform]: [mt6893]
[ro.miui.has_security_keyboard]: [1]
[ro.product.board]: [ares]
[sys.haptic.intensityforkeyboard]: [true]

补环境

@Override
public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
    switch (signature){
        case "android/os/Build->BOARD:Ljava/lang/String;":{
            return new StringObject(vm, "mt6893");
        }
    }
    return super.getStaticObjectField(vm, dvmClass, signature);
}

继续运行

java.lang.UnsupportedOperationException: android/os/Build->MANUFACTURER:Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:103)
	at com.test4.TEST4.getStaticObjectField(TEST4.java:366)
	at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:53)
	at com.github.unidbg.linux.android.dvm.DvmField.getStaticObjectField(DvmField.java:106)

同理

ares:/ $ getprop | grep manufacturer
[ro.product.bootimage.manufacturer]: [xiaomi]
[ro.product.manufacturer]: [Xiaomi]
[ro.product.odm.manufacturer]: [Xiaomi]
[ro.product.product.manufacturer]: [Xiaomi]
[ro.product.system.manufacturer]: [xiaomi]
[ro.product.system_ext.manufacturer]: [xiaomi]
[ro.product.vendor.manufacturer]: [Xiaomi]
[ro.product.vendor_dlkm.manufacturer]: [xiaomi]
[ro.soc.manufacturer]: [Mediatek]

结果是 Xiaomi,补环境

case "android/os/Build->MANUFACTURER:Ljava/lang/String;":{
    return new StringObject(vm, "Xiaomi");
}

继续运行

java.lang.UnsupportedOperationException: android/os/Build->BRAND:Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:103)
	at com.test4.TEST4.getStaticObjectField(TEST4.java:369)
	at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:53)
	at com.github.unidbg.linux.android.dvm.DvmField.getStaticObjectField(DvmField.java:106)

brand 肯定是红米,不过还是运行一下看看

ares:/ $ getprop | grep brand
[ro.product.bootimage.brand]: [redmi]
[ro.product.brand]: [Redmi]
[ro.product.odm.brand]: [Redmi]
[ro.product.product.brand]: [Redmi]
[ro.product.system.brand]: [redmi]
[ro.product.system_ext.brand]: [redmi]
[ro.product.vendor.brand]: [Redmi]
[ro.product.vendor_dlkm.brand]: [redmi]

结果是 Redmi

case "android/os/Build->BRAND:Ljava/lang/String;":{
    return new StringObject(vm, "Redmi");
}

继续运行,接着是 MODEL

java.lang.UnsupportedOperationException: android/os/Build->MODEL:Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:103)
	at com.test4.TEST4.getStaticObjectField(TEST4.java:372)
	at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:53)

同理

ares:/ $ getprop | grep model
[ro.product.bootimage.model]: [ares]
[ro.product.model]: [M2012K10C]
[ro.product.odm.model]: [M2012K10C]
[ro.product.product.model]: [M2012K10C]
[ro.product.system.model]: [missi]
[ro.product.system_ext.model]: [missi]
[ro.product.vendor.model]: [M2012K10C]
[ro.product.vendor_dlkm.model]: [ares]
[ro.soc.model]: [MT6893]

补环境

case "android/os/Build->MODEL:Ljava/lang/String;":{
    return new StringObject(vm, "M2012K10C");
}

继续运行

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->getSysProp(Ljava/lang/String;)Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:269)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

getSysProp 是一个用于获取系统属性的工具函数,反编译代码如下。

public static String getSysProp(String str) {
    Object[] objArr = {str};
    ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
    if (PatchProxy.isSupport(objArr, null, changeQuickRedirect2, true, "31aaabc5cd1287cb6e2e62ee32d788e0", RobustBitConfig.DEFAULT_VALUE)) {
        return (String) PatchProxy.accessDispatch(objArr, null, changeQuickRedirect2, true, "31aaabc5cd1287cb6e2e62ee32d788e0");
    }
    if (TextUtils.isEmpty(str)) {
        return "";
    }
    try {
        Class<?> cls = Class.forName("android.os.SystemProperties");
        return (String) cls.getDeclaredMethod("get", String.class).invoke(cls, str);
    } catch (Throwable th) {
        c.a(th);
        MTGuardLog.error(th);
        return "";
    }
}

浅浅打印一下参数

case "com/meituan/android/common/mtguard/NBridge$SIUACollector->getSysProp(Ljava/lang/String;)Ljava/lang/String;":{
    String name = vaList.getObjectArg(0).getValue().toString();
    System.out.println("getSysProp:"+name);
}

运行代码

[!] getSysProp: ro.product.cpu.abi
[15:54:43 498]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=-452987096, svcNumber=0x120, PC=unidbg@0xfffe0294, LR=RX@0x1200d2a7[libmtguard.so]0xd2a7, syscall=null
java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->getSysProp(Ljava/lang/String;)Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:273)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)
	at com.github.unidbg.linux.android.dvm.DalvikVM$33.handle(DalvikVM.java:578)

既然是属性信息,用 adb 获取很方便,第一个就是

ares:/ $ getprop | grep ro.product.cpu.abi
[ro.product.cpu.abi]: [arm64-v8a]
[ro.product.cpu.abilist]: [arm64-v8a,armeabi-v7a,armeabi]
[ro.product.cpu.abilist32]: [armeabi-v7a,armeabi]
[ro.product.cpu.abilist64]: [arm64-v8a]

补环境

case "com/meituan/android/common/mtguard/NBridge$SIUACollector->getSysProp(Ljava/lang/String;)Ljava/lang/String;":{
    String name = vaList.getObjectArg(0).getValue().toString();
    System.out.println("getSysProp:"+name);
    if(name.equals("ro.product.cpu.abi")){
        return new StringObject(vm, "arm64-v8a");
    }
    throw new UnsupportedOperationException(signature);
}

继续运行

[!] getSysProp: ro.product.cpu.abi2
[15:58:21 719]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=-452987096, svcNumber=0x120, PC=unidbg@0xfffe0294, LR=RX@0x12018613[libmtguard.so]0x18613, syscall=null
java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->getSysProp(Ljava/lang/String;)Ljava/lang/String;
	at com.test4.TEST4.callObjectMethodV(TEST4.java:274)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

我的测试机就一个分区,所以用 adb 指令查不到,那么直接返回空字符串就好

else if(name.equals("ro.product.cpu.abi2")) {
    return new StringObject(vm, "");
}

继续运行

java.lang.UnsupportedOperationException: android/os/Build->PRODUCT:Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:103)
	at com.test4.TEST4.getStaticObjectField(TEST4.java:386)
	at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:53)

接连着的是 PRODUCT、HARDWARE、DEVICE,依次补环境

case "android/os/Build->PRODUCT:Ljava/lang/String;":{
    return new StringObject(vm, "missi");
}
case "android/os/Build->HARDWARE:Ljava/lang/String;":{
    return new StringObject(vm, "mt6893");
}
case "android/os/Build->DEVICE:Ljava/lang/String;":{
    return new StringObject(vm, "ares");
}

穿插

[!] getSysProp: ro.build.product
[16:04:14 251]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=-452987096, svcNumber=0x120, PC=unidbg@0xfffe0294, LR=RX@0x1201831f[libmtguard.so]0x1831f, syscall=null
java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->getSysProp(Ljava/lang/String;)Ljava/lang/String;
	at com.test4.TEST4.callObjectMethodV(TEST4.java:277)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

显然又是所谓的多重校验,多个 API 获取相同信息

然后又是 HOST 和 ID

case "android/os/Build->HOST:Ljava/lang/String;":{
    return new StringObject(vm, "c4-xm-ota-bd093.bj");
}
case "android/os/Build->ID:Ljava/lang/String;":{
    return new StringObject(vm, "SP1A.210812.016");
}

继续运行

java.lang.UnsupportedOperationException: android/os/Build$VERSION->RELEASE:Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:103)
	at com.test4.TEST4.getStaticObjectField(TEST4.java:405)
	at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:53)
	at com.github.unidbg.linux.android.dvm.DvmField.getStaticObjectField(DvmField.java:106)

在 adb 中找到

[ro.build.version.release]: [12]

然后补环境,继续运行

java.lang.UnsupportedOperationException: java/lang/Integer->toString(I)Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethodV(AbstractJni.java:504)
	at com.test4.TEST4.callStaticObjectMethodV(TEST4.java:327)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethodV(AbstractJni.java:438)

补环境

case "java/lang/Integer->toString(I)Ljava/lang/String;":{
    return new StringObject(vm, Integer.toString(vaList.getIntArg(0)));
}

继续运行

java.lang.UnsupportedOperationException: android/content/Context->getResources()Landroid/content/res/Resources;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:284)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)

Resources 即资源,看来是新的逻辑。

case "android/content/Context->getResources()Landroid/content/res/Resources;":{
    return vm.resolveClass("android/content/res/Resources").newObject(null);
}

继续运行,发现原来是想获取配置信息

java.lang.UnsupportedOperationException: android/content/res/Resources->getConfiguration()Landroid/content/res/Configuration;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:286)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)

补环境

case "android/content/res/Resources->getConfiguration()Landroid/content/res/Configuration;":{
    return vm.resolveClass("android/content/res/Configuration").newObject(null);
}

继续运行

java.lang.UnsupportedOperationException: android/content/res/Configuration->locale:Ljava/util/Locale;
	at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:171)
	at com.test4.TEST4.getObjectField(TEST4.java:299)
	at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:141)

具体而言,是获取区域信息,locale在 JDK 里也有,所以改用 createObject。

case "android/content/res/Configuration->locale:Ljava/util/Locale;":{
    return ProxyDvmObject.createObject(vm, Locale.getDefault());
}

继续运行

java.lang.UnsupportedOperationException: android/os/Build->TAGS:Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:103)
	at com.test4.TEST4.getStaticObjectField(TEST4.java:421)
	at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:53)
	at com.github.unidbg.linux.android.dvm.DvmField.getStaticObjectField(DvmField.java:106)

ADB grep 一下,ro.build.tags 的值是 release-keys。

ares:/ $ getprop | grep tags
[debug.atrace.tags.enableflags]: [0]
[ro.bootimage.build.tags]: [release-keys]
[ro.build.tags]: [release-keys]
[ro.product.build.tags]: [release-keys]
[ro.system.build.tags]: [release-keys]
[ro.system_ext.build.tags]: [release-keys]
[ro.vendor.build.tags]: [release-keys]
[ro.vendor_dlkm.build.tags]: [release-keys]

补环境

case "android/os/Build->TAGS:Ljava/lang/String;":{
    return new StringObject(vm, "release-keys");
}

继续运行,

java.lang.UnsupportedOperationException: android/os/Build->FINGERPRINT:Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:103)
	at com.test4.TEST4.getStaticObjectField(TEST4.java:424)
	at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:53)
	at com.github.unidbg.linux.android.dvm.DvmField.getStaticObjectField(DvmField.java:106)

接下来是 FINGERPRINT、TYPE。

case "android/os/Build->FINGERPRINT:Ljava/lang/String;":{
    return new StringObject(vm, "redmi/missi/missi:12/SP1A.210812.016/V13.0.8.0.SKJCNXM:user/release-keys");
}
case "android/os/Build->TYPE:Ljava/lang/String;":{
    return new StringObject(vm, "user");
}

运行,又是 getSysProp

[!] getSysProp: ro.build.description
[16:53:59 982]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=-452987096, svcNumber=0x120, PC=unidbg@0xfffe0294, LR=RX@0x1200ca17[libmtguard.so]0xca17, syscall=null
java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->getSysProp(Ljava/lang/String;)Ljava/lang/String;
	at com.test4.TEST4.callObjectMethodV(TEST4.java:282)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

adb 获取一下信息,然后补环境

ares:/ $ getprop | grep ro.build.description
[ro.build.description]: [missi-user 12 SP1A.210812.016 V13.0.8.0.SKJCNXM release-keys]

继续补环境

else if(name.equals("ro.build.description")){
    return new StringObject(vm, "missi-user 12 SP1A.210812.016 V13.0.8.0.SKJCNXM release-keys");
}

运行,还是 getSysProp 调用,参数是 ro.secure

else if(name.equals("ro.secure")){
    return new StringObject(vm, "1");
}

继续,接着是 ro.debuggable

if(name.equals("ro.debuggable")){
    return new StringObject(vm, "0");
}

运行代码,得出结果。

[*] getHWProperty: mt6893|Xiaomi|Redmi|M2012K10C|arm64-v8a|-|missi|mt6893|ares|missi|c4-xm-ota-bd093.bj|SP1A.210812.016|12|35|zh|CN|release-keys|redmi/missi/missi:12/SP1A.210812.016/V13.0.8.0.SKJCNXM:user/release-keys|user|missi-user 12 SP1A.210812.016 V13.0.8.0.SKJCNXM release-keys|1|0|

九、getHWStatus

发起调用

public String getHWStatus(){
    String result = SIUACollector.callJniMethodObject(emulator, "getHWStatus()Ljava/lang/String;").getValue().toString();
    return result;
}

开始补环境

[!] getSysProp: persist.sys.usb.config
[17:04:12 708]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=-452987096, svcNumber=0x120, PC=unidbg@0xfffe0294, LR=RX@0x12023a11[libmtguard.so]0x23a11, syscall=null
java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->getSysProp(Ljava/lang/String;)Ljava/lang/String;
	at com.test4.TEST4.callObjectMethodV(TEST4.java:297)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)
	at com.github.unidbg.linux.android.dvm.DalvikVM$33.handle(DalvikVM.java:578)

这是在获取设备是否连接了 ADB,因此返回空字符串为好,接连三个都是类似的属性。

if(name.equals("persist.sys.usb.config")){
    return new StringObject(vm, "");
}
if(name.equals("sys.usb.config")){
    return new StringObject(vm, "");
}
if(name.equals("sys.usb.state")){
    return new StringObject(vm, "");
}

继续运行

java.lang.UnsupportedOperationException: android/content/pm/PackageManager->hasSystemFeature(Ljava/lang/String;)Z
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:625)
	at com.test4.TEST4.callBooleanMethodV(TEST4.java:351)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:603)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callBooleanMethodA(DvmMethod.java:124)
	at com.github.unidbg.linux.android.dvm.DalvikVM$36.handle(DalvikVM.java:655)

查询可知,hasSystemFeature 用于确认设备是否有特定的功能模块,打印参数看看

case "android/content/pm/PackageManager->hasSystemFeature(Ljava/lang/String;)Z":{
    String name = vaList.getObjectArg(0).getValue().toString();
    System.out.println("check hasSystemFeature:"+name);
}

发现是检测是否存在加速度传感器

check hasSystemFeature:android.hardware.sensor.accelerometer

返回 True,继续运行

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->boolean2Integer(Z)I
	at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethodV(AbstractJni.java:563)
	at com.test4.TEST4.callIntMethodV(TEST4.java:400)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethodV(AbstractJni.java:529)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callIntMethodA(DvmMethod.java:134)

jadx 查看对应伪代码

private int boolean2Integer(boolean z) {
         return z ? 1 : 0;
}

很简单,直接补环境

case "com/meituan/android/common/mtguard/NBridge$SIUACollector->boolean2Integer(Z)I": {
                return vaList.getIntArg(0);
            }

然后运行,发现又检测了不少传感器和硬件支持,参考文章中补了下面这么多,但是我并没有补这么多

case "android/content/pm/PackageManager->hasSystemFeature(Ljava/lang/String;)Z":{
    String name = vaList.getObjectArg(0).getValue().toString();
    System.out.println("check hasSystemFeature:"+name);
    switch (name){
            // 检测加速传感器
        case "android.hardware.sensor.accelerometer":{
            return true;
        }
            // 检测陀螺仪传感器
        case "android.hardware.sensor.gyroscope":{
            return true;
        }
            // wifi
        case "android.hardware.wifi":{
            return true;
        }
            // 蓝牙
        case "android.hardware.bluetooth":{
            return true;
        }
            // 蓝牙低功耗
        case "android.hardware.bluetooth_le":{
            return true;
        }
            // 电话
        case "android.hardware.telephony":{
            return true;
        }
            // USB 配件API
        case "android.hardware.usb.accessory":{
            return true;
        }
            // gps
        case "android.hardware.location.gps":{
            return true;
        }
            // nfc
        case "android.hardware.nfc":{
            return true;
        }
    }
}
case "android/content/pm/PackageManager->hasSystemFeature(Ljava/lang/String;)Z": {
                String name = vaList.getObjectArg(0).getValue().toString();
                System.out.println("[!] Check hasSystemFeature: " + name);
                switch (name) {
                    // 检测加速度传感器
                    case "android.hardware.sensor.accelerometer": {
                        return true;
                    }
                    // 检测陀螺仪传感器
                    case "android.hardware.sensor.gyroscope": {
                        return true;
                    }
                }
            }

在这个过程中,也还涉及到其他的几个补环境

[!] getSysProp: gsm.version.baseband
[17:27:30 570]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=-452987096, svcNumber=0x120, PC=unidbg@0xfffe0294, LR=RX@0x12024d7f[libmtguard.so]0x24d7f, syscall=null
java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->getSysProp(Ljava/lang/String;)Ljava/lang/String;
	at com.test4.TEST4.callObjectMethodV(TEST4.java:306)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)
	at com.github.unidbg.linux.android.dvm.DalvikVM$33.handle(DalvikVM.java:578)

也还是和之前一样补一下

else if (name.equals("gsm.version.baseband")) {
                    return new StringObject(vm, "MOLY.NR15.R3.TC8.PR2.SP.V2.1.P23,MOLY.NR15.R3.TC8.PR2.SP.V2.1.P23");
                }
                else if (name.equals("gsm.version.ril-impl")) {
                    return new StringObject(vm, "android reference-ril 1.0");
                }
                else if (name.equals("gsm.sim.state")) {
                    return new StringObject(vm, "ABSENT,ABSENT");
                }
                else if (name.equals("gsm.sim.state.2")) {
                    return new StringObject(vm, "");
                }
                else if (name.equals("wifi.interface")) {
                    return new StringObject(vm, "");
                }

还有一些其他类型的错误

java.lang.UnsupportedOperationException: java/io/File->allocObject
	at com.github.unidbg.linux.android.dvm.AbstractJni.allocObject(AbstractJni.java:812)
	at com.test4.TEST4.allocObject(TEST4.java:122)
	at com.github.unidbg.linux.android.dvm.DvmClass.allocObject(DvmClass.java:74)
	at com.github.unidbg.linux.android.dvm.DalvikVM$24.handle(DalvikVM.java:365)

和之前一样处理

case "java/io/File->allocObject": {
                return dvmClass.newObject(signature);
            }

继续运行

java.lang.UnsupportedOperationException: java/io/File-><init>(Ljava/lang/String;)V
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:1007)
	at com.test4.TEST4.callVoidMethodV(TEST4.java:167)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:990)

和先前一样,用全局变量处理,因为担心会初始化多个文件,所以命名为 file1

public class TEST4 extends AbstractJni implements IOResolver {
    private final AndroidEmulator emulator;
    private final DvmObject<?> SIUACollector;
    private final VM vm;
    public FileInputStream fileInputStream;
    public InputStreamReader inputStreamReader;
    public BufferedReader bufferedReader;
    public File file1;

补环境

case "java/io/File-><init>(Ljava/lang/String;)V":{
    String path = vaList.getObjectArg(0).getValue().toString();
    System.out.println("filePath:"+path);
}

运行

filePath:/sys/class/power_supply/battery/voltage_now

这是电池信息有关的文件

1|ares:/ $ su
ares:/ # cat /sys/class/power_supply/battery/voltage_now
4227000

在 Unidbg 中建立对应的文件,补环境,重定向

case "java/io/File-><init>(Ljava/lang/String;)V":{
    String path = vaList.getObjectArg(0).getValue().toString();
    System.out.println("filePath:"+path);
    if(path.equals("/sys/class/power_supply/battery/voltage_now")){
        file1 = new File("unidbg-android/src/test/resources/dianping/files/voltage_now");
        return;
    }
}

继续运行

java.lang.UnsupportedOperationException: java/io/File->exists()Z
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:625)
	at com.test4.TEST4.callBooleanMethodV(TEST4.java:391)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:603)

继续补环境

case "java/io/File->exists()Z":{
    return file1.exists();
}

直接返回 true 也行,因为这里判断的就是我们刚处理的 file1 是否存在。
运行,然后样本又开始处理另一个文件

Filepath: /sys/class/power_supply/battery/temp
[17:52:27 885]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=-452987096, svcNumber=0x13b, PC=unidbg@0xfffe0444, LR=RX@0x12026601[libmtguard.so]0x26601, syscall=null
java.lang.UnsupportedOperationException: java/io/File-><init>(Ljava/lang/String;)V
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:1007)
	at com.test4.TEST4.callVoidMethodV(TEST4.java:176)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:990)

补环境

case "java/io/File-><init>(Ljava/lang/String;)V": {
                String path = vaList.getObjectArg(0).getValue().toString();
                System.out.println("Filepath: " + path);
                if (path.equals("/sys/class/power_supply/battery/voltage_now")) {
                    file1 = new File("unidbg-android/src/test/java/com/test4/voltage_now");
                    return;
                }
                else if (path.equals("/sys/class/power_supply/battery/temp")) {
                    file2 = new File("unidbg-android/src/test/java/com/test4/temp");
                    return;
                }
            }

继续运行,得到结果

[*] getHWStatus: -|-|-|1|1|MOLY.NR15.R3.TC8.PR2.SP.V2.1.P23,MOLY.NR15.R3.TC8.PR2.SP.V2.1.P23|android reference-ril 1.0|ABSENT,ABSENT|-|1|1|1|-|1|1|1|1|1|1|1|

十、getLocationInfo

发起调用

public String getLocationInfo(){
    String result = SIUACollector.callJniMethodObject(emulator, "getLocationInfo()Ljava/lang/String;").getValue().toString();
    return result;
}

运行,报错

java.lang.UnsupportedOperationException: android/net/wifi/WifiManager->getConnectionInfo()Landroid/net/wifi/WifiInfo;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:353)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

补环境

case "android/net/wifi/WifiManager->getConnectionInfo()Landroid/net/wifi/WifiInfo;":{
    return vm.resolveClass("android/net/wifi/WifiInfo").newObject(signature);
}

继续运行

java.lang.UnsupportedOperationException: android/net/wifi/WifiInfo->getSSID()Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:356)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)

Google 的这个API 的作用是获取 WiFi 名,查看手机所连接的 WiFi 名,做补环境

case "android/net/wifi/WifiInfo->getSSID()Ljava/lang/String;":{
    return new StringObject(vm, "DX-MOBILE");
}

继续运行

java.lang.UnsupportedOperationException: java/lang/String->equalsIgnoreCase(Ljava/lang/String;)Z
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:625)
	at com.test4.TEST4.callBooleanMethodV(TEST4.java:412)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:603)

正常补环境

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

报错

java.lang.UnsupportedOperationException: java/lang/String->equals(Ljava/lang/Object;)Z
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:625)
	at com.test4.TEST4.callBooleanMethodV(TEST4.java:415)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:603)

补环境

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

继续运行

java.lang.UnsupportedOperationException: java/lang/String->length()I
	at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethodV(AbstractJni.java:563)
	at com.test4.TEST4.callIntMethodV(TEST4.java:457)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethodV(AbstractJni.java:529)

依旧正常补环境

case "java/lang/String->length()I": {
    return dvmObject.getValue().toString().length();
}

运行,继续报错

java.lang.UnsupportedOperationException: java/lang/StringBuilder->append(Ljava/lang/CharSequence;II)Ljava/lang/StringBuilder;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:359)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)

这个稍微有点复杂

				case "java/lang/StringBuilder->append(Ljava/lang/CharSequence;II)Ljava/lang/StringBuilder;": {
                // 参数
                CharSequence csq = (CharSequence) vaList.getObjectArg(0).getValue();
                int start = vaList.getIntArg(1);
                int end = vaList.getIntArg(2);

                // 构造新内容
                StringBuilder currentValue = (StringBuilder) dvmObject.getValue();
                StringBuilder sb = new StringBuilder(currentValue);
                sb.append(csq.subSequence(start, end));

                // 返回新的 StringBuilder 对象(因为 setValue() 不能直接改)
                return vm.resolveClass("java/lang/StringBuilder").newObject(sb);
            }

不过一步一步拆开来写就好了。然后继续运行,报错

java.lang.UnsupportedOperationException: android/net/wifi/WifiInfo->getBSSID()Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:373)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)

上网查了下这个 API 的功能,是获取 WiFi 接入点的唯一标识符,一般就是路由器无线网卡的 MAC 地址

。一样采用写一个 demo 的方式来获取这个值

        // 检查并申请 Wi-Fi 和定位权限
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED ||
                ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_WIFI_STATE) != PackageManager.PERMISSION_GRANTED) {

            ActivityCompat.requestPermissions(this,
                    new String[]{
                            Manifest.permission.ACCESS_FINE_LOCATION,
                            Manifest.permission.ACCESS_WIFI_STATE
                    }, PERMISSION_REQUEST_CODE);
        } else {
            getBssid();
        }
    }

    // 获取 BSSID
    private void getBssid() {
        WifiManager wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
        if (wifiManager != null) {
            WifiInfo wifiInfo = wifiManager.getConnectionInfo();
            if (wifiInfo != null && wifiInfo.getBSSID() != null) {
                Log.e("当前BSSID", wifiInfo.getBSSID());
            } else {
                Log.e("当前BSSID", "无法获取BSSID,权限不足或未连接Wi-Fi");
            }
        }
    }

    // 处理运行时权限回调
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == PERMISSION_REQUEST_CODE) {
            boolean granted = true;
            for (int res : grantResults) {
                if (res != PackageManager.PERMISSION_GRANTED) {
                    granted = false;
                    break;
                }
            }
            if (granted) {
                getBssid();
            } else {
                Log.e("当前BSSID", "权限被拒绝,无法获取BSSID");
            }
        }
    }

稍微有点复杂,因为需要申请权限。然后我获取的值是 3c:d2:e5: ab:81:b1。补环境

            case "android/net/wifi/WifiInfo->getBSSID()Ljava/lang/String;": {
                return new StringObject(vm, "3c:d2:e5:ab:81:b1");
            }

继续

java.lang.UnsupportedOperationException: android/net/wifi/WifiInfo->getRssi()I
	at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethodV(AbstractJni.java:563)
	at com.test4.TEST4.callIntMethodV(TEST4.java:477)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethodV(AbstractJni.java:529)

这个 API 是用来获取 WiFi 信号强度的,也可以使用 上面的方法 来获取,当然也可以随意返回一个正常的值也可以

WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
int rssi = wifiInfo.getRssi();
Log.e("WiFi RSSI", String.valueOf(rssi));

我的测试机获取到的值是 -47,补环境

            case "android/net/wifi/WifiInfo->getRssi()I": {
                return -47;
            }

继续运行报错

java.lang.UnsupportedOperationException: android/telephony/TelephonyManager->getNetworkOperator()Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:376)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)

这个 API 是获取当前移动网络运行商的 MCC + MNC。没插卡,直接返回空字符串。

            case "android/telephony/TelephonyManager->getNetworkOperator()Ljava/lang/String;": {
                return new StringObject(vm, "");
            }

继续运行,就跑通了

[*] getLocationInfo: -|-|-|-|-|

十一、getPlatformInfo

发起调用

public String getPlatformInfo(){
    String result = SIUACollector.callJniMethodObject(emulator, "getPlatformInfo()Ljava/lang/String;").getValue().toString();
    return result;
}

运行,报错

java.lang.UnsupportedOperationException: java/text/SimpleDateFormat->allocObject
	at com.github.unidbg.linux.android.dvm.AbstractJni.allocObject(AbstractJni.java:812)
	at com.test4.TEST4.allocObject(TEST4.java:139)
	at com.github.unidbg.linux.android.dvm.DvmClass.allocObject(DvmClass.java:74)
	at com.github.unidbg.linux.android.dvm.DalvikVM$24.handle(DalvikVM.java:365)

同理处理

case "java/text/SimpleDateFormat->allocObject":{
    return dvmClass.newObject(signature);
}

继续运行

java.lang.UnsupportedOperationException: java/text/SimpleDateFormat-><init>(Ljava/lang/String;Ljava/util/Locale;)V
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:1007)
	at com.test4.TEST4.callVoidMethodV(TEST4.java:196)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:990)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callVoidMethodA(DvmMethod.java:234)

依旧处理为全局变量,然后补环境

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

继续运行

java.lang.UnsupportedOperationException: android/os/Build$VERSION->SDK:Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:103)
	at com.test4.TEST4.getStaticObjectField(TEST4.java:567)
	at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:53)
	at com.github.unidbg.linux.android.dvm.DvmField.getStaticObjectField(DvmField.java:106)
	at com.github.unidbg.linux.android.dvm.DalvikVM$142.handle(DalvikVM.java:2275)

前面我们补过 android/os/Build$VERSION->SDK_INT:I,它和此处作用一致,区别仅在于返回数字还是字符串。

case "android/os/Build$VERSION->SDK:Ljava/lang/String;":{
    return new StringObject(vm, "35");
}

继续运行

java.lang.UnsupportedOperationException: java/util/Date->allocObject
	at com.github.unidbg.linux.android.dvm.AbstractJni.allocObject(AbstractJni.java:812)
	at com.test4.TEST4.allocObject(TEST4.java:144)
	at com.github.unidbg.linux.android.dvm.DvmClass.allocObject(DvmClass.java:74)

补环境

case "java/util/Date->allocObject":{
    return dvmClass.newObject(signature);
}

继续运行

java.lang.UnsupportedOperationException: java/util/Date-><init>()V
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:1007)
	at com.test4.TEST4.callVoidMethodV(TEST4.java:207)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callVoidMethodV(AbstractJni.java:990)

这是一个无参构造,那可以回头修改 allocObject 那里的代码,是得思路更清晰

case "java/util/Date->allocObject":{
    return ProxyDvmObject.createObject(vm, new Date());
}

然后下面初始化的时候就不用做什么了

case "java/util/Date-><init>()V":{
    return;
}

继续运行

java.lang.UnsupportedOperationException: java/text/SimpleDateFormat->format(Ljava/util/Date;)Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:403)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)

补环境,直接返回一个固定的已格式化的时间字符串

            case "java/text/SimpleDateFormat->format(Ljava/util/Date;)Ljava/lang/String;": {
                return new StringObject(vm, "2025-08-04 09:05:05");
            }

继续运行

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->androidAppCnt(Landroid/content/Context;)I
	at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethodV(AbstractJni.java:563)
	at com.test4.TEST4.callIntMethodV(TEST4.java:510)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethodV(AbstractJni.java:529)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callIntMethodA(DvmMethod.java:134)

jadx 中看 androidAppCnt 的实现

public static int androidAppCnt(Context context) {
    int i;
    Object[] objArr = {context};
    ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
    if (PatchProxy.isSupport(objArr, null, changeQuickRedirect2, true, "e2af58080aad5d311267bb7c71e6cce2", RobustBitConfig.DEFAULT_VALUE)) {
        return ((Integer) PatchProxy.accessDispatch(objArr, null, changeQuickRedirect2, true, "e2af58080aad5d311267bb7c71e6cce2")).intValue();
    }
    int i2 = androidAppCntCache;
    if (i2 != 0) {
        return i2;
    }
    if (context == null) {
        return 0;
    }
    if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
        return androidAppCntCache;
    }
    try {
        lock.lock();
    } catch (Throwable th) {
        c.a(th);
    }
    if (androidAppCntCache != 0) {
        i = androidAppCntCache;
    } else {
        PackageManager pkgManager = getPkgManager(context);
        if (pkgManager == null) {
            i = androidAppCntCache;
        } else {
            androidAppCntCache = pkgManager.getInstalledApplications(128).size();
            lock.unlock();
            return androidAppCntCache;
        }
    }
    lock.unlock();
    return i;
}

代码逻辑是在获取设备安装了多少 App,相比较正常的用户设备,爬虫、黑灰产专用设备往往只安装很少的一些 App。读者可以随机返回一个大于 200 的数字,如果想确定测试机上的应用数,可以 Hook androidAppCnt 函数或者使用 ADB。

└─Δ adb shell
ares:/ $ pm list package
package:com.miui.screenrecorder
package:com.mediatek.ims
package:com.mediatek.op01.phone.plugin
package:icu.nullptr.applistdetector
package:com.android.cts.priv.ctsshim
...

补环境

case "com/meituan/android/common/mtguard/NBridge$SIUACollector->androidAppCnt(Landroid/content/Context;)I":{
    return 408;
}

新错误

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->appCache(Landroid/content/Context;)Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:406)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)

具体代码逻辑是计算 App 缓存目录大小,直接 Hook 获取

function callSIUACollector() {
  setTimeout(function () {
    Java.perform(function () {
      const ActivityThread = Java.use("android.app.ActivityThread");
      let context = ActivityThread.currentApplication().getApplicationContext();
      const ContextClass = Java.use("android.content.Context");
      const ctx = Java.cast(context, ContextClass);

      let SIUACollector = Java.use(
        "com.meituan.android.common.mtguard.NBridge$SIUACollector"
      );
      // 传context创建实例
      let instance = SIUACollector.$new(ctx);

      let result = instance.appCache(ctx);
      console.log("ret: " + result);
    });
  }, 3000);
}
callSIUACollector();

我的测试机拿到的数据是 114324295。补环境

case "com/meituan/android/common/mtguard/NBridge$SIUACollector->appCache(Landroid/content/Context;)Ljava/lang/String;":
    return new StringObject(vm, "114324295");

继续运行

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->availableSystem()Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:409)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)

看伪代码

public static String availableSystem() {
    Object[] objArr = new Object[0];
    ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
    if (PatchProxy.isSupport(objArr, null, changeQuickRedirect2, true, "234b4d16d3aab7d4062d61d2146219de", RobustBitConfig.DEFAULT_VALUE)) {
        return (String) PatchProxy.accessDispatch(objArr, null, changeQuickRedirect2, true, "234b4d16d3aab7d4062d61d2146219de");
    }
    try {
        File dataDirectory = Environment.getDataDirectory();
        if (!readable(dataDirectory)) {
            return "unknown";
        }
        StatFs statFs = new StatFs(dataDirectory.getPath());
        return StringUtils.toString(statFs.getAvailableBlocks() * statFs.getBlockSize());
    } catch (Throwable th) {
        c.a(th);
        return "unknown";
    }
}

用 frida hook 测试一下

Java.perform(function() {
  let DeviceInfoWorker = Java.use("com.meituan.android.common.dfingerprint.collection.workers.DeviceInfoWorker");
  DeviceInfoWorker["availableSystem"].implementation = function () {
    console.log('availableSystem is called');
    let ret = this.availableSystem();
    console.log('availableSystem ret value is ' + ret);
    return ret;
  };
})

hook 结果

availableSystem is called
availableSystem ret value is unknown

补环境

case "com/meituan/android/common/mtguard/NBridge$SIUACollector->availableSystem()Ljava/lang/String;":{
    return new StringObject(vm, "unknown");
}

继续运行,还是在获取设备存储相关信息

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->totalMemory()Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:412)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)

Jadx 中查看具体代码逻辑

public static String totalMemory() {
    Object[] objArr = new Object[0];
    ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
    if (PatchProxy.isSupport(objArr, null, changeQuickRedirect2, true, "a240e07aebe4d91708d281e29b9fc189", RobustBitConfig.DEFAULT_VALUE)) {
        return (String) PatchProxy.accessDispatch(objArr, null, changeQuickRedirect2, true, "a240e07aebe4d91708d281e29b9fc189");
    }
    try {
        FileReader fileReader = new FileReader("/proc/meminfo");
        BufferedReader bufferedReader = new BufferedReader(fileReader);
        bufferedReader.close();
        fileReader.close();
        return StringUtils.toString(Long.valueOf(bufferedReader.readLine().split("\\s+")[1]).intValue() * 1024);
    } catch (Throwable th) {
        c.a(th);
        return "unknown";
    }
}

这里是在访问 /proc/meminfo 的首行,我们也可以在 adb 中复现这一过程

└─Δ adb shell
ares:/ $ cat /proc/meminfo
MemTotal:        7714228 kB
MemFree:          187216 kB
MemAvailable:    3950016 kB
Buffers:            7428 kB
Cached:          4022680 kB
SwapCached:       139544 kB

进行一下单位转换,7714228 * 1024 = 7,899,369,472 B
然后也可以 frida hook 验证一下

function callSIUACollector() {
  setTimeout(function () {
    Java.perform(function () {
      const ActivityThread = Java.use("android.app.ActivityThread");
      let context = ActivityThread.currentApplication().getApplicationContext();
      const ContextClass = Java.use("android.content.Context");
      const ctx = Java.cast(context, ContextClass);

      let SIUACollector = Java.use(
        "com.meituan.android.common.mtguard.NBridge$SIUACollector"
      );
      // 传context创建实例
      let instance = SIUACollector.$new(ctx);

      let result = instance.totalMemory();
      console.log("ret: " + result);
    });
  }, 3000);
}
callSIUACollector();

得到的结果

ret: 7899369472

符合预期。补环境

case "com/meituan/android/common/mtguard/NBridge$SIUACollector->totalMemory()Ljava/lang/String;":{
    return new StringObject(vm, "7899369472");
}

继续运行

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->getFirstLaunchTime(Landroid/content/Context;)Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:415)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
	at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodA(DvmMethod.java:94)

getFirstLaunchTime 语义清晰,即 App 安装后的首次启动时间。直接 Hook 查看

function callSIUACollector() {
  setTimeout(function () {
    Java.perform(function () {
      const ActivityThread = Java.use("android.app.ActivityThread");
      let context = ActivityThread.currentApplication().getApplicationContext();
      const ContextClass = Java.use("android.content.Context");
      const ctx = Java.cast(context, ContextClass);

      let SIUACollector = Java.use(
        "com.meituan.android.common.mtguard.NBridge$SIUACollector"
      );
      // 传context创建实例
      let instance = SIUACollector.$new(ctx);

      let result = instance.getFirstLaunchTime(ctx);
      console.log("ret: " + result);
    });
  }, 3000);
}
callSIUACollector();

hook 结果

ret: 1755054762052

这是时间戳,转一下,不过得注意单位是毫秒
image-20250814115612408

补环境

case "com/meituan/android/common/mtguard/NBridge$SIUACollector->getFirstLaunchTime(Landroid/content/Context;)Ljava/lang/String;":{
    return new StringObject(vm, "1755054762052");
}

运行得到结果

[*] getPlatformInfo: Android|com.dianping.v1|10.41.15|35|-|2025-08-04 09:05:05|408|114324295|-|7899369472|1755054762052|-|-|

十二、getUserAction

发起调用

public String getUserAction(){
    String result = SIUACollector.callJniMethodObject(emulator, "getUserAction()Ljava/lang/String;").getValue().toString();
    return result;
}

开始补环境

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->batteryHelper:Lcom/meituan/android/common/dfingerprint/collection/utils/BatteryHelper;
	at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:171)
	at com.test4.TEST4.getObjectField(TEST4.java:437)
	at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:141)

从名字可以看出,是和电池有关的处理

case "com/meituan/android/common/mtguard/NBridge$SIUACollector->batteryHelper:Lcom/meituan/android/common/dfingerprint/collection/utils/BatteryHelper;":{
    return vm.resolveClass("com/meituan/android/common/dfingerprint/collection/utils/BatteryHelper").newObject(null);
}

继续运行

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->getBatteryInfo()Z
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:625)
	at com.test4.TEST4.callBooleanMethodV(TEST4.java:486)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:603)

看起来是在确认是否可以获取电池相关的信息

private boolean getBatteryInfo() {
    Object[] objArr = new Object[0];
    ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
    if (PatchProxy.isSupport(objArr, this, changeQuickRedirect2, false, "2d10aac5384d6818e2765f99df76bffe", RobustBitConfig.DEFAULT_VALUE)) {
        return ((Boolean) PatchProxy.accessDispatch(objArr, this, changeQuickRedirect2, false, "2d10aac5384d6818e2765f99df76bffe")).booleanValue();
    }
    Context context = this.mContext;
    if (context == null) {
        return false;
    }
    BatteryStatus batteryStatus = BatteryHelper.getInstance(context).getBatteryStatus();
    this.level = batteryStatus.batteryLevel;
    this.scale = batteryStatus.batteryScale;
    int i = batteryStatus.batteryStatus;
    int i2 = batteryStatus.batteryPlugged;
    if (i == 2 && i2 == 2) {
        this.status = 1;
    } else {
        this.status = 0;
    }
    if (i2 == 2) {
        this.plugged = true;
    } else {
        this.plugged = false;
    }
    return true;
}

补环境

case "com/meituan/android/common/mtguard/NBridge$SIUACollector->getBatteryInfo()Z":{
    return true;
}

继续运行

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->level:I
	at com.github.unidbg.linux.android.dvm.AbstractJni.getIntField(AbstractJni.java:648)
	at com.github.unidbg.linux.android.dvm.AbstractJni.getIntField(AbstractJni.java:640)

this.level 在上面的 getBatteryInfo 方法里进行了赋值,如果追根溯源,逻辑则来自于下面这段代码

private void recordStatus(Intent intent) {
    Object[] objArr = {intent};
    ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
    if (PatchProxy.isSupport(objArr, this, changeQuickRedirect2, false, "ca80b285aa559be35b7dd31c0b707222", RobustBitConfig.DEFAULT_VALUE)) {
        PatchProxy.accessDispatch(objArr, this, changeQuickRedirect2, false, "ca80b285aa559be35b7dd31c0b707222");
    } else if (intent == null) {
    } else {
        try {
            int intExtra = intent.getIntExtra("level", 0);
            int intExtra2 = intent.getIntExtra("scale", 100);
            int intExtra3 = intent.getIntExtra("status", -1);
            int intExtra4 = intent.getIntExtra("plugged", 0);
            this.mBatteryStatus.batteryTemperature = intent.getIntExtra("temperature", 0) / 10.0f;
            this.mBatteryStatus.batteryLevel = intExtra;
            this.mBatteryStatus.batteryScale = intExtra2;
            this.mBatteryStatus.batteryStatus = intExtra3;
            this.mBatteryStatus.batteryPlugged = intExtra4;
        } catch (Exception e) {
            c.a(e);
            DFPLog.error(e);
        }
    }
}

level 就是当前电量,scale 是总电量,它并不一定是 100.status 表示是否处于充电状态,plugged 则表示充电模式,Android 有两种充电模式,1 是充电器充电,也叫 AC 充电。2 是 USB 充电,比如插在电脑上充电。

样本接下来会依次获取这四个字段,读者可以 Hook SIUACollector 实例,获取这些字段。查看实例中某些字段的值,这个场景用 r0tracer 会很舒服。

//A. 简易trace单个函数
traceClass("com.meituan.android.common.mtguard.NBridge$SIUACollector")

在输出日志中可以找到下面这些

int     level => 100 => 100
boolean         plugged => false => false
int     scale => 100 => 100
int     status => 0 => 0

根据结果补环境

public int getIntField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
        switch (signature) {
            case "com/meituan/android/common/mtguard/NBridge$SIUACollector->level:I": {
                return 100;
            }
            case "com/meituan/android/common/mtguard/NBridge$SIUACollector->scale:I": {
                return 100;
            }
            case "com/meituan/android/common/mtguard/NBridge$SIUACollector->status:I": {
                return 0;
            }
        }
        return  super.getIntField(vm, dvmObject, signature);
    }

继续

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->plugged:Z
	at com.github.unidbg.linux.android.dvm.AbstractJni.getBooleanField(AbstractJni.java:717)

补环境

@Override
public boolean getBooleanField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
    switch (signature){
        case "com/meituan/android/common/mtguard/NBridge$SIUACollector->plugged:Z":{
            return false;
        }
    }
    return super.getBooleanField(vm, dvmObject, signature);
}

电池相关的大概告一段落了

java.lang.UnsupportedOperationException: com/meituan/android/common/mtguard/NBridge$SIUACollector->getDataActivity(Landroid/content/Context;)Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
	at com.test4.TEST4.callObjectMethodV(TEST4.java:424)
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)

刚才的 r0tracer 还有用,日志中搜索 getDataActivity。

retval: 0 => "0"
*** exiting com.meituan.android.common.mtguard.NBridge$SIUACollector.getDataActivity

补环境

case "com/meituan/android/common/mtguard/NBridge$SIUACollector->getDataActivity(Landroid/content/Context;)Ljava/lang/String;":
    return new StringObject(vm, "0");

得出结果

[*] getUserAction: -|100|1|0|0|-|92cbbca8-4858-40cd-b96c-4c52cd6eb17b|0|

十三、尾声

写过最长最枯燥的一次,写了三天…

十四、参考

Unidbg 的基本使用(六)


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

💰

×

Help us with donation