当前位置: 代码网 > it编程>编程语言>Asp.net > 安全风险 - 检测设备是否为模拟器

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

2024年08月03日 Asp.net 我要评论
在很多安全机构的检测中,关于模拟器的运行环境一般也会做监听处理,有的可能允许执行但是会提示用户,有的可能直接禁止在模拟器上运行我方APP可能做 Framework 的朋友思维会更开阔一些,不过现在也可以跟我这门外汉一起来稍微了解下。

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

攻略过程

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

基础思考

以下的一些思考主要结合了 、、一行代码帮你检测android模拟器、 等多篇新旧文章

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

模拟器的 imei 可以修改,早期平板可能没有imei,但是随着时代发展很多平板设备已拥有了属于自己的imei

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

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

  • 通过调用公开或者隐藏的系统api判断放弃

因为调用结果可以轻易被修改,比如直接修改android的源代码或者借助 xposed framework 进行修改(这种场景我虽未参与,但是应该可以参考java的反射机制)

  • 功能验证:初期模拟器功能并不完善,可以采用类似 打电话、发短信 等方式进行功能测试,但是后续随着模拟器升级已补全对应功能 (放弃)
public boolean issimulator1() {
        string url = "tel:" + "10086";
        intent intent = new intent();
        intent.setdata(uri.parse(url));
        intent.setaction(intent.action_dial);
        // 是否可以处理跳转到拨号的 intent
        boolean 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!");
                return true;
            }
        }
        log.v("result:","not find phonenumber!");
        return false;
    }

特有文件检测 - 权限要求

在这里插入图片描述

设备ids检测 - 权限要求

在这里插入图片描述

  • cpu检测方法:现在的模拟器基本可以做到模拟手机号码,手机品牌,cpu信息等,比如逍遥/夜神模拟器读取 ro.product.board 进行了处理,能得到预先设置的cpu信息(放弃
public static boolean checkisnotrealphone() {
    string cpuinfo = readcpuinfo();
    if ((cpuinfo.contains("intel") || cpuinfo.contains("amd"))) {
        return true;
    }
    return false;
}
public static string readcpuinfo() {
    string result = "";
    try {
        string[] args = {"/system/bin/cat", "/proc/cpuinfo"};
        processbuilder cmd = new processbuilder(args);
        process process = cmd.start();
        stringbuffer sb = new stringbuffer();
        string readline = "";
        bufferedreader responsereader = new bufferedreader(new inputstreamreader(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 框架解析

我之所以在 github 选这个框架,主要有几点原因

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

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

  1. 调用了 easyprotectorlib.checkisrunninginemulator 方法

在这里插入图片描述

  1. easyprotectorlib 中找到了 checkisrunninginemulator 实际调用了 emulatorcheckutil

在这里插入图片描述

  1. 查看 emulatorcheckutil - readsysproperty 源码即可

在这里插入图片描述

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

在这里插入图片描述


剥离 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 .
 */
public class commandutil {
    private commandutil() {
    }

    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) {
            return null;
        } 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 .
 */
public class emulatorcheckutil {
    private emulatorcheckutil() {

    }

    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);
                return true;
        }

        //检测渠道
        checkresult flavorresult = checkfeaturesbyflavor();
        switch (flavorresult.result) {
            case result_maybe_emulator:
                ++suspectcount;
                break;
            case result_emulator:
                if (callback != null) callback.findemulator("flavor = " + flavorresult.value);
                return true;
        }

        //检测设备型号
        checkresult modelresult = checkfeaturesbymodel();
        switch (modelresult.result) {
            case result_maybe_emulator:
                ++suspectcount;
                break;
            case result_emulator:
                if (callback != null) callback.findemulator("model = " + modelresult.value);
                return true;
        }

        //检测硬件制造商
        checkresult manufacturerresult = checkfeaturesbymanufacturer();
        switch (manufacturerresult.result) {
            case result_maybe_emulator:
                ++suspectcount;
                break;
            case result_emulator:
                if (callback != null)
                    callback.findemulator("manufacturer = " + manufacturerresult.value);
                return true;
        }

        //检测主板名称
        checkresult boardresult = checkfeaturesbyboard();
        switch (boardresult.result) {
            case result_maybe_emulator:
                ++suspectcount;
                break;
            case result_emulator:
                if (callback != null) callback.findemulator("board = " + boardresult.value);
                return true;
        }

        //检测主板平台
        checkresult platformresult = checkfeaturesbyplatform();
        switch (platformresult.result) {
            case result_maybe_emulator:
                ++suspectcount;
                break;
            case result_emulator:
                if (callback != null) callback.findemulator("platform = " + platformresult.value);
                return true;
        }

        //检测基带信息
        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);
                return true;
        }

        //检测传感器数量
        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)) return 0;
        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;
        else if (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;
        else if (tempvalue.contains("emulator")) result = result_emulator;
        else if (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;
        else if (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;
        else if (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");
        return getuserappnum(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) return false;
        else return true;
    }

    /**
     * 特征参数-进程组信息
     */
    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 (配置)

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

public interface emulatorcheckcallback {
    void findemulator(string emulatorinfo);
}

checkresult(配置)

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

public class 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;

    public checkresult(int result, string value) {
        this.result = result;
        this.value = value;
    }
}

调用方式

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

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com