Java Reverse Engineering

by quel

Companies have an amusing habit of obfuscating Java code and then distributing the application and think that it is secure.

Unfortunately for them, Java classes (a.k.a. byte code) are trivial to decompile.  The obfuscation serves as little more than speed bumps which means more fun for us and a little extra time.  Of course what reverse engineering project is worth it if it is trivial?

Now many of you are familiar with PHP.

The company behind PHP, Zend, produces a tool called Zend Studio.  The tool lets you step through code and basically is one of the few ways to actually get the feature set you would expect out of a programming language such as debuggers.

The basic premise behind the first version I reversed was that I had installed a 30-day trial but had been too busy at the office to actually get to really try it.  Well, time to hack it so I could finish testing it.

We will go ahead and start with Version 3.0.0 of the studio as this was the first version I reversed.  Now it would be trivial to patch the Java, compile the class file, and repackage the JAR.

Cracks are ugly hacks.  We are going to reverse it to our satisfaction so we can write a full keygen.  (Not a half-assed keygen, but one that will actually generate any and all possible valid keys.)

First, find the ZendIDE.jar file and using jar x, or even unzip -x, to extract the contents:

$ unzip -x ZendIDE.jar
Archive:  ZendIDE.jar
   creating: META-INF/
  inflating: META-INF/MANIFEST.MF
   creating: com/
   creating: com/b/
   creating: com/b/a/
  inflating: com/b/a/a.class
  inflating: com/b/a/b.class
  inflating: com/b/a/c.class
  inflating: com/b/a/d.class
   creating: com/a/
  inflating: com/a/d.class
  inflating: com/a/b.class
   creating: com/a/a/
  inflating: com/a/a/b.class
  inflating: com/a/a/a.class
  inflating: com/a/a/f.class
  inflating: com/a/a/g.class
  inflating: com/a/a/c.class
  inflating: com/a/a/h.class
  inflating: com/a/a/m.class
  inflating: com/a/a/d.class
  inflating: com/a/a/i.class

...

  inflating: com/zend/ide/util/dh.class
  inflating: com/zend/ide/util/di.class
  inflating: com/zend/ide/util/dc.class
  inflating: com/zend/ide/util/dd.class
  inflating: com/zend/ide/util/de.class
  inflating: com/zend/ide/util/df.class
  inflating: com/zend/ide/util/dg.class
  inflating: com/zend/ide/util/ab.sh

You will (also want to get a copy of JAD, a Java decompiler.

Now you can use grep, strings, etc. to track) down what you are looking for or of course start by tracing all the way through the code.

After some time grep'ing and checking out the decompiled code from JAD, I found that com/zend/ide/util/f/a.class was going to be the primary target.

grep'ing for the string you find on the screen to enter your username and license key is a good place to start.

$ jad com/zend/ide/util/f/a.class
Parsing com/zend/ide/util/f/a.class... Generating a.jad
Overlapped try statements detected. Not all exception handlers will be resolved in the method a
Overlapped try statements detected. Not all exception handlers will be resolved in the method a
Couldn't fully decompile method b
Couldn't resolve all exception handlers in method b
$ ls -l a.jad
-rw-r--r-- 1 gbppr gbppr 9765 Apr  7 00:27 a.jad

Check out a.jad and you'll see things like: USER_NAME and LICENSE_KEY

You'll notice everything is named a, b, c, etc.

This is part of their obfuscation and sometimes part of the decompiling process.

In any case, use the return types and the overloaded types in function arguments to help you find your way.

Check out:

public void a(String s, String s1)

Then look at this:

public void a() {
  String s = bi.a("USER_NAME");
  String s1 = bi.a("LICENSE_KEY");
  a(s, s1);
}

Let's trace this code:

b = b(s, s1);

Hrm...  Something special about starting with lk - let's note that for later.

