一行代码帮你检测Android多开软件
声明:本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
目录
- 简介
- 借鉴方案&测试结果
- 端口法检测思路
- 实现方案
- 测试结果
- Demo地址
简介
最近有业务上的要求,要求app在本地进行诸如软件多开、hook框架、模拟器等安全检测,防止作弊行为。
防作弊一直是老生常谈的问题,而软件多开检测往往是防作弊中的重要一环,在查找资料的过程中发现多开软件公司对防多开手段进行了针对性的升级,即使非常新的资料也无法做到通杀。
所以站在前人的肩膀上,继续研究。
借鉴方案
借鉴方案来自以下两个帖子
《Android多开/分身检测》blog.darkness463.top/2018/05/04/…/
《Android虚拟机多开检测》www.jianshu.com/p/216d65d99…
文中的方案简单总结起来是4点 1.私有文件路径检测; 2.应用列表检测; 3.maps检测; 4.ps检测;
代码此处不贴了,这四种方案测试结果如下
测试机器/多开软件* | 多开分身6.9 | 平行空间4.0.8389 | 双开助手3.8.4 | 分身大师2.5.1 | VirtualXP0.11.2 | Virtual App * |
---|---|---|---|---|---|---|
红米3S/Android6.0/原生eng | XXXO | OXOO | OXOO | XOOO | XXXO | XXXO |
华为P9/Android7.0/EUI 5.0 root | XXXX | OXOX | OXOX | XOOX | XXXX | XXXO |
小米MIX2/Android8.0/MIUI稳定版9.5 | XXXX | OXOX | OXOX | XOOX | XXXX | XXXO |
一加5T/Android8.1/氢OS 5.1 稳定版 | XXXX | OXOX | OXOX | XOOX | XXXX | XXXO |
*测试方案顺序1234,测试结果X代表未能检测O成功检测多开; *virtual app测试版本是git开源版,商用版已经修复uid的问题;
可以看到的是,检测效果不是很理想,没有哪一种方法可以做到通杀市面排名靠前的这些多开软件,甚至在高版本机器上,多开软件完美避开了检测。
端口监听法思路
为了避免歧义,我们接下来所说的app都是指的同一款软件,并定义普通运行的app叫做本体,运行在多开软件上的app叫克隆体。并提出以下两个概念
狭义多开:只要app是通过多开软件打开的,则认为多开,即使同一时间内只运行了一个app
广义多开:无论app是否运行在多开软件上,只要app在运行期间,有其余的『自己』在运行,则认为多开 (有点《第六日》的意思,克隆人以为自己是真人,发现跟自己一模一样的人,都认为对方是克隆人)
我们前面所借鉴的四种方案,都是去针对狭义多开进行检测,通过判断运行在多开软件时的特征进行反制,多开软件也会针对这些检测方案进行研究,提出相应措施。
那么我们退一步,顺着检测广义多开的方向进行思考,我们允许app运行在多开软件上,但是在一台机器上同一时间有且只能运行一个app(无论本体or克隆体),只要app能发现有一个同样的自己,然后干掉对方或自杀,就达到防止广义多开的目的。
那么我们怎样让这两个app见面呢?
微信同一账号不能同时登录在不同的手机上,靠的是网络请求,限定登录设备。
那在本地如何处理这种情况呢?是不是也可以靠网络通信的方式完成见面? 答案当然是肯定的啊,不然我写这篇干嘛,利用socket,自己既当客户端又当服务端就能完成我们的需求。
1.app运行后,先做发送端,在合适的时候去连接本地端口并发送一段密文消息,如果有端口连接且密文匹配,则认为之前已经有app在运行了(广义多开),接收端进行处理; 2.app再成为接收端,接收可能到来连接; 3.后续若有app启动(无论本体or克隆体),则重复1&2步骤,达到『同一时间只有一个app在运行』的目的,解决广义多开的问题。
实现方案
思路有了,接下来就是实现,完整代码地址见文章底部。
第1步:扫描本地端口
想当然利用netstat指令来扫描已经开启的本地端口
但是这个方法有3个坑 1.netstat在部分机器上用不了; 410063005.iteye.com/blog/192354…
2.busybox 在部分机器用不了;
3.netstat的输出从源码上看,实际是纯打印; blog.csdn.net/earbao/arti…
既然有这些坑,干脆直接手动处理,因为netstat的本质上还是去读取/proc/net/tcp等文件再格式化处理,tcp文件格式也是很标准化的,通过研究源码,找出端口之间的关系。 0100007F:8CA7 其实就是 127.0.0.1:36007
最终扫描tcp文件并格式化端口的关键代码
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);//16进制转成10进制portList.add(port);}
复制代码
第2步:发起连接请求
接下来向每个端口都发起一个线程进行连接,并发送自定义消息,该段消息用app的包名就行了(多开软件很大程度会hook getPackageName方法,干脆就顺着多开软件做)
try {//发起连接,并发送消息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();//获取输入流,这里没做处理,纯打印InputStream inputStream = socket.getInputStream();BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));String info = null;while ((info = bufferedReader.readLine()) != null) {Log.i(TAG, "ClientThread: " + info);}
bufferedReader.close();inputStream.close();socket.close();} catch (ConnectException e) {Log.i(TAG, port + "port refused");}
复制代码
主动连接的过程完成,先于自己启动的app(可能是本体or克隆体)接收到消息并进行处理。
第3步:成为接收端,等待连接
接下来就是成为接收端,监听某端口,等待可能到来的app连接(可能是本体or克隆体)。
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));//开一个10000~65535之间的端口while (true) {Socket socket = serverSocket.accept();ReadThread readThread = new ReadThread(secret, socket);//假如这个方案很多app都在用,还是每个连接都开线程处理一些readThread.start();
// serverSocket.close();}} catch (BindException e) {startServer(secret);//may be loop forever} catch (IOException e) {e.printStackTrace();}}
复制代码
开启端口时为了避免开一个已经开启的端口,主动捕获BindExecption,并迭代调用,可能会因此无限循环,如果怕死循环的话,可以加一个类似ConcurrentHashMap最坏尝试次数的计数值。不过实际测试没那么衰,随机端口范围10000~65535,最多尝试两次就好了。
每一个处理线程,做的事情就是匹配密文,对应上了就是某个克隆体or本体发送的密文,这里是接收端主动运行一个空指针异常,杀死自己。处理方式有点像《三体》的黑暗森林法则,谁先暴露谁先死。
private class ReadThread extends Thread {private ReadThread(String secret, Socket socket) {InputStream inputStream = null;try {inputStream = socket.getInputStream();byte buffer[] = new byte[1024 * 4];int temp = 0;while ((temp = inputStream.read(buffer)) != -1) {String result = new String(buffer, 0, temp);if (result.contains(secret)) {checkCallback.findSuspect();//提供回调,开发者自行处理checkCallback = null;}}inputStream.close();socket.close();} catch (IOException e) {e.printStackTrace();}}}
复制代码
*因为端口通信需要Internet权限,本库不会通过网络上传任何隐私
测试结果
以之前提到的那些机型和多开软件做测试样本,目前测试效果基本做到通杀。 因安卓机型太广,真机覆盖测试不完全,有空大家去git提issue;
在application的mainProcess里调用一次即可。 模拟器因为会抢localhost,demo里做了模拟器判断。
Demo地址
本文方案已经集成到EasyProtectorLib
github地址: github.com/lamster2018…
中文文档见:www.jianshu.com/p/c37b1bdb4…
使用方法 VirtualApkCheckUtil.getSingleInstance().checkByPortListening(String secret);
Todo
1.检测到多开应该提供回调给开发者自行处理;--v1.0.4 support
2.同样的思路,利用ContentProvider也应该可以完成
*感谢同事大龙提供的思路
一行代码帮你检测Android多开软件相关推荐
- 一行代码帮你检测Android模拟器优劣
码个蛋(codeegg)第 797 次推文 作者:普通的程序员 博客:https://www.jianshu.com/p/434b3075b5dd 文章目录 简介 初代常规手段 进阶手段 改良手段和新 ...
- 一行代码帮你彻底解决pip下载速度慢的问题,更改pip源至国内镜像(无须新建文件夹), 享受飞一般的速度
目录 1.pip安装慢的原因 2.一行代码更改pip源至国内镜像 3.一些主流的镜像网站 1.pip安装慢的原因 使用Python的人必然会用到一个工具就是pip, 它帮助我们安装各种第三方库, 用起 ...
- android代码图片编辑,怎样修改android系统apk软件里面的代码和图片?
你好,你的问题我算是看明白了,从你的问题可以设计三个方面,apk反编译.apk回编译以及apk签名,看来,我得从头说起了. 首先,我在这里提供下反编译Android所需的软件,当然是全套,刚刚收集整理 ...
- 一行代码,你就可以制作出放大镜软件
首先,咱们打开这次编程需要用到的软件--Quick Batch File Compiler 然后,输入仅仅一行代码: magnify 点击"输出"重命名为"放大镜.exe ...
- Python 代码转 Latex 公式,这个开源库用一行代码帮你搞定
转自 | 机器之心 数学是数据科学和机器学习的重要基础,数学运算的结果对于机器学习项目而言是至关重要的.在编写代码时,我们常常需要定义数学公式的计算形式.像 S=r^2 这样简单的数学公式,大概不会出 ...
- Python代码转Latex公式,这个开源库用一行代码帮你搞定
视学算法报道 编辑:小舟 转载自公众号:机器之心 你的代码中有数学公式吗? 数学是数据科学和机器学习的重要基础,数学运算的结果对于机器学习项目而言是至关重要的.在编写代码时,我们常常需要定义数学公式的 ...
- python识别latex公式_Python代码转Latex公式,这个开源库用一行代码帮你搞定
来源:机器之心 数学是数据科学和机器学习的重要基础,数学运算的结果对于机器学习项目而言是至关重要的.在编写代码时,我们常常需要定义数学公式的计算形式.像 S=r^2 这样简单的数学公式,大概不会出现拼 ...
- python一行代码实现白噪声检测
print(u'The result of white noise detection:', acorr_ljungbox(diff1, lags=1)) 会打印出来两个值,如果第二个值小于0.05, ...
- 北信源管理网页卸载密码_Homebrew: 一行代码实现mac软件管理
Homebrew是一款帮助我们管理软件的软件.任何开源软件都可以通过Homebrew的一行代码完成软件的下载.升级或卸载等.目前Homebrew主要适用macOS或Linux系统.(Windows系统 ...
- Studio Bot - 让 AI 帮我写 Android 代码
Google I/O 2023 不出所料,今年的 I/O 大会只突出了一个关键词 "AI".Google 旗下的各类产品都成了其展示 AI 实力的舞台.连面向开发者的产品 Andr ...
最新文章
- 【HDOJ】3275 Light
- 《大数据导论》一第1章 理解大数据
- redis使用sysc超时_基于redis的分布式锁实现
- getContextPath、getServletPath、getRequestURI的区别
- 动态将表中的列名全部转换成小写
- win定时关机_怎么让电脑定时关机,有多种办法
- linux-ubuntu下调出中文输入法
- Python | Socket02 - 使用with语句建立一个TCP服务器(阻塞+单线程),将TCP客户端发过来的字符串原路返回
- Redis学习总结(2)——Java使用Redis
- Q118:PBRT-V3材质及其对应的反射模型(笔记)
- python不支持的数据类型是_opencv python:mat数据类型= 17不支持
- android 清理大师 编程,清理大师 Android v2.3.3
- Oracle 同音字查询,汉字的演变过程100字,汉字的演变图片
- 《Redis开发与运维》学习第四章
- 递归算法php,PHP递归算法的详细示例分析
- Python格式化输出%.2f%%
- Structed Streaming(Continuous Processing报错):StreamingQueryException;java.util.NoSuchElementException
- New Year Snowmen(贪心)
- SecureCRT Backspace/delete 键失效的解决方法
- fr4速度 微带线_微带线和带状线(microstrip and stripline)