破解 Zelix KlassMaster 的字串加密 在java遊戲中作弊(轉載文章) (7千字)

看雪資料發表於2001-07-24

破解 Zelix KlassMaster 的字串加密
在java遊戲中作弊

版本 1.1

作者; Morten Poulsen
翻譯; 夢醒時分 /*夢俠俱樂部[http://www.patching.net/dream]*/
日期; 2001年7月10日

相關訊息;  http://www.zelix.com/klassmaster/featuresStringEncryption.html
     

簡介
~~~~

  反編譯 Java 小程式,並且轉變為程式碼都很難,人們為了尋找隱藏的訊息經常迷失在
java 程式碼中.一種方法就是打亂行號,替換所有的 classes 的名稱, 方法和變數,我們
將會發現我們所需要的:字串加密.

分析程式碼你所需要的工具:
- Java 反編譯器 (例如:JAD)
- 編輯工具 (例如:vim)
- 感覺能力 (例如:大腦)

    最好能夠有些 JVM 的知識, 不過不懂也不用擔心,在這兒我會把一切都告訴你的.
但是想要製作你自己的破解程式,你就一定需要那些知識了.

要作弊 Java 遊戲,你所需要的工具:

- TCP 嗅探器 (例如:tcpdump)
- 支援 Java 的瀏覽器 (例如:Mozilla)
- C 編譯器 (例如:gcc)
- GNU 的工具 (例如:grep)
- 支援 PHP 的伺服器 ;-)


第一步:是什麼呢?

  當然是尋找破解的目標嘍 :) 在 Coca~Cola's 主頁上的遊戲看起來不錯(nordic).打
開 TCP 嗅探器(tcpdump -w file), 執行遊戲.玩一會,然後記錄你的分數.TCP 嗅探器可
以停止了.開啟記錄檔案(vi file).在檔案中尋找你的分數,你應該看到這一行:


GET /magazine/servlet/SetHighscoreServlet?score=6324&game=0&cookie=yourname&md5=c404cd019e1a214487cd4c841

    我們所要解決的就是 md5 的數值是怎麼生成的,那麼我們就可以作弊,自己加分
數了.


第二步:反編譯和分析

    下載遊戲的 .jar 資料夾(提示: 檢視原始檔, <applet> 標籤中的 archive=...),
