Write-up-MaxXor-KeygenMe-V6

From aldeid
Jump to navigation Jump to search

Description

This crackme is available here: http://crackmes.de/users/maxxor/keygenme_v6/ and is rated "2 - Needs a little brain (or luck)". I had a lot of fun resolving it.

When run, it shows a form with 2 fields respectively for the username and the serial, as follows:

Analysis

start (sub_401AAB)

The start function calls load_funcs (sub_4012E8) at offset 0x401AB4

.text:00401AAB start           proc near
.text:00401AAB
.text:00401AAB var_5C          = WNDCLASSEXA ptr -5Ch
.text:00401AAB Msg             = MSG ptr -2Ch
.text:00401AAB Rect            = tagRECT ptr -10h
.text:00401AAB hInstance       = dword ptr  8
.text:00401AAB
.text:00401AAB                 push    ebp
.text:00401AAC                 mov     ebp, esp
.text:00401AAE                 sub     esp, 5Ch
.text:00401AB1                 push    ebx
.text:00401AB2                 push    esi
.text:00401AB3                 push    edi
.text:00401AB4                 call    f_load_funcs

Then, the f_check (sub_4013B1 function is called as lpfnWndProc parameter to the LoadCursorA function:

.text:00401B15                 mov     [ebp+var_5C.hIconSm], eax
.text:00401B18                 mov     [ebp+var_5C.hInstance], ebx
.text:00401B1B                 mov     [ebp+var_5C.lpszClassName], edi
.text:00401B1E                 mov     [ebp+var_5C.lpfnWndProc], offset f_check
.text:00401B25                 mov     [ebp+var_5C.style], 8
.text:00401B2C                 mov     [ebp+var_5C.cbSize], 30h
.text:00401B33                 call    ds:LoadCursorA

f_load_funcs (sub_4012E8)

f_load_funcs (sub_4012E8) actually loads several functions (strlen, sprintf, strcmp, memset, isdigit, atoi) to memory locations that will be called later in the code. The purpose is to make the calls to these functions less obvious.

.text:004012E8 f_load_funcs    proc near
.text:004012E8                 push    esi
.text:004012E9                 push    edi
.text:004012EA                 push    offset ModuleName ; "ntdll.dll"
.text:004012EF                 call    ds:GetModuleHandleA
.text:004012F5                 mov     esi, ds:GetProcAddress
.text:004012FB                 mov     edi, eax
.text:004012FD                 push    offset ProcName ; "strlen"
.text:00401302                 push    edi             ; hModule
.text:00401303                 call    esi ; GetProcAddress
.text:00401305                 push    offset aSprintf ; "sprintf"
.text:0040130A                 push    edi             ; hModule
.text:0040130B                 mov     strlen, eax     ; mem loc 0x402000
.text:00401310                 call    esi ; GetProcAddress
.text:00401312                 push    offset aStrncmp ; "strncmp"
.text:00401317                 push    edi             ; hModule
.text:00401318                 mov     sprintf, eax    ; mem loc 0x402004
.text:0040131D                 call    esi ; GetProcAddress
.text:0040131F                 push    offset aMemset  ; "memset"
.text:00401324                 push    edi             ; hModule
.text:00401325                 mov     strncmp, eax    ; mem loc 0x402008
.text:0040132A                 call    esi ; GetProcAddress
.text:0040132C                 push    offset aIsdigit ; "isdigit"
.text:00401331                 push    edi             ; hModule
.text:00401332                 mov     memset, eax     ; mem loc 0x40200C
.text:00401337                 call    esi ; GetProcAddress
.text:00401339                 push    offset aAtoi    ; "atoi"
.text:0040133E                 push    edi             ; hModule
.text:0040133F                 mov     isdigit, eax    ; mem loc 0x402010
.text:00401344                 call    esi ; GetProcAddress
.text:00401346                 pop     edi
.text:00401347                 mov     atoi, eax       ; mem loc 0x402014
.text:0040134C                 pop     esi
.text:0040134D                 retn
.text:0040134D f_load_funcs    endp

; ...[SNIP]...

.data:00402000 strlen          dd ?
.data:00402004 sprintf         dd ?
.data:00402008 strncmp         dd ?
.data:0040200C memset          dd ?
.data:00402010 isdigit         dd ?
.data:00402014 atoi            dd ?

f_check (sub_4013B1)

Graph overview

Below is the graph overview of the function:

Check username length

Starting from offset 0x401426, the function checks whether the username is 4 characters long.

.text:00401426                 push    hWnd            ; hWnd
.text:0040142C                 call    ds:GetWindowTextLengthA
.text:00401432                 cmp     eax, 4          ; len(my_username) = 4
.text:00401435                 jnz     loc_40174A

Check serial length

Starting from offset 0x40143B, the function then checks whether the serial is 20 characters long.

.text:0040143B                 push    dword_402020    ; hWnd
.text:00401441                 call    ds:GetWindowTextLengthA
.text:00401447                 cmp     eax, 14h        ; len(my_serial) = 20
.text:0040144A                 jnz     loc_40174A

Check username format

Starting from offset 0x4014BA, the function starts by checking the length of the username, which should be 4 digits.

Note
At offset 004014D0, notice the call to memory location 0x402010 which has been set to isdigit by the f_load_funcs (sub_4012E8) function. Renaming these memory locations helps :)
.text:004014BA loc_4014BA:
.text:004014BA                 mov     eax, [ebp+counter_i]
.text:004014BD                 inc     eax
.text:004014BE                 mov     [ebp+counter_i], eax ; i+=1
.text:004014C1
.text:004014C1 loc_4014C1:
.text:004014C1                 cmp     [ebp+counter_i], 4 ;
.text:004014C1                                         ; while (i<=4)
.text:004014C5                 jnb     short loc_4014FC
.text:004014C7                 mov     eax, [ebp+counter_i]
.text:004014CA                 movsx   eax, [ebp+eax+my_username]
.text:004014CF                 push    eax             ; eax = my_username[i]
.text:004014D0                 call    isdigit         ; mem loc 0x402010
.text:004014D6                 pop     ecx
.text:004014D7                 test    eax, eax        ;  \ username should be
.text:004014D9                 jnz     short loc_4014FA ; / 4 digits
.text:004014DB                 push    30h             ; uType
.text:004014DD                 push    offset aKeygenmeV6 ; "KeygenMe V6"
.text:004014E2                 push    offset aPleaseEnterANu ; "Please enter a number as username!"
.text:004014E7                 push    hDlg            ; hWnd
.text:004014ED                 call    ds:MessageBoxA
.text:004014F3                 xor     eax, eax
.text:004014F5                 jmp     loc_401AA5
; ...[SNIP]...
.data:00402010 isdigit         dd ?
.data:00402010

