android模拟器检测常用方法,Android模拟器检测方案优化

时间:2025-03-09 08:13:05

Android模拟器检测方案

项目背景:由于美柚当前检测模拟器的方案存在识别率(指模拟器没被检测出来)和准确率(错误地将真机判定为模拟器)都比较低的情况。导致黑产猖獗,所以提出该方案用于改善检测 Android 模拟器的识别率和准确率

PS: 经过309台真机测试

目前市面上所有的模拟器如下:

Android模拟器名

支持平台

Android内核版本

CPU架构方式

Adb连接方式

Android原生模拟器

Mac、 Linux 、Windows

All

x86

adb connect 127.0.0.1:5555

Genymotion

Mac、Linux、Windows

All

x86

adb connect 127.0.0.1:5555

海马玩模拟器(Droid4X)

Windows、Mac

4.2.2

x86

adb connect 127.0.0.1:26944

Bluestacks(蓝叠)模拟器

Windows

4.4.2

arm

adb connect 127.0.0.1:5555

雷电模拟器

Windows

5.1.1

x86

adb connect 127.0.0.1:5555

逍遥安卓模拟器

Windows

5.1.1

x86

adb connect 127.0.0.1:21503

天天模拟器

Windows

4.4.4、6.0

arm

adb connect 127.0.0.1:6555

网易MuMu安卓模拟器

Mac、Windows(为荒野求生)

6.0.1

x86

adb connect 127.0.0.1:7555

夜神安卓模拟器

Windows、Mac

4.4.2

x86

adb connect 127.0.0.1:62001

靠谱助手模拟器 / 猩猩助手模拟器

Windows

4.4.2、4.4.4、6.0

x86、arm

蓝叠、天天模拟器集成的

PS:后面的测试都会基于这些模拟器的基础上做测试,并且附加条件范围内能拿到的其他Android真机

目前市场上主流的模拟器:一种是基于Qemu,另一类是基于Genymotion

网上现在流行用一些模拟器特征进行鉴别,比如:

1.通过判断IMEI是否全部为0000000000格式,判断手机号码是否特殊的模拟器值,判断IMSI是否特殊值

2.判断Build中的一些模拟器特征值

3.匹配Qemu的一些特征文件以及属性

4.通过获取cpu信息,将x86的给过滤掉(真机一般都是基于ARM)

一、Android模拟器初级检测方案(基础特征值检测[原APP中只检测了部分,不够全面])

1.已知模拟器的IMEI:“000000000000000”, “e21833235b6eef10”, “012345678912345”

2.已知模拟器的手机号:“15555215554”, “15555215556”, “15555215558”, “15555215560”,“15555215562”, “15555215564”, “15555215566”, “15555215568”,“15555215570”, “15555215572”, “15555215574”, “15555215576”,“15555215578”, “15555215580”, “15555215582”, “15555215584”

3.已知模拟器的IMSI:“310260000000000”

4.已知模拟器的Build信息(可能误判):

private static Property[] known_props = {new Property("", null),

new Property("-props", null), new Property("", null),

new Property(".fake_camera", null), new Property(".lcd_density", null),

new Property("", "unknown"), new Property("", "unknown"),

new Property("", "goldfish"), new Property("", null),

new Property("", null), new Property("", "1"),

new Property("", "generic"), new Property("", "sdk"),

new Property("", "sdk"),

// Need to double check that an "empty" string ("") returns null

new Property("", null)};

5.特殊手机的运营商:“android”

((TelephonyManager) (Context.TELEPHONY_SERVICE)).getNetworkOperatorName().toLowerCase().equals(“android”)

[2] 检测模拟器特征文件是否存在

“/dev/socket/qemud”,

“/dev/qemu_pipe”,

"/system/lib/libc_malloc_debug_qemu.so",

“/sys/qemu_trace”,

"/system/bin/qemu-props(不可靠,可能误判,很多真机有这项)",

"/dev/socket/genyd",

“/dev/socket/baseband_genyd”

-------新版本方案-----

(184个特征文件检测)

// =================== 主要证据 ===================

/**

* 特殊的模拟器特征文件

* 186项文件,查到一个文件就是百分百模拟器,权重最高

*

* 模拟器内核GoldFish

* /data下的特征文件需要权限才可以访问,其他目录下的特征文件不需要

* 因为模拟器的话,都是root权限,所以并不需要担心权限问题。

* /ljphhj/

*/

