Join the discord

crackmes.one : TheClueCreator's AGZ Crackme 5

03 May, 2018 22:15
Seems like I got the Stockholm syndrome, because even thought I dislike Java (and I really mean it), I'm starting to enjoy reverse engineering it.
That's a terrible perspective and I should fight it back... but some other day.

This challenge is flagged as a level 3, that is - Medium difficulty.
challenge descriptionAGZ Crackme 4, some fun in here if you can get past the byte code mashup.
Would love an keygen and also a roadmap on how you got there.
Have fun folks!
"Byte code mashup" sounds like obfuscation, but we'll see about that.

First, let's see the input-output, by executing it with some fake credentials:


As usual I'll be using BytecodeViewer for decompilation:


Two classes and a odd GIF, that is totally not a GIF file, but that's for later.

Although the challenge decryption implies it has obfuscation, the code got completely decompiled, and I easily got the main(String[]) method:
main(String[])public static final void main(final String[] array) {
    exit(5);
    System.out.println(getRuntime(I.I(1)));
    final String equals = equals();
    System.out.println(getRuntime(I.I(14)));
    final String equals2 = equals();
    if (!append(equals2)) {
        System.out.println(I.I(35));
    } else {
        final double double1 = Double.parseDouble(equals2);
        final double decode = decode(double1);
        final double decode2 = decode(availableProcessors(equals));
        final int n = (int)decode2;
        final int n2 = (int)decode;
        length(getDecoder(decode2, double1));
    }
    in();
}

getRuntime(), equals(), length()... ok, I got it now. That's what the author meant by saying it has "mashup bytecode" - using the names of core Java methods as his own naming convention, thus trying to fool us.
Well, let's think logically.
There's a System.out.println() that takes whatever getRuntime() and/or I.I(int) returns, and I already know what the crackme outputs on execution, so both getRuntime() and I.I(int) should return printable string.

I checked out getRuntime() and it turned out it's a simple base64 decoder:
getRuntime()private static String getRuntime(final String s) {
    return new String(Base64.getDecoder().decode(s));
}

So, the actual string is coming from I.class's I.I()
Alright, the class is simple XOR based decoder, that decrypts the odd looking I.gif from the JAR package:
I.classpublic class I {
    static byte[] getResourceAsStream;
    static String[] intern;
    static int[] read;

    // Static initialization
    static {
        I.intern = new String[256];
        I.read = new int[256];
        try {
            // Open I.gif as stream of bytes
            final InputStream resourceAsStream = new I().getClass().getResourceAsStream(
                new StringBuffer().append('I').append('.').append('g').append('i').append('f').toString()
            );
            if (resourceAsStream != null) {
                // Read the first 3 bytes of I.gif as a integer
                int i = resourceAsStream.read() << 16 | resourceAsStream.read() << 8 | resourceAsStream.read();
                I.getResourceAsStream = new byte[i];
                int j = 0;
                // Casting i as (byte) it takes the LOBYTE of the three byte value
                final byte b = (byte)i;
                final byte[] getResourceAsStream = I.getResourceAsStream;
                // Outer loop, "chunkifies" the I.gif data
                while (i != 0) {
                    final int read = resourceAsStream.read(getResourceAsStream, j, i);
                    if (read == -1) {
                        break;
                    }
                    i -= read;
                    // Inner loop decrypts the chunk, by the LOBYTE of the data length
                    while (j < read + j) {
                        final byte[] array = getResourceAsStream;
                        final int n = j;
                        array[n] ^= b;
                        ++j;
                    }
                }
                resourceAsStream.close();
            }
        } catch (Exception ex) {}
    }

    // Parser
    public static final synchronized String I(int n) {
        final int n2 = n & 0xFF;
        if (I.read[n2] != n) {
            if ((I.read[n2] = n) < 0) {
                n & 0xFFFF;
            }
			// this is basically a String.substring(int, int) implementation
            I.intern[n2] = new String(I.getResourceAsStream, n, I.getResourceAsStream[n - 1] & 0xFF).intern();
        }
        return I.intern[n2];
    }
}

The I.gif is XOR decrypted, and the I.I(int) method returns a string from that decrypted data, based on it's entry point that is a Pascal-like string convention, where the first byte is the string's length.
I decrypted them all to look for hints and generally see what's going on there:
decrypted stringdescription
VXNlcm5hbWU=decrypted to "Username"
RW50ZXIgYSBudW1iZXI=decrypted to "Enter a number"
Sorry, only Double accepted.Input validation error
Got the solution?Exit message, line 1
ship it off to me then..Exit message, line 2
crackme [at] agz [dot] nameExit message, line 3
https://www.agz.nameExit message, line 4
https://www.facebook.com/AGZ.name/Exit message, line 5
Q29uZ3JhdHosIG5vdyB3cml0ZSBtZSBhbmQg
a2V5Z2VuIHdpdGggYSBleHBsYW5hdGlvbg==
decrypted to "Congratz, now write me and keygen with a explanation". That's the good end.
U29ycnkgbGFkZGllLCBuZWVkIHNvbWUgbW9y
ZSBkZWJ1Z2dpbmcgaGVyZS4uLg==
decrypted to "Sorry laddie, need some more debugging here...". That's the bad end.
U29ycnksIHlvdSBuZWVkIHRvIGVudGVyIGFuIGNvZGU=decrypted to "Sorry, you need to enter an code". Input validation error.
AGZ Crackme no Welcome message, line 1. The 5 comes as a integer.
There will be some fun in hereWelcome message, line 2.
See if you can beat the encryptionWelcome message, line 3.