Build accumulator array

Starting from offset 0x4014FC, there is a loop that is executed 4 times and builds a 4 items array, based on the username.

.text:004014FC loc_4014FC:
.text:004014FC                 lea     eax, [ebp+my_username]
.text:004014FF                 push    eax
.text:00401500                 call    atoi
.text:00401506                 pop     ecx
.text:00401507                 mov     [ebp+my_username_as_int], eax
.text:0040150A                 and     [ebp+counter_j], 0            ; j = 0
.text:0040150E                 jmp     short loc_401517
.text:00401510 ; ---------------------------------------------------------------------------
.text:00401510
.text:00401510 loc_401510:
.text:00401510                 mov     eax, [ebp+counter_j]
.text:00401513                 inc     eax
.text:00401514                 mov     [ebp+counter_j], eax          ; j += 1
.text:00401517
.text:00401517 loc_401517:
.text:00401517                 cmp     [ebp+counter_j], 4            ; while(j<=4)
.text:0040151B                 jge     short loc_40153A
.text:0040151D                 mov     eax, [ebp+counter_j]
.text:00401520                 lea     ecx, ds:10h[eax*8]            ; ecx = i*8+0x10
.text:00401527                 mov     eax, [ebp+my_username_as_int] ; eax = my_username_as_int
.text:0040152A                 cdq
.text:0040152B                 idiv    ecx                           ; eax = eax / (i*8 + 0x10)
.text:0040152D                 imul    eax, [ebp+my_username_as_int] ; eax *= my_username_as_int
.text:00401531                 mov     ecx, [ebp+counter_j]
.text:00401534                 mov     [ebp+ecx*4+accumulator], eax  ; accumulator[j] = eax
.text:00401538                 jmp     short loc_401510

Check serial

Main loop

Then, there is a loop that will be executed 20 times, for each character of the serial. Every 5 characters (when the remainder of the division of the loop counter by 5 is zero), the code will jump to 0x401564 to check the separator, excepted when the counter is equal to 0.

.text:0040153A loc_40153A:
.text:0040153A                 and     [ebp+counter_k], 0 ; k = 0
.text:0040153E                 jmp     short loc_401547
.text:00401540 ; ---------------------------------------------------------------------------
.text:00401540