static final String[] emulatorFiles = {

// vbox模拟器文件

"/data/youwave_id",

"/dev/vboxguest",

"/dev/vboxuser",

"/mnt/prebundledapps/",

"/mnt/prebundledapps/propfiles/",

"/mnt/prebundledapps/propfiles/.s2",

"/mnt/prebundledapps/propfiles/.s3",

"/mnt/sdcard/bstfolder/InputMapper/",

"/mnt/sdcard/",

"/mnt/sdcard/windows/InputMapper/",

"/proc/irq/9/vboxguest",

"/sys/bus/pci/drivers/vboxguest",

"/sys/bus/pci/drivers/vboxguest/0000:00:04.0",

"/sys/bus/pci/drivers/vboxguest/bind",

"/sys/bus/pci/drivers/vboxguest/module",

"/sys/bus/pci/drivers/vboxguest/new_id",

"/sys/bus/pci/drivers/vboxguest/remove_id",

"/sys/bus/pci/drivers/vboxguest/uevent",

"/sys/bus/pci/drivers/vboxguest/unbind",

"/sys/bus/platform/drivers/qemu_pipe",

"/sys/bus/platform/drivers/qemu_trace",

"/sys/class/bdi/vboxsf-c",

"/sys/class/misc/vboxguest",

"/sys/class/misc/vboxuser",

"/sys/devices/virtual/bdi/vboxsf-c",

"/sys/devices/virtual/misc/vboxguest",

"/sys/devices/virtual/misc/vboxguest/dev",

"/sys/devices/virtual/misc/vboxguest/power",

"/sys/devices/virtual/misc/vboxguest/subsystem",

"/sys/devices/virtual/misc/vboxguest/uevent",

"/sys/devices/virtual/misc/vboxuser",

"/sys/devices/virtual/misc/vboxuser/dev",

"/sys/devices/virtual/misc/vboxuser/power",

"/sys/devices/virtual/misc/vboxuser/subsystem",

"/sys/devices/virtual/misc/vboxuser/uevent",

"/sys/module/vboxguest",

"/sys/module/vboxguest/coresize",

"/sys/module/vboxguest/drivers",

"/sys/module/vboxguest/drivers/pci:vboxguest",

"/sys/module/vboxguest/holders",

"/sys/module/vboxguest/holders/vboxsf",

"/sys/module/vboxguest/initsize",

"/sys/module/vboxguest/initstate",

"/sys/module/vboxguest/notes",

"/sys/module/vboxguest/notes/.-id",

"/sys/module/vboxguest/parameters",

"/sys/module/vboxguest/parameters/log",

"/sys/module/vboxguest/parameters/log_dest",

"/sys/module/vboxguest/parameters/log_flags",

"/sys/module/vboxguest/refcnt",

"/sys/module/vboxguest/sections",

"/sys/module/vboxguest/sections/.altinstructions",

"/sys/module/vboxguest/sections/.altinstr_replacement",

"/sys/module/vboxguest/sections/.bss",

"/sys/module/vboxguest/sections/.data",

"/sys/module/vboxguest/sections/.",

"/sys/module/vboxguest/sections/.",

"/sys/module/vboxguest/sections/.fixup",

"/sys/module/vboxguest/sections/..this_module",

"/sys/module/vboxguest/sections/.",

"/sys/module/vboxguest/sections/.-id",

"/sys/module/vboxguest/sections/.rodata",

"/sys/module/vboxguest/sections/.rodata.str1.1",

"/sys/module/vboxguest/sections/.smp_locks",

"/sys/module/vboxguest/sections/.strtab",

"/sys/module/vboxguest/sections/.symtab",

"/sys/module/vboxguest/sections/.text",

"/sys/module/vboxguest/sections/__ex_table",

"/sys/module/vboxguest/sections/__ksymtab",

"/sys/module/vboxguest/sections/__ksymtab_strings",

"/sys/module/vboxguest/sections/__param",

"/sys/module/vboxguest/srcversion",

"/sys/module/vboxguest/taint",

"/sys/module/vboxguest/uevent",

"/sys/module/vboxguest/version",

"/sys/module/vboxsf",

"/sys/module/vboxsf/coresize",

"/sys/module/vboxsf/holders",

"/sys/module/vboxsf/initsize",

"/sys/module/vboxsf/initstate",

"/sys/module/vboxsf/notes",

"/sys/module/vboxsf/notes/.-id",

"/sys/module/vboxsf/refcnt",

"/sys/module/vboxsf/sections",

"/sys/module/vboxsf/sections/.bss",

"/sys/module/vboxsf/sections/.data",

"/sys/module/vboxsf/sections/.",

"/sys/module/vboxsf/sections/..this_module",

"/sys/module/vboxsf/sections/.",

"/sys/module/vboxsf/sections/.-id",

"/sys/module/vboxsf/sections/.rodata",

"/sys/module/vboxsf/sections/.rodata.str1.1",

"/sys/module/vboxsf/sections/.smp_locks",

"/sys/module/vboxsf/sections/.strtab",

"/sys/module/vboxsf/sections/.symtab",

"/sys/module/vboxsf/sections/.text",

"/sys/module/vboxsf/sections/__bug_table",

"/sys/module/vboxsf/sections/__param",

"/sys/module/vboxsf/srcversion",

"/sys/module/vboxsf/taint",

"/sys/module/vboxsf/uevent",

"/sys/module/vboxsf/version",

"/sys/module/vboxvideo",

"/sys/module/vboxvideo/coresize",

"/sys/module/vboxvideo/holders",

"/sys/module/vboxvideo/initsize",

"/sys/module/vboxvideo/initstate",

"/sys/module/vboxvideo/notes",

"/sys/module/vboxvideo/notes/.-id",

"/sys/module/vboxvideo/refcnt",

"/sys/module/vboxvideo/sections",

"/sys/module/vboxvideo/sections/.data",

"/sys/module/vboxvideo/sections/.",

"/sys/module/vboxvideo/sections/..this_module",

"/sys/module/vboxvideo/sections/.",

"/sys/module/vboxvideo/sections/.-id",

"/sys/module/vboxvideo/sections/.rodata.str1.1",

"/sys/module/vboxvideo/sections/.strtab",

"/sys/module/vboxvideo/sections/.symtab",

"/sys/module/vboxvideo/sections/.text",

"/sys/module/vboxvideo/srcversion",

"/sys/module/vboxvideo/taint",

"/sys/module/vboxvideo/uevent",

"/sys/module/vboxvideo/version",

"/system/app/",

"/system/bin/androVM-prop",

"/system/bin/androVM-vbox-sf",

"/system/bin/androVM_setprop",

"/system/bin/get_androVM_host",

"/system/bin/",

"/system/etc/",

"/system/etc/",

"/system/lib/hw/.",

"/system/lib/hw/camera.",

"/system/lib/hw/gps.",

"/system/lib/hw/gralloc.",

"/system/lib/hw/sensors.",

"/system/lib/modules/3.0.8-android-x86+/extra/vboxguest",

"/system/lib/modules/3.0.8-android-x86+/extra/vboxguest/",

"/system/lib/modules/3.0.8-android-x86+/extra/vboxsf",

"/system/lib/modules/3.0.8-android-x86+/extra/vboxsf/",

"/system/lib/",

"/system/lib/",

"/system/lib/",

"/system/usr/idc/androVM_Virtual_Input.idc",

"/system/usr/keylayout/androVM_Virtual_Input.kl",

"/system/xbin/",

"/ueventd.android_x86.rc",

"/ueventd.",

"/",

"/fstab.vbox86",

"/init.",

"/",

// ========针对原生Android模拟器 内核:goldfish===========

"/sys/module/goldfish_audio",

"/sys/module/goldfish_sync",

// ========针对蓝叠模拟器===========

"/data/app/",

"/data/app/",

"/data/app/",

"/data/app/",

"/data/app/.",

"/data/app/",

"/data/",

"/data/data/",

"/data/data/",

"/data/data/",

"/data/data/",

"/data/data/",

"/data/data/",

"/data/data/",

"/data/data/",

"/data/data/",

"/data/data/.s2p",

"/data/data/",

"/data/data/",

"/data/data/",

"/data/data/",

// ========针对逍遥安卓模拟器===========

// 虚拟化网卡和pci,可能存在误判,不可靠

// "/sys/module/virtio_net",

// "/sys/module/virtio_pci",

"/data/data/",

"/data/data/",

"/data/data/.installer",

"/data/data/",

"/data/data/",

"/data/data/",

"/data/data/",

// ========针对Mumu模拟器===========

"/data/data/",

"/data/data/",

"/data/data/"

};