解開後,反編譯每一個 class 檔案.現在試著在程式碼中查詢返回資料到伺服器的地方
(fgrep -rn "URL" *). b/a/a.java 的第 122 行 (用 jad 反編譯的結果) 看起來可以
作為我們開始的地方:

    URL url = new URL(c, b("L9\001 qMdK|)\016rZ!\f\007cfg8\ndMa-\007DK|)
    \016rZ1,\001x\\kb") + Integer.toString(i) + b("DpOc:_") + d + b("DtA
    a4\013r\023") + e + b("DzJ;b") + a.a.a.a.a.a(i, Integer.parseInt(d), e));

    URL 看起來是這樣的:
    "long text"+i+"text"+d+"text"+e+"text"+result_of_calculation(i,d,e)

    現在的問題是"這些是不是我們看到的字串?"(不要緊張嘛~) 和 " a.a.a.a.a.a()
都幹了些什麼?".

    開啟檔案 (vi b/a/a.java) 到 122 行.我們可以看到加密字串的方法是 b() .
檢視 b() (line 224).是這樣的:

224:    private static String b(String s)
225:    {
226:        char ac[];
227:        int i;
228:        int j;
229:        ac = s.toCharArray();
230:        i = ac.length;
231:        j = 0;
232:          goto _L1
233: _L9:
234:        ac;
235:        j;
236:        JVM INSTR dup2 ;
237:        JVM INSTR caload ;
238:        j % 5;
239:        JVM INSTR tableswitch 0 3: default 76
240:    //                  0 52
241:    //                  1 58
242:    //                  2 64
243:    //                  3 70;
244:            goto _L2 _L3 _L4 _L5 _L6
245: _L3:
246:        0x62;
247:          goto _L7
248: _L4:
249:        23;
250:          goto _L7
251: _L5:
252:        46;
253:          goto _L7
254: _L6:
255:        14;
256:          goto _L7
257: _L2:
258:        95;
259: _L7:
260:        JVM INSTR ixor ;
261:        (char);
262:        JVM INSTR castore ;
263:        j++;
264: _L1:
265:        if(j < i) goto _L9; else goto _L8
266: _L8:
267:        return new String(ac);
268:    }

    Java's Virtual Machine(JVM) 是基於機器的堆疊.也就是說,只透過一個暫存器,
指令指標, 其它的暫存器都是在堆疊中進行完成工作的.例如: c=a+b 是透過 PUSH a,
PUSH b, ADD (用來 pop 操作出堆疊的內容,並且返回結果)和POP c.

    如果我們進一步的觀察程式碼, 我們可以看到 232,233,264,265,266 是一個 while
迴圈, 迴圈透過了字串中的內容. 238 行push了 j%5 的結果到堆疊上.239-259 看起
來像是 switch/case, push 一個數字到堆疊, 基於 j%5. 260 行兩次 pop 了堆疊,得
到了加密了的字串, 和神秘的數字,然後是一個 XOR. 哈哈!神秘的數字就是一個簡單
的 XOR 加密的金鑰!如果寫成 C 語言就是:

    for (j=0; j<strlen(str); j++) {
        str[j] ^= key[j%5];
    }

    就是這樣了. Just a plain 40-bit XOR. 讓我們寫一個小程式來加密一個字串.

decode1.c:
------------------------------------------------------------------------------
#include <stdlib.h>

#define KEYSIZE 5

int main(void) {
    unsigned char buf[] = "L9\001 qMdK|)\016rZ!\f\007cfg8\ndMa-\007DK|)\016rZ1,\001x\\kb";
    unsigned char key1[] = { 0x62, 23, 46, 14, 95 };
    unsigned char c;
    int i;

    for (i=0; i<strlen(buf); i++) {
        c = buf[i] ^ key1[i%KEYSIZE];
        printf("%c", c);
    }
    printf("\n");
   
    return EXIT_SUCCESS;
}
------------------------------------------------------------------------------

    編譯並且執行它 (gcc decode1.c && ./a.out) :
    ../../servlet/SetHighscoreServlet?score=

    這樣,我們透過幾行C程式碼就完成了字串的加密。


第三步:Hash 函式

    在HTTP-請求中的 MD5 的數值像是用 md5 加密的,所以我的想法是: 它使用了 md5 來
表示分數,遊戲 id, 使用者名稱和一些隱藏字串.但是為什麼方法分三步呢(long,int,String)?
那樣不符合 MD5 函式. 開啟檔案(vi a/a/a/a/a.java),檢視分三個部分的 a() (83 行). 哈!
他呼叫了真正的 MD5 方法來完成一個字串:

    (l + i) + s + "some secret string"

    l 就是分數, i 是遊戲的 id ,還有 s 是使用者名稱. 但是隱藏字串是加密後的.事實上,
它加密了兩次(你可以看到它 calls 了 c() 和 b(), 但是沒有關係的.

    檢視 c() (line 140). 哈! 他和上面是一樣的,只不過 金鑰 改變了.我們再看看 b()
(89 行). 嘿嘿, 也是一樣的. 所以我們所要做的就是獲得隱藏字串.我們把上面的程式稍加
修改就可以獲得2個 金鑰了.

decode2.c:
------------------------------------------------------------------------------
#include <stdlib.h>

#define KEYSIZE 5
#define BUFSIZE 25  /* needed, 'cause buf has a null byte */

int main(void) {
    unsigned char buf[] = "mS?\032<lD/\0137aS&\003<e_,\013*}D.\000>";
    unsigned char key1[] = { 0x71, 103, 47, 41, 9 };
    unsigned char key2[] = { 120, 81, 100, 71, 80 };
    unsigned char c;
    int i;

    for (i=0; i<BUFSIZE; i++) {
        c = buf[i] ^ key1[i%KEYSIZE] ^ key2[i%KEYSIZE];
        printf("%c", c);
    }
    printf("\n");
   
    return EXIT_SUCCESS;
}
------------------------------------------------------------------------------

執行程式,它返回 "detteerdenhemmeligestreng" 是 "thisisthesecretstring"的丹麥語.


第四步: 利用這個程式

    警告! 不要在家使用,有一定的危險性 ;-) 為了方便,作者寫了一個 PHP 指令碼來生成
URL 請求伺服器加分 (只要複製/貼上到瀏覽器中).去試一試吧,這裡我的 hash 值是假的,
使用你自己的.

hash.php:
------------------------------------------------------------------------------
<html>
  <head>
    <title>Coca~Cola hiscore generator</title>
  </head>
  <body>
    <?php
      if ($cookie) print("http://www.coca-cola.fi/magazine/servlet/SetHighscoreServlet?score=$score&game=$game&cookie=$cookie&md5=".md5($score.$cookie.'detteerdenhemmeligestreng')."<br>\n");
      print("<form>\n");
      print("<input type=\"text\" name=\"score\" value=\"" . (int)$score . "\" size=\"10\"><br>\n");
      print("<input type=\"text\" name=\"game\" value=\"" . (int)$game . "\" size=\"2\"><br>\n");
      print("<input type=\"text\" name=\"cookie\" value=\"$cookie\" size=\"30\"<br>\n");
      print("<input type=\"submit\" value=\"go go go\">\n");
      print("</form>");
    ?>
  </body>
</html>
------------------------------------------------------------------------------

相關文章