;...[SNIP]...

.text:00401547 loc_401547:
.text:00401547                 cmp     [ebp+counter_k], 14h ;
.text:00401547                                         ; while (k <= 20)
.text:0040154B                 jnb     loc_401716
.text:00401551                 cmp     [ebp+counter_k], 0
.text:00401555                 jz      short loc_401592 ; if k=0, goto loc_401592
.text:00401557                 mov     eax, [ebp+counter_k] ; if k != 0
.text:0040155A                 cdq
.text:0040155B                 push    5
.text:0040155D                 pop     ecx             ; ecx = 5
.text:0040155E                 idiv    ecx             ; eax = k / 5
.text:00401560                 test    edx, edx         ; \ if k%5 != 0
.text:00401562                 jnz     short loc_401592 ; / goto loc_401592

Check separator character

Every 5 runs in the loop, the code will jump to check whether the probed character is '-' (0x2D).

.text:00401564                 mov     eax, [ebp+counter_k]
.text:00401567                 movsx   eax, [ebp+eax+my_serial] ; eax = my_serial[k]
.text:0040156C                 sub     eax, '-'        ; eax = my_serial[k] - 0x2D
.text:0040156F                 neg     eax             ; eax = -eax (CF = 1 if -eax != 0)
.text:00401571                 sbb     eax, eax        ; eax = eax - (eax + CF)
.text:00401573                 inc     eax             ; eax += 1
.text:00401574                 mov     [ebp+var_51], al
.text:00401577                 movzx   eax, [ebp+var_51]
.text:0040157B                 test    eax, eax        ;  \ if serial[k] == '-'
.text:0040157D                 jnz     short loc_401586 ; / goto loc_401586
.text:0040157F                 jmp     loc_401716
.text:00401584 ; ---------------------------------------------------------------------------
.text:00401584                 jmp     short loc_40158D
.text:00401586 ; ---------------------------------------------------------------------------
.text:00401586
.text:00401586 loc_401586:
.text:00401586                 mov     eax, [ebp+counter_l]
.text:00401589                 inc     eax
.text:0040158A                 mov     [ebp+counter_l], eax ; l += 1
Note
The developer used a combination of several instructions (sub, neg, sbb, inc) to check whether the character is '-'. The purpose here again is to make the reverse engineering process less obvious because a call to the strcmp function would have catched your attention immediately.

Check other characters

You will certainly have noticed the following fork that splits the code into two branches that look very similar:

The code will jump to the left branch when the loop counter is even and to the right one when it is odd:

.text:00401592 loc_401592:
.text:00401592                 mov     eax, [ebp+counter_k] ; if k = 0 or k%5 != 0
.text:00401595                 and     eax, 80000001h  ; eax = k & 0x80000001
.text:00401595                                         ; (trick to %2)
.text:0040159A                 jns     short loc_4015A1 ; if k%2 = 0, goto loc_4015A1
.text:0040159C                 dec     eax
.text:0040159D                 or      eax, 0FFFFFFFEh
.text:004015A0                 inc     eax
.text:004015A1
.text:004015A1 loc_4015A1:
.text:004015A1                 test    eax, eax        ;
.text:004015A1                                         ; \ if k%2 != 0
.text:004015A3                 jnz     loc_401661      ; / goto loc_401661

The 2 branches are almost identical, with only 1 difference:

Hence, for the sake of simplicity, we will only analyze one of these 2 branches.

Below is the code of the left branch:

