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

2023-11-12

前言:

现在大厂的设备指纹层出不穷,但是想要确保稳定性和唯一性高精准其实也挺难的一件事,有的是通过设备信息比重进行的设备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 idString 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, "<init>", "(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 "<<i;


char buffer[1024];FILE *fp = popen("stat -f /storage/emulated/0", "r");if (fp != nullptr) {    while (fgets(buffer, sizeof(buffer), fp) != nullptr) {        //LOGI("ps -ef %s",buffer)        LOG(INFO) << "stat -f /storage/emulated/0" << buffer;    }    pclose(fp);}

struct statfs64 buf={};if (statfs64("/storage/emulated/0", &buf) == -1) {    LOG(ERROR) << "statfs64系统信息失败";    return;}LOG(INFO) << "f_type (文件系统类型): " << buf.f_type;LOG(INFO) << "f_bsize (块大小): " << buf.f_bsize;LOG(INFO) << "f_blocks (总数据块): " << buf.f_blocks;LOG(INFO) << "f_bfree (空闲块): " << buf.f_bfree;LOG(INFO) << "f_bavail (非特权用户可用的空闲块): " << buf.f_bavail;LOG(INFO) << "f_files (总文件节点数): " << buf.f_files;LOG(INFO) << "f_ffree (空闲文件节点数): " << buf.f_ffree;LOG(INFO) << "f_fsid (文件系统 ID): " << buf.f_fsid.__val[0] << ", " << buf.f_fsid.__val[1];LOG(INFO) << "f_namelen (最大文件名长度): " << buf.f_namelen;

,Hook的Java方法,但是发现Native层并不能全量拦截,

上面这种方式只适合Java获取,


Mac地址:

这个没啥好说的,基础字段,Java层获取,netlink获取,命令行获取。读文件获取,四种获取方法,和上面类似,直接在svc的 recvmsg,recv,recvfrom的after进行数据包替换即可

如果判断是netlink的消息,并且是获取网卡类型直接对里面的数据包解析和替换即可。​​​​​​​

case SC_recvmsg: {    //LOGI("start handle SC_recvmsg systexit after")    if (isMockFingerptint()) {        NetlinkMacHandler::netlinkHandler_recmsg(tracee);    }    break;}case SC_recv:case SC_recvfrom: {    //LOGE("start handle SC_recvfrom systexit after")    //recv底层走的recvfrom,所以不需要处理recvfrom    if (isMockFingerptint()) {        NetlinkMacHandler::netlinkHandler_recv(tracee);    }    break;}

在读文件获取这块因为网卡信息已经在内存里面,所以直接IO重定向过去即可。

常用的获取网卡信息的文件,以wlan0为例子,场景的获取目录如下:可以cat获取,也可以直接读文件。​​​​​​​

/sys/class/net/wlan0/address/sys/devices/virtual/net/wlan0/address...

附近网卡信息:

这个字段主要是监控群控的一些信息的,主要作用是获取当前wifi 附近的人MAC信息的。

比如大厂一般检测群控的手段就是获取附近的网卡,如果有聚集性就可以认为是群控。获取的方式也也跟上面一样,五种获取方法。

获取方法底层也是和MAC获取方法一样,底层都是netlink,比如可以直接执行 popen获取。

popen("ip neigh show", "r");

也可以直接直接读文件,路径如下:

/proc/net/arp

还可以直接netlink获取,在收到消息以后判断消息类型是 hdr->nlmsg_type == RTM_NEWNEIGH 直接进行替换即可。


IPV6:

设个设备指纹也是很核心的设备指纹,这个玩意底层获取也是netlink,但是netlink获取,但是这块处理很不好处理,我暂时也没进行处理。

常用的获取方式比如,Java获取,命令获取。如果需要进行替换的话,只需要处理命令行和Java的Hook即可。

命令行可以在对方执行命令之前,将命令换成cat命令,去cat自己提前Mock好的文件,效果是一样的。

当然,还有另一种思路,其实这个字段可以服务端获取,客户端二次上报,进行匹配。​​​​​​​

try {    NetworkInterface networkInterface;    InetAddress inetAddress;    for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {        networkInterface = en.nextElement();        for (Enumeration<InetAddress> 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: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN qlen 1000    inet6 ::1/128 scope host       valid_lft forever preferred_lft forever3: dummy0: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1500 state UNKNOWN qlen 1000    inet6 fe80::b86c:79ff:fe96:4945/64 scope link       valid_lft forever preferred_lft forever10: rmnet_data0@rmnet_ipa0: <UP,LOWER_UP> mtu 1500 state UNKNOWN qlen 1000    inet6 fe80::2ad1:b5a0:792b:9ec4/64 scope link       valid_lft forever preferred_lft forever30: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> 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 "";}

模拟器基础特征:

      public static ListItemBean checkEmulator(Context context) {        ArrayList<String> choose = new ArrayList<>();//        try {//            String[] strArr = {//                    "/boot/bstmods/vboxguest.ko",//                    "/boot/bstmods/vboxsf.ko",//                    "/dev/mtp_usb",//                    "/dev/qemu_pipe",//                    "/dev/socket/baseband_genyd",//                    "/dev/socket/genyd",//                    "/dev/socket/qemud",//                    "/dev/socket/windroyed-audio",//                    "/dev/socket/windroyed-camera",//                    "/dev/socket/windroyed-gps",//                    "/dev/socket/windroyed-sensors",//                    "/dev/vboxguest",//                    "/dev/vboxpci",//                    "/dev/vboxuser",//                    "/fstab.goldfish",//                    "/fstab.nox",//                    "/fstab.ranchu-encrypt",//                    "/fstab.ranchu-noencrypt",//                    "/fstab.ttVM_x86",//                    "/fstab.vbox86",//                    "/init.goldfish.rc",//                    "/init.magisk.rc",//                    "/init.nox.rc",//                    "/init.ranchu-encrypt.rc",//                    "/init.ranchu-noencrypt.rc",//                    "/init.ranchu.rc",//                    "/init.ttVM_x86.rc",//                    "/init.vbox86.rc",//                    "/init.vbox86p.rc",//                    "/init.windroye.rc",//                    "/init.windroye.sh",//                    "/init.x86.rc",//                    "/proc/irq/20/vboxguest",//                    "/sdcard/Android/data/com.redfinger.gamemanage",//                    "/stab.andy",//                    "/sys/bus/pci/drivers/vboxguest",//                    "/sys/bus/pci/drivers/vboxpci",//                    "/sys/bus/platform/drivers/qemu_pipe",//                    "/sys/bus/platform/drivers/qemu_pipe/qemu_pipe",//                    "/sys/bus/platform/drivers/qemu_trace",//                    "/sys/bus/virtio/drivers/itolsvmlgtp",//                    "/sys/bus/virtio/drivers/itoolsvmhft",//                    "/sys/class/bdi/vboxsf-1",//                    "/sys/class/bdi/vboxsf-2",//                    "/sys/class/bdi/vboxsf-3",//                    "/sys/class/misc/qemu_pipe",//                    "/sys/class/misc/vboxguest",//                    "/sys/class/misc/vboxuser",//                    "/sys/devices/platform/qemu_pipe",//                    "/sys/devices/virtual/bdi/vboxsf-1",//                    "/sys/devices/virtual/bdi/vboxsf-2",//                    "/sys/devices/virtual/bdi/vboxsf-3",//                    "/sys/devices/virtual/misc/qemu_pipe",//                    "/sys/devices/virtual/misc/vboxguest",//                    "/sys/devices/virtual/misc/vboxpci",//                    "/sys/devices/virtual/misc/vboxuser",//                    "/sys/fs/selinux/booleans/in_qemu",//                    "/sys/kernel/debug/bdi/vboxsf-1",//                    "/sys/kernel/debug/bdi/vboxsf-2",//                    "/sys/kernel/debug/x86",//                    "/sys/module/qemu_trace_sysfs",//                    "/sys/module/vboxguest",//                    "/sys/module/vboxguest/drivers/pci:vboxguest",//                    "/sys/module/vboxpcism",//                    "/sys/module/vboxsf",//                    "/sys/module/vboxvideo",//                    "/sys/module/virtio_pt/drivers/virtio:itoolsvmhft",//                    "/sys/module/virtio_pt_ie/drivers/virtio:itoolsvmlgtp",//                    "/sys/qemu_trace",//                    "/system/app/GenymotionLayout",//                    "/system/bin/OpenglService",//                    "/system/bin/androVM-vbox-sf",//                    "/system/bin/droid4x",//                    "/system/bin/droid4x-prop",//                    "/system/bin/droid4x-vbox-sf",//                    "/system/bin/droid4x_setprop",//                    "/system/bin/enable_nox",//                    "/system/bin/genymotion-vbox-sf",//                    "/system/bin/microvirt-prop",//                    "/system/bin/microvirt-vbox-sf",//                    "/system/bin/microvirt_setprop",//                    "/system/bin/microvirtd",//                    "/system/bin/mount.vboxsf",//                    "/system/bin/nox",//                    "/system/bin/nox-prop",//                    "/system/bin/nox-vbox-sf",//                    "/system/bin/nox_setprop",//                    "/system/bin/noxd",//                    "/system/bin/noxscreen",//                    "/system/bin/noxspeedup",//                    "/system/bin/qemu-props",//                    "/system/bin/qemud",//                    "/system/bin/shellnox",//                    "/system/bin/ttVM-prop",//                    "/system/bin/windroyed",//                    "/system/droid4x",//                    "/system/etc/init.droid4x.sh",//                    "/system/etc/init.tiantian.sh",//                    "/system/lib/egl/libEGL_emulation.so",//                    "/system/lib/egl/libEGL_tiantianVM.so",//                    "/system/lib/egl/libEGL_windroye.so",//                    "/system/lib/egl/libGLESv1_CM_emulation.so",//                    "/system/lib/egl/libGLESv1_CM_tiantianVM.so",//                    "/system/lib/egl/libGLESv1_CM_windroye.so",//                    "/system/lib/egl/libGLESv2_emulation.so",//                    "/system/lib/egl/libGLESv2_tiantianVM.so",//                    "/system/lib/egl/libGLESv2_windroye.so",//                    "/system/lib/hw/audio.primary.vbox86.so",//                    "/system/lib/hw/audio.primary.windroye.so",//                    "/system/lib/hw/audio.primary.x86.so",//                    "/system/lib/hw/autio.primary.nox.so",//                    "/system/lib/hw/camera.vbox86.so",//                    "/system/lib/hw/camera.windroye.jpeg.so",//                    "/system/lib/hw/camera.windroye.so",//                    "/system/lib/hw/camera.x86.so",//                    "/system/lib/hw/gps.nox.so",//                    "/system/lib/hw/gps.vbox86.so",//                    "/system/lib/hw/gps.windroye.so",//                    "/system/lib/hw/gralloc.nox.so",//                    "/system/lib/hw/gralloc.vbox86.so",//                    "/system/lib/hw/gralloc.windroye.so",//                    "/system/lib/hw/sensors.nox.so",//                    "/system/lib/hw/sensors.vbox86.so",//                    "/system/lib/hw/sensors.windroye.so",//                    "/system/lib/init.nox.sh",//                    "/system/lib/libGM_OpenglSystemCommon.so",//                    "/system/lib/libc_malloc_debug_qemu.so",//                    "/system/lib/libclcore_x86.bc",//                    "/system/lib/libdroid4x.so",//                    "/system/lib/libnoxd.so",//                    "/system/lib/libnoxspeedup.so",//                    "/system/lib/modules/3.10.30-android-x86.hd+",//                    "/system/lib/vboxguest.ko",//                    "/system/lib/vboxpcism.ko",//                    "/system/lib/vboxsf.ko",//                    "/system/lib/vboxvideo.ko",//                    "/system/lib64/egl/libEGL_emulation.so",//                    "/system/lib64/egl/libGLESv1_CM_emulation.so",//                    "/system/lib64/egl/libGLESv2_emulation.so",//                    "/vendor/lib64/egl/libEGL_emulation.so",//                    "/vendor/lib64/egl/libGLESv1_CM_emulation.so",//                    "/vendor/lib64/egl/libGLESv2_emulation.so",//                    "/vendor/lib64/libandroidemu.so",//                    "/system/lib64/hw/gralloc.ranchu.so",//                    "/system/lib64/libc_malloc_debug_qemu.so",//                    "/system/usr/Keylayout/droid4x_Virtual_Input.kl",//                    "/system/usr/idc/Genymotion_Virtual_Input.idc",//                    "/system/usr/idc/droid4x_Virtual_Input.idc",//                    "/system/usr/idc/nox_Virtual_Input.idc",//                    "/system/usr/idc/windroye.idc",//                    "/system/usr/keychars/nox_gpio.kcm",//                    "/system/usr/keychars/windroye.kcm",//                    "/system/usr/keylayout/Genymotion_Virtual_Input.kl",//                    "/system/usr/keylayout/nox_Virtual_Input.kl",//                    "/system/usr/keylayout/nox_gpio.kl",//                    "/system/usr/keylayout/windroye.kl",//                    "system/etc/init/ndk_translation_arm64.rc",//                    "/system/xbin/noxsu",//                    "/ueventd.android_x86.rc",//                    "/ueventd.andy.rc",//                    "/ueventd.goldfish.rc",//                    "/ueventd.nox.rc",//                    "/ueventd.ranchu.rc",//                    "/ueventd.ttVM_x86.rc",//                    "/ueventd.vbox86.rc",//                    "/vendor/lib64/libgoldfish-ril.so",//                    "/vendor/lib64/libgoldfish_codecs_common.so",//                    "/vendor/lib64/libstagefright_goldfish_avcdec.so",//                    "/vendor/lib64/libstagefright_goldfish_vpxdec.so",//                    "/x86.prop"//            };//            for (int i = 0; i < 7; i++) {//                String f = strArr[i];//                if (new File(f).exists())//                    choose.add(f);//            }//        } catch (Exception e) {//            e.printStackTrace();//        }
        try {            String[] myArr = {                    "generic",                    "vbox"            };            for (String str : myArr) {                if (Build.FINGERPRINT.contains(str))                    choose.add(Build.FINGERPRINT);            }        } catch (Exception e) {            e.printStackTrace();        }
        try {            String[] myArr = {                    "google_sdk",                    "emulator",                    "android sdk built for",                    "droid4x"            };            for (String str : myArr) {                if (Build.MODEL.contains(str))                    choose.add(Build.MODEL);            }        } catch (Exception e) {            e.printStackTrace();        }
        try {            String[] myArr = {                    "Genymotion"            };            for (String str : myArr) {                if (Build.MANUFACTURER.contains(str))                    choose.add(Build.MANUFACTURER);            }        } catch (Exception e) {            e.printStackTrace();        }
        try {            String[] myArr = {                    "google_sdk", "sdk_phone", "sdk_x86", "vbox86p", "nox"            };            for (String str : myArr) {                if (Build.PRODUCT.toLowerCase(Locale.ROOT).contains(str))                    choose.add(Build.PRODUCT);            }        } catch (Exception e) {            e.printStackTrace();        }
        try {            String[] myArr = {                    "nox"            };            for (String str : myArr) {                if (Build.BOARD.toLowerCase(Locale.ROOT).contains(str))                    choose.add(Build.BOARD);            }        } catch (Exception e) {            e.printStackTrace();        }
        try {            String[] myArr = {                    "nox"            };            for (String str : myArr) {                if (Build.BOOTLOADER.toLowerCase(Locale.ROOT).contains(str))                    choose.add(Build.BOOTLOADER);            }        } catch (Exception e) {            e.printStackTrace();        }
        try {            String[] myArr = {                    "ranchu", "vbox86", "goldfish"            };            for (String str : myArr) {                if (Build.HARDWARE.equalsIgnoreCase(str))                    choose.add(Build.HARDWARE);            }        } catch (Exception e) {            e.printStackTrace();        }
        try {            Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();            while (networkInterfaces.hasMoreElements()) {                NetworkInterface ele = networkInterfaces.nextElement();                if (ele != null) {                    Enumeration<InetAddress> inetAddresses = ele.getInetAddresses();                    while (inetAddresses.hasMoreElements()) {                        InetAddress nextElement = inetAddresses.nextElement();                        if (!nextElement.isLoopbackAddress() &&                                (nextElement instanceof Inet4Address)) {                            String ip = nextElement.getHostAddress();                            if (ip == null) continue;                            if (ip.equalsIgnoreCase("10.0.2.15") ||                                    ip.equalsIgnoreCase("10.0.2.16")) {                                choose.add(ip);                            }                        }                    }                }            }        } catch (Exception e) {            e.printStackTrace();        }
//        try {//            String[] qemuProps = {//                    "ro.kernel.qemu.avd_name",//                    "ro.kernel.qemu.gles",//                    "ro.kernel.qemu.gltransport",//                    "ro.kernel.qemu.opengles.version",//                    "ro.kernel.qemu.uirenderer",//                    "ro.kernel.qemu.vsync",//                    "ro.qemu.initrc",//                    "init.svc.qemu-props",//                    "qemu.adb.secure",//                    "qemu.cmdline",//                    "qemu.hw.mainkeys",//                    "qemu.logcat",//                    "ro.adb.qemud",//                    "qemu.sf.fake_camera",//                    "qemu.sf.lcd_density",//                    "qemu.timezone",//                    "init.svc.goldfish-logcat",//                    "ro.boottime.goldfish-logcat",//                    "ro.hardware.audio.primary",//                    "init.svc.ranchu-net",//                    "init.svc.ranchu-setup",//                    "ro.boottime.ranchu-net",//                    "ro.boottime.ranchu-setup",//                    "init.svc.droid4x",//                    "init.svc.noxd",//                    "init.svc.qemud",//                    "init.svc.goldfish-setup",//                    "init.svc.goldfish-logcat",//                    "init.svc.ttVM_x86-setup",//                    "vmos.browser.home",//                    "vmos.camera.enable",//                    "ro.trd_yehuo_searchbox",//                    "init.svc.microvirtd",//                    "init.svc.vbox86-setup",//                    "ro.ndk_translation.version",//                    "redroid.width",//                    "redroid.height",//                    "redroid.fps",//                    "ro.rf.vmname"//            };////            for (String str : qemuProps) {//                String val = SystemPropertiesUtils.getProperty(str, null);//                if (val != null) {//                    choose.add(str);//                }//            }//        } catch (Throwable e) {//            e.printStackTrace();//        }        //判断是否存在指定硬件        PackageManager pm = null;        try {            pm = context.getPackageManager();            String[] features = {                    //PackageManager.FEATURE_RAM_NORMAL,//这个存在问题,自己组装的手机可能导致这个痕迹找不到                    PackageManager.FEATURE_BLUETOOTH,                    PackageManager.FEATURE_CAMERA_FLASH,                    PackageManager.FEATURE_TELEPHONY            };            for (String feature : features) {                if (!pm.hasSystemFeature(feature)) {                    choose.add(feature);                }            }        } catch (Throwable ignored) {    }
    try {        String[] emuPkgs = {                "com.google.android.launcher.layouts.genymotion",                "com.bluestacks",                "com.bignox.app"        };
        for (String pkg : emuPkgs) {            try {                if (pm != null) {                    pm.getPackageInfo(pkg, 0);                }                choose.add(pkg);            } catch (Throwable e) {                //e.printStackTrace();            }        }    } catch (Throwable ignored) {
    }
    try {        SensorManager sensor = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);        int sensorSize = sensor.getSensorList(Sensor.TYPE_ALL).size();        for (int i = 0; i < sensorSize; i++) {            Sensor s = sensor.getDefaultSensor(i);            if (s != null && s.getName().contains("Goldfish")) {                choose.add(s.getName());            }        }    } catch (Throwable ignored) {
    }
    try {        if (checkSelfPermission(context, "android.permission.READ_SMS") == 0 ||                checkSelfPermission(context, "android.permission.READ_PHONE_NUMBERS") == 0 ||                    checkSelfPermission(context, "android.permission.READ_PHONE_STATE") == 0) {            TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);            String phoneNumber = telephonyManager.getLine1Number();
            String[] phoneNumbers = {                    "15555215554",                    "15555215556",                    "15555215558",                    "15555215560",                    "15555215562",                    "15555215564",                    "15555215566",                    "15555215568",                    "15555215570",                    "15555215572",                    "15555215574",                    "15555215576",                    "15555215578",                    "15555215580",                    "15555215582",                    "15555215584"            };            if(phoneNumber!=null) {                for (String phone : phoneNumbers) {                    if (phoneNumber.equalsIgnoreCase(phone)) {                        choose.add(phone);                        break;                    }                }            }        }    } catch (Exception e) {        e.printStackTrace();    }
    if (choose.size() > 0) {        ListItemBean item = new ListItemBean("检测到APK运行在虚拟机&模拟器中",                ListItemBean.RiskLeave.Deadly,                choose.toString()        );        for (String str : choose) {            item.putData(str);        }        return item;    }    return null;}

检测云手机:

这块思路还是很多的,不同的云手机检测的思路也不一样。大部分云手机做的还是很好的,很多都可以过掉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<Sensor> sensorlist = sm.getSensorList(Sensor.TYPE_ALL);
    ArrayList<Integer> 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<Sensor> 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线程检测。可以配置一个服务,然后服务里使用如下变量即可。

​​​​​​​

<service    android:name=".ZhenxiServer"    android:isolatedProcess="true"    android:useAppZygote="true"    />

这个服务非常恶心,一般沙箱对这个线程都会进行跳过。

这块有人可能会问什么是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可能会拿不到,拿不到则不进行mockPUT_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")

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

软件大厂,环境检测思路和规避思路,安卓改机应该改什么数据和参数,安卓boot内核修改 环境检测对抗 部分参数解析 的相关文章

随机推荐

  • 封校大学生无聊玩起图像大找茬——游戏脚本(一起领略Python脚本的风采吧)

    一个帅气的boy 你可以叫我Love And Program 个人主页 Love And Program的个人主页 如果对你有帮助的话希望三连 支持一下博主 图像大找茬 游戏脚本项目地址 图像大找茬 前言 基础知识 图片找茬 抓取句柄图片
  • Python爬虫:百度数据轻松抓取!

    百度是全球最大的中文搜索引擎 每天都有海量的数据被用户输入和查询 这些数据蕴含着巨大的商业价值 作为一名数据分析师或者算法工程师 如何利用这些数据来提升工作效率和商业竞争力呢 这时候 我们需要一种叫做 爬虫 的技术手段来帮助我们 本文将介绍
  • 关于vuepress打包之后页面样式丢失问题两种解决方案

    问题描述 最近打算使用vuepress为公司项目集成一下前端开发文档 在打包的时候遇到了样式丢失的问题 在网络上参考了一些解决方案 记录一下自己遇到的问题 有什么不足的地方多多指教 集成打包之后 打开入口文件展示页面如下 在本地直接运行的页
  • Java项目:眼镜商城系统(java+SSM+JSP+jQuery+Mysql)

    源码获取 俺的博客首页 资源 里下载 项目介绍 管理员角色包含以下功能 管理员登录 管理员管理 管理商城会员 新闻公告管理 眼睛类型管理 城市信息管理 连锁配镜店管理 眼镜商品管理 用户订单管理 管理用户的评价信息等功能 用户角色包含以下功
  • 蓝桥杯2013年第四届真题-公式求值

    题目描述 输入n m k 输出下面公式的值 其中C n m是组合数 表示在n个人的集合中选出m个人组成一个集合的方案数 组合数的计算公式如下 输入格式 输入的第一行包含一个整数n 第二行包含一个整数m 第三行包含一个整数k 数据规模和约定
  • 通过H5(浏览器/WebView/其他)唤起本地app

    前两天接到一个无线的需求 我这个小白可是忙活了好几天 在页面上有一个连接 如果用户安装了APP 则点击打开对应的APP如果用户没有安装 则点击打开对应的设置连接 上网搜索了一下 基本都说可以实现 但是实际情况却不乐观 当然只是其中的一个需求
  • Http的body变空格的问题解决方案

    最近在做iOS的内购功能 需要把内购的凭证转化为base64传给服务器 服务器再去AppStore的接口进行二次验证 这中间有一个问题是base64编码的字符串里有 号 这样的字符 传到服务器上 号 字符就变成空格字符了 原因是我们在进行h
  • 建设数据仓库的八个步骤

    摘要 建立数据仓库是一个解决企业问题的过程 业务人员往往不懂如何建立和使用数据仓库 发挥其决策支持的作用 信息部门的人员往往又不懂业务 不知道应该建立哪些决策主题 关键词 数据仓库 元数据 建设数据仓库 建立数据仓库是一个解决企业问题的过程
  • Windows下搭建FTP服务器

    一 什么是ftp FTP 是File Transfer Protocol 文件传输协议 的英文简称 而中文简称为 文传协议 用于Internet上的控制文件的双向传输 同时 它也是一个应用程序 Application 基于不同的操作系统有不
  • 课程笔记1

    一 密码学原理 1 密码学中的哈希函数被称为cryptographic hash function 它具有三点性质 1 哈希碰撞 collision resistance 对于不相等的x和y 对应的哈希值H x H y 没有有效的办法人为地
  • VMWare Fusion虚拟机安装与配置教程

    很多时候 我们都有用虚拟机的需求 比如用着Mac突然有一个软件只支持Windows 并且还需要与macOS上的软件搭配使用 况且你没有Windows电脑 这个时候虚拟机就能帮上大忙 在macOS上 笔者用的是MacBook Air 所以这里
  • 刷脸支付不需要媒介将进一步推动消费升级

    从现金 银行卡 到现在的手机支付移动支付 支付媒介不断发生变化 并最终以手机这样的通用媒介代替了现金 银行卡这样的专用媒介 同时也是一个逐渐脱媒的过程 现在支付宝主推的刷脸支付则相当于在用户端完全不再需要媒介 这也将进一步推动消费升级 4月
  • Vue中如何定义一个全局变量(Trick)

    img class lazyload lazybanner 页面中图片使用懒加载 默认图片想通过全局变量实现 实现方案 Vue filter default img function str return 你的图片路径 img class
  • 【CSDN竞赛第17期】简要题解 92.5分

    目录 1 判断胜负 简单字符串 题目 题解 比赛时代码 2 买铅笔 简单算数 题目 题解 代码 3 拯救爱情 得分70 题目 题解 比赛时代码 4 拯救公主 中国剩余定理 或 模拟 题目 题解 模拟 中国剩余定理 比赛时代码 1 判断胜负
  • mongo 复制一个表的数据到另一个表中

    club表 id ObjectId 592e94fee820cc1813f0b9a2 id 1 name test club preload 表 id ObjectId 592e94fee820cc1813f07383 club id 1
  • 使用python爬取英雄联盟官方英雄皮肤图片

    前言 本文的文字及图片来源于网络 仅供学习 交流使用 不具有任何商业用途 版权归原作者所有 如有问题请及时联系我们以作处理 PS 如有需要Python学习资料的小伙伴可以加点击下方链接自行获取 python免费学习资料以及群交流解答点击即可
  • EA使用教程

    文章目录 创建新工程 属性设置 导出图片到剪切板 时序图中取消消息后面自动生成的括号 在文本框中回车 取消流程图的背景渐变 导出更清晰图片 组合片段 设置字体和字体大小 官方教程地址 https sparxsystems cn enterp
  • 详解C#中的反射

    反射 Reflection 2008年01月02日 星期三 11 21 两个现实中的例子 1 B超 大家体检的时候大概都做过B超吧 B超可以透过肚皮探测到你内脏的生理情况 这是如何做到的呢 B超是B型超声波 它可以透过肚皮通过向你体内发射B
  • Web开发中的AJAX技术介绍

    读音 e j ks AJAX即 Asynchronous JavaScript and XML 异步JavaScript和XML AJAX并非缩写词 而是由Jesse James Gaiiett创造的名词 是指一种创建交互式网页应用的网页开
  • 软件大厂,环境检测思路和规避思路,安卓改机应该改什么数据和参数,安卓boot内核修改 环境检测对抗 部分参数解析

    前言 现在大厂的设备指纹层出不穷 但是想要确保稳定性和唯一性高精准其实也挺难的一件事 有的是通过设备信息比重进行的设备ID唯一值确认 比如A设备信息占比10 B设备信息占比20 当比重超过60 以上 设备指纹才会发生变化 这样的好处就是当你