遊戲開發日誌 #04


在經過各路取經後,遊戲由純解迷更改為輕動作的故事類解迷 ARPG。
現在正踏入戰鬥系統的設計和編程。

說到戰鬥系統,就會出現不少參數,要從遊戲修改程式保護這些參數是一個重要的課題。

這次就分享小弟的加密方法。

Encrypt Method 加密方法

在分享小弟的方法之前,先簡談一下遊戲修改程式的運作和基本的防範措施。


[遊戲修改程式 Game Cheater 的運作]

每個程式 Application 在執行時,都會向系統索取記憶體的一部份來使用,然後把主程序和一堆運作用的參數放進去,一切的運作只會對記憶體進行讀寫,除非程序要把資料寫入硬碟或讀取新的運作用資料。

遊戲修改程序會向系統取得權限,掃瞄指定程式的記憶體範圍,從中找出使用者指定的參數作修改。

例:遊戲中玩家的生命是 100,利用修改程序掃瞄 100 這個數值,型態包括 Int16, Int32, Int64, Float,並把找到的所有記憶體位置記錄;玩家回到遊戲,被攻擊了,生命變成 75,然後到修改程序再掃瞄,但這次使用 75 去找之前 100 的記錄中哪一個變成 75。

基本上只要兩三次就可以找到正確的記憶體位置,然後利用修改程序把這個記憶體位置的數值更改,甚至凍結,這樣就會變成不死了。

除了實數的掃瞄,針對如 RPG Maker 的加密算法(下面詳談)所衍生的另一類掃瞄法,就是數字趨勢和百份比預測。
無論使用什麼加密算法,只要是*可逆算的話,數字在加密前後的增減趨勢不是一樣就是相反,絕不會隨機,所以只要找出同樣趨勢的變化就可以找到參數的正確記憶體位置,再以多個樣板作逆推測,就可以知道加密算法了。

*可逆算 - 可以從加密後的狀態計算回原本的數值。登入密碼的加密法是不可逆算的,伺服器的資料庫中並沒有使用者的原密碼記錄,使用者登入時,程序會把輸入的密碼加密後核對資料庫中的加密資料;要破解登入密碼,只有暴力破解一途,即是不斷嘗試直到成功為止。


[防範措施]

由於遊戲修改程式也是靠玩家決定去掃瞄什麼,只要在記憶體內的參數和畫面顯示的數值並不一樣,基本就可以解決。

過去有一款 RPG Maker 的軟體,使用者可以自己制造 RPG 角色扮演遊戲,由於可以自己編程,到現在還有不少人以此軟體來制作遊戲。
RPG Maker 會把參數乘以2再加1後套用在參數中,即是畫面上生命值是 10 的話,在記憶體中的數值則是 21,玩家使用修改程式掃瞄 10 的話並不會找到任何結果。

不過,隨著數字趨勢和百份比預測的功能,RPG Maker 的算法很快就被公開了。
即使沒有這個預測功能,把遊戲程序反編譯的話,也可以把加密算法找出來。

也有一些遊戲公司,把本來是數字的參數以文字 String 來儲存,不過也有部份修改程式會同時掃瞄不同型態的 String,所以現在也沒有什麼遊戲使用這方法。

另外也有一些遊戲會用一堆假的參數去記錄,讓修改程式永遠找到一大堆結果,無法正確判斷哪一個才是真的,不過也沒有太多遊戲會這樣浪費資源。

到最後,為了防範遊戲修改程式,遊戲基本都只會進行以下兩個方法:
  1. 獨自開法的加密法
  2. 代碼混淆 Obfuscator
獨自開法的加密法主要讓參數變成完全不同東西,例如 10 會變成 Oa84I3F 之類的東西。由於不是數字,文字和本來的數值完全沒有趨勢關係,修改程式就無用武之地。

代碼混淆是防範反編譯的手段,不是阻止反編譯,而是把所有代碼都變成亂碼,即使反編譯後都看不懂程序內容。

代碼混淆 Obfuscator 可以在各遊戲引擎的市場中找到並購入,不算是太昂貴的東西。
但加密法就必須自行開法了,而這次分享的就是本人使用中的加密法。


[加密方法 Encrypt Method]

先看看結果:

小弟使用的加密法是亂碼。
使用亂碼的原因很簡單,因為記錄的是文字,和原本數值沒有任何趨勢關係,直接讓修改程式失去作用。

由於是可逆算的加密法,多樣性變成最大關鍵。
多樣性指的是加密的多變性,有幾種方式:
  1. 10 加密後是 O4ju3P,11 加密後是 pd5kns,12 加密後是 qw4kj9
  2. 這次 10 加密後是 O4ju3P,下次是 pd5kns ,再下次是 qw4kj9
兩種方式都是以多個樣板或金鑰來加密不同的對象,讓加密後的結果難以逆算或多變。
小弟是採用第2種方法。

