CrackMe URL
Answer: antidote

First, we want to find the entry point. Let’s view META-INF/MANIFEST.MF.

Manifest-Version: 1.0
Protected-Notice: AV contact email - [email protected]
JAR-Signature: S94EzrNEa9XPP3HJnTSKTHY7H5pMzIkyOckf2zlblNDR1oPS
Class-Path: .
Protected-By: 3.0.9 Stringer (20170608)
Main-Class: jcc.part2.CrackMe

Name: jcc/part2/v.class
SHA-256-Digest: I+hSWBo3DZwEtTlqVXyzdxRRKW9N/usMoCpOi8gYBUI=

Name: jcc/part2/CrackMe.class
SHA-256-Digest: REigvNjpIrv3Ht4aGavXQ8xVJp1ltZmarAxOg/XYYpY=

Name: hpl/tko/hx.class
SHA-256-Digest: HF4KWRh1bmN6zBzytVHEdImHyNHJEOeOYPxFhOLIRlc=

Name: hpl/tko/bp.class
SHA-256-Digest: CCw/Anec+ntGwgTqnMVgLmA3b09cLlVFMhP5WNnve9Y=

Name: jcc/part2/sv.class
SHA-256-Digest: TCB0kj/YJHSyY9IbGrbjKW//Rj01GT4B3ittqvr6dh8=

Name: hpl/tko/f.class
SHA-256-Digest: k4NdPnwzAWbNoeD7S3NuocywgXEp35iTG9ZbvYn6VnA=

Name: jcc/part2/f.class
SHA-256-Digest: 8BVz0nTL6PWCJTT29yzz2jXhojODAN3MtjZ/XTdqXNM=

Oh dear, it’s obfuscated! Sure enough, when we view the main class, there is Zelix exception obfuscation, Zelix flow control obfuscation, Zelix enhanced string encryption, Stringer signing, Stringer string encryption, Stringer hide access obfuscation and who knows what.

Fortunately, java-deobfuscator contains transformers for these obfuscations (except zelix flow obfuscation which was custom-made), so we can avoid wasting time on making a deobfuscator.

To remove the flow, I used my own transformer which can be found here.

Next, we need to remove the Manifest signings to prevent errors being thrown by the JVM for incorrect signatures.

Once we finished deobfuscation, we should have something similar to this result when decompiled by FernFlower

package jcc.part2;

public class CrackMe {
   public static void main(String[] var0) {
      int var2 = 256;

      while(true) {
         try {
            try {
               if (var0[0].length() > var0[1].length()) {
                  continue;
               }
            } catch (Throwable var13) {
               for(int var4 = 0; var4 < Integer.MAX_VALUE; ++var4) {
                  var2 ^= var4;
               }

               char[] var15 = ",Wb]Wd_ZS]]".toCharArray();
               char[] var5 = var0[0].toCharArray();
               char[] var6 = new char[var15.length];
               if (var2 > var15.length) {
                  label155:
                  while(true) {
                     label153:
                     while(true) {
                        try {
                           int var7 = 0;

                           while(true) {
                              int var8;
                              try {
                                 if (var5.length == var6.length) {
                                    break label155;
                                 }

                                 if (var15[var7] - var5[var7 % var5.length] < 0) {
                                    var8 = var15[var7] - var5[var7 % var5.length] + 128;
                                 } else {
                                    var8 = (var15[var7] - var5[var7 % var5.length]) % 128;
                                 }

                                 var6[var7] = (char)var8;
                              } catch (Throwable var10) {
                                 break;
                              }

                              label174: {
                                 try {
                                    var0[Math.abs(var8) + 1] = new String(var6);
                                 } catch (Throwable var11) {
                                    if ((new String(var6)).equals("Kintsukuroi")) {
                                       if (var0[0] != null) {
                                          throw new ArrayIndexOutOfBoundsException();
                                       }
                                       break;
                                    }

                                    if (var6.length != var5.length) {
                                       break label174;
                                    }
                                 }

                                 var6 = (char[])var15.clone();
                                 if (var8 < 0) {
                                    throw new ArrayIndexOutOfBoundsException(var8);
                                 }

                                 if (var8 == 0) {
                                    continue label153;
                                 }
                              }

                              ++var7;
                           }
                        } catch (Throwable var12) {
                           if (var12.getMessage() != null) {
                              var5 = var15;
                              continue;
                           }

                           System.out.println("Right key found");
                           return;
                        }

                        if (var6 != null) {
                           break label155;
                        }
                     }
                  }
               }

               System.out.println("Wrong key");
            }

            return;
         } catch (Throwable var14) {
            ;
         }
      }
   }
}

