BuKoG KeyGenMe #1
Today, I ate some little keygenme, quite interesting in fact for how it was conceived.
First of all, since it’s level 2 :
- no packer
- no obfuscator
- no protections but serial checking
It was pretty straightforward to find the serial checking routine. It’s located in DialogFunc().
From there, we just need to identify username and serial fields using manual boron tagging. After that, stumble upon an interesting routine :
1
2
3
4
.text:004010FB serial_check_wrapper proc near ; CODE XREF: DialogFunc+93
.text:004010FB call serial_check
.text:00401100 retn
.text:00401100 serial_check_wrapper endp
As we can see, it’s wrapping a call to some routine, which is in fact the serial checking routine. Why is this wrapped? We’ll see that later ;) . Anyway, the serial checking routine is as followed :
1
2
3
4
5
6
7
8
9
10
11
12
.text:00401200 serial_check proc near ; CODE XREF: serial_check_wrapper
.text:00401200 push offset username
.text:00401205 call hash
.text:0040120A push eax
.text:0040120B push offset serial
.text:00401210 call hash
.text:00401215 pop ebx
.text:00401216 sub ebx, eax
.text:00401218 mov [ebp-8], bl ; change return address
.text:0040121B mov eax, 0
.text:00401220 retn
.text:00401220 serial_check endp
Having identified the username and serial buffers previously, it’s get a lot easier. We see that the return address depend on the last byte of the following operation : hash(username) - hash(serial) . Since a byte contains 256 values, it means that we’ll have 256 return values too.
We don’t need to launch the crackme yet. Static analysis is a bit harder but way more challenging :) . So we called serial_check_wrapper() located at address 0x004010FB. We should return to 0x00401100 after the serial_check() routine but since we modify the last byte, it never happens. The address we return should be of the form : 0x004011XX where XX is our modified byte.
Looking after the serial_check_wrapper() routine, we see a bunch of returns and an interesting line around 0x00401113. So 255 out of 256 we get the “Wrong Serial” message. Before making the keygen, we need to analyse the hash() function.
The hash function is as follow :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
.text:00401247 hash proc near ; CODE XREF: serial_check+5
.text:00401247 ; serial_check+10
.text:00401247
.text:00401247 str = dword ptr 8
.text:00401247
.text:00401247 push ebp
.text:00401248 mov ebp, esp
.text:0040124A mov eax, 0FFFFFFFFh
.text:0040124F mov esi, [ebp+str]
.text:00401252 xor edx, edx
.text:00401254
.text:00401254 loc_401254: ; CODE XREF: hash+2D
.text:00401254 mov ebx, eax
.text:00401256 shr ebx, 8
.text:00401259 mov ecx, eax
.text:0040125B and ecx, 0FFh
.text:00401261 mov dl, [esi]
.text:00401263 cmp dl, 0
.text:00401266 jz short loc_401276
.text:00401268 xor edx, ecx
.text:0040126A xor ebx, dword_4030CC[edx*4]
.text:00401271 mov eax, ebx
.text:00401273 inc esi
.text:00401274 jmp short loc_401254
.text:00401276 ; ---------------------------------------------------------------------------
.text:00401276
.text:00401276 loc_401276: ; CODE XREF: hash+1F
.text:00401276 xor eax, 0FFFFFFFFh
.text:00401279 leave
.text:0040127A retn 4
.text:0040127A hash endp
We can see here that it’s using some array. Where is it generated? We can easily try to locate it using cross referencing, it happens that only hash() use it. Let’s see the data section. Just before the used array, there is an hInstance variable. Cross referencing it, happens to land us in a CRC32 802.3 routine :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.text:00401221 crc32_ieee8023 proc near ; CODE XREF: start
.text:00401221 mov ecx, 100h
.text:00401226 mov edx, 0EDB88320h
.text:0040122B
.text:0040122B loc_40122B: ; CODE XREF: crc32_ieee8023+23
.text:0040122B lea eax, [ecx-1]
.text:0040122E push ecx
.text:0040122F mov ecx, 8
.text:00401234
.text:00401234 loc_401234: ; CODE XREF: crc32_ieee8023:loc_40123A
.text:00401234 shr eax, 1
.text:00401236 jnb short loc_40123A
.text:00401238 xor eax, edx
.text:0040123A
.text:0040123A loc_40123A: ; CODE XREF: crc32_ieee8023+15
.text:0040123A loop loc_401234
.text:0040123C pop ecx
.text:0040123D mov hInstance[ecx*4], eax
.text:00401244 loop loc_40122B
.text:00401246 retn
.text:00401246 crc32_ieee8023 endp
The idenfitication process of this function went through reversing and searching about that magic number : 0x0EDB88320.
So we have all we need for a keygen :
- hash() use crc32 802.3 table
- serial_check() returns to 0x004011XX
- XX must be equal to 0x13 if we want correct message
- (hash(username) - hash(serial)) & 0xFF must be equal to 0x13
Using all this, I used the bruteforce approach. I only bruteforce the first 1000 numbers but it’s enough. Here is the keygen :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// BuKoBG Keygenme #1
#include <stdio.h>
#include <stdlib.h>
// crc32
unsigned int* crc32 (unsigned int polynom) {
unsigned short int carry;
int i, j;
size_t n;
unsigned int *crc32_table;
n = 256;
crc32_table = calloc (n, sizeof(*crc32_table));
if (!crc32_table)
return NULL;
for (i = n - 1; i >= 0; i--) {
crc32_table[i] = i;
for (j = 7; j >= 0; j--) {
carry = crc32_table[i] & 1;
crc32_table[i] >>= 1;
if (carry)
crc32_table[i] ^= polynom;
}
}
return crc32_table;
}
// hash
unsigned int hash (unsigned char *str, size_t len) {
size_t i, index;
unsigned int hashed = -1, *hashtable;
// check pointers
if (!str || !len)
return -1;
// generate crc32 IEEE 802.3 table
hashtable = crc32(0xedb88320);
// generate serial
i = 0;
index = 0;
while (str[i] && i < len) {
index = str[i] ^ (hashed & 0xff);
hashed = (hashed >> 8) ^ hashtable[index];
++i;
}
hashed ^= -1;
// clean up
free(hashtable);
return hashed;
}
// keygen
unsigned int keygen (unsigned char *username, size_t userlen) {
unsigned int hash1, hash2, key = -1, licence, serial;
unsigned char serial_str[4] = {0};
size_t seriallen = 4;
hash1 = hash (username, userlen);
// bruteforce serial
for (licence = 0, serial = 0; (licence & 0xff) != 0x13; serial++) {
// convert integer to string
snprintf(serial_str, seriallen, "%03u", serial);
hash2 = hash (serial_str, seriallen);
licence = hash1 - hash2;
}
return --serial;
}
int main (int argc, char *argv[]) {
unsigned char username[256] = {0};
unsigned int serial;
size_t len = 256;
printf("Username : ");
gets(username);
serial = keygen (username, len);
// limit search to first 1000 numbers
if (serial < 1000)
printf("\nSerial : %03u\n", serial);
else
printf("No serial found\n");
return 0;
}
Hope you enjoyed this small snack :) .
m_101
Resources
- link : BuKoG KeyGenMe #1