一行代碼幫你檢測Android模擬器(更新至1.1.0)

聲明:本文章獨(dú)家授權(quán)微信公眾號碼個蛋原創(chuàng)推文

目錄

  • 簡介
  • 初代常規(guī)手段
  • 進(jìn)階手段
  • 改良手段和新思路
  • 最終方案
  • 測試結(jié)果
  • Demo地址

簡介

最近有業(yè)務(wù)上的要求圾结,要求app在本地進(jìn)行諸如軟件多開渣触、hook框架、模擬器等安全檢測届腐,防止作弊行為。

防作弊一直是老生常談的問題蜂奸,而模擬器的檢測往往是防作弊中的重要一環(huán)犁苏,但在查找資料的過程中發(fā)現(xiàn),網(wǎng)上的模擬器檢測方案已經(jīng)有些過時了扩所,只能自己再跟進(jìn)學(xué)習(xí)围详,本文對這次學(xué)習(xí)內(nèi)容進(jìn)行總結(jié):
模擬器的檢測秉持一句話:抓取特征值與真機(jī)比較。

初代常規(guī)手段

早期模擬器沒那么多套路,特征值非常明顯助赞,某些值甚至是一長串的0买羞,檢測起來很方便,常規(guī)的方案如

  • 檢查手機(jī)IMEI等一系列編號
TelephonyManager tm = (TelephonyManager)this.getSystemService(Context.TELEPHONY_SERVICE);  
  String deviceid = tm.getDeviceId();//獲取IMEI號  
  String te1  = tm.getLine1Number();//獲取本機(jī)號碼  
  String imei = tm.getSimSerialNumber();//獲得SIM卡的序號  
  String imsi = tm.getSubscriberId();//得到用戶Id 
 
  • 讀取手機(jī)品牌信息
    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;
        }

優(yōu)點(diǎn):通過檢查真機(jī)上最直白的幾個特征雹食,就可以完成模擬器的檢測

缺點(diǎn):

1.現(xiàn)在的模擬器基本可以做到模擬手機(jī)號碼畜普,手機(jī)品牌,cpu信息等群叶,比如逍遙/夜神模擬器讀取ro.product.board進(jìn)行了處理吃挑,能得到預(yù)先設(shè)置的cpu信息;

2.真機(jī)的手機(jī)號碼也不一定就能拿到(比如電信卡);

3.拿手機(jī)號碼這個需要權(quán)限街立,用戶不一定喜歡舶衬。

所以決定棄用以上方案。

進(jìn)階手段

再思考真機(jī)上的特征赎离,進(jìn)一步我們有通過檢查硬件信息的思路逛犹,形如藍(lán)牙,語音輸入設(shè)備梁剔,wlan虽画,相機(jī)等

  • 檢查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();
                 ...//方法太長
                }
            }
        }
}
  • 檢查電池溫度,輪詢檢查電量荣病,充電狀態(tài)
        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充電

優(yōu)點(diǎn):比初代方案有深入狸捕;

缺點(diǎn):
1.mac地址現(xiàn)在可以被模擬,且獲取mac地址的代碼有點(diǎn)長(M以下版本還要傳context)寫起來不不優(yōu)雅众雷;

2.通過電池信息來準(zhǔn)確檢測灸拍,需要一定的時間間隔,屬于非實(shí)時方案砾省;

3.藍(lán)牙和相機(jī)需要添加相應(yīng)權(quán)限鸡岗。

所以不推薦集成。

改進(jìn)方案和新的研究

在研究各個模擬器的過程中编兄,尤其是在研究build.prop文件時轩性,發(fā)現(xiàn)以下(但不限于)問題
1.基帶信息幾乎沒有;
2.處理器信息ro.product.board和ro.board.platform有沖突或者不一致狠鸳;
3.部分模擬器在讀控制組信息時讀取不到揣苏;
4.連上wifi但會出現(xiàn) Link encap:UNSPEC未指定網(wǎng)卡類型的情況。

借著問題依次進(jìn)行解析件舵。

  • 基帶信息

基帶是手機(jī)上的一塊電路板卸察,刷基帶實(shí)際上就是刷這個電路的控制軟件。

