标 题:破解 Zelix KlassMaster 的字符串加密 在java游戏中作弊(转载文章) (7千字)
发信人:梦醒时分
时 间:2001-7-24 20:18:35
详细信息:
破解 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>
------------------------------------------------------------------------------