从实践中学习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 目前一共实现有以下几个虚拟模块。
四、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;
}
运行发现报错,需要开始补环境了
分配一个 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);
}
继续运行,报错
刚分配的 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 中看看它的实现,样本采用了自家的热修复技术Android热更新方案 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 时,会创建 tun0 或 ppp0 节点,所以出现带有明显特征的网络接口名称,就可以认定是使用了 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 函数的实现,它将很大的比特值转为更为直观和合适的 MB 和 GB。
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 官方好像关闭了它的访问)
找到具体实现
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
这是时间戳,转一下,不过得注意单位是毫秒
补环境
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|
十三、尾声
写过最长最枯燥的一次,写了三天…
十四、参考
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1621925986@qq.com