[Keygen] Slightly More Advanced Keygen

reverseengineering
keygen

(Mostly whitehat) #1

The goal of the challenge is to generate a valid key. Bonus points for generating multiple keys

Binary

###Windows binary
https://mega.nz/#!oJg2hCpK!dfpai3jiJWwotCqNvBPvmI6I0_XBOAD6vKiwdLm_lKU
###Windows debug symbols (easy mode)
https://mega.nz/#!hUoS3IKI!j6_FL7ajYVpgAP7w6qyMNM7YZQjQJqA1esWm25GS6T4
###Linux Binary
https://mega.nz/#!EUpGxAoD!7NUJQW7RALJClTxRLdaR_QPU5gGfYz7m1ZQB0FN6DkY
Virus-total scan: https://www.virustotal.com/en/file/cc1fcde5ff0b3ca4c3429d5b41b0f35e506bc301f372a6309ca9a8d1b81783b1/analysis/1478457352/
Note: I didn’t compile the Linux binary and don’t have Linux installed, sorry if it doesn’t work.


Challenge Collection: Reverse Engineering and CrackMe
(oaktree) #2

Windows? Would you mind compiling for Linux x86_64 as well? :smiley:


(Command-Line Ninja) #3

Second that :stuck_out_tongue_winking_eye: I don’t know anything about decompiling windows binaries.

EDIT: Lol just seen it! thanks!


#4

Thanks for the challenge.

Here is my solution @oaktree.

When reversing any application, we will want to see how it behaves to gain as many clues as we can so we know where to start. Run the application.

We get a string Invaild key! so let’s search for the string. Make sure you get the (in)correct spelling.

Now we’ll need to find references to the string and locate the function(s) where they are used.

We can see that this is a small subroutine which doesn’t provide much information to us. We’ll backtrack to where this function is called.

We can instantly see another set of good and bad boys. What gives? Have a look at the jump instruction before them and the required condition. The eax register is compared with 1 but how do we get eax? Take a careful look while we trace it.

call     0x0C23440
add      esp, 4
mov      BYTE [ebp - 1], al

...

mov      BYTE [ebp - 3], al    ; <--- irrelevant
movzx    eax, BYTE [ebp - 1]
cmp      eax, 1
jnz      0x0C2437D

What we see is that this conditional is only affected by the call to 0x0C23440 so we can assume that the function we initially found is irrelevant. We will need to check this assumption later (we should always check our assumptions) after we analyse more parts of the binary.

Let’s now shift our focus to the beginning of the bad boy which has been highlighted. Note the locations which lead to the instruction. One is from the condition we have just examined, the other is from the top of the current function. Take a look at the condition there

cmp    DWORD [esp + 8], 2
jnz    0x0C2437D

and recognise that it is comparing the first parameter of this function with 2. When you reverse engineer enough applications, your Spidey senses will fire up and instantly assume that this a possible comparison to check for a valid argc of main. Of course, we will need to verify this assumption and we can do so if we step the instructions there if we add in a command line argument. We will see that this jump is not taken if we add it. Now that we’ve cleared this assumption, we know that we are currently in main.

Now, let’s step into the function which decides the good or bad boy (0x0C23440).

Due to the size of the function, I have decided to cut it into two smaller sections. This first one represents an inlined strlen which essentially decompiles into this:

char *p = input + 1;             // + 1 to remove the counted null terminator
while (*input++)
    i++;
size_t len = (input + i) - p;    // length = final address - first address

if (len != 0x1E)                 // 0x1E == 30
    return 0;                    // false

The jump instruction goes too far down to be included into this section, but trust me, it does return 0, or possibly false since this is a C++ application.

The following is the second section of this function which checks the validity of the key.

There are a lot of conditionals here so we need to note down what each of them are (see my comments). Again, with enough experience, we can tell this is probably a for loop. This loop checks each character of the input in groups of 5 (note the modulo 5), sums them together and then checks a buffer for a valid combination. It repeats this 6 times to satisfy the string length of 0x1E. Decompiling this would resemble something like so:

unsigned int buffer = { 0xC9, 0xFF, 0x9C, 0xB7, 0xA6, 0xB6 };
for (unsigned int sum = 0, int i = 1; i < 0x1F; i++) {
    char c = input[i];
    // check if alphanumeric
    if (c < '0' || c > 'z')
        return 0;
    if (c > '9' || c < 'A')
        return 0;
    if (c > 'Z' || c < 'a')
        return 0;

    // verify in 5-byte groups
    sum += c - '0';
    if (i % 5 == 0) {
        if (buffer[i/5] != sum)
            return 0;
        sum = 0;
    }
}

return 1;

How do we generate a key from this? An easy way is to iterate the verification buffer and for each element, apply the following algorithm:

1. Choose an alphanumeric X
2. Y = (buffer[i/5] + 0x30 * 5) - (X * 4) where Y is also alphanumeric
3. 5-byte group for buffer[i/5] = XXXXY
4. Repeat until 30 characters have been generated

Example: aaaa5aaaakEEEExLLLLwKKKKjKKKKz
This will only provide a subset of the entire possible range.