在应用开发中,如果有签到打卡之类的功能,你是否会遇到检测用户是否使用了虚拟定位软件来进行打卡?如果有,那么请仔细阅读这篇文章。该文章会带你认识什么是虚拟定位、什么是应用分身,以及如何通过代码来检测用户是否使用了虚拟定位进行打卡...在写之前首先要感觉这位博主的分享https://blog.csdn.net/mawei7510/article/details/80250416;他主要写的是应用分身、应用双开之类的内容,受他的影响,研究了一下虚拟定位,其实这两者的原理是一样的,虚拟定位也是通过一些第三方应用,然后把你自己的应用克隆了一个。启动的时候需要在第三方的应用里面来启动,这样的话在私有文件里面生成的包名势必会和直接启动自己的应用有区别,知道了这些,我们将通过以下三个方法来一一检测;
首先介绍一些那些使用应用分身双开和虚拟定位的应用和自己的应用在私有目录下生成的包名有什么区别:
我们知道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进行过滤,得到类似下面的结果
可以看到在多开环境下,会获取到自己的包名和多开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(" "); 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; }
2.应用列表检测
这里的应用列表检测不是指简单的遍历应用列表判断是不是安装了多开App,我们并不阻止用户安装多开App并多开其他App,我们只是不希望用户多开我们自己的App,因此不能检测到用户安装了多开App就把他干掉。
多开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; }
以上三种检测方法有的会被第三方虚拟定位软件或者多开分身软件躲避掉,有的则不会,所以使用的时候建议三种方法全部用上。好了今天就写到这里,再次感谢mawei7510的分享;