新旧方案在模拟器上的表现:从表中可以看出,旧方案在针对一些较新的版本的模拟器时已经无效了,模拟器都无法准确识别出来,旧版本识别模拟器为30%识别率,新版本方案是100%识别率,所以旧的方案决定摒弃不再使用。

Android模拟器名

旧方式(imei,imsi,phonenumber,build,files)

新特征判断(模拟器特征文件)

Android原生模拟器

模拟器-build,imsi,pipies,emuprops

模拟器

Genymotion

模拟器-build,imei,files

模拟器

海马玩模拟器(Droid4X)

真机(000000)

模拟器

Bluestacks(蓝叠)模拟器

真机(000000)

模拟器

雷电模拟器

真机(000000)

模拟器

逍遥安卓模拟器

真机(000000)

模拟器

天天模拟器

真机(000000)

模拟器

网易MuMu安卓模拟器

模拟器-build

模拟器

夜神安卓模拟器

真机(000000)

模拟器

靠谱助手模拟器 / 猩猩助手模拟器

真机(000000)

模拟器

采用云测平台TestIn,测试真机309台,判断模拟器,新旧方案对比:

旧方案:误判为模拟器的概率为18% , 新方案:误判率为0%

对应的Excel结果表格为:

**二、Android模拟器中级判断方案==[辅证,权重低]== **

