mercredi 2 juin 2010

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 :

.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 :

.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 :
.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 :

.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 :

// BuKoBG Keygenme #1

#include 
#include 

// 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

- link : BuKoG KeyGenMe #1

1 commentaire :

  1. yep ;-)

    -Good job , c'est relativement basique mais ça reste un trés bon petit article sur le sujet .

    Bonne continuation et vivement le 19 :P

    RépondreSupprimer