.text:004015A9                 mov     eax, [ebp+counter_k] ; eax = k
.text:004015AC                 cdq
.text:004015AD                 and     edx, 3          ; edx = 0
.text:004015B0                 add     eax, edx        ; eax = k
.text:004015B2                 sar     eax, 2          ; eax = k >> 2
.text:004015B5                 mov     [ebp+index], eax
.text:004015B8                 mov     eax, [ebp+index]
.text:004015BB                 inc     eax             ; eax = (k >> 2) + 1
.text:004015BC                 mov     [ebp+index], eax ; index = (k >> 2) + 1
.text:004015BF                 cmp     [ebp+index], 3  ;  \ if (k >> 2) + 1 < 3
.text:004015C3                 jle     short loc_4015CE ; / goto loc_4015CE
.text:004015C5                 mov     eax, [ebp+index]
.text:004015C8                 sub     eax, 4
.text:004015CB                 mov     [ebp+index], eax ; index -= 4
.text:004015CE
.text:004015CE loc_4015CE:
.text:004015CE                 mov     eax, [ebp+index]
.text:004015D1                 mov     eax, [ebp+eax*4+accumulator] ; eax = accumulator[index]
.text:004015D5                 mov     [ebp+accumulator_i], eax
.text:004015D8                 mov     ecx, [ebp+counter_k]
.text:004015DB                 inc     ecx             ; ecx = k + 1
.text:004015DC                 mov     eax, [ebp+accumulator_i] ; eax = accumulator[index]
.text:004015DF                 cdq
.text:004015E0                 idiv    ecx             ; eax = accumulator[index] / (k+1)
.text:004015E2                 mov     [ebp+accumulator_i], edx
.text:004015E5                 mov     eax, [ebp+accumulator_i] ; eax = accumulator[index] % (k+1)
.text:004015E8                 shl     eax, 1
.text:004015EA                 mov     [ebp+accumulator_i], eax ; accumulator_i = (accumulator[index] % (k+1)) * 2
.text:004015ED                 inc     eax             ; \
.text:004015EE                 inc     eax             ; |
.text:004015EF                 inc     eax             ; |
.text:004015F0                 dec     eax             ; | do nothing
.text:004015F1                 dec     eax             ; |
.text:004015F2                 dec     eax             ; /
.text:004015F3                 cmp     [ebp+accumulator_i], 9 ; \ if (accumulator[index] % (k+1)) * 2 > 9
.text:004015F7                 jg      short loc_401602 ;       / goto loc_401602
.text:004015F9                 mov     eax, [ebp+accumulator_i]
.text:004015FC                 add     eax, 4                   ; \ accumulator_i += 4 (branch on the left: when k is even)
.text:004015FF                 mov     [ebp+accumulator_i], eax ; / accumulator_i += 3 (branch on the right: when k is odd)
.text:00401602
.text:00401602 loc_401602:
.text:00401602                 cmp     [ebp+accumulator_i], 24h ; \ if accumulator_i < 0x24
.text:00401606                 jl      short loc_401611 ;         / goto loc_401611
.text:00401608                 mov     eax, [ebp+accumulator_i]
.text:0040160B                 sub     eax, 0Ah
.text:0040160E                 mov     [ebp+accumulator_i], eax ; accumulator_i -= 0x0A
.text:00401611
.text:00401611 loc_401611:
.text:00401611                 cmp     [ebp+accumulator_i], 0 ; \ if accumulator_i < 0
.text:00401615                 jl      short loc_40161D ;       / goto loc_40161D
.text:00401617                 cmp     [ebp+accumulator_i], 23h ; \ if accumulator_i <= 0x23
.text:0040161B                 jle     short loc_401626 ;         / goto loc_401626
.text:0040161D
.text:0040161D loc_40161D:
.text:0040161D                 mov     eax, [ebp+counter_k]
.text:00401620                 add     eax, 7
.text:00401623                 mov     [ebp+accumulator_i], eax ; accumulator_i = k+7
.text:00401626
.text:00401626 loc_401626:
.text:00401626                 inc     eax             ;
.text:00401626                                         ; \
.text:00401627                 inc     eax             ; |
.text:00401628                 inc     eax             ; |
.text:00401629                 dec     eax             ; | do nothing
.text:0040162A                 dec     eax             ; |
.text:0040162B                 dec     eax             ; /

This code computes an index at offset 0x4015B5 that will be used to read a value from the array (accumulator) previously built. Then, it performs some tests and computations that could be sum up as follows:

index = (k >> 2) + 1
if index > 3:
    index -= 4
accumulator_i = (accumulator[index] % (k+1)) * 2
if accumulator_i <= 9:
    # code of the 2 branches is similar expect here
    # 4 is added to accumulator_i when the loop counter is even
    # 3 is added to accumulator_i when the loop counter is odd
    accumulator_i += [4, 3][k%2]
if accumulator_i >= 0x24:
    accumulator_i -= 0xA
if accumulator_i < 0 or accumulator_i > 0x23:
    accumulator_i = k + 7

Once accumulator_i has been computed, the same instruction combination (sub, neg, sbb, inc) as detailed previously is used to compare the probed character of the serial with the one expected (accumulator_i)

