Keygen 1: seVeb's crackme05

Mar 19, 2018

Link: http://crackmes.cf/users/seveb/crackme05/

This challenge is somewhat different, since our aim is to build a keygen instead of simply breaking the binary. It will therefore involve more reversing and no patching.

$ ./crackme05_32bit
Welcome to crackme05 reverser!
Your task is simple, figure out a way to generate valid serials.
Patching is as expected not allowed. Write a keygen and tell us
how you solved the crackme.

Invoke the crackme with your serial as the first argument to check
for a valid serial.

Usage: ./crackme05_32bit serial

$ ./crackme05_32bit foo
ROCK 4: Serial not 19 chars!

          ,--.!,
       __/   -*-
     ,d08b.  '|`
     0088MM
     `9MMP'
I have not failed. I've just found 10,000 ways that won't work.
- Thomas Edison

Note “ROCK 4” and the message about the length. Hopper shows that there might be multiple validation steps, with shortcuts likely going to an error function: overview

main starts with a check on argc > 1, and goes to usage if it doesn’t match. We then have a sequence of calls to rock, paper, scissors, and cracker, all called with our input as a single parameter. Here’s the first one:

mov        eax, dword [esp+0x10c+var_100]
add        eax, 0x4
mov        eax, dword [eax]
mov        dword [esp+0x10c+var_10C], eax ; argument #1 for method rock
call       rock         ; rock

Since they are in sequence and with branching instructions, we can assume that they will all call the failure function if at any point the input is incorrect.

In rock, a loop goes over the characters of our input until it reaches a null byte:

loc_80489dd:
mov        edx, dword [ebp+var_C] ; CODE XREF=rock+20
mov        eax, dword [ebp+arg_0]
add        eax, edx
movzx      eax, byte [eax]
test       al, al
jne        loc_80488d8

It compares the character to 0x2c (‘,’) and if lower or equal, goes to a procedure that calls bomb:

mov        edx, dword [ebp+var_C]
mov        eax, dword [ebp+arg_0]
add        eax, edx
movzx      eax, byte [eax]
cmp        al, 0x2f
jg         loc_8048934 ; continues below

[... bomb ... ]

loc_8048934:
mov        edx, dword [ebp+var_C] ; CODE XREF=rock+53, rock+68
mov        eax, dword [ebp+arg_0]
add        eax, edx
movzx      eax, byte [eax]
cmp        al, 0x39
jle        loc_804897e

There are multiple blocks comparing the current character to various constants. With the help of Hopper’s disassembly tool, we can reconstruct the logic:

int rock(char * input) {
    int count = 0x0;
    int offset = 0x0;
    do {
        eax = input[offset] & 0xff;
        if (eax == 0x0) {
            break;
        }
        if ((input[offset] > ',') && (((input[offset] <= '-') || (input[offset] > '/')))) {
            if ((input[offset] > '9') && (input[offset] <= '@')) {
                printf("ROCK 2: %i - %c\n", offset, input[offset]);
                bomb();
            }
            else {
                if (input[offset] > 'Z') {
                    if (input[offset] > '`') {
                        if (input[offset] > 'z') {
                            printf("ROCK 3: %i - %c\n", offset, input[offset]);
                            bomb();
                        }
                    }
                    else {
                        printf("ROCK 3: %i - %c\n", offset, input[offset]);
                        bomb();
                    }
                }
                else {
                    if (input[offset] > 'z') {
                        printf("ROCK 3: %i - %c\n", offset, input[offset]);
                        bomb();
                    }
                }
            }
        }
        else {
            printf("ROCK 1: %i - %c\n", offset, input[offset]);
            bomb();
        }
        count = count + 0x1;
        offset = offset + 0x1;
    } while (true);
    if (count != 0x13) {
        puts("ROCK 4: Serial not 19 chars!");
        eax = bomb();
    }
    return eax;
}

