软件大厂,环境检测思路和规避思路,安卓改机应该改什么数据和参数,安卓boot内核修改

前言: https://www.bilibili.com/read/cv24568137/?opus_fallback=1 现在大厂的设备指纹层出不穷,但是想要确保稳定性和唯一性高精准其实也挺难的一件事,有的是通过设备信息比重进行的设备ID唯一值确认。比如A设备信息占比10%,B设备信息占比20%,当比重超过60%以上,设备指纹才会发生变化。这样的好处就是当你只修改某一个字段的时候,设备指纹不发生变化。还有的干脆找一个隐蔽的并且唯一的设备信息,作为缓存,每次读取缓存的方式去判断,设备信息是唯一,比如常见的有Native获取DRM,popen cat  /sys/devices/soc0/serial_number  ,svc读取bootid并且保存到文件,netlinker获取网卡。都是很常见并且隐蔽的的设备指纹。一个设备指纹大厂会使用多种方式去获取,那么我们应该如何进行对抗,我也会在文章里面说一下我自己的见解和方案,如何在一个“最佳”点去解决问题 设备指纹: 设备指纹主要分为三部分,Java层设备指纹,Native设备指纹,popen执行一些命令获取设备信息,包括一些核心的设备指纹。 Android Id 聊到设备指纹最经典的一个字段就是Android id,就我目前所知,他的获取方式不下5种,分别介绍一下。 方法1: 最基础的Android id获取方式,这个不多说,直接Hook就行 //原始获取android id String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); CLog.i(String.format("android_id -> 2222 %s", androidId)); 方法2: 第一种获取以后,系统会把Android id 保存起来,保存到一个HashMap里面,防止多次IPC初始化,所以为了验证第一种方法的准确性,可以二次获取cache。 和上面的Android id进行对比,9.0以上需要绕过Android id 的反射限制 方法3: 方法3也是很基础的Api,主要通过ContentResolver 进行间接获取,很多大厂也都在使用。 方法4: 通过query命令去查询,获取Android id,这种方式底层走的也是ContentResolver。 硬盘字节总大小: 在设备指纹里面,如果想回复出厂设置也能保证原有的设备信息,这个字段可以在服务端的相似度算法里面占比很重,可以以型号进行分类。我之前测试过,回复出厂设置指纹也不发生变化的设备指纹核心的设备指纹就几个。 比如硬盘大小,ipv6,还有一个就是MAC地址,这几个设备指纹也是很核心的设备指纹。首先先介绍硬盘字节大小。也是三种获取方法,但是方法底层都是一条系统调用。所以如果要进行对抗的话,只需要在SVC层进行处理即可。获取三种方法如下,不建议分别进行处理,可能会导致有地方泄漏,特别是直接开启一条进程通过execve去执行,然后管道传过来,导致很容易Hook不全。 jclass pJclass = env->FindClass("android/os/StatFs"); jmethodID id = env->GetMethodID(pJclass, "", "(Ljava/lang/String;)V"); jobject pJobject =         env->NewObject(pJclass, id, env->NewStringUTF("/storage/emulated/0")); jlong i = env->CallLongMethod(pJobject, env->GetMethodID(pJclass, "getTotalBytes", "()J")); LOG(ERROR) << "Java获取getTotalBytes "<nlmsg_type == RTM_NEWNEIGH 直接进行替换即可。 IPV6: 设个设备指纹也是很核心的设备指纹,这个玩意底层获取也是netlink,但是netlink获取,但是这块处理很不好处理,我暂时也没进行处理。 常用的获取方式比如,Java获取,命令获取。如果需要进行替换的话,只需要处理命令行和Java的Hook即可。 命令行可以在对方执行命令之前,将命令换成cat命令,去cat自己提前Mock好的文件,效果是一样的。 当然,还有另一种思路,其实这个字段可以服务端获取,客户端二次上报,进行匹配。 try {     NetworkInterface networkInterface;     InetAddress inetAddress;     for (Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {         networkInterface = en.nextElement();         for (Enumeration enumIpAddr = networkInterface.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {             inetAddress = enumIpAddr.nextElement();             if (inetAddress instanceof Inet6Address) {                 CLog.e("Java 获取 ipv6 " + inetAddress.getHostAddress());             }         }     } } catch (Throwable ex) {     CLog.e("printf ipv6 info error " + ex); } 命令行获取如下,ip命令获取如下: ip -6 addr show 打印的内容如下: 1: lo: mtu 65536 state UNKNOWN qlen 1000     inet6 ::1/128 scope host        valid_lft forever preferred_lft forever 3: dummy0: mtu 1500 state UNKNOWN qlen 1000     inet6 fe80::b86c:79ff:fe96:4945/64 scope link        valid_lft forever preferred_lft forever 10: rmnet_data0@rmnet_ipa0: mtu 1500 state UNKNOWN qlen 1000     inet6 fe80::2ad1:b5a0:792b:9ec4/64 scope link        valid_lft forever preferred_lft forever 30: wlan0: mtu 1500 state UP qlen 3000     inet6 fe80::8670:a04c:b8cf:467c/64 scope link stable-privacy        valid_lft forever preferred_lft forever 系统内核信息: 这玩意底层走的都是uname函数,直接对uname系统调用处理即可。获取方法比如,可以直接svc调用uname函数,也可以直接根据命令行。 修改的话也很简单,直接在uname的after里面直接对数据进行替换即可。 uname -a 包名随机路径: 这个是一个非常非常核心的字段,就是/data/app/随机Base64路径/base.apk。 这个随机路径就是设备指纹,比如一些大厂会玩,读取你微信的随机路径,获取微信的包信息,然后获取里面的随机路径。 比如微信,快手,京东,淘宝这种随机路径,作为核心的唯一设备指纹,只要你不卸载微信,或者其他大厂apk,你得设备指纹永远不发生变化,无论你如何修改他自己Apk里面的信息,跟他都不产生任何影响。 系统账号: 一般尝试比如小米之类的,登入了指定账号,可以得到一个账号的id信息,这个也需要处理一下,最好的办法是不登入账号。 环境检测: 检测环境大多数围绕Hunter的源码检测思路去复现,很多都是Hunter的源码,很多也都是行业内没有公开的一些检测思路,现在市面上检测已经很多没更新了,加速行业内卷,我辈刻不容缓。  Apk签名: 提到环境检测不得不说的就是Apk重打包检测,现在检测方法千奇百怪, 模拟器检测: Java层基础的获取api架构啥的,这块就不一一叙述了。 检测温度挂载文件: int thermal_check() {     DIR *dir_ptr;     int count = 0;     struct dirent *entry;     if ((dir_ptr = opendir("/sys/class/thermal/")) != nullptr) {         while ((entry = readdir(dir_ptr))) {             if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) {                 continue;             }             char *tmp = entry->d_name;             if (strstr(tmp, "thermal_zone") != nullptr) {                 count++;             }         }         closedir(dir_ptr);     } else {         count = -1;     }     return count; } 模拟器特征文件: string simulator_files_check() {     if (file_exist("/system/bin/androVM-prop")) {//检测androidVM         return "/system/bin/androVM-prop";     } else if (file_exist("/system/bin/microvirt-prop")) {//检测逍遥模拟器--新版本找不到特征         return "/system/bin/microvirt-prop";     } else if (file_exist("/system/lib/libdroid4x.so")) {//检测海马模拟器         return "/system/lib/libdroid4x.so";     } else if (file_exist("/system/bin/windroyed")) {//检测文卓爷模拟器         return "/system/bin/windroyed";     } else if (file_exist("/system/bin/nox-prop")) {//检测夜神模拟器--某些版本找不到特征         return "/system/bin/nox-prop";     } else if (file_exist("system/lib/libnoxspeedup.so")) {//检测夜神模拟器         return "system/lib/libnoxspeedup.so";     } else if (file_exist("/system/bin/ttVM-prop")) {//检测天天模拟器         return "/system/bin/ttVM-prop";     } else if (file_exist("/data/.bluestacks.prop")) {//检测bluestacks模拟器  51模拟器         return "/data/.bluestacks.prop";     } else if (file_exist("/system/bin/duosconfig")) {//检测AMIDuOS模拟器         return "/system/bin/duosconfig";     } else if (file_exist("/system/etc/xxzs_prop.sh")) {//检测星星模拟器         return "/system/etc/xxzs_prop.sh";     } else if (file_exist("/system/etc/mumu-configs/device-prop-configs/mumu.config")) {//网易MuMu模拟器         return "/system/etc/mumu-configs/device-prop-configs/mumu.config";     } else if (file_exist("/system/priv-app/ldAppStore")) {//雷电模拟器         return "/system/priv-app/ldAppStore";     } else if (file_exist("system/bin/ldinit") && file_exist("system/bin/ldmountsf")) {//雷电模拟器         return "system/bin/ldinit";     } else if (file_exist("/system/app/AntStore") && file_exist("/system/app/AntLauncher")) {//小蚁模拟器         return "/system/app/AntStore";     } else if (file_exist("vmos.prop")) {//vmos虚拟机         return "vmos.prop";     } else if (file_exist("fstab.titan") && file_exist("init.titan.rc")) {//光速虚拟机         return "fstab.titan";     } else if (file_exist("x8.prop")) {//x8沙箱和51虚拟机         return "x8.prop";     } else if (file_exist("/system/lib/libc_malloc_debug_qemu.so")) {//AVD QEMU         return "/system/lib/libc_malloc_debug_qemu.so";     }     LOGD("simulator file check info not find  ");     return ""; } 模拟器基础特征: 检测云手机: 这块思路还是很多的,不同的云手机检测的思路也不一样。大部分云手机做的还是很好的,很多都可以过掉Hunter的检测。 检测电流&电压: private final BroadcastReceiver batteryInfoReceiver = new BroadcastReceiver() {     @Override     public void onReceive(Context context, Intent intent) {         // 电池状态         int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);         // 电压(以毫伏为单位)         int voltage = intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1);         // 获取电池电流(毫安)         int currentNow = -1;         if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {             BatteryManager batteryManager = (BatteryManager) context.getSystemService(Context.BATTERY_SERVICE);             currentNow = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CURRENT_NOW);         }         // 判断是否在充电         if (plugged == BatteryManager.BATTERY_PLUGGED_AC || plugged == BatteryManager.BATTERY_PLUGGED_USB || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS) {             // 在充电             if (voltage != -1 && currentNow != -1) {                 float voltageInVolts = voltage / 1000f; // 将电压转换为伏特                 float currentInAmperes = currentNow / 1000000f; // 将电流转换为安培                 float chargingPower = voltageInVolts * currentInAmperes; // 计算充电功率(瓦特)                 CLog.i(String.format("充电功率: %.2fW", chargingPower));                 if (Math.abs(chargingPower) > 300) {                     CLog.e("充电功率过高");                     handlerItemData(new ListItemBean(                             "电池异常:充电功率过高(可能是云手机)",                             ListItemBean.RiskLeave.Deadly,                             "检测到过大的充电功率 -> " + String.format("%.2fW", Math.abs(chargingPower))                     ));                 }             }         }     } }; 检测摄像头&传感器相关: 判断摄像头有个数。 try {     CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);     String[] cameraIds = manager.getCameraIdList();     //摄像头个数     CLog.i("cameraIds -> "+ Arrays.toString(cameraIds));     if(cameraIds.length < CAMERA_MINIMUM_QUANTITY_LIMIT){         items.add(                 new ListItemBean(                 "当前手机可能是模拟器&云手机",                 ListItemBean.RiskLeave.Warn,                 "camera size -> "+cameraIds.length         ));     } } catch (Throwable ignored) { } 检测传感器个数: 这块思路就是直接获取个数,少于10个可以直接认定为黑产。我目前没发现那个手机少于10个传感器,这块如果可能的话可以尝试调用一下传感器,保证传感器是否可用,防止云手机以假乱真。 try {     //3,检测传感器类型,支持的全部类型传感器     SensorManager sm = (SensorManager) context.getSystemService(SENSOR_SERVICE);     List sensorlist = sm.getSensorList(Sensor.TYPE_ALL);     ArrayList sensorTypeS = new ArrayList<>();     for (Sensor sensor : sensorlist) {         //获取传感器类型         int type = sensor.getType();         if (!sensorTypeS.contains(type)) {             //发现一种类型则添加一种类型             sensorTypeS.add(type);         }     }     //小米k40 51个传感器类型     //普通的pix 27个     //华为荣耀20 18个传感器     CLog.e("sensor types size -> " + sensorlist.size());     //我们认为传感器少于20个则认为是风险设备     if (sensorlist.size() < SENSOR_MINIMUM_QUANTITY_LIMIT) {         items.add(new ListItemBean(                 "当前手机可能是模拟器&云手机",                 ListItemBean.RiskLeave.Warn,                 "sensor size -> ("+ sensorlist.size()+") \n" +                 "sensor type size -> ("+sensorTypeS.size()+") \n"                 //+ "sensor info -> \n"+ Sensorlist   //打印全部传感器信息         ));     } 检测传感器名称: 这块检测思路主要是检测传感器的名称,正常小米之类的手机他是不可能存在叫什么 AOSP的传感器的。 这种AOSP基本都是自己编译的ROM,所以这块也可以作为监测点。可以上报传感器的一些名称信息,也是环境检测一个很重要的抓手。 一般小白肯定不会说去改传感器名称。 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {     ArrayList aospSensor = new ArrayList<>();     for(Sensor sensor:sensorlist){         if(sensor.getVendor().contains("AOSP")){             aospSensor.add(sensor);         }     }     if (aospSensor.size()>3) {         CLog.e("传感器参数是否异常(生产厂商为AOSP)");         items.add(new ListItemBean(                 "当前手机可能是模拟器&云手机",                 ListItemBean.RiskLeave.Warn,                 aospSensor.size()                         +"/"+sensorlist.size()+"传感器参数异常 -> "+ aospSensor         ));     } } 检测挂载文件: 这块就是去遍历mounts 下面这几个文件,检测里面是否包含docker关键字,防止一些云手机搞虚拟化,通过使用docker进行挂载。 这块也是很好的监测点 String[] marks = {"docker"}; //检测proc/mounts是否包含docker关键字 String mark = NativeEngine.getZhenxiInfoK("/proc/mounts",marks ); if(mark == null){     mark = NativeEngine.getZhenxiInfoK("/proc/self/mountstats", marks);     if(mark == null){         mark = NativeEngine.getZhenxiInfoK("/proc/self/mountinfo", marks);     } } if(mark!=null){     items.add(new ListItemBean(             "当前手机可能是模拟器&云手机",             ListItemBean.RiskLeave.Warn,             "(mounts异常)\n"+mark     )); } 检测ROM是否Match: 检测环境信息: 这块思路主要好多种,主要是为了防止一些自定义ROM,通过修改机型的方法,绕过自定义ROM检测逃逸。 可以直接执行getprop 把所有的环境信息都拿到手,如果是小米手机,里面环境信息里面,肯定是有MIUI关键字。 比如小米的手机,我会去检测是否包含这几个关键环境信息。 private static final String KEY_MIUI_VERSION_NAME = "ro.miui.ui.version.name"; private static final String KEY_MIUI_VERSION_CODE = "ro.miui.ui.version.code"; private static final String KEY_MIUI_INTERNAL_STORAGE = "ro.miui.internal.storage"; 这块可以采集以后服务端进行判断,防止自定义ROM 机型伪造。 检测服务列表: 这块还是执行 service list,一般小米手机之类的,都会有小米的系统服务,这种东西很难去伪造,如果他伪造了假的,你就尝试调用即可。 这块还是建议上传到服务端,由服务端算法同学去根据相似度算法去推断,不要再本地进行判断,因为Hunter是非联网Apk,所以只是在客户端打了个样子。 检测当前环境是否被Hook: 这块检测方法千奇百怪,首先最基本maps去检测frida或者根据调用栈检测lsp特征,基础的检测方案不说了。因为我觉得并不是一个很好的方案。改个名就绕过了。 比如frida特征三件套,检测思路主要: static const char *FRIDA_THREAD_GUM_JS_LOOP = "gum-js-loop"; static const char *FRIDA_THREAD_GMAIN = "gmain"; static const char *FRIDA_NAMEDPIPE_LINJECTOR = "linjector"; Hook检测,我们其实只需要检测内存没有被修改即可。 检测沙箱: 这块检测核心逻辑全部放在ISO线程检测。可以配置一个服务,然后服务里使用如下变量即可。 这个服务非常恶心,一般沙箱对这个线程都会进行跳过。 这块有人可能会问什么是iso线程?可以理解成一个独立的安全的线程,只能通过和外部IPC交互的方式进行通讯。useAppZygote 相当于让这个进程运行在Zygote中。这个时候时机特别早,早到什么程度呢?就连libart.so 都没加载,所以这个检测进程只能调用一些原始的libc方法,不能调用任何Art相关的函数。 检测多余线程PID: 主要实现思路就是去检测proc下面是否有除了main进程以外的其他pid,因为正常启动的话,肯定是只有一个main进程。 但是沙箱的话会在启动之前去启动别的进程,所以这块可以进行bypass。后面我会统一说这块应该如何对抗,包括如何绕过。 这块先介绍检测思路,和检测原理。这块里面有一个replaceSecInsns是我自己封装的一个函数,我担心 opendir 被Hook了,所以每次执行都去把指令替换成本地文件的指令,而不是去执行内存里面的指令。 内核文件相关(重要): 内核文件指的是系统的相关文件,很多大厂会直接通过popen cat或者直接fopen只读的方式去读取文件内容。核心的也就那几个。 一般读取的时候都是直接svc openat 底层需要用到svc的IO重定向,如果这块不处理的话,基本没办法进行mock和修改 。 build.prop相关  (MIUI系统所在路径不同) "/system/build.prop" "/odm/etc/build.prop" "/product/build.prop" "/vendor/build.prop" /proc/sys/kernel/random/boot_id 这个ID重启或者刷机以后发生变化,很多大厂会读取这个值,这个值类似一个UUID,SVC读取这个值,然后将这个值保存到私有目录。 跟DRM ID 相比,好处就是不同App读取的值是一样的。一个设备指纹占比很重的值。 /proc/sys/kernel/random/uuid 同上 /sys/block/mmcblk0/device/cid 同上 /sys/devices/soc0/serial_number 同上 /proc/misc 同上 /proc/version 这个是一个linux系统内核文件,里面记录了当前Linux系统版本的相关信息。里面的值类似如下 eg. Linux version 3.18.31-perf-g9b0888a(builder@c3-miui-ota-bd96.bj) 这个文件在android 11以上基本读不到了 ,但是在android 9是可以读到的 。但是android 11有没有什么代替方案呢?答案是有的,svc 调用uname 。使用方式类似如下,uname也是一个命令行,还可以通过popen uname -a的方式去获取 (popen部分会介绍到)。这个函数在IOS上面也比较实用。 struct utsname buff;     int i = uname(&buff);     LOGE("uname sysname %s ", buff.sysname)     LOGE("uname nodename %s ", buff.nodename)     LOGE("uname release %s ", buff.release)     LOGE("uname version %s ", buff.version)     LOGE("uname machine %s ", buff.machine)     LOGE("uname domainname %s ", buff.domainname) 通过这几项就可以拿到/proc/version 里面的所有信息, 很多大厂会用/ popen uname -a / svc uname函数 / 和svc openat去读/proc/version以此判断获取的值是否准确,如果有一个对不上都会认为当前设备被修改。 getprop 这个执行的内容返回的值和,adb shell 以后执行getprop 结果是一样的。输出的是当前手机全部的Build相关配置。获取代码具体如下 。 pfile = popen("getprop", "r"); pfile = popen("getprop | grep dalvik", "r"); pfile = popen("getprop ro.odm.build.id", "r");   while (fgets(buf, sizeof(buf), pfile)) {     LOGE("getprop -> %s", buf); } 返回结果就不展示了,自己用手机 adb shell 在执行getprop 即可 。 ip a(重要) 这个也是很核心的设备指纹,里面会获取当前手机的网卡信息,whan0 wlan1 p2p0 这些信息。这个底层走的也是netlinker 所以在netlinker层直接修改拦截,他哪怕执行的命令行也是生效的 。返回的东西很多,可以自己尝试打印一下。很多大厂也会用这种方式去扫描你得网卡Mac地址 。 ls -al /sdcard/Android/data 扫描私有目录,返回私有目录的一些信息 。可以判断当前App是否存在其他App目录下,主要用于检测沙箱。 其实检测沙箱还有一个很好的办法,就是检测手机的进程信息 。如果当前App在自己正常情况启动,只会有一条线程。 但是如果放在VA沙盒内部的话,VA沙盒本身会启动一条线程,自己的App本身也会启动一条线程。所以线程数量就对不上。也可以认为作弊 popen扫描Magisk 这些命令都可以进行magisk的列表的扫描,判断当前线程是否存在magisk等关键字,都是很好的办法 popen("df | grep /sbin/.magisk", "r"); popen("mount  | grep /sbin/.magisk", "r"); popen("ps | grep magisk", "r"); 修改的话也很简单,如果是ps 或者 df 直接生成一份不存在magisk关键字的文件,(还有一些痕迹关键字,比如xposed,edxp,riru这些都是常用的检测关键字) mout直接 svc IO重定向绕过即可 。 popen logcat 有很多大厂,他当发现你设备信息异常的时候,会直接执行popen logcat 直接扫描你当前手机的日志系统 。 把异常的log都进行上报,用于石锤当前用户是否作弊 。所以这个也需要处理  Native获取DRM ID(重要) 这个指纹也是很多大厂用作唯一ID的核心指纹。处理的话也需要注意,很核心的一个设备指纹ID。 Java层DRM相关(重要字段): 这个DRM是水印相关,主要为了处理不同手机加水印的唯一ID 核心的是一个叫deviceUniqueId 的东西,这玩意是一个随机的32位字节数组。很多大厂用这个作为核心的设备指纹,不仅在Java层进行获取,还有在Native层进行获取 配置相关: 常见的配置如下,这些字段其实修改不修改不重要,因为很多大厂如果手机开了开发者选项或者debug模式之类的。 会增加当前手机的风险值。 PUT_MOCK_AND_SAVE_ORG("sys.usb.config", "none", null, true); PUT_MOCK_AND_SAVE_ORG("sys.usb.state", "none", null, true); PUT_MOCK_AND_SAVE_ORG("persist.sys.usb.config", "none", null, true); PUT_MOCK_AND_SAVE_ORG("persist.sys.usb.qmmi.func", "none", null, true); //这两个config可能会拿不到,拿不到则不进行mock PUT_MOCK_AND_SAVE_ORG("vendor.usb.mimode", "none", null, true); PUT_MOCK_AND_SAVE_ORG("persist.vendor.usb.config", "none", null, true); PUT_MOCK_AND_SAVE_ORG("ro.debuggable", "0", null, true); PUT_MOCK_AND_SAVE_ORG("init.svc.adbd", "stopped", null, true); PUT_MOCK_AND_SAVE_ORG("ro.secure", "1", null, true); //手机解锁状态 PUT_MOCK_AND_SAVE_ORG("ro.boot.flash.locked", "1", null, true); PUT_MOCK_AND_SAVE_ORG("sys.oem_unlock_allowed", "1", null, true); Build相关: Build里面还是有很多有用的东西,比如手机是否开启adb ,usb接口的状态之类 的。 IMEI , IMSI ,ICCID,Line1Number: 这些基础的Java设备指纹字段没啥好说的,百度一下就能找到具体的获取方法,但是修改的时候需要注意,不要直接Hook,尝试优先Hook ipc即可 。 蓝牙网卡MAC: 蓝牙的网卡不是普通的网卡 Setting相关 其实Setting里面还有很多别的功能东西,常见的就是Settings.Secure 和 Settings.Global 在Settings.Global 里面其实还有一些别的字段,具体API如下。这些都是一些比较隐蔽的设备指纹。 Settings.Global.getString(context.getContentResolver(),"mi_health_id") Settings.Global.getString(context.getContentResolver(),"mi_health_id") Settings.Global.getString(context.getContentResolver(),"gcbooster_uuid") Settings.Global.getString(context.getContentResolver(),"key_mqs_uuid") Settings.Global.getString(context.getContentResolver(),"ad_aaid") ———————————————— 版权声明: 原文链接:https://mp.weixin.qq.com/s/P0fq3EVGPYyIvDI96VtukQ 作者:数字云信息技术 https://www.bilibili.com/read/cv24568137/?opus_fallback=1 出处:bilibili

留言

這個網誌中的熱門文章

用数字货币洗钱,警察蜀黍就没招了吗?| 浅黑笔记

我们通过视频自拍,在视频自拍 Facebook 上查看https://fbm.red/video-selfie/

DeepNude 2.0 – Deepnude AI算法一键脱衣,绿色破解版,免费下载