0


安全风险 - 检测设备是否为模拟器

在很多安全机构的检测中,关于模拟器的运行环境一般也会做监听处理,有的可能允许执行但是会提示用户,有的可能直接禁止在模拟器上运行我方APP

如何判断当前 app 是运行在Android真机,还是运行在模拟器? 可能做 Framework 的朋友思维会更开阔一些,不过现在也可以跟我这门外汉一起来稍微了解下

攻略过程

其实我已经很久没有用过模拟器了,不过可以肯定的是模拟器与真机的本质区别大概率在于运行载体

Android 有非常多的模拟器,我已知的有官方自带的

Genymotion

模拟器,三方平台的夜神模拟器、天天模拟器等,所以想要完全鉴别出设备的运行环境,其实应该是存在一定问题的,我们只能说尽可能保证一定的容错率(我有想过很多应用平台提供的云机,但好像大多提供的都是真机,所以此项不在考虑范围之内)

基础思考

以下的一些思考主要结合了 如何判断是否是模拟器还是真机、全面检测设备是否模拟器、一行代码帮你检测Android模拟器、安卓逆向环境检测–模拟器 等多篇新旧文章

  • IMEI 设备识别码:用于唯一标识移动设备(**放弃**)

模拟器的

IMEI

可以修改,早期平板可能没有

IMEI

,但是随着时代发展很多平板设备已拥有了属于自己的

IMEI
  • MAC地址:物理地址,硬件地址,也称局域网地址,由网络设备制造商生产时烧录在网卡(**放弃**)

模拟器的MAC地址是固定的几种,但是这些固定的地址随着模拟器类型递增,没有找到合适的,同时mac地址现在可以被模拟…

  • 通过调用公开或者隐藏的系统API判断(**放弃**)

因为调用结果可以轻易被修改,比如直接修改Android的源代码或者借助 Xposed

Framework

进行修改(这种场景我虽未参与,但是应该可以参考Java的反射机制)

  • 功能验证:初期模拟器功能并不完善,可以采用类似 打电话、发短信 等方式进行功能测试,但是后续随着模拟器升级已补全对应功能 (放弃)