label0:
        {
            if(s1.toLowerCase().startsWith("lk"))
                s1 = s1.substring(2);
            f1 = new f(s, s1);
            s2 = "";
            s3 = "";
            if(s1.length() >= 18)
            {
                f1.h = true;
                s2 = s1.substring(16, 18);
                try
                {
                    s3 = s1.substring(18);
                    f1.e = Integer.valueOf(s3).intValue();
                }
                catch(Exception exception1)
                {
                    f1.e = 0;
                }
                s1 = s1.substring(0, 16);
                if(Integer.valueOf(s2).intValue() == 0)
                {
                    f1.g = true;
                    if(i1 == 0)
                        break label0;
                }
                f1.g = false;
                if(i1 == 0)
                    break label0;
            }
            f1.h = false;
            f1.g = true;
        }
        s4 = s1.substring(8, 16);
        String s5 = "Zend" + s + s4 + s2 + s3;
        CRC32 crc32 = new CRC32();
        crc32.update(s5.getBytes());
        int i = (int)crc32.getValue();
        String s6 = Integer.toHexString(i) + s4;
        do
        {
            if(s6.length() >= 16)
                break;
            s6 = "0" + s6;
        } while(i1 == 0);
        if(s6.compareToIgnoreCase(s1) != 0)
            break MISSING_BLOCK_LABEL_459;
        if(a(f1, s2))
            return f1;
        int j = Integer.parseInt(s4.substring(4));
        if(a(f1, j))
            return f1;
        if(a(f1))
            return f1;
        f1.g = false;
        int k = Integer.parseInt(s4.substring(0, 2));
        int l = Integer.parseInt(s4.substring(2, 4));
        Calendar calendar = Calendar.getInstance();
        Calendar calendar1 = Calendar.getInstance();
        calendar1.set(j, l - 1, k);
        calendar1;
        calendar;
        f1.d = (calendar1.get(6) - calendar.get(6)) + 1;
        calendar1;
        calendar;
        f1.d += (calendar1.get(1) - calendar.get(1)) * 365;
        f1.c = 2;
        return f1;
        Exception exception;
        exception;
        return new f(s, s1);
    }

O.K.  So we know:

s1 has to be greater than or equal to a length of 18 (license key).

s2 is the substring() from 16 to 18 of s1, and s3 is the first 18 characters of s1.

Now s1 is the first 16 characters.  s2 must be 0.

s4 is a substring() of s1 from 8-16.

String s5 = "Zend" + s + s4 + s2 + s3;

Bingo!  At this point you can trial and error or just keep tracing the code until you have all the limitations and checks duplicated.

Here's a PHP script to create the keys.

(Editing the same file took me about ten minutes to update the keygen for Zend IDE 4.  I haven't looked at 5, but I don't expect their method to have gotten much harder.)

createkeys.php:

<?
if (isset($_GET) && count($_GET) > 0) {
  if (!isset($_GET["user"]) || !$_GET["user"])
    $name = "0wn3d";
  else {
    $name = $_GET["user"];
    //first 2 must not be lk
    if (strcasecmp(substr($name,0,2),"lk") == 0) {
      $name = substr($name,2);
      echo "The first 2 chars must not be lk<br>";
    }
  }
   if (strlen($name) <= 0)
     $name = "0wn3d";
       if (!isset($_GET["howmany"]))
         $_GET["howmany"] = 0;
         $howmany = intval($_GET["howmany"]);
         if ($howmany <= 0)
           $howmany = 2600;
           if ($howmany > 2147483647) {
             $howmany = 2600;
             echo "Limit of licenses is: 2147483647<br>";
           }
           if (!isset($_GET["seed"]))
             $seed = "36";
           else {
             if (intval($_GET["seed"]) >= 35 && intval($_GET["seed"]) <= 99)
               $seed = $_GET["seed"];
             else
               $seed = "36";
           }

           $str = "Zend";
           $hardcode = "0304";
           $str .= $name;
           $pad = '';

           for ($i = ( 5 - strlen($name)); $i > 0; $i--)
             $pad .= "0";
             $pad .= "00";
             $str .= $hardcode . $seed . $pad . "000" . $howmany;
             printf ("LICENSE_KEY: %08X%s%s%s000%s<br>USER_NAME: %s", crc32($str), $hardcode, $seed, $pad, $howmany, $name);
             echo "<br><br>Now find ZendIDE.config and replace the LICENSE_KEY and USER_NAME<br><br>";
}

echo "<form method='get'>
Enter the username: <br>
<input type='textbox' name='user' maxlength='50'><br>
Number of licenses: <br>
<input type='textbox' name='howmany'><br>
Enter a random number between 35 and 99: <br>
<input type='textbox' name='seed' maxlength='2'><br>
<input type='submit' name='Submit'>
</form>";
?>

Shouts to amatus who worked with me on the initial reverse engineering project.

Code: createkeys.php

Return to $2600 Index