.text:0040162C                 mov     eax, [ebp+counter_k]
.text:0040162F                 movsx   eax, [ebp+eax+my_serial] ; eax = my_serial[i]
.text:00401634                 mov     ecx, [ebp+accumulator_i] ; ecx = accumulator_i
.text:00401637                 movsx   ecx, [ebp+ecx+a0123456789abcd] ; ecx = a0123456789abcd[accumuator_i]
.text:0040163C                 sub     eax, ecx        ;   \
.text:0040163E                 neg     eax             ;   |
.text:00401640                 sbb     eax, eax        ;   | trick to simply
.text:00401642                 inc     eax             ;   | compare chars
.text:00401643                 mov     [ebp+var_51], al ;  |
.text:00401646                 movzx   eax, [ebp+var_51] ; /
.text:0040164A                 test    eax, eax        ; my_serial[i] = a0123456789abcd[accumuator_i]?
.text:0040164C                 jnz     short loc_401655

Success message

Once all characters of the serial have been verified, the code jumps to 0x401716. The function that displayes the sucess message is called at offset 0x401727:

.text:00401716 loc_401716:
.text:00401716                 push    ss
.text:00401717                 pop     ss
.text:00401718                 mov     eax, 5
.text:0040171D                 xor     edx, edx
.text:0040171F                 movzx   eax, [ebp+var_51]
.text:00401723                 test    eax, eax
.text:00401725                 jz      short loc_40174A
.text:00401727                 mov     eax, offset f_success ; success message
.text:0040172C                 add     eax, 15h
.text:0040172F                 movzx   ecx, [ebp+var_51]
.text:00401733                 sub     eax, ecx
.text:00401735                 sub     eax, [ebp+counter_l]
.text:00401738                 mov     [ebp+var_84], eax
.text:0040173E                 push    offset loc_40174A
.text:00401743                 push    [ebp+var_84]
.text:00401749                 retn

Below is the code of the function that displays the success message:

.text:0040134E f_success       proc near
.text:0040134E                 push    esi
.text:0040134F                 push    offset String   ; "KeygenMe V5 - Full"
.text:00401354                 push    hDlg            ; hWnd
.text:0040135A                 call    ds:SetWindowTextA
.text:00401360                 push    offset aThankYouForReg ; "Thank you for registering"
.text:00401365                 push    68h             ; nIDDlgItem
.text:00401367                 push    hDlg            ; hDlg
.text:0040136D                 call    ds:SetDlgItemTextA
.text:00401373                 mov     esi, ds:EnableWindow
.text:00401379                 push    0               ; bEnable
.text:0040137B                 push    hWnd            ; hWnd
.text:00401381                 call    esi ; EnableWindow
.text:00401383                 push    0               ; bEnable
.text:00401385                 push    dword_402020    ; hWnd
.text:0040138B                 call    esi ; EnableWindow
.text:0040138D                 push    0               ; bEnable
.text:0040138F                 push    dword_40201C    ; hWnd
.text:00401395                 call    esi ; EnableWindow
.text:00401397                 push    40h             ; uType
.text:00401399                 push    offset Caption  ; "Success"
.text:0040139E                 push    offset Text     ; "Thank you for registering!"
.text:004013A3                 push    hDlg            ; hWnd
.text:004013A9                 call    ds:MessageBoxA
.text:004013AF                 pop     esi
.text:004013B0                 retn
.text:004013B0 f_success       endp

Solution

Keygen

Below is my keygen:

#!/usr/bin/env python
import sys

def make_serial(user):
    accumulator = []
    a0123456789abcd = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    my_username = int(user)
    serial = []
    
    for i in range(4):
        accumulator.append(my_username / (i*8+0x10) * my_username)

    for k in range(20):
        if k == 0 or k%5 != 0:
            index = (k >> 2) + 1
            if index > 3:
                index -= 4
            accumulator_i = (accumulator[index] % (k+1)) * 2
            if accumulator_i <= 9:
                accumulator_i += [4, 3][k%2]
            if accumulator_i >= 0x24:
                accumulator_i -= 0xA
            if accumulator_i < 0 or accumulator_i > 0x23:
                accumulator_i = k + 7
            serial.append(a0123456789abcd[accumulator_i])
        else:
            serial.append('-')

    return ''.join([i for i in serial])
        
        
if __name__ == '__main__':
    if len(sys.argv) < 2:
        print "Usage: %s <4_digits_username>" % sys.argv[0]
        sys.exit()
    print make_serial(sys.argv[1])

Some valid solutions

And here is it in action:

Keygen Validation
$ ./keygen.py 1234
43478-CBA3-363G-4CCS
$ ./keygen.py 8462
4383A-83G7-7ABC-A7QB

Comments

Keywords: crackme reverse-engineering maxxor keygen