一行代碼幫你檢測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.檢測到多開應該提供回調給開發者自行處理;

相關文章
相關標籤/搜索