赞
踩
在很多安全机构的检测中,关于模拟器的运行环境一般也会做监听处理,有的可能允许执行但是会提示用户,有的可能直接禁止在模拟器上运行我方APP
如何判断当前 app 是运行在Android真机,还是运行在模拟器? 可能做 Framework 的朋友思维会更开阔一些,不过现在也可以跟我这门外汉一起来稍微了解下
其实我已经很久没有用过模拟器了,不过可以肯定的是模拟器与真机的本质区别大概率在于运行载体
Android 有非常多的模拟器,我已知的有官方自带的 Genymotion
模拟器,三方平台的夜神模拟器、天天模拟器等,所以想要完全鉴别出设备的运行环境,其实应该是存在一定问题的,我们只能说尽可能保证一定的容错率(我有想过很多应用平台提供的云机,但好像大多提供的都是真机,所以此项不在考虑范围之内)
以下的一些思考主要结合了 如何判断是否是模拟器还是真机、全面检测设备是否模拟器、一行代码帮你检测Android模拟器、安卓逆向环境检测–模拟器 等多篇新旧文章
放弃
)模拟器的 IMEI
可以修改,早期平板可能没有IMEI
,但是随着时代发展很多平板设备已拥有了属于自己的IMEI
放弃
)模拟器的MAC地址是固定的几种,但是这些固定的地址随着模拟器类型递增,没有找到合适的,同时mac地址现在可以被模拟…
放弃
)因为调用结果可以轻易被修改,比如直接修改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;
}
放弃
)涉及到敏感权限时需要申请权限,根据授权结果容易出现误判,同时影响用户体验
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检测 - 权限要求
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框架
文档可以参考 一行代码帮你检测Android模拟器(更新至1.1.0) 会更详细一些
我之所以在 github
选这个框架,主要有几点原因
直接通过框架源码,查看下模拟器检测的执行过程
EasyProtectorLib.checkIsRunningInEmulator
方法EasyProtectorLib
中找到了 checkIsRunningInEmulator
实际调用了 EmulatorCheckUtil
EmulatorCheckUtil
- readSysProperty
源码即可源码中
ro.build
、ro.product
、gsm.version
含义,做Framework朋友可能比较了解,主要用于检测一些系统级信息
之前有提到功能扩展和延伸,大家可自行在该类源码中进行扩展,不过最好另起方法,有自信的话改原方法也行
因为 easyprotector框架
中涉及的功能比较多,我习惯性只抽出了我所需要的部分
简单来看主要是通过反射机制获取一些系统的公共资源信息
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(); } }
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); } }
回调监听,可以获取到具体检测结果
public interface EmulatorCheckCallback {
void findEmulator(String emulatorInfo);
}
对检测结果进行类别划分,方便管理
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("您当前可能运行在模拟器设备,请谨防安全风险!")
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。