最近有業務上的要求,要求app在本地進行諸如軟件多開、hook框架、模擬器等安全檢測,防止做弊行爲。html
防做弊一直是老生常談的問題,而模擬器的檢測每每是防做弊中的重要一環,但在查找資料的過程當中發現,網上的模擬器檢測方案已經有些過期了,只能本身再跟進學習,本文對此次學習內容進行總結: 模擬器的檢測秉持一句話:抓取特徵值與真機比較。java
早期模擬器沒那麼多套路,特徵值很是明顯,某些值甚至是一長串的0,檢測起來很方便,常規的方案如android
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
...
複製代碼
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
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();
複製代碼
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
本文方案已經集成到EasyProtectorLib
github地址: github.com/lamster2018…
中文文檔見:www.jianshu.com/p/c37b1bdb4…
1.檢測到多開應該提供回調給開發者自行處理;