我是這樣去理解模擬器沒有基帶信息的情況"因?yàn)槟M器沒有真實(shí)的電路板(基帶電路)铅祸,所以沒法刷基帶軟件進(jìn)去坑质,所以沒辦法得到基帶信息"合武,不知道這樣理解對不對,歡迎拍磚涡扼。

當(dāng)然了稼跳,部分真機(jī)在刷機(jī)失敗的時候也會出現(xiàn)丟失基帶的情況,這部分機(jī)器我們不多討論吃沪。

        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汤善,實(shí)際上也是去讀取ro.product.board值,

這個值代表cpu型號票彪,比如msm8998是驍龍835萎津,hi3650是麒麟950。

這個值真機(jī)幾乎不為空抹镊,AS模擬器會有如gphone的特征值,部分模擬器上是可以隨時變更的(因?yàn)槟媚M器來玩高幀率模式的手游)荤傲。

可是還有一個ro.board.platform值垮耳,這個值代表主板平臺,極少的模擬器會去更改這個值遂黍,甚至有的模擬器沒有這個值终佛,一般來說真機(jī)的兩值相等。

當(dāng)然真機(jī)也有例外雾家,測試機(jī)一加5T兩者都是msm8998铃彰,而華為P9 board值EVA-AL10,platform值hi3650。

兩者不一致

根據(jù)處理器信息做一個檢測指標(biāo)芯咧。

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值牙捉,在有限的真機(jī)和模擬機(jī)器的測試情況下,有以下推測
『真機(jī)基本上都有這個值敬飒,部分模擬器沒有這個值邪铲,基于vbox的模擬器上有特征值:vbox』

vbox特征值,平臺信息空

根據(jù)渠道信息做一個檢測指標(biāo)

String buildFlavor = CommandUtil.getSingleInstance().getProperty("ro.build.flavor");
if (buildFlavor == null | "".equals(buildFlavor) | (buildFlavor != null && buildFlavor.contains("vbox")))
    ++suspectCount;
  • 進(jìn)程組信息

利用讀取maps文件檢測軟件多開的時候无拗,在部分模擬器上卻遇到了runtimeException異常带到。
原因是讀取/proc/self/cgroup進(jìn)程組信息的時候,部分模擬器沒有這個值英染,因?yàn)閭€人水平有限揽惹,暫時不知道原因是什么,不過卻剛好拿這個做檢測方案四康。

關(guān)鍵代碼
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驅(qū)動未指定異常

Android離不開unix搪搏,所以嘗試了adb shell 運(yùn)行指令。運(yùn)行ifconfig時闪金,發(fā)現(xiàn)在連接wifi的情況下慕嚷,AS模擬器顯示 『wlan0 Link encap:UNSPEC』 未指定網(wǎng)卡類型,而真機(jī)情況下是『wlan0 Link encap:Ethernet』以太網(wǎng)。

AS模擬器的wlan情況

不過接著測試非wifi情況下喝检,該值都拿不到嗅辣,所以不推薦使用。

新增思路

增加特定值的檢測挠说,比如天天模擬器的hardware是ttVM
傳感器的檢測澡谭,部分模擬器的傳感器個數(shù)只有1個
安裝包的檢測,模擬器的用戶預(yù)裝app很少

最終方案

結(jié)合以上研究损俭,得出一個嫌疑指數(shù)蛙奖,綜合判斷是否運(yùn)行在模擬器中。