Good. I can now replace those cryptic looking println() calls with these strings, and get rid entirely of I.class
I can also get rid or replace with the appropriate code all of these methods inside the main class:
s// Welcome message lines 1, 2 and 3
// I can ignore all these
private static void exit(final int n) {
    System.out.println("");
    System.out.println(I.I(364) + n);    // AGZ Crackme no 5
    System.out.println(I.I(380));        // There will be some fun in here
    System.out.println(I.I(411));        // See if you can beat the encryption
    System.out.println("");
}

// Exit message lines 1, 2, 3, 4 and 5
// The whole function can go away
private static void in() {
    System.out.println("");
    System.out.println(I.I(65));         // "Got the solution?"
    System.out.println(I.I(83));         // "ship it off to me then.."
    System.out.println(I.I(108));        // "crackme [at] agz [dot] name"
    System.out.println(I.I(125));        // "https://www.agz.name"
    System.out.println(I.I(146));        // "https://www.facebook.com/AGZ.name/"
}

// This is basically is_string_double() implementation
// And since I already know the correct password is a Double, I can strip the input validation entirely
private static boolean append(final String s) {
    try {
        Double.parseDouble(s);
        return true;
    } catch (Exception ex) {
        return false;
    }
}

// That's the user data input prompt
// I'll hardcode my username and password so this can go away too
private static String equals() {
    return new Scanner(System.in).nextLine();
}

// That's printing the good or bad message, based on a Boolean argument
// I'll move that code to the main() method and remove this one
private static void length(final boolean b) {
    if (b) {
        System.out.println(getRuntime(I.I(181)));   // "Congratz, now write me and keygen with a explanation"
    } else {
        System.out.println(getRuntime(I.I(254)));   // "Sorry laddie, need some more debugging here..."
    }
}

// This is just converting a Double to its String representation
// So, I'll just replace it around the code with Double.toString() and remove this
private static String doubleValue(final double n) {
    return Double.toString(n);
}

// This is just a comparison of two Doubles converted to strings
// I can remove this code entirely, and just use Double.toString(n).equals(Double.toString(n2)) in main()
private static boolean getDecoder(final double n, final double n2) {
    return doubleValue(n).equals(doubleValue(n2));
}

Alright, stripping these and modifying my main, I now have this piece of deobfuscated code:
main(String[]), deobfuscatedpublic static final void main(final String[] array) {
    final String username = "XpoZed";
    final String password = "3.14";

    final double password_gen = decode(availableProcessors(username));
    if (Double.toString(password_gen).equals(password)) {
    	System.out.println("Congratz, now write me and keygen with a explanation");
    } else {
    	System.out.println("Sorry laddie, need some more debugging here...");
    }
}

Easy! So, the last thing that left is to see how password_gen is generated, starting with availableProcessors() that takes my username "XpoZed" as its only parameter:
availableProcessors(String)private static double availableProcessors(final String s) {
    final StringBuilder sb = new StringBuilder();
    final char[] charArray = s.toCharArray();
    for (int length = charArray.length, i = 0; i < length; ++i) {
        sb.append((int)charArray[i]);
    }
    return new Double(sb.toString());
}

My username is converted into array of integers, they got appended to each other and the resulting number is converted into Double, so:
Username charASCII valueChar to ASCII stringDouble representation
X=> 88=> 8811211190101100=> 8.8112111901011E15
p=> 112
o=> 111
Z=> 90
e=> 101
d=> 100

The resulting value of 8.8112111901011E15 is immediately passed to decode():
decode()private static double decode(final double n) {
    if (n == 0.0) {
        System.out.println(getRuntime(I.I(319)));    // "Sorry, you need to enter an code"
        System.exit(0);
        return 0.0;
    }
    final double n2 = Runtime.getRuntime().availableProcessors();
    final double n3 = Runtime.getRuntime().totalMemory();
    return n - n3 / 1.0E8 + n2 * 28.0 + n3 / 107543.0 - n3 / Double.toString(n).length();
}
So this function will slightly modify the Double we got from our username, by using system specific information like the number of CPUs and total memory available values.

Well, that's all I guess. Pretty easy challenge by my opinion, and here is my keygen:
TheClueCreator's AGZ Crackme 5 keygen/** TheClueCreator's AGZ Crackme 5
 * 
 * keygen solution by XpoZed / nullsecurity.org / 03 May, 2018 
 * 
 */
package keygen;

public class main {

    public static void main(String[] args) {
        if (args.length != 1) {
        	System.out.println("Usage example: java -cp . keygen.main USERNAME");
        	return;
        }
        String username = args[0];

        // Generate the username's hash string
        StringBuilder username_hash_str = new StringBuilder();
        for (int i = 0; i < username.length(); i++) {
        	username_hash_str.append((int)username.charAt(i));
        }

        // Convert it to Double
        double username_hash = Double.parseDouble(username_hash_str.toString());

        // System specific modifier
        double mod_cpu = Runtime.getRuntime().availableProcessors();
        double mod_ram = Runtime.getRuntime().totalMemory();
        double password = username_hash - mod_ram / 1.0E8 + mod_cpu * 28.0 + 
                          mod_ram / 107543.0 - mod_ram / Double.toString(username_hash).length();

        // Print the result
        System.out.println(String.format("Username: %s\nPassword: %s", username, Double.toString(password)));
    }
}

And the output:

Comments

* You have an opinion? Let us all hear it!

There's no comments on this article yet. Be the first!
© nullsecurity.org 2011-2024 | legal | terms & rules | contacts