Android安全防护/检查root/检查Xposed/反调试/应用多开/模拟器检测(持续更新1.0.5)
参考地址: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.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
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
更多相关文章
- Android(安卓)WebView 总结 —— 使用HTML5播放视频及全屏方案
- conversion to dalvik format failed with error 1 错误解决方案
- 【Android性能优化】内存泄露和内存溢出(OOM)的引发原因及优化方案
- Android(安卓)AdbCommandRejectedException和cannot bind to套接
- 【android】数据库升级完整解决方案
- Android连续获取当前所连接WiFi及周围热点列表信息的解决方案
- Cocos2d-x 3.2编译生成Android程序出错的解决方案:c++_static报
- Appium+Python appium启动夜神模拟器定位元素(三)
- android 6.0 动态权限解决方案