1.传感器判断:判断模拟器上不存在的传感器类型,而真机上普遍存在的,最后决定辅证传感器为:【计步传感器,光线传感器,距离传感器】这三个传感器的存在与否用来作为辅助验证条件。

传感器类型

在上述10款模拟器中检测是否可靠(模拟器上有支持 or 大部分真机上没有支持)

TYPE_STEP_DETECTOR or TYPE_STEP_COUNTER //计步传感器(硬件)

不完全可靠(少数机器没有)

TYPE_LIGHT //光线传感器(硬件)

相对可靠(300台真机仅几台没有此传感器)

TYPE_PROXIMITY //距离传感器(硬件)

不完全可靠(少数机器没有)

TYPE_PRESSURE //压力传感器(硬件)

不可靠(大部分真机没有)

TYPE_ACCELEROMETER //加速度传感器(硬件)

不可靠(有模拟器支持)

TYPE_MAGNETIC_FIELD //磁场传感器(硬件)

不可靠(有模拟器支持)

TYPE_ORIENTATION //方向传感器(软件传感器,数据来自重力和磁场传感器)

不可靠(有模拟器支持)

TYPE_GYROSCOPE //陀螺仪传感器(硬件)

不可靠(有模拟器支持)

TYPE_GRAVITY //重力传感器(硬件或软件)

不可靠(有模拟器支持)

TYPE_LINEAR_ACCELERATION //线性加速度传感器(硬件或软件)

不可靠(有模拟器支持)

TYPE_ROTATION_VECTOR //旋转矢量传感器(硬件或软件)

不可靠(有模拟器支持)

TYPE_RELATIVE_HUMIDITY //湿度传感器(硬件)

不可靠(大部分真机没有)

TYPE_AMBIENT_TEMPERATURE or TYPE_TEMPERATURE //温度传感器(硬件)

不可靠(大部分真机没有)

检测CPU是否是PC

/**

* cat /proc/cpuinfo

* 从cpuinfo中读取cpu架构

*/

public static boolean isPcKernel(){

String str = "";

try {

Process start = new ProcessBuilder(new String[]{"/system/bin/cat", "/proc/cpuinfo"}).start();

StringBuffer stringBuffer = new StringBuffer();

String str2 = "";

BufferedReader bufferedReader = new BufferedReader(new InputStreamReader((), "utf-8"));

while (true) {

String readLine = ();

if (readLine == null) {

break;

}

(readLine);

}

();

str = ().toLowerCase();

} catch (IOException e) {

}

if (("intel") || ("amd")) {

return true;

}

return false;

}

库确认CPU架构

/**

* 获取CPU架构模式,

* -1 : so库加载失败,不能使用jni函数

* 0 : 三种cpu架构模式都不符合(jni函数判断有误了)

* 1 : x86

* 2 : mips

* 3 : arm

* @return

*/

public static int getCpuType(){

if (){

if (isX86Cpu()){

return CPU_TYPE_X86;

}else if (isMipsCpu()){

return CPU_TYPE_MIPS;

}else if (isArmCpu()){

return CPU_TYPE_ARM;

}

return CPU_TYPE_UNKNOWN;

}else{

return LOAD_SO_FAILED;

}

}

4.[so可能导致闪退,不采用]利用真机和模拟器(pc)的cache设计不同来区分

大致思路:真机的cache为一级,模拟器的cache为两级(指令cache和数据cache分开), 因此执行一段相同的机器码往一个address地址中赋值,得到的结果真机和模拟器上是不会一样的

加载异常检测cpu架构

通过加载一个不存在的so库,使系统抛出异常load堆栈信息,匹配异常堆栈信息中的nativeLibraryDirectories值,arm的so会去找arm64,再寻找arm32,而x86会去lib/x86中寻找so,从堆栈中就可以得出cpu架构模式

有些模拟器堆栈中只加载到/lib,而没有细分(如:夜神模拟器[Couldn’t load noexits_lib from loader [DexPathList[[zip file “/data/app/”],nativeLibraryDirectories=[/data/app-lib/-1, /system/lib]]]: findLibrary returned null]),这样的话这个项便仅仅只能作为辅证,后端发现异常后作为一个判断参考依据。

6.[不采用]电池电量、手机信号、Location位置(电池需要两个时间点差值检测,不方便也不靠谱,手机信号的检测7.0后检测不了,定位信息模拟器完全可以模拟器,不靠谱)

三、Android模拟器进阶判断方案(黑产,番外篇)[以上仅仅防君子]

XPOSED HOOK (JAVA)

PLT and Inline Hook (Native)

Virtual HOOK (JAVA and Native)

自制ROM HOOK (JAVA and Native)