$Revision: 1.1 $ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX Cracking Zelix KlassMaster's String Encryption (and how to get hiscores in Java games) XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX by Morten Poulsen m0rtenp@ofir.dk http://www.zelix.com/klassmaster/featuresStringEncryption.html "However, note that Zelix KlassMaster's String Encryption isn't and cannot be fundamentally irreversible." Introduction ~~~~~~~~~~~~ To make decompilation of Java applets and making changes to the code more difficult, and to hide secret information (eg. how to calculate hashes for hiscores) people obfuscate their java bytecode. Some of the ways to do so is to scramble the line number tables, replace all names of classes, methods and variables with nonsense, and the one we will look at, string encryption. You will need a few tools to help you do the job: - Java decompiler (Jad) - editor (vim) - common sense (brain) some knowlage about JVMs will be nice, but I tell you everything you need to know here. If you want to make your own cracks, you need that knowlage. Some tools are nice to have when cracking the hiscore tables of Java games: - TCP sniffer (tcpdump) - "Java enabled" browser (Mozilla) - C compiler (gcc) - all the GNU tools (eg. grep) - webserver with PHP ;-) What's going on? ~~~~~~~~~~~~~~~~ Forst of all we need to find a game to crack. The games on Coca~Cola's (nordic) website looks like great candidates. Start the TCP sniffer (tcpdump -w file), and launch the game. Play the game and write down your score. Stop the TCP sniffer, and open the file (vi file). Search for your score in the binary masses. You shuld find a line a'la: GET /magazine/servlet/SetHighscoreServlet?score=6324&game=0&cookie=yourname& md5=c404cd019e1a214487cd4c841 So all we need to know is how to generate the md5 hash, then we will be able to make our own hiscores, hopefully. Decompiling and Thinking ~~~~~~~~~~~~~~~~~~~~~~~~ Download the .jar archive containing the game (hint: view source, tag, archive=...), unpack it and decompile each class. Now try to find the place in the code where the result is send back to the server (fgrep -rn "URL" *). b/a/a.java line 122 (decompiled with jad) looks like a god place to start. It reads: 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)); The URL looks a lot like what we seek: "long text"+i+"text"+d+"text"+e+"text"+result_of_calculation(i,d,e) the problems are now "are these strings the ones we seek?" (just for the fun of it - lets find out) and "what does a.a.a.a.a.a() do?". Open the file (vi b/a/a.java) and seek to line 122. We can see that the method used to decrypt the string is b(). Seek to b() (line 224). It looks like this: 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 is a stack based machine. That is, there is only one register, the instruction pointer, the rest of what registers are normaly used for is done on the stack. Example: c=a+b is done PUSH a, PUSH b, ADD (which pop the operands off the stack, and push the result back), POP c. If we look closer at the code, we can see that the lines 232,233,264,265, 266 are just a while-loop, looping through the chars in the string. Line 238 pushes the result of j (the index into the string) modulus 5 on the stack. Lines 239-259 looks like a switch/case, pushing a nubmer onto the stack, based on j%5. Line 260 pops two operands off the stack - a char from the encrypted string, and the mysterious number, and pushes back the result of an XOR. Ah! The mysterious numbers are the key of a simple XOR encryption! Written in C it would look like: for (j=0; j #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 #define KEYSIZE 5 #define BUFSIZE 25 /* needed, 'cause buf has a null byte */ int main(void) { unsigned char buf[] = "mS?\032"; 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 Coca~Cola hiscore generator \n"); print("
\n"); print("
\n"); print("
\n"); print("\n"); print("\n"); print("
"); ?> ------------------------------------------------------------------------------