This gives us the following constraints:

  • The serial needs to be 19 characters long.
  • Each character needs to be after ‘,’ and before (including) ‘-’ or after ‘/’.
  • They can be between ‘0’ (‘/’ + 1) and ‘9’
  • They can’t be between ‘9’ and ‘@’ included, which is the range “:;<=>?@”
  • They can’t be after ‘Z’ and after ‘z’, which means ‘A’-‘Z’ is fine
  • They can’t be after ‘`’ and after ‘z’, which means ‘a’-‘z’ is fine

So something like f00-b4r-123-abc-DEF would pass. Let’s try it:

$ ./crackme05_32bit f001-b4r0-1234-abcD
Paper 1

We’passed the Rock challenge, we’re onto the next one. We can disassemble paper and clean it up into the following:

int paper(char *input) {
    var_10 = input[0xa] ^ input[0x8] + 0x30;
    var_c = input[0xd] ^ input[0x5] + 0x30;
    if (var_10 <= 0x39 && var_C <= 0x39) {
        if ((var_10 <= 0x2f) || (var_C <= 0x2f)) {
            puts("Paper 1 lower");
            bomb();
        }
    }
    else {
        puts("Paper 1");
        bomb();
    }
    if (input[0x3]  == var_10 && input[0xf]  == var_10) {
        if (input[0] == var_C) {
            if (input[0x12] != var_C) {
                puts("Paper 3");
                eax = bomb();
            }
        }
        else {
            puts("Paper 3");
            eax = bomb();
        }
    }
    else {
        puts("Paper 2");
        eax = bomb();
    }
    return eax;
}

We derive the following constraints:

  • input[0xa] ^ input[0x8] + 0x30 <= 0x39
  • input[0xd] ^ input[0x5] + 0x30 <= 0x39
  • input[0xa] ^ input[0x8] + 0x30 > 0x2f
  • input[0xd] ^ input[0x5] + 0x30 > 0x2f
  • input[0x3] == input[0xa] ^ input[0x8] + 0x30
  • input[0xf] == input[0xa] ^ input[0x8] + 0x30
  • input[0x0] = input[0xd] ^ input[0x5] + 0x30
  • input[0x12] = input[0xd] ^ input[0x5] + 0x30

This gets us closer to a keygen, but we’re not quite there yet. Let’s continue with scissors to get more data. It disassembles to:

int scissors(char *input) {
    var_10 = input[0x2] + input[0x1];
    var_C = input[0x11] + input[0x10];
    if ((var_10 > 0xaa) && (var_C > 0xaa)) {
        if (var_10 == var_C) {
            puts("Scissors 2");
            eax = bomb();
        }
    }
    else {
        puts("Scissors 1");
        eax = bomb();
    }
    return eax;
}

New constraints: - input[0x2] + input[0x1] > 0xaa - input[0x11] + input[0x10] > 0xaa - input[0x2] + input[0x1] != input[0x11] + input[0x10]

Finally, the last procedure is cracker:

int cracker(char *input) {
    var_C = input[0xe] + input[0x4] + input[0x9];
    if (var_C != 0x87) {
        puts("cracker 1");
        eax = bomb();
    }
    else {
        eax = (HIDWORD(var_C * 0x55555556) - (SAR(var_C, 0x1f)))
            + (HIDWORD(var_C * 0x55555556) - (SAR(var_C, 0x1f)))
            + (HIDWORD(var_C * 0x55555556) - (SAR(var_C, 0x1f)));
        if (var_C != eax) {
            puts("cracker 1");
            eax = bomb();
        }
    }
    return eax;
}

Note that 0x87 / 3 is 45, which is the ascii code for ‘-’, one of the characters allowed. Let’s try to generate a serial at random given our constraints, and with the format “????-????-????-????”.

$ ./crackme05_32bit 2aP3-MdCK-HGgO-3bR2
In order to succeed you must fail, so that you know what not to do the next time.
-Anthony J. D'Angelo

Good Job!