一行代码帮你检测Android模拟器

声明:本文章独家受权微信公众号码个蛋原创推文

目录

  • 简介
  • 初代常规手段
  • 进阶手段
  • 改良手段和新思路
  • 最终方案
  • 测试结果
  • Demo地址

简介

最近有业务上的要求,要求app在本地进行诸如软件多开、hook框架、模拟器等安全检测,防止做弊行为。html

防做弊一直是老生常谈的问题,而模拟器的检测每每是防做弊中的重要一环,但在查找资料的过程当中发现,网上的模拟器检测方案已经有些过期了,只能本身再跟进学习,本文对此次学习内容进行总结: 模拟器的检测秉持一句话:抓取特征值与真机比较。java

初代常规手段

早期模拟器没那么多套路,特征值很是明显,某些值甚至是一长串的0,检测起来很方便,常规的方案如android

  • 检查手机IMEI等一系列编号
TelephonyManager tm = (TelephonyManager)this.getSystemService(Context.TELEPHONY_SERVICE);  
  String deviceid = tm.getDeviceId();//获取IMEI号 
  String te1  = tm.getLine1Number();//获取本机号码 
  String imei = tm.getSimSerialNumber();//得到SIM卡的序号 
  String imsi = tm.getSubscriberId();//获得用户Id 
 
复制代码
  • 读取手机品牌信息
android.os.Build.BRAND,
	android.os.Build.MANUFACTURER,
	android.os.Build.MODEL
	...
复制代码
  • 检查cpu信息
String value = null;
        Object roSecureObj;
        try {
            roSecureObj = Class.forName("android.os.SystemProperties")
                    .getMethod("get", String.class)
                    .invoke(null, "ro.product.board");
            if (roSecureObj != null) value = (String) roSecureObj;
        } catch (Exception e) {
            value = null;
        } finally {
            return value;
        }

复制代码

优势:经过检查真机上最直白的几个特征,就能够完成模拟器的检测git

缺点:github

1.如今的模拟器基本能够作到模拟手机号码,手机品牌,cpu信息等,好比逍遥/夜神模拟器读取ro.product.board进行了处理,能获得预先设置的cpu信息;shell

2.真机的手机号码也不必定就能拿到(好比电信卡);安全

3.拿手机号码这个须要权限,用户不必定喜欢。微信

因此决定弃用以上方案。网络

进阶手段

再思考真机上的特征,进一步咱们有经过检查硬件信息的思路,形如蓝牙,语音输入设备,wlan,相机等app

  • 检查mac地址
Enumeration networkInterfaces;
        String str = null;
        networkInterfaces = NetworkInterface.getNetworkInterfaces();
        while (networkInterfaces.hasMoreElements()) {
            NetworkInterface networkInterface = (NetworkInterface) networkInterfaces.nextElement();
            if (networkInterface != null) {
                byte[] hardwareAddress;
                byte[] bArr = new byte[0];
                hardwareAddress = networkInterface.getHardwareAddress();
                if (!(hardwareAddress == null || hardwareAddress.length == 0)) {
                    String str2;
                    StringBuilder stringBuilder = new StringBuilder();
                 ...//方法太长
                }
            }
        }
}
复制代码
  • 检查电池温度,轮询检查电量,充电状态
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
        Intent batteryStatus = context.registerReceiver(null, filter);
        if (batteryStatus == null) return false;
        int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
        return chargePlug == BatteryManager.BATTERY_PLUGGED_USB;//检测usb充电
复制代码

优势:比初代方案有深刻;

缺点: 1.mac地址如今能够被模拟,且获取mac地址的代码有点长(M如下版本还要传context)写起来不不优雅;

2.经过电池信息来准确检测,须要必定的时间间隔,属于非实时方案;

3.蓝牙和相机须要添加相应权限。

因此不推荐集成。

改进方案和新的研究

在研究各个模拟器的过程当中,尤为是在研究build.prop文件时,发现如下(但不限于)问题 1.基带信息几乎没有;

2.处理器信息ro.product.board和ro.board.platform有冲突或者不一致;

3.部分模拟器在读控制组信息时读取不到;

4.连上wifi但会出现 Link encap:UNSPEC未指定网卡类型的状况。

借着问题依次进行解析。

  • 基带信息

基带是手机上的一块电路板,刷基带实际上就是刷这个电路的控制软件。

我是这样去理解模拟器没有基带信息的状况"由于模拟器没有真实的电路板(基带电路),因此无法刷基带软件进去,因此没办法获得基带信息",不知道这样理解对不对,欢迎拍砖。

固然了,部分真机在刷机失败的时候也会出现丢失基带的状况,这部分机器咱们很少讨论。

try {
            roSecureObj = Class.forName("android.os.SystemProperties")
                    .getMethod("get", String.class)
                    .invoke(null, "gsm.version.baseband");
            if (roSecureObj != null) value = (String) roSecureObj;
        } catch (Exception e) {
            value = null;
        }
复制代码
  • 处理器信息

最简单的方法就是直接拿android.os.Build.BOARD,实际上也是去读取ro.product.board值,

这个值表明cpu型号,好比msm8998是骁龙835,hi3650是麒麟950。

这个值真机几乎不为空,AS模拟器会有如gphone的特征值,部分模拟器上是能够随时变动的(由于拿模拟器来玩高帧率模式的手游)。

