参考地址:https://www.jianshu.com/p/c37b1bdb4757

多开软件检测

多开软件的检测方案这里提供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.1VirtualXP0.11.2Virtual App *
红米3S/Android6.0/原生engXXXOOOXOOOOXOOOXOOOOXXXOOXXXOO
华为P9/Android7.0/EUI 5.0 rootXXXXOOXOXOOXOXOXOOXOXXXXOXXXOO
小米MIX2/Android8.0/MIUI稳定版9.5XXXXOOXOXOOXOXOXOOXOXXXXOXXXOO
一加5T/Android8.1/氢OS 5.1 稳定版XXXXOOXOXOOXOXOXOOXOXXXXOXXXOO

*测试方案顺序如下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<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检测

需要维护多款分身包名

    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<Integer> 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
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

 

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