Target: vhly’s Java CrackMe #4
URL: http://www.crackmes.de/users/vhly/java_crackme_4/
Protection: Keyfile.
Description: Crackme with a keyfile protection.
Tools: Java Decompiler / Java Compiler.
If we load the JAR-file into our decompiler we see that the only class we managed to decompile is Loader.class. If we look through this file we find out that the loader decodes the other class-files when the crackme is started. The important part to check out is the method loadClassData where the decoding of the classes is handled.
private byte[] loadClassData(String paramString)
throws Exception
{
String str = paramString.replace('.', '/') + ".class";
InputStream localInputStream = getResourceAsStream(str);
BufferedInputStream localBufferedInputStream = new BufferedInputStream(localInputStream);
ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream(1024);
int i = 0;
while ((i = localBufferedInputStream.read()) != -1)
{
i ^= 0xA8;
i ^= 0x63;
localByteArrayOutputStream.write(i);
}
byte[] arrayOfByte = localByteArrayOutputStream.toByteArray();
localInputStream.close();
return arrayOfByte;
}
Here we see that the loader runs two XOR operations on each byte of each class-file to decode it.
If we unpack the JAR-file we can take a look at what class-files are included in the package.
KeyFile.class
Loader.class
Main$1.class
Main.class
If we decode these files with Java Class Decoder, we end up with three new class-files.
decoded_KeyFile.class
decoded_Main$1.class
decoded_Main.class
When we open the new files in our decompiler we get the source for each of the classes. If we take a look at the decoded_Main.class we find an actionPerformed-method that looks interesting.
public void actionPerformed(ActionEvent paramActionEvent)
{
JFileChooser localJFileChooser = new JFileChooser(new File("."));
int i = localJFileChooser.showOpenDialog(this);
if (i == 0)
{
this.txtFile.setText(localJFileChooser.getSelectedFile().getPath());
try
{
FileInputStream localFileInputStream = new FileInputStream(localJFileChooser.getSelectedFile().getPath());
ObjectInputStream localObjectInputStream = new ObjectInputStream(localFileInputStream);
KeyFile localKeyFile = (KeyFile)localObjectInputStream.readObject();
if (localKeyFile.isVal()) {
this.lblResult.setText(" Registed to " + localKeyFile.getName());
} else {
this.lblResult.setText(" UnRegisted!");
}
localKeyFile = null;
localObjectInputStream.close();
localFileInputStream.close();
}
catch (Exception localException)
{
this.lblResult.setText(" UnRegisted!");
localException.printStackTrace();
return;
}
}
}
So after we have opened our keyfile the crackme then deserializes the contents to create a new KeyFile object. And if we pass the isVal-method in KeyFile we got a valid keyfile. Lets take a look at the decoded_KeyFile.class.
package com.vhly.crackmes.cm4;
import java.io.Serializable;
import java.security.MessageDigest;
import java.util.zip.CRC32;
import sun.misc.BASE64Encoder;
public class KeyFile
implements Serializable
{
private String magic;
private String name;
private String serial;
private long crcvalue;
public KeyFile()
{
this.magic = "vhly[FR]'s CrackMe #4 KeyFile";
this.name = "vhly[FR]";
this.serial = "null";
this.crcvalue = 0L;
}
public final String getName()
{
return this.name;
}
public final void setName(String paramString)
{
this.name = paramString;
}
public final void setSerial(String paramString)
{
this.serial = paramString;
}
public final void setCRC(long paramLong)
{
this.crcvalue = paramLong;
}
public final boolean isVal()
{
CRC32 localCRC32 = new CRC32();
localCRC32.update(this.magic.getBytes());
localCRC32.update(this.name.getBytes());
localCRC32.update(this.serial.getBytes());
long l = localCRC32.getValue();
localCRC32 = null;
if (l != this.crcvalue) {
return false;
}
StringBuffer localStringBuffer1 = new StringBuffer();
localStringBuffer1.append("aAF#@QRAJSEFjkaos0dvjkl;asefQ$@#%@Q$T%Jmoa0pl_)(*&(^*%^%$");
localStringBuffer1.append(" ");
localStringBuffer1.append(this.name);
localStringBuffer1.append(" asdfjkl;asdfQ#$TSHSDFHGSdfgjkopasdu90zxcv");
StringBuffer localStringBuffer2 = localStringBuffer1.reverse();
localStringBuffer1 = null;
String str1 = localStringBuffer2.toString();
localStringBuffer2 = null;
try
{
MessageDigest localMessageDigest = MessageDigest.getInstance("SHA-1");
localMessageDigest.update(str1.getBytes());
byte[] arrayOfByte = localMessageDigest.digest();
localMessageDigest = null;
BASE64Encoder localBASE64Encoder = new BASE64Encoder();
String str2 = localBASE64Encoder.encodeBuffer(arrayOfByte);
localBASE64Encoder = null;
if (str2.equals(this.serial)) {
return true;
}
}
catch (Exception localException)
{
return false;
}
return false;
}
}
The first thing the isVal-method checks is the CRC32 value of our fields. If we pass that we go on to the serial check.
The serial is calculated by reversing the string “aAF#@QRAJSEFjkaos0dvjkl;asefQ$@#%@Q$T%Jmoa0pl_)(*&(^*%^%$ ” + this.name + ” asdfjkl;asdfQ#$TSHSDFHGSdfgjkopasdu90zxcv”, generating a SHA-1-hash and finally encode it as a Base64 string.
Now we’ve got all we need to code our own keymaker. The first thing we have to do is to copy the KeyFile-class to our project. After we’ve done this we can begin to code our keymaker routine.
package com.klefz.vhlycm4.keygen;
import com.vhly.crackmes.cm4.KeyFile;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.zip.CRC32;
import sun.misc.BASE64Encoder;
public class Keygen {
private static final int BUFFER = 104;
private final transient String magic;
private final transient String name;
private final transient String serial;
private final transient long crcvalue;
private final transient KeyFile keyfile;
public Keygen(final String inputName) throws NoSuchAlgorithmException {
this.magic = "vhly[FR]'s CrackMe #4 KeyFile";
this.name = inputName;
this.serial = generateSerial();
this.crcvalue = generateCrc();
this.keyfile = generateKeyfile();
}
public final KeyFile getKeyFile() {
return this.keyfile;
}
public final long generateCrc() {
final CRC32 localCRC32 = new CRC32();
localCRC32.update(this.magic.getBytes());
localCRC32.update(this.name.getBytes());
localCRC32.update(this.serial.getBytes());
return localCRC32.getValue();
}
public final String generateSerial() throws NoSuchAlgorithmException {
StringBuffer stringBuffer = new StringBuffer(BUFFER);
stringBuffer.append(
"aAF#@QRAJSEFjkaos0dvjkl;asefQ$@#%@Q$T%Jmoa0pl_)(*&(^*%^%$ ");
stringBuffer.append(this.name);
stringBuffer.append(" asdfjkl;asdfQ#$TSHSDFHGSdfgjkopasdu90zxcv");
stringBuffer = stringBuffer.reverse();
final MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
messageDigest.update(stringBuffer.toString().getBytes());
final BASE64Encoder b64Encoder = new BASE64Encoder();
return b64Encoder.encodeBuffer(messageDigest.digest());
}
private KeyFile generateKeyfile() {
final KeyFile key = new KeyFile();
key.setName(name);
key.setSerial(serial);
key.setCRC(crcvalue);
return key;
}
}
Now we got a class that generates a KeyFile object that we can use to serialize and export to a file.
package com.klefz.vhlycm4.keygen;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import com.vhly.crackmes.cm4.KeyFile;
import java.security.NoSuchAlgorithmException;
import java.util.Scanner;
public class VhlysJavaCrackMe4Keygen {
public static void main(String args[])
throws NoSuchAlgorithmException {
System.out.print("Enter name: ");
final Scanner scan = new Scanner(System.in);
final String name = scan.nextLine();
generateKeyfile(name);
}
private static void generateKeyfile(final String name)
throws NoSuchAlgorithmException {
final Keygen keygen = new Keygen(name);
final KeyFile keyfile = keygen.getKeyFile();
try {
final FileOutputStream fout =
new FileOutputStream(".\\keyfile.cm4");
try (ObjectOutputStream oos = new ObjectOutputStream(fout)) {
oos.writeObject(keyfile);
}
System.out.println("Keyfile exported to keyfile.cm4");
} catch(Exception ex){
System.out.println(ex.getMessage());
}
}
}
If we run our keygen to generate a keyfile we can try to open it in the crackme. And bam! It’s registered!