From the first part of the code (as seen below), we can see that the code purposely throws an exception to execute the key generation check in the catch statement.

if (var0[0].length() > var0[1].length()) {
    continue;
}

The first statement we see, is:

for (int var4 = 0; var4 < Integer.MAX_VALUE; ++var4) {
    var2 ^= var4;
}

Earlier in the program, var2 is initialized as the integer 256, so var2 would be 2147483391 after this particular operation.

Next, we get to this:

char[] var15 = ",Wb]Wd_ZS]]".toCharArray();
char[] var5 = var0[0].toCharArray();
char[] var6 = new char[var15.length];
if (var2 > var15.length) {

Since var2 (2147483391) is obviously more than the length of “,Wb]Wd_ZS]]“, the if conditional is true and we go on.

The next interesting snip of code we see, is

]if (var5.length == var6.length) {
    break label155;
}

Since this breaks over the entire key generation part of the code, this is probably an attempt to confuse us. Thus, we shall ignore it.

Next, we see this

if (var15[var7] - var5[var7 % var5.length] < 0) {
    var8 = var15[var7] - var5[var7 % var5.length] + 128;
} else {
    var8 = (var15[var7] - var5[var7 % var5.length]) % 128;
}

var8 was in the earlier part of the code which was an uninitialized integer, so the previous value can be ignored (it still could have been ignored if it wasn’t uninitialized, it’s getting over-written).

However, when we view the key generation statement as a whole, we receive

int var7 = 0;

while (true) {
    int var8;
    try {
        if (var5.length == var6.length) {
            break label155;
        }

        if (var15[var7] - var5[var7 % var5.length] < 0) {
            var8 = var15[var7] - var5[var7 % var5.length] + 128;
        } else {
            var8 = (var15[var7] - var5[var7 % var5.length]) % 128;
        }

        var6[var7] = (char) var8;
    } catch (Throwable var10) {
        break;
    }

    label174:
    {
        try {
            var0[Math.abs(var8) + 1] = new String(var6);
        } catch (Throwable var11) {
            if ((new String(var6)).equals("Kintsukuroi")) {
                if (var0[0] != null) {
                     throw new ArrayIndexOutOfBoundsException();
                }
                break;
            }

            if (var6.length != var5.length) {
                break label174;
            }
	    }

        var6 = (char[]) var15.clone();
        if (var8 < 0) {
		    throw new ArrayIndexOutOfBoundsException(var8);
        }

        if (var8 == 0) {
            continue label153;
        }
    }

    ++var7;
}

We notice that this is a disguised for loop because var7 is initialized to 0, and it is increased by 1 at the end of each iteration of the infinite loop.

Thus, we conclude that var6 must be the array which is the byte (or char) some kind of representation of the correct key to input or related.

When we look for the correct key verification code, we find this:

if (var12.getMessage() != null) {
    var5 = var15;
    continue;
}

System.out.println("Right key found");
return;

Therefore, an exception must be raised from the program with no message specified.

The only code which matches this description is:

if ((new String(var6)).equals("Kintsukuroi")) {
    if (var0[0] != null) {
        throw new ArrayIndexOutOfBoundsException();
    }
    break;
}

Since var6 is a char array, we need to figure out how to make an inputted key which produces the correct ASCII code for each character in “Kintsukuroi”.

Because of “char[] var15 = “,Wb]Wd_ZS]]“.toCharArray();”, we immediately know we can’t have a key with a length of more than 11 characters otherwise the crackme will throw exceptions.

After reviewing the key generation algorithm a few times, we notice something.

if (var15[var7] - var5[var7 % var5.length] < 0) {
    var8 = var15[var7] - var5[var7 % var5.length] + 128;
} else {
    var8 = (var15[var7] - var5[var7 % var5.length]) % 128;
}

We can have more than one possible key! However, doing the one with the modulus looks nasty so let’s go with the first one.

Since we have var15[var7], “,Wb]Wd_ZS]]” (var15) must be the “encrypted” text we have to reverse.

First character of “,Wb]Wd_ZS]]” is a comma. The ASCII value of a comma is 44. Turning this into an algebraic expression, 44 - x + 128 = 75 (ASCII value of capital K)

Obviously, x = 97. Doing this with each of the characters in the “encrypted” array, we get this: [97, 110, 116, 105, 100, 111, 116, 101, 97, 110, 116].

Since the last 3 values are the same from the first 3 and we use a modulus to grab characters, our byte-representation of our correct key must be 97, 110, 116, 105, 100, 111, 116, 101.

Therefore, our answer is the result of System.out.println(new String(new byte[]{97, 110, 116, 105, 100, 111, 116, 101})); which is “antidote” and when this is inputted, it is correct. Yay! :D