但是还有一个ro.board.platform值,这个值表明主板平台,极少的模拟器会去更改这个值,甚至有的模拟器没有这个值,通常来讲真机的两值相等。

固然真机也有例外,测试机一加5T二者都是msm8998,而华为P9 board值EVA-AL10,platform值hi3650。

二者不一致

根据处理器信息作一个检测指标。

String productBoard = CommandUtil.getSingleInstance().getProperty("ro.product.board");
if (productBoard == null | "".equals(productBoard)) ++suspectCount;

String boardPlatform = CommandUtil.getSingleInstance().getProperty("ro.board.platform");
if (boardPlatform == null | "".equals(boardPlatform)) ++suspectCount;

if (productBoard != null && boardPlatform != null && !productBoard.equals(boardPlatform))
    ++suspectCount;
复制代码
  • 渠道信息

渠道信息是ro.build.flavor值,在有限的真机和模拟机器的测试状况下,有如下推测 『真机基本上都有这个值,部分模拟器没有这个值,基于vbox的模拟器上有特征值:vbox』

vbox特征值,平台信息空

根据渠道信息作一个检测指标

String buildFlavor = CommandUtil.getSingleInstance().getProperty("ro.build.flavor");
if (buildFlavor == null | "".equals(buildFlavor) | (buildFlavor != null && buildFlavor.contains("vbox")))
    ++suspectCount;
复制代码
  • 进程组信息

利用读取maps文件检测软件多开的时候,在部分模拟器上却遇到了runtimeException异常。 缘由是读取/proc/self/cgroup进程组信息的时候,部分模拟器没有这个值,由于我的水平有限,暂时不知道缘由是什么,不过却恰好拿这个作检测方案。

关键代码
process = Runtime.getRuntime().exec("sh");
            bufferedOutputStream = new BufferedOutputStream(process.getOutputStream());
            bufferedInputStream = new BufferedInputStream(process.getInputStream());
            bufferedOutputStream.write("cat /proc/self/cgroup");
            bufferedOutputStream.write('\n');
            bufferedOutputStream.flush();
复制代码
  • wlan驱动未指定异常

Android离不开unix,因此尝试了adb shell 运行指令。运行ifconfig时,发如今链接wifi的状况下,AS模拟器显示 『wlan0 Link encap:UNSPEC』 未指定网卡类型,而真机状况下是『wlan0 Link encap:Ethernet』以太网。

AS模拟器的wlan状况

不过接着测试非wifi状况下,该值都拿不到,因此不推荐使用。

最终方案

结合以上研究,得出一个嫌疑指数,综合判断是否运行在模拟器中。

*EasyProtectorLib.checkIsRunningInEmulator()*的代码实现以下

public boolean readSysProperty() {
        int suspectCount = 0;
        //读基带信息
        String basebandVersion = CommandUtil.getSingleInstance().getProperty("gsm.version.baseband");
        if (TextUtils.isEmpty(baseBandVersion))
           ++suspectCount;
        //读渠道信息,针对一些基于vbox的模拟器
        String buildFlavor = CommandUtil.getSingleInstance().getProperty("ro.build.flavor");
        if (TextUtils.isEmpty(buildFlavor) | (buildFlavor != null && buildFlavor.contains("vbox")))
            ++suspectCount;
        //读处理器信息,这里常常会被处理
        String productBoard = CommandUtil.getSingleInstance().getProperty("ro.product.board");
        if (TextUtils.isEmpty(productBoard) | (productBoard != null && productBoard.contains("android")))
            ++suspectCount;
        //读处理器平台,这里不常会处理
        String boardPlatform = CommandUtil.getSingleInstance().getProperty("ro.board.platform");
        if (TextUtils.isEmpty(boardPlatform) | (boardPlatform != null && boardPlatform.contains("android"))) 
            ++suspectCount;
        //高通的cpu二者信息通常是一致的
       if (!TextUtils.isEmpty(productBoard)
                && !TextUtils.isEmpty(boardPlatform)
                && !productBoard.equals(boardPlatform))
            ++suspectCount;
        //一些模拟器读取不到进程租信息
        String filter = CommandUtil.getSingleInstance().exec("cat /proc/self/cgroup");
        if (filter == null || filter.length() == 0) ++suspectCount;

        return suspectCount > 2;
    }
复制代码

如下是测试状况*

机器/测试方案 基带信息 渠道信息 处理器信息 进程组 检测结果
AS自带模拟器 O O O X 模拟器
Genymotion2.12.1 O O O X 模拟器
逍遥模拟器5.3.2 O X X O 模拟器
Appetize O X O X 模拟器
夜神模拟器6.1.1 O O O O 模拟器
腾讯手游助手2.0.5 O O O X 模拟器
雷电模拟器3.27 O X X X 真机*
一加5T X X X X 真机
华为P9 X X O X 真机

*O表明该方案检测为模拟器,X表明检测正常

*Xamarin/Manymo由于网络缘由暂未进行测试

*雷电模拟器后续修复

*因安卓机型太广,真机覆盖测试不彻底,有空你们去git提issue

Demo地址

本文方案已经集成到EasyProtectorLib

github地址: github.com/lamster2018…

中文文档见:www.jianshu.com/p/c37b1bdb4…

Todo

1.检测到多开应该提供回调给开发者自行处理;

相关文章
相关标签/搜索