Keygen 1: seVeb’s crackme05
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:
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!