publicbooleanisSimulator1(){String url ="tel:"+"10086";Intent intent =newIntent();
        intent.setData(Uri.parse(url));
        intent.setAction(Intent.ACTION_DIAL);// 是否可以处理跳转到拨号的 Intentboolean canResolveIntent = intent.resolveActivity(mContext.getPackageManager())!=null;return!canResolveIntent;}
  • 设备IDS、特有文件验证(放弃
涉及到敏感权限时需要申请权限,根据授权结果容易出现误判,同时影响用户体验
private static String[]known_numbers ={"15555215554","15555215556","15555215558","15555215560","15555215562","15555215564","15555215566","15555215568","15555215570","15555215572","15555215574","15555215576","15555215578","15555215580","15555215582","15555215584",};public static Boolean CheckPhoneNumber(Context context){
        TelephonyManager telephonyManager =(TelephonyManager)context
                .getSystemService(Context.TELEPHONY_SERVICE);
        String phonenumber =telephonyManager.getLine1Number();for(String number :known_numbers){if(number.equalsIgnoreCase(phonenumber)){
                Log.v("Result:","Find PhoneNumber!");returntrue;}}
        Log.v("Result:","Not Find PhoneNumber!");returnfalse;}

特有文件检测 - 权限要求

在这里插入图片描述

设备IDS检测 - 权限要求

在这里插入图片描述

  • CPU检测方法:现在的模拟器基本可以做到模拟手机号码,手机品牌,cpu信息等,比如逍遥/夜神模拟器读取 ro.product.board 进行了处理,能得到预先设置的cpu信息(**放弃**)
publicstaticbooleancheckIsNotRealPhone(){String cpuInfo =readCpuInfo();if((cpuInfo.contains("intel")|| cpuInfo.contains("amd"))){returntrue;}returnfalse;}publicstaticStringreadCpuInfo(){String result ="";try{String[] args ={"/system/bin/cat","/proc/cpuinfo"};ProcessBuilder cmd =newProcessBuilder(args);Process process = cmd.start();StringBuffer sb =newStringBuffer();String readLine ="";BufferedReader responseReader =newBufferedReader(newInputStreamReader(process.getInputStream(),"utf-8"));while((readLine = responseReader.readLine())!=null){
            sb.append(readLine);}
        responseReader.close();
        result = sb.toString().toLowerCase();}catch(IOException ex){}return result;}

进阶思考

在一篇Blog内有看到这样一副图,值得借鉴,因为我最后使用的

easyprotector

框架就做了硬件信息检测

在这里插入图片描述

**Tip:有兴趣的可以参考以下方法扩展

easyprotector

框架内的模拟器检测部分**

模拟器框架文件

 
UNEXPORT void AntiEmulator::check_file(){
    char *(path[])={"/system/bin/androVM-prop",//检测androidVM"/system/bin/microvirt-prop",//检测逍遥模拟器--新版本找不到特征"/system/lib/libdroid4x.so",//检测海马模拟器"/system/bin/windroyed",//检测文卓爷模拟器"/system/bin/nox-prop",//检测夜神模拟器--某些版本找不到特征"/system/lib/libnoxspeedup.so",//检测夜神模拟器"/system/bin/ttVM-prop",//检测天天模拟器"/data/.bluestacks.prop",//检测bluestacks模拟器  51模拟器"/system/bin/duosconfig",//检测AMIDuOS模拟器"/system/etc/xxzs_prop.sh",//检测星星模拟器"/system/etc/mumu-configs/device-prop-configs/mumu.config",//网易MuMu模拟器"/system/priv-app/ldAppStore",//雷电模拟器"/system/bin/ldinit",//雷电模拟器"/system/bin/ldmountsf",//雷电模拟器"/system/app/AntStore",//小蚁模拟器"/system/app/AntLauncher",//小蚁模拟器"vmos.prop",//vmos虚拟机"fstab.titan",//光速虚拟机"init.titan.rc",//光速虚拟机"x8.prop",//x8沙箱和51虚拟机"/system/lib/libc_malloc_debug_qemu.so",//AVD QEMU"/system/bin/microvirtd","/dev/socket/qemud","/dev/qemu_pipe"};for(int i =0; i <sizeof(path)/sizeof(char*); i++){if(Syscall::check_file_or_dir_exists(path[i])){LOGI("check_file  %s file existing", path[i]);// TODO 风险}}}

easyprotector 框架解析

关于

easyprotector框架

文档可以参考 一行代码帮你检测Android模拟器(更新至1.1.0) 会更详细一些

我之所以在

github

选这个框架,主要有几点原因

  • 模拟器检测框架有限
  • 该框架star高
  • 检测方面考虑全面(检测渠道、设备型号、硬件制造商、主板名称、基带信息、第三方应用数量、传感器数量、是否支持蓝牙、是否支持相机、是否支持闪光灯等)
  • 能力之内可以适当扩展检测项
  • 为了避免误判设备类型,内部加入条件判断,只有满足3项模拟器特征才会判定为模拟器

直接通过框架源码,查看下模拟器检测的执行过程

  1. 调用了 EasyProtectorLib.checkIsRunningInEmulator 方法

在这里插入图片描述

  1. EasyProtectorLib 中找到了 checkIsRunningInEmulator 实际调用了 EmulatorCheckUtil

在这里插入图片描述

  1. 查看 EmulatorCheckUtil - readSysProperty 源码即可

源码中

ro.build

ro.product

gsm.version

含义,做Framework朋友可能比较了解,主要用于检测一些系统级信息

在这里插入图片描述

之前有提到功能扩展和延伸,大家可自行在该类源码中进行扩展,不过最好另起方法,有自信的话改原方法也行

在这里插入图片描述


剥离 easyprotector框架 模拟器检测功能

因为

easyprotector框架

中涉及的功能比较多,我习惯性只抽出了我所需要的部分

CommandUtil (公共、基础)

简单来看主要是通过反射机制获取一些系统的公共资源信息

import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.IOException;/**
 * Project Name:EasyProtector
 * Package Name:com.lahm.library
 * Created by lahm on 2018/6/8 16:23 .
 */publicclass CommandUtil {privateCommandUtil(){}private static class SingletonHolder {private static final CommandUtil INSTANCE = new CommandUtil();}public static final CommandUtil getSingleInstance(){return SingletonHolder.INSTANCE;}public String getProperty(String propName){
        String value =null;
        Object roSecureObj;try{
            roSecureObj = Class.forName("android.os.SystemProperties").getMethod("get", String.class).invoke(null, propName);if(roSecureObj !=null) value =(String) roSecureObj;}catch(Exception e){
            value =null;}finally{return value;}}public String exec(String command){
        BufferedOutputStream bufferedOutputStream =null;
        BufferedInputStream bufferedInputStream =null;
        Process process =null;try{
            process = Runtime.getRuntime().exec("sh");
            bufferedOutputStream = new BufferedOutputStream(process.getOutputStream());

            bufferedInputStream = new BufferedInputStream(process.getInputStream());
            bufferedOutputStream.write(command.getBytes());
            bufferedOutputStream.write('\n');
            bufferedOutputStream.flush();
            bufferedOutputStream.close();

            process.waitFor();

            String outputStr =getStrFromBufferInputSteam(bufferedInputStream);return outputStr;}catch(Exception e){returnnull;}finally{if(bufferedOutputStream !=null){try{
                    bufferedOutputStream.close();}catch(IOException e){
                    e.printStackTrace();}}if(bufferedInputStream !=null){try{
                    bufferedInputStream.close();}catch(IOException e){
                    e.printStackTrace();}}if(process !=null){
                process.destroy();}}}private static String getStrFromBufferInputSteam(BufferedInputStream bufferedInputStream){if(null== bufferedInputStream){return"";}
        int BUFFER_SIZE =512;
        byte[] buffer = new byte[BUFFER_SIZE];
        StringBuilder result = new StringBuilder();try{while(true){
                int read = bufferedInputStream.read(buffer);if(read >0){
                    result.append(new String(buffer,0, read));}if(read < BUFFER_SIZE){break;}}}catch(Exception e){
            e.printStackTrace();}return result.toString();}}

EmulatorCheckUtil (核心)

import android.content.Context;import android.content.pm.PackageManager;import android.hardware.Sensor;import android.hardware.SensorManager;import android.text.TextUtils;import static android.content.Context.SENSOR_SERVICE;import static cn.com.huaan.fund.acts.base.safe.CheckResult.RESULT_EMULATOR;import static cn.com.huaan.fund.acts.base.safe.CheckResult.RESULT_MAYBE_EMULATOR;import static cn.com.huaan.fund.acts.base.safe.CheckResult.RESULT_UNKNOWN;/**
 * Project Name:EasyProtector
 * Package Name:com.lahm.library
 * Created by lahm on 2018/6/8 15:01 .
 */publicclass EmulatorCheckUtil {privateEmulatorCheckUtil(){}private static class SingletonHolder {private static final EmulatorCheckUtil INSTANCE = new EmulatorCheckUtil();}public static final EmulatorCheckUtil getSingleInstance(){return SingletonHolder.INSTANCE;}public boolean readSysProperty(Context context, EmulatorCheckCallback callback){if(context ==null)throw new IllegalArgumentException("context must not be null");

        int suspectCount =0;//检测硬件名称
        CheckResult hardwareResult =checkFeaturesByHardware();switch(hardwareResult.result){
            case RESULT_MAYBE_EMULATOR:++suspectCount;break;
            case RESULT_EMULATOR:if(callback !=null) callback.findEmulator("hardware = "+ hardwareResult.value);returntrue;}//检测渠道
        CheckResult flavorResult =checkFeaturesByFlavor();switch(flavorResult.result){
            case RESULT_MAYBE_EMULATOR:++suspectCount;break;
            case RESULT_EMULATOR:if(callback !=null) callback.findEmulator("flavor = "+ flavorResult.value);returntrue;}//检测设备型号
        CheckResult modelResult =checkFeaturesByModel();switch(modelResult.result){
            case RESULT_MAYBE_EMULATOR:++suspectCount;break;
            case RESULT_EMULATOR:if(callback !=null) callback.findEmulator("model = "+ modelResult.value);returntrue;}//检测硬件制造商
        CheckResult manufacturerResult =checkFeaturesByManufacturer();switch(manufacturerResult.result){
            case RESULT_MAYBE_EMULATOR:++suspectCount;break;
            case RESULT_EMULATOR:if(callback !=null)
                    callback.findEmulator("manufacturer = "+ manufacturerResult.value);returntrue;}//检测主板名称
        CheckResult boardResult =checkFeaturesByBoard();switch(boardResult.result){
            case RESULT_MAYBE_EMULATOR:++suspectCount;break;
            case RESULT_EMULATOR:if(callback !=null) callback.findEmulator("board = "+ boardResult.value);returntrue;}//检测主板平台
        CheckResult platformResult =checkFeaturesByPlatform();switch(platformResult.result){
            case RESULT_MAYBE_EMULATOR:++suspectCount;break;
            case RESULT_EMULATOR:if(callback !=null) callback.findEmulator("platform = "+ platformResult.value);returntrue;}//检测基带信息
        CheckResult baseBandResult =checkFeaturesByBaseBand();switch(baseBandResult.result){
            case RESULT_MAYBE_EMULATOR:
                suspectCount +=2;//模拟器基带信息为null的情况概率相当大break;
            case RESULT_EMULATOR:if(callback !=null) callback.findEmulator("baseBand = "+ baseBandResult.value);returntrue;}//检测传感器数量
        int sensorNumber =getSensorNumber(context);if(sensorNumber <=7)++suspectCount;//检测已安装第三方应用数量
        int userAppNumber =getUserAppNumber();if(userAppNumber <=5)++suspectCount;//检测是否支持闪光灯
        boolean supportCameraFlash =supportCameraFlash(context);if(!supportCameraFlash)++suspectCount;//检测是否支持相机
        boolean supportCamera =supportCamera(context);if(!supportCamera)++suspectCount;//检测是否支持蓝牙
        boolean supportBluetooth =supportBluetooth(context);if(!supportBluetooth)++suspectCount;//检测光线传感器
        boolean hasLightSensor =hasLightSensor(context);if(!hasLightSensor)++suspectCount;//检测进程组信息
        CheckResult cgroupResult =checkFeaturesByCgroup();if(cgroupResult.result == RESULT_MAYBE_EMULATOR)++suspectCount;if(callback !=null){
            StringBuffer stringBuffer = new StringBuffer("Test start").append("\r\n").append("hardware = ").append(hardwareResult.value).append("\r\n").append("flavor = ").append(flavorResult.value).append("\r\n").append("model = ").append(modelResult.value).append("\r\n").append("manufacturer = ").append(manufacturerResult.value).append("\r\n").append("board = ").append(boardResult.value).append("\r\n").append("platform = ").append(platformResult.value).append("\r\n").append("baseBand = ").append(baseBandResult.value).append("\r\n").append("sensorNumber = ").append(sensorNumber).append("\r\n").append("userAppNumber = ").append(userAppNumber).append("\r\n").append("supportCamera = ").append(supportCamera).append("\r\n").append("supportCameraFlash = ").append(supportCameraFlash).append("\r\n").append("supportBluetooth = ").append(supportBluetooth).append("\r\n").append("hasLightSensor = ").append(hasLightSensor).append("\r\n").append("cgroupResult = ").append(cgroupResult.value).append("\r\n").append("suspectCount = ").append(suspectCount);
            callback.findEmulator(stringBuffer.toString());}//嫌疑值大于3,认为是模拟器return suspectCount >3;}private int getUserAppNum(String userApps){if(TextUtils.isEmpty(userApps))return0;
        String[] result = userApps.split("package:");return result.length;}private String getProperty(String propName){
        String property = CommandUtil.getSingleInstance().getProperty(propName);return TextUtils.isEmpty(property)?null: property;}/**
     * 特征参数-硬件名称
     *
     * @return 0表示可能是模拟器,1表示模拟器,2表示可能是真机
     */private CheckResult checkFeaturesByHardware(){
        String hardware =getProperty("ro.hardware");if(null== hardware)return new CheckResult(RESULT_MAYBE_EMULATOR,null);
        int result;
        String tempValue = hardware.toLowerCase();switch(tempValue){
            case "ttvm"://天天模拟器
            case "nox"://夜神模拟器
            case "cancro"://网易MUMU模拟器
            case "intel"://逍遥模拟器
            case "vbox":
            case "vbox86"://腾讯手游助手
            case "android_x86"://雷电模拟器
                result = RESULT_EMULATOR;break;
            default:
                result = RESULT_UNKNOWN;break;}return new CheckResult(result, hardware);}/**
     * 特征参数-渠道
     *
     * @return 0表示可能是模拟器,1表示模拟器,2表示可能是真机
     */private CheckResult checkFeaturesByFlavor(){
        String flavor =getProperty("ro.build.flavor");if(null== flavor)return new CheckResult(RESULT_MAYBE_EMULATOR,null);
        int result;
        String tempValue = flavor.toLowerCase();if(tempValue.contains("vbox")) result = RESULT_EMULATOR;elseif(tempValue.contains("sdk_gphone")) result = RESULT_EMULATOR;else result = RESULT_UNKNOWN;return new CheckResult(result, flavor);}/**
     * 特征参数-设备型号
     *
     * @return 0表示可能是模拟器,1表示模拟器,2表示可能是真机
     */private CheckResult checkFeaturesByModel(){
        String model =getProperty("ro.product.model");if(null== model)return new CheckResult(RESULT_MAYBE_EMULATOR,null);
        int result;
        String tempValue = model.toLowerCase();if(tempValue.contains("google_sdk")) result = RESULT_EMULATOR;elseif(tempValue.contains("emulator")) result = RESULT_EMULATOR;elseif(tempValue.contains("android sdk built for x86")) result = RESULT_EMULATOR;else result = RESULT_UNKNOWN;return new CheckResult(result, model);}/**
     * 特征参数-硬件制造商
     *
     * @return 0表示可能是模拟器,1表示模拟器,2表示可能是真机
     */private CheckResult checkFeaturesByManufacturer(){
        String manufacturer =getProperty("ro.product.manufacturer");if(null== manufacturer)return new CheckResult(RESULT_MAYBE_EMULATOR,null);
        int result;
        String tempValue = manufacturer.toLowerCase();if(tempValue.contains("genymotion")) result = RESULT_EMULATOR;elseif(tempValue.contains("netease")) result = RESULT_EMULATOR;//网易MUMU模拟器else result = RESULT_UNKNOWN;return new CheckResult(result, manufacturer);}/**
     * 特征参数-主板名称
     *
     * @return 0表示可能是模拟器,1表示模拟器,2表示可能是真机
     */private CheckResult checkFeaturesByBoard(){
        String board =getProperty("ro.product.board");if(null== board)return new CheckResult(RESULT_MAYBE_EMULATOR,null);
        int result;
        String tempValue = board.toLowerCase();if(tempValue.contains("android")) result = RESULT_EMULATOR;elseif(tempValue.contains("goldfish")) result = RESULT_EMULATOR;else result = RESULT_UNKNOWN;return new CheckResult(result, board);}/**
     * 特征参数-主板平台
     *
     * @return 0表示可能是模拟器,1表示模拟器,2表示可能是真机
     */private CheckResult checkFeaturesByPlatform(){
        String platform =getProperty("ro.board.platform");if(null== platform)return new CheckResult(RESULT_MAYBE_EMULATOR,null);
        int result;
        String tempValue = platform.toLowerCase();if(tempValue.contains("android")) result = RESULT_EMULATOR;else result = RESULT_UNKNOWN;return new CheckResult(result, platform);}/**
     * 特征参数-基带信息
     *
     * @return 0表示可能是模拟器,1表示模拟器,2表示可能是真机
     */private CheckResult checkFeaturesByBaseBand(){
        String baseBandVersion =getProperty("gsm.version.baseband");if(null== baseBandVersion)return new CheckResult(RESULT_MAYBE_EMULATOR,null);
        int result;if(baseBandVersion.contains("1.0.0.0")) result = RESULT_EMULATOR;else result = RESULT_UNKNOWN;return new CheckResult(result, baseBandVersion);}/**
     * 获取传感器数量
     */private int getSensorNumber(Context context){
        SensorManager sm =(SensorManager) context.getSystemService(SENSOR_SERVICE);return sm.getSensorList(Sensor.TYPE_ALL).size();}/**
     * 获取已安装第三方应用数量
     */private int getUserAppNumber(){
        String userApps = CommandUtil.getSingleInstance().exec("pm list package -3");returngetUserAppNum(userApps);}/**
     * 是否支持相机
     */private boolean supportCamera(Context context){return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);}/**
     * 是否支持闪光灯
     */private boolean supportCameraFlash(Context context){return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);}/**
     * 是否支持蓝牙
     */private boolean supportBluetooth(Context context){return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);}/**
     * 判断是否存在光传感器来判断是否为模拟器
     * 部分真机也不存在温度和压力传感器。其余传感器模拟器也存在。
     *
     * @return false为模拟器
     */private boolean hasLightSensor(Context context){
        SensorManager sensorManager =(SensorManager) context.getSystemService(SENSOR_SERVICE);
        Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);//光线传感器if(null== sensor)returnfalse;elsereturntrue;}/**
     * 特征参数-进程组信息
     */private CheckResult checkFeaturesByCgroup(){
        String filter = CommandUtil.getSingleInstance().exec("cat /proc/self/cgroup");if(null== filter)return new CheckResult(RESULT_MAYBE_EMULATOR,null);return new CheckResult(RESULT_UNKNOWN, filter);}}

EmulatorCheckCallback (配置)

回调监听,可以获取到具体检测结果

publicinterface EmulatorCheckCallback {
    void findEmulator(String emulatorInfo);}

CheckResult(配置)

对检测结果进行类别划分,方便管理

publicclass CheckResult {public static final int RESULT_MAYBE_EMULATOR =0;//可能是模拟器public static final int RESULT_EMULATOR =1;//模拟器public static final int RESULT_UNKNOWN =2;//可能是真机public int result;public String value;publicCheckResult(int result, String value){this.result = result;this.value = value;}}

调用方式

val readSysProperty = EmulatorCheckUtil.getSingleInstance().readSysProperty(context,null)if(readSysProperty){//根据需要进行风险提示等相关业务
     ToastUtils.showToast("您当前可能运行在模拟器设备,请谨防安全风险!")}

本文转载自: https://blog.csdn.net/qq_20451879/article/details/139180794
版权归原作者 远方那座山 所有, 如有侵权,请联系我们删除。

“安全风险 - 检测设备是否为模拟器”的评论:

还没有评论