多开软件的检测方案这里提供5种,首先4种来自
《Android多开/分身检测》
https://blog.darkness463.top/2018/05/04/Android-Virtual-Check/
《Android虚拟机多开检测》
https://www.jianshu.com/p/216d65d9971e
这里提供代码整理,一键调用,
VirtualApkCheckUtil.getSingleInstance().checkByPrivateFilePath(this);
VirtualApkCheckUtil.getSingleInstance().checkByOriginApkPackageName(this);
VirtualApkCheckUtil.getSingleInstance().checkByHasSameUid();
VirtualApkCheckUtil.getSingleInstance().checkByMultiApkPackageName();
VirtualApkCheckUtil.getSingleInstance().checkByPortListening(getPackageName(),CallBack);
第5种方案来自我同事的启发,我起名叫端口检测法,具体思路已经单独成文见
《一行代码帮你检测Android多开软件》
https://www.jianshu.com/p/65c841749dd6
测试情况
测试机器/多开软件* | 多开分身6.9 | 平行空间4.0.8389 | 双开助手3.8.4 | 分身大师2.5.1 | VirtualXP0.11.2 | Virtual App * |
---|---|---|---|---|---|---|
红米3S/Android6.0/原生eng | XXXOO | OXOOO | OXOOO | XOOOO | XXXOO | XXXOO |
华为P9/Android7.0/EUI 5.0 root | XXXXO | OXOXO | OXOXO | XOOXO | XXXXO | XXXOO |
小米MIX2/Android8.0/MIUI稳定版9.5 | XXXXO | OXOXO | OXOXO | XOOXO | XXXXO | XXXOO |
一加5T/Android8.1/氢OS 5.1 稳定版 | XXXXO | OXOXO | OXOXO | XOOXO | XXXXO | XXXOO |
*测试方案顺序如下12345,测试结果X代表未能检测O成功检测多开
*virtual app测试版本是git开源版,商用版已经修复uid的问题
*现在方案1234检测狭义多开已经失效,建议使用方案5做广义多开检测
1.文件路径检测
public boolean checkByPrivateFilePath(Context context) {
String path = context.getFilesDir().getPath();
for (String virtualPkg : virtualPkgs) {
if (path.contains(virtualPkg)) return true;
}
return false;
}
2.应用列表检测
简单来说,多开app把原始app克隆了,并让自己的包名跟原始app一样,当使用克隆app时,会检测到原始app的包名会和多开app包名一样(就是有两个一样的包名)
public boolean checkByOriginApkPackageName(Context context) {
try {
if (context == null) return false;
int count = 0;
String packageName = context.getPackageName();
PackageManager pm = context.getPackageManager();
List pkgs = pm.getInstalledPackages(0);
for (PackageInfo info : pkgs) {
if (packageName.equals(info.packageName)) {
count++;
}
}
return count > 1;
} catch (Exception ignore) {
}
return false;
}
3.maps检测
需要维护多款分身包名
public boolean checkByMultiApkPackageName() {
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.ps检测
简单来说,检测自身进程,如果该进程下的包名有不同多个私有文件目录,则认为被多开
public boolean checkByHasSameUid() {
String filter = getUidStrFormat();//拿uid
String result = CommandUtil.getSingleInstance().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;
}
5.端口检测
前4种方案,有一种直接对抗的意思,不希望我们的app运行在多开软件中,第5种方案,我们不直接对抗,只要不是在同一机器上同时运行同一app,我们都认为该app没有被多开。
假如同时运行着两个app(无论先开始运行),两个app进行一个通信,如果通信成功,我们则认为其中有一个是克隆体。
//遍历查找已开启的端口
String tcp6 = CommandUtil.getSingleInstance().exec("cat /proc/net/tcp6");
if (TextUtils.isEmpty(tcp6)) return;
String[] lines = tcp6.split("\n");
ArrayList portList = new ArrayList<>();
for (int i = 0, len = lines.length; i < len; i++) {
int localHost = lines[i].indexOf("0100007F:");//127.0.0.1:
if (localHost < 0) continue;
String singlePort = lines[i].substring(localHost + 9, localHost + 13);
Integer port = Integer.parseInt(singlePort, 16);
portList.add(port);
}
对每个端口开启线程尝试连接,并且发送一段自定义的消息,作为钥匙,这里一般发送包名就行(刚好多开软件会把包名处理)
Socket socket = new Socket("127.0.0.1", port);
socket.setSoTimeout(2000);
OutputStream outputStream = socket.getOutputStream();
outputStream.write((secret + "\n").getBytes("utf-8"));
outputStream.flush();
socket.shutdownOutput();
之后自己再开启端口监听作为服务器,等待连接,如果被连接上之后且消息匹配,则认为有一个克隆体在同时运行。
private void startServer(String secret) {
Random random = new Random();
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress("127.0.0.1",
random.nextInt(55534) + 10000));
while (true) {
Socket socket = serverSocket.accept();
ReadThread readThread = new ReadThread(secret, socket);
readThread.start();
// serverSocket.close();
}
} catch (BindException e) {
startServer(secret);//may be loop forever
} catch (IOException e) {
e.printStackTrace();
}
}
*因为端口通信需要Internet权限,本库不会通过网络上传任何隐私
作者:普通的程序员
链接:https://www.jianshu.com/p/c37b1bdb4757
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。