參考:https://blog.csdn.net/mawei7510/article/details/80250416
在應用開發中,如果有簽到打卡之類的功能,我們肯定需要在項目中禁止用戶開啓虛擬定位,導致在***米之外的距離模擬定位然後進行了打卡操作!
(一)
首先:獲取用戶手機是否打開了 “允許模擬位置” 選項?
其實很簡單,這些設置項,基本都是寫在數據庫裏,所以只要看看setting的源碼(或者查看logcat可能也可以得到些有用的信息),就能知道該配置是寫了數據庫的哪個字段。
boolean isOpen = Settings.Secure.getInt(context.getContentResolver(),Settings.Secure.ALLOW_MOCK_LOCATION, 0) != 0;
//很明顯,Settings.Secure.ALLOW_MOCK_LOCATION 就是存放允許模擬位置的數據庫字段了
//當isOpen爲ture時,則是用戶打開了允許模擬位置的選項,否則則沒有開啓!
(二)
其次:虛擬定位可以通過一些第三方應用,然後把我們自己的應用克隆一個。在啓動的時候需要在第三方的應用裏面來啓動,這樣的話在私有文件裏面生成的包名勢必會和直接啓動自己的應用有區別,知道了這些,我們將通過以下三個方法來一一檢測;
首先介紹一些那些使用應用分身雙開和虛擬定位的應用和自己的應用在私有目錄下生成的包名有什麼區別:
我們知道App的私有目錄是/data/data/包名/
或/data/user/用戶號/包名
,通過Context.getFilesDir()
方法可以拿到私有目錄下的files
目錄。在多開環境下,獲取到目錄會變爲/data/data/多開App的包名/xxxxxxxx
或/data/user/用戶號/多開App的包名/xxxxxxxx
。
舉個例子,在我手機上,正常使用App上面的代碼獲取到的路徑爲/data/user/0/top.darkness463.virtualcheck/files
。在多開分身的多開環境下,路徑爲/data/user/0/dkmodel.zom.rxo/virtual/data/user/0/top.darkness463.virtualcheck/files
。
當然,多開軟件是可以hook處理讓你拿到正常的目錄,但截至寫這篇文章爲止,市面上大部分多開App沒有繞過這項檢測,僅有360家的分身大師可以繞過。
下面開始正式檢測:
1. ps檢測(詳見https://www.jianshu.com/p/216d65d9971e)
我們先通過執行對uid進行過濾,得到類似下面的結果
// 正常情況下
u0_a148 8162 423 1806036 56368 SyS_epoll+ 0 S top.darkness463.virtualcheck
// 多開環境下
u0_a155 19752 422 4437612 62752 SyS_epoll+ 0 S top.darkness463.virtualcheck
u0_a155 19758 422 564234 54356 SyS_epoll+ 0 S com.lbe.parallel
u0_a155 19747 422 734562 24542 SyS_epoll+ 0 S com.lbe.parallel:mdserver
可以看到在多開環境下,會獲取到自己的包名和多開App的包名這2個包名,通過這些包名去/data/data/
下找會找到2個目錄,而正常情況下只能在/data/data/
下找到自己的App的目錄。看下具體代碼實現;
public static boolean isRunInVirtual() {
String filter = getUidStrFormat();
String result = exec("ps");
if (result == null || result.isEmpty()) {
return false;
}
String[] lines = result.split("\n");
if (lines == null || lines.length <= 0) {
return false;
}
int exitDirCount = 0;
for (int i = 0; i < lines.length; i++) {
if (lines[i].contains(filter)) {
int pkgStartIndex = lines[i].lastIndexOf(" ");
String processName = lines[i].substring(pkgStartIndex <= 0
? 0 : pkgStartIndex + 1, lines[i].length());
File dataFile = new File(String.format("/data/data/%s",
processName, Locale.CHINA));
if (dataFile.exists()) {
exitDirCount++;
}
}
}
return exitDirCount > 1;
}
這裏的應用列表檢測不是指簡單的遍歷應用列表判斷是不是安裝了多開App,我們並不阻止用戶安裝多開App並多開其他App,我們只是不希望用戶多開我們自己的App,因此不能檢測到用戶安裝了多開App就把他幹掉。2.應用列表檢測
多開App都會對context.getPackageName()
進行處理,讓這個方法返回原始App的包名,因此在被多開的App看來,多開App的包名和原始的那個App的包名一樣,因此在多開環境下遍歷應用列表時會發現包名等於原始App的包名的應用會有兩個。
private boolean checkPkg(Context context) {
try {
if (context == null) {
return false;
}
int count = 0;
String packageName = context.getPackageName();
PackageManager pm = context.getPackageManager();
List<PackageInfo> pkgs = pm.getInstalledPackages(0);
for (PackageInfo info : pkgs) {
if (packageName.equals(info.packageName)) {
count++;
}
}
return count > 1;
} catch (Exception ignore) {}
return false;
}
3.maps檢測
讀取/proc/self/maps
,多開App會加載一些自己的so到內存空間,舉個例子,360的分身大師加載了其目錄下的某個so,/data/app/com.qihoo.magic-gdEsg8KRAuJy0MuY18BlqQ==/lib/arm/libbreakpad-jni-1.5.so
,通過對各種多開App的包名的匹配,如果maps中有多開App的包名的東西,那麼當前就是運行在多開環境下。目前沒有發現多開App繞過該項檢測,但缺點是需要收集所有多開App的包名,一旦多開App改個包名就失效了。
Set<String> virtualPkgs; // 多開第三方App包名列表
private boolean check() {
BufferedReader bufr = null;
try {
bufr = new BufferedReader(new FileReader("/proc/self/maps"));
String line;
while ((line = bufr.readLine()) != null) {
for (String pkg : virtualPkgs) {
if (line.contains(pkg)) {
return true;
}
}
}
} catch (Exception ignore) {
} finally {
if (bufr != null) {
try {
bufr.close();
} catch (IOException e) {
}
}
}
return false;
}
以上三種檢測方法有的會被第三方虛擬定位軟件或者多開分身軟件躲避掉,有的則不會,所以使用的時候建議三種方法全部用上。