1. 程式人生 > >京東H5小遊戲《瘋狂足球》Android外掛實現

京東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技術瞭解一下,哈哈。
這裡寫圖片描述
謝謝觀賞~