京东H5小游戏《疯狂足球》Android外挂实现

前言

首先声明,此文仅用于技术交流,若用于牟利,后果自负!由于这个小游戏高分者可获得实体奖励,通过外挂作弊取得高分获取奖励实属诈骗,相信游戏团队也有辨别作弊的实力,请大家不要拿自己的信用作赌注,三思后行!

正文

这里写图片描述
最近,相信大家也被《疯狂足球》这个小游戏刷屏了,得分前三名送手机啊,再便宜也要上千块一部吧。我也玩了几天,得分最高只能取得280分,再也上不去了。看来我还是不适合玩游戏,我还是回归本行继续写我的Bug吧…  ̄□ ̄||
这里写图片描述

所以,就有了今天的这篇分享…

言归正传,我们开始分析游戏。这个游戏和微信《跳一跳》的玩法很相识,都是用按压的时间长度来控制力度。但是《疯狂足球》还得控制方向,就是手指按下时的点指向手指抬起时的点。

力度控制

按压的时间非常好控制,都是同一个值,触摸屏幕固定的毫秒数后力度会达到正中间,这样我们就能保证力度控制不会失误了。

方向控制

方向控制就稍微麻烦了点,我们可以通过判断截图的像素来获取球门的位置;也可以在球门位置上加一层透明的窗口,手动指示球门的位置;甚至可以人肉计算球门的位置,这个就看大家喜欢了。

实现

知道了按压时间、球门位置,再通过人肉测量足球的初始位置,那实现简直不是事。Swipe就可以搞定了。
假设球的初始位置是x:100,y:200
球门的位置是x:300,y400
按压时间250ms
那么实现代码为:
adb shell input swipe 100 200 300 400 250
搞定!So easy!终于可以像姚明一样踢足球了!
这里写图片描述
等等!~好像哪里不对!
如果这样做的话,我们每次踢球都是划出一条完美的直线,如果防作弊系统记录了我们的触摸坐标,我们岂不是一秒就被红牌警告?
所以不行,我们还得想想办法解决这个问题。

手指触摸移动坐标问题

要解决这个问题,我们还得模拟出真实的手指触摸移动坐标,试问生成技术哪家强?出门左拐找Deep Learning。但是生成模拟坐标我们需要大量的训练数据啊,数据去哪里找?所以,生成模拟坐标这一项我们就放弃吧…
这里写图片描述
既然无法模拟,那我们就用真人手指去操作吧。那么问题又来了,真人操作怎么做到精准的控制时间?考验我们变通能力的时候到了~
我们可以监听手指的触摸事件,在手指按下的瞬间开始计时,250毫秒之后向系统发送手指抬起的事件啊~我简直是太聪明了,就这么干。
上代码,人生苦短,我用python,5行代码搞定:

import subprocess
import time
import random

for ii in range(20):
    output = subprocess.getstatusoutput('adb shell getevent -c 1')
    time.sleep(random.uniform(0.150,0.155))
    output = subprocess.getstatusoutput('adb shell "cat /sdcard/up_event > /dev/input/event1"')
    time.sleep(2)

但是,运行下来你会发现,功能是ok了,但是时间控制得不准啊。好吧,又要创建shell又要执行adb,是无法控制好时间的了,那我们先看看代码吧。后面再说说怎么改进这个问题。
首先,adb shell getevent –c 1。getevent大家应该都知道了,这个命令可以打印出android设备的所有事件,触摸屏事件只是其一,这里我们不考虑其他事件的影响。-c 1这个参数一定要加,否则getevent会一直处于打印状态,无法返回,我们的程序就会卡在这一行代码上。然后cat /sdcard/up_event > /dev/input/event1这段代码是模拟手指抬起事件的。up_event中的数据如下图所示,16进制,可以通过cat /dev/input/event1 > /sdcard/events获得,然后截取最后一段手指抬起事件的数据就行了。/dev/input/event1是我手机的触摸屏设备,在其他手机上可能是event0或者其他。
这里写图片描述

时间控制不准问题

前面我们还遗留了一个问题,就是python代码时间控制不准。那我们可以将代码转移到手机内去执行,这样就可以避免启动shell和adb时间的不确定性了。如果把这个过程做成一个APP,你会发现APP完全没有权限调用getevent,即使是系统级APP也不行。所以我把这段程序改用Java写并编译成dex文件,chmod 777 crack.dex后,就可以执行了,当然,需要root权限。虽然时间上还是有些偏差,但是没有大问题。
Java代码:

public class Main {     
    public static void main(String args[]) {
        for (int i = 0; i < 20; i++) {
            runOperation();
        }
    }

    static Random r = new Random();
    static DataOutputStream dos = null;
    static Process p;

    private static void runOperation() {
        try {
            p = Runtime.getRuntime().exec("sh");
            // 监听触摸屏事件
            dos = new DataOutputStream(p.getOutputStream());
            dos.write("getevent -c 1 /dev/input/event1".getBytes());
            dos.writeBytes("\n");
            dos.writeBytes("exit\n");
            dos.flush();

            BufferedReader successResult = new BufferedReader(new InputStreamReader(p.getInputStream()));
            BufferedReader errorResult = new BufferedReader(new InputStreamReader(p.getErrorStream()));
            // 输出错误信息
            String s = null;
            StringBuilder errorMsg = new StringBuilder();
            while ((s = errorResult.readLine()) != null) errorMsg.append(s);
            if (errorMsg.toString().length() > 0)
                System.out.println("fail getevent : " + errorMsg.toString());

            int ignore = successResult.read();

            // 关闭输入输出
            successResult.close();
            errorResult.close();
            dos.close();

            //利用sleep时间,在另一个线程中打开sh
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        p = Runtime.getRuntime().exec("sh");
                        dos = new DataOutputStream(p.getOutputStream());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();


            Thread.sleep(240 + r.nextInt(20));

            // 模拟手指抬起事件
            dos.write("cat /sdcard/up_event >> /dev/input/event1".getBytes());
            dos.writeBytes("\n");
            dos.writeBytes("exit\n");
            dos.flush();

            errorResult = new BufferedReader(new InputStreamReader(p.getErrorStream()));
            // 输出错误信息
            errorMsg = new StringBuilder();
            while ((s = errorResult.readLine()) != null) errorMsg.append(s);
            if (errorMsg.toString().length() > 0)
                System.out.println("fail cat : " + errorMsg.toString());

            //关闭输入输出
            errorResult.close();
            dos.close();


            Thread.sleep(2000);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行dex文件:dalvik –cp /sdcard/crack.dex packagename.Main

其他问题

即便做到了这种程度,还是无法躲过作弊检测的。一是我们这种做法的缺陷,在我们模拟了up事件之后,由于手指还没有离开,系统又会产生一个down事件和一系列move事件及up事件,如果游戏检测了这一部分触摸事件,那我们一秒就穿帮了。还有就是游戏制作方可以记录我们每一次游戏的得分,再通过概率分布的相关算法来检测我们是否作弊。
当然,这两个问题完全可以通过深度学习解决,如果有人能完美的模拟人的操作和得分的概率分布,那么防作弊检测将无法判断是不是人在玩游戏。除非他们也用上深度学习技术,GAN技术了解一下,哈哈。
这里写图片描述 谢谢观赏~

相关文章
相关标签/搜索