在小弟的加密法中,如上圖所見,可以加密的對象有 Integer、Float、String 和 Boolean,不論是那一種類,都會以加密算法轉成 Integer / float,並以下面的加密組去隨機加密成文字串記錄下來。
private enum EncryptGroup
{
    //0    1    2    3    4    5    6
    aFB, jC2, oR2, jp5, IXH, Jty, iRh,  // 0
    VCw, TIg, kyg, Zqb, HRj, OQM, Jgp,  // 1
    yQZ, rKy, cUK, dRc, IwY, FdO, F7L,  // 2
    aPy, xeu, NCI, Ojt, ZO0, p87, Sa2,  // 3
    JPP, EsZ, kLZ, fgp, S2q, XUK, Wpg,  // 4
    Kkp, arI, MVo, DOJ, DeO, ny1, dnu,  // 5
    hpI, GJe, wPh, QsU, KwK, jVH, GBv,  // 6
    kHN, Au5, Fla, kot, CBU, Amc, ogl,  // 7
    LtP, uGE, fJs, MLR, x88, JmE, fxe,  // 8
    e6W, VQ3, vzE, bDb, KXB, LIC, sfF,  // 9
    xAj, fiD, Ndf, k51, VGr, lcW, wXr,  // .
    Lze, LMb, Jyw, vVu, w3Z, t3N, F8K,  // -
    uuS, rUc, UAr, zOm, mZd, Ypd, FbV,  // ""
    pNE, qnI, M5v, iOU, avE, iSU, tAK   // null
}

以加密算法和隨機算法合併使用的加密法,就是小弟的加密法了。


[使用方法]

使用上非常簡單,如上圖中一樣,跟一般的 struct 差不多。
public class PlayerLife : MonoBehaviour {

    private static EncInt life = new EncInt().SetMin(0).SetMax(10).Set(10);
    private static EncFloat timer = new EncFloat().SetMin(0);
    private static EncBool isDead = new EncBool();
    private static EncString playerName = new EncString();
  
    public bool IsDead
    {
        get
        {
            isDead.value = life.value <= 0;
            return isDead.value;
        }
    }

    private void Start()
    {
        playerName.value = "Hero";
    }

    private void Update()
    {
        timer.Add(Time.deltaTime);
    }

    private void LifeTest()
    {
        life.Subtract(8);
        Debug.Log("Current Life: " + life.value);
        life.Add(6);
        Debug.Log("Current Life: " + life.value);
      
        if (IsDead)
        {
            // Player Dead.
            Debug.Log("You are dead!");        }
    }
}

由於加密數值基本就是 Serializable,必須是 static,所以使用時請留意。
使用了 Fluent Interface,所以在初始化時的方法可以同一行接續的編寫,非常方便。

全部的修改可以使用 .value = yourValue; 來進行,而 EncInt 和 EncFloat 提供了 .Add() 和 .Subtract() 兩個方法作加減。
EncInt 和 EncFloat 除了值外,還可以設置上下限,會限制數值的變化,在 get 或 set 時都會自動修正。
EncBool 加入了 .Invert() 方法,方便開關。
EncString 功能最普通,但做法最麻煩的一個。使用上沒有分別,就是編寫時多了一點功夫。要把文字變成數字,就要顧及英文以外的所有文字,因此採用了 utf-8 coding 的轉換,把所有文字轉成 byte array,再逐一進行加密算法和隨機加密來記錄。

在使用上完全不會直接使用任何加密方法,只是直接去使用這4個變數就可以,記錄的一切就自然會加密。


[加密組生成器 Encrypt Group Generator]

看到上面那個加密組就頭痛了吧,特別是加密的話,當然要使用自己的加密組吧。
小弟雖然笨笨的,但也不會笨到自己逐個鍵入,當然是寫一個程序來生成吧。


加密組生成器 Encrypt Group Generator 是 Excel 檔案,會自動生成最重要的加密組。
可以設定生成組的數量(Encrypt Group 欄)、每組的長度(Enctype Str Length)、以及加密文字的數量(Encrypt Char)。
生成組的數量和每組的長度必須於程序中改成一樣(下面詳談),否則並不能正常運作。
而加密文字的數量,已在程序中作為加密的標準,如要修改的話必須對程序作修正,在不了解的情況下請不要修改這項。

在按下 Generate 後就會生成新的加密組,生成後把 H 行的結果複製並貼上到 Script 中的 enum 中,就可以使用自己一套的加密組了。


[客制設定]

既然是分享,當然可以讓大家簡單修改加密算法和加密組的設定,而不用修改任何程序。
private static readonly int     // Change follow int for your way
    encStrLength = 3,   // Length of each encrypt string
    encStrGroup = 10,   // how many group of each character (column)
    intMultiply = 4,
    intPlus = 3,
    floatMultiply = 3,
    floatPlus = 2;

在 class EncryptMethod 中,所以加密方法都在這裡,第一組就是 Settings 設定。
enum EncryptGroup 可以用上面的加密組生成器來做自己獨有的加密組,可以增加組的數量和字串的長度。我自己使用中的是 10 組,長度是 5。
如更改了組的數量和字串的長度,就要更改 int encStrLength 和 encStrGroup。

要修改加密算法的話,可以更改 intMultiply, intPlus, floatMultiply 和 floatPlus。
int 和 float 的算法就是 value * multiply + plus,如希望更改這算法,則要自行修改程序了,不過這算法已經很足夠和多變。
注意不要把這四個參數改成 float,浮點運算在乘除運算後會有誤差,所以一定要使用 integer。


[下載]

在這裡提供下載,可以下載 Sample Project,可以只下載 Script。
加密組生成器已設定成程序中的設定。

Script 中有比較詳細的說明,請參考。

下載:Encrypt Method - Google Drive

留言

此網誌的熱門文章

[教學]一起來開發遊戲吧 - Unity C# 基礎

QUMARION

[教學]一起來開發遊戲吧(二) - Character Controller, Pool System