宣告:本文章獨家授權微信公眾號碼個蛋原創推文
目錄
- 簡介
- 初代常規手段
- 進階手段
- 改良手段和新思路
- 最終方案
- 測試結果
- Demo地址
簡介
最近有業務上的要求,要求app在本地進行諸如軟體多開、hook框架、模擬器等安全檢測,防止作弊行為。
防作弊一直是老生常談的問題,而模擬器的檢測往往是防作弊中的重要一環,但在查詢資料的過程中發現,網上的模擬器檢測方案已經有些過時了,只能自己再跟進學習,本文對這次學習內容進行總結: 模擬器的檢測秉持一句話:抓取特徵值與真機比較。
初代常規手段
早期模擬器沒那麼多套路,特徵值非常明顯,某些值甚至是一長串的0,檢測起來很方便,常規的方案如
- 檢查手機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;
}
複製程式碼
優點:通過檢查真機上最直白的幾個特徵,就可以完成模擬器的檢測
缺點:
1.現在的模擬器基本可以做到模擬手機號碼,手機品牌,cpu資訊等,比如逍遙/夜神模擬器讀取ro.product.board進行了處理,能得到預先設定的cpu資訊;
2.真機的手機號碼也不一定就能拿到(比如電信卡);
3.拿手機號碼這個需要許可權,使用者不一定喜歡。
所以決定棄用以上方案。
進階手段
再思考真機上的特徵,進一步我們有通過檢查硬體資訊的思路,形如藍芽,語音輸入裝置,wlan,相機等
- 檢查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』
根據渠道資訊做一個檢測指標
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』乙太網。
不過接著測試非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.檢測到多開應該提供回撥給開發者自行處理;