EasyProtectorLib.checkIsRunningInEmulator()的代碼實(shí)現(xiàn)如下

    @Deprecated
    public boolean readSysProperty() {
        return readSysProperty(null, null);
    }

    public boolean readSysProperty(Context context, EmulatorCheckCallback callback) {
        this.emulatorCheckCallback = callback;
        int suspectCount = 0;

        String baseBandVersion = getProperty("gsm.version.baseband");
        if (null == baseBandVersion || baseBandVersion.contains("1.0.0.0"))
            ++suspectCount;//基帶信息

        String buildFlavor = getProperty("ro.build.flavor");
        if (null == buildFlavor || buildFlavor.contains("vbox") || buildFlavor.contains("sdk_gphone"))
            ++suspectCount;//渠道

        String productBoard = getProperty("ro.product.board");
        if (null == productBoard || productBoard.contains("android") | productBoard.contains("goldfish"))
            ++suspectCount;//芯片

        String boardPlatform = getProperty("ro.board.platform");
        if (null == boardPlatform || boardPlatform.contains("android"))
            ++suspectCount;//芯片平臺

        String hardWare = getProperty("ro.hardware");
        if (null == hardWare) ++suspectCount;
        else if (hardWare.toLowerCase().contains("ttvm")) suspectCount += 10;//天天
        else if (hardWare.toLowerCase().contains("nox")) suspectCount += 10;//夜神

        String cameraFlash = "";
        String sensorNum = "sensorNum";
        if (context != null) {
            boolean isSupportCameraFlash = context.getPackageManager().hasSystemFeature("android.hardware.camera.flash");//是否支持閃光燈
            if (!isSupportCameraFlash) ++suspectCount;
            cameraFlash = isSupportCameraFlash ? "support CameraFlash" : "unsupport CameraFlash";

            SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
            int sensorSize = sm.getSensorList(Sensor.TYPE_ALL).size();
            if (sensorSize < 7) ++suspectCount;//傳感器個數(shù)
            sensorNum = sensorNum + sensorSize;
        }

        String userApps = CommandUtil.getSingleInstance().exec("pm list package -3");
        String userAppNum = "userAppNum";
        int userAppSize = getUserAppNums(userApps);
        if (userAppSize < 5) ++suspectCount;//用戶安裝的app個數(shù)
        userAppNum = userAppNum + userAppSize;

        String filter = CommandUtil.getSingleInstance().exec("cat /proc/self/cgroup");
        if (null == filter) ++suspectCount;//進(jìn)程租

        if (callback != null) {
            StringBuffer stringBuffer = new StringBuffer("ceshi start|")
                    .append(baseBandVersion).append("|")
                    .append(buildFlavor).append("|")
                    .append(productBoard).append("|")
                    .append(boardPlatform).append("|")
                    .append(hardWare).append("|")
                    .append(cameraFlash).append("|")
                    .append(sensorNum).append("|")
                    .append(userAppNum).append("|")
                    .append(filter).append("|end");
            callback.findEmulator(stringBuffer.toString());
        }
        return suspectCount > 3;
    }

以下是測試情況*

機(jī)器/測試方案 檢測結(jié)果
AS自帶模擬器 9.0 模擬器
Genymotion2.12.1 模擬器
逍遙模擬器6.0.0 模擬器
Appetize 模擬器
夜神模擬器6.2.5.3010 模擬器
騰訊手游助手2.0.6.8 模擬器
雷電模擬器3.41 模擬器
木木模擬器2.0.25 模擬器
一加5T 真機(jī)
華為P9 真機(jī)

*因安卓機(jī)型太廣杆兵,真機(jī)覆蓋測試不完全雁仲,有空大家去git提issue

Demo地址

本文方案已經(jīng)集成到EasyProtectorLib

github地址: https://github.com/lamster2018/EasyProtector

中文文檔見:http://www.reibang.com/p/c37b1bdb4757

Todo

1.檢測到多開應(yīng)該提供回調(diào)給開發(fā)者自行處理;---v1.0.4 support

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末琐脏,一起剝皮案震驚了整個濱河市攒砖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌日裙,老刑警劉巖吹艇,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異昂拂,居然都是意外死亡受神,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門格侯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鼻听,“玉大人,你說我怎么就攤上這事联四【悖” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵碎连,是天一觀的道長灰羽。 經(jīng)常有香客問我,道長鱼辙,這世上最難降的妖魔是什么廉嚼? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮倒戏,結(jié)果婚禮上怠噪,老公的妹妹穿的比我還像新娘。我一直安慰自己杜跷,他們只是感情好傍念,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布矫夷。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上躲惰,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天豌拙,我揣著相機(jī)與錄音摔踱,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的嘶摊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼评矩,長吁一口氣:“原來是場噩夢啊……” “哼叶堆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起斥杜,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤虱颗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后果录,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咐熙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年弱恒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片棋恼。...
    茶點(diǎn)故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡返弹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出爪飘,到底是詐尸還是另有隱情义起,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布师崎,位于F島的核電站默终,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏犁罩。R本人自食惡果不足惜齐蔽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望床估。 院中可真熱鬧含滴,春花似錦、人聲如沸丐巫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至碑韵,卻和暖如春赡茸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背泼诱。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工坛掠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人治筒。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓屉栓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親耸袜。 傳聞我的和親對象是個殘疾皇子友多,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容