Android开发中虚拟位置定位、应用双开、IP代理检测
1、虚拟位置定位、应用双开原理
目前市面上的多开App的原理类似,都是以新进程运行被多开的App,并hook各类系统函数,使被多开的App认为自己是一个正常的App在运行。
从形式上来说多开App有2种形式,一种是从多开App中直接加载被多开的App,如平行空间、VirtualApp等,另一种是让用户新安装一个App,但这个App本质上就是一个壳,用来加载被多开的App,其原理和前一种是一样的,市面上多开分身这款App是用的这种形式,用户每分身一个App需新安装一个包名为dkmodel.xxx.xxx的App。
2、检测方案:检测files目录路径
我们知道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
。
3、检测方法
(1)ps检测
我们先通过执行ps命令并以自己的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的目录。
具体的实现原文已经贴了,这里也不重复了。目前未发现有多开App能绕过该项检测,但在Google Pixel Android 8.0下执行ps获取不到进程信息,在Android 8.1的类AOSP rom下都能正常获取,不知道那个手机什么情况。
(2)应用列表检测
这里的应用列表检测不是指简单的遍历应用列表判断是不是安装了多开App,我们并不阻止用户安装多开App并多开其他App,我们只是不希望用户多开我们自己的App,因此不能检测到用户安装了多开App就把他干掉。
多开App都会对context.getPackageName()进行处理,让这个方法返回原始App的包名,因此在被多开的App看来,多开App的包名和原始的那个App的包名一样,因此在多开环境下遍历应用列表时会发现包名等于原始App的包名的应用会有两个。
代码如下。只对部分多开App有效,例如360的分身大师,不少多开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;
}
4、代理检测
APP在发起网络请求前会检测系统是否设置了代理,如果发现有代理,就不发起请求。以下是一段APP检测系统是否有代理的实例代码
public static boolean isWifiProxy(Context context) {
final boolean IS_ICS_OR_LATER = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
String proxyAddress;
int proxyPort;
if (IS_ICS_OR_LATER) {
proxyAddress = System.getProperty("http.proxyHost"); //获取代理主机
String portStr = System.getProperty("http.proxyPort"); //获取代理端口
proxyPort = Integer.parseInt((portStr != null ? portStr : "-1"));
} else {
proxyAddress = android.net.Proxy.getHost(context);
proxyPort = android.net.Proxy.getPort(context);
}
Log.i("代理信息","proxyAddress :"+proxyAddress + "prot : " proxyPort")
return (!TextUtils.isEmpty(proxyAddress)) && (proxyPort != -1);
}
另外一种方法:
以下是一段使用No Proxy参数发起网络请求的代码:
public void run() {
Looper.prepare();
OkHttpClient okHttpClient = new OkHttpClient.Builder().
proxy(Proxy.NO_PROXY). //使用此参数,可绕过系统代理直接发包
build();
Request request = new Request.Builder()
.url("http://www.baidu.com")
.build();
Response response = null;
try {
response = okHttpClient.newCall(request).execute();
Toast.makeText(this, Objects.requireNonNull(response.body()).string(), Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
}
Looper.loop();
}