Solution-DaXXoR-101-KeygenMe-3

From aldeid
Jump to navigation Jump to search

Introduction

Description

In this crackme available here, it is requested to patch the nag screen and write a keygen.

When run, the crackme shows a nag screen and then a form containing 2 fields (probably respectively for the username and the serial) as well as a Check button as follows:

Code overview

The code overview from the start function is as follows:

Code analysis

sub_401031

We can see from the start function that it calls sub_401031. Let's analyze this function.

.text:00401031                               ; int __stdcall sub_401031(HINSTANCE hInstance, int, int, int)
.text:00401031                               sub_401031      proc near
.text:00401031
.text:00401031                               hWnd            = dword ptr -50h
.text:00401031                               Msg             = tagMSG ptr -4Ch
.text:00401031                               var_30          = WNDCLASSEXA ptr -30h
.text:00401031                               hInstance       = dword ptr  8
.text:00401031
.text:00401031 55                                            push    ebp
.text:00401032 8B EC                                         mov     ebp, esp
.text:00401034 83 C4 B0                                      add     esp, 0FFFFFFB0h
.text:00401037 E8 3B 04 00 00                                call    f_patch_memory
.text:0040103C
.text:0040103C                               loc_40103C:
.text:0040103C 6A 10                                         push    10h             ; uType
.text:0040103C                                                                       ; (patch: NOP)
.text:0040103E
.text:0040103E                               loc_40103E:
.text:0040103E 68 35 30 40 00                                push    offset Caption  ;
.text:0040103E                                                                       ; 'Nag'
.text:0040103E                                                                       ; (patch: NOP)
.text:00401043
.text:00401043                               loc_401043:
.text:00401043 68 21 30 40 00                                push    offset Text     ;
.text:00401043                                                                       ; 'Patch Me if you can'
.text:00401043                                                                       ; (patch: NOP)
.text:00401048
.text:00401048                               loc_401048:
.text:00401048 6A 00                                         push    0               ;
.text:00401048                                                                       ; hWnd
.text:00401048                                                                       ; (patch: NOP)
.text:0040104A
.text:0040104A                               loc_40104A:
.text:0040104A E8 33 06 00 00                                call    MessageBoxA     ;
.text:0040104A                                                                       ; (patch: NOP)
.text:0040104F C7 45 D0 30 00 00 00                          mov     [ebp+var_30.cbSize], 30h
.text:00401056 C7 45 D4 03 00 00 00                          mov     [ebp+var_30.style], 3
.text:0040105D C7 45 D8 3E 11 40 00                          mov     [ebp+var_30.lpfnWndProc], offset sub_40113E
.text:00401064 C7 45 DC 00 00 00 00                          mov     [ebp+var_30.cbClsExtra], 0
.text:0040106B C7 45 E0 00 00 00 00                          mov     [ebp+var_30.cbWndExtra], 0
.text:00401072 FF 35 40 30 40 00                             push    dword_403040
.text:00401078 8F 45 E4                                      pop     [ebp+var_30.hInstance]
.text:0040107B 33 C0                                         xor     eax, eax
.text:0040107D B4 F4                                         mov     ah, 0F4h
.text:0040107F C1 E0 08                                      shl     eax, 8
.text:00401082 B4 D9                                         mov     ah, 0D9h
.text:00401084 B0 BC                                         mov     al, 0BCh
.text:00401086 50                                            push    eax             ; color
.text:00401087 E8 56 06 00 00                                call    CreateSolidBrush
.text:0040108C 89 45 F0                                      mov     [ebp+var_30.hbrBackground], eax
.text:0040108F C7 45 F4 00 00 00 00                          mov     [ebp+var_30.lpszMenuName], 0
.text:00401096 C7 45 F8 00 30 40 00                          mov     [ebp+var_30.lpszClassName], offset ClassName ; "Window"
.text:0040109D 68 F4 01 00 00                                push    1F4h            ; lpIconName
.text:004010A2 FF 75 08                                      push    [ebp+hInstance] ; hInstance
.text:004010A5 E8 D2 05 00 00                                call    LoadIconA
.text:004010AA 89 45 E8                                      mov     [ebp+var_30.hIcon], eax
.text:004010AD 89 45 FC                                      mov     [ebp+var_30.hIconSm], eax
.text:004010B0 68 03 7F 00 00                                push    7F03h           ; lpCursorName
.text:004010B5 6A 00                                         push    0               ; hInstance
.text:004010B7 E8 BA 05 00 00                                call    LoadCursorA
.text:004010BC 89 45 EC                                      mov     [ebp+var_30.hCursor], eax
.text:004010BF 8D 45 D0                                      lea     eax, [ebp+var_30]
.text:004010C2 50                                            push    eax             ; WNDCLASSEXA *
.text:004010C3 E8 C6 05 00 00                                call    RegisterClassExA
.text:004010C8 6A 00                                         push    0               ; lpParam
.text:004010CA FF 75 08                                      push    [ebp+hInstance] ; hInstance
.text:004010CD 6A 00                                         push    0               ; hMenu
.text:004010CF 6A 00                                         push    0               ; hWndParent
.text:004010D1 68 96 00 00 00                                push    96h             ; nHeight
.text:004010D6 68 2C 01 00 00                                push    12Ch            ; nWidth
.text:004010DB 68 00 00 00 80                                push    80000000h       ; Y
.text:004010E0 68 00 00 00 80                                push    80000000h       ; X
.text:004010E5 68 00 00 CF 00                                push    0CF0000h        ; dwStyle
.text:004010EA 68 07 30 40 00                                push    offset WindowName ; "Crackme"
.text:004010EF 68 00 30 40 00                                push    offset ClassName ; "Window"
.text:004010F4 6A 00                                         push    0               ; dwExStyle
.text:004010F6 E8 57 05 00 00                                call    CreateWindowExA
.text:004010FB 89 45 B0                                      mov     [ebp+hWnd], eax
.text:004010FE 6A 01                                         push    1               ; nCmdShow
.text:00401100 FF 75 B0                                      push    [ebp+hWnd]      ; hWnd
.text:00401103 E8 8C 05 00 00                                call    ShowWindow
.text:00401108 FF 75 B0                                      push    [ebp+hWnd]      ; hWnd
.text:0040110B E8 90 05 00 00                                call    UpdateWindow
.text:00401110
.text:00401110                               loc_401110:
.text:00401110 6A 00                                         push    0               ; wMsgFilterMax
.text:00401112 6A 00                                         push    0               ; wMsgFilterMin
.text:00401114 6A 00                                         push    0               ; hWnd
.text:00401116 8D 45 B4                                      lea     eax, [ebp+Msg]
.text:00401119 50                                            push    eax             ; lpMsg
.text:0040111A E8 45 05 00 00                                call    GetMessageA
.text:0040111F 0B C0                                         or      eax, eax
.text:00401121 74 14                                         jz      short loc_401137
.text:00401123 8D 45 B4                                      lea     eax, [ebp+Msg]
.text:00401126 50                                            push    eax             ; lpMsg
.text:00401127 E8 6E 05 00 00                                call    TranslateMessage
.text:0040112C 8D 45 B4                                      lea     eax, [ebp+Msg]
.text:0040112F 50                                            push    eax             ; lpMsg
.text:00401130 E8 29 05 00 00                                call    DispatchMessageA
.text:00401135 EB D9                                         jmp     short loc_401110
.text:00401137                               ; ---------------------------------------------------------------------------
.text:00401137
.text:00401137                               loc_401137:
.text:00401137 8B 45 BC                                      mov     eax, [ebp+Msg.wParam]
.text:0040113A C9                                            leave
.text:0040113B C2 10 00                                      retn    10h
.text:0040113B                               sub_401031      endp

As we can see, at offset 0x401037, the function starts by calling sub_401477 (renamed f_patch_memory), which we will analyze later. Then, starting from offset 0x40103C, it pushes the 4 arguments for the MessageBoxA function that is called at offset 0x40104A. This is actually the nag screen. At least, we can see that the sub_40113E function is used as the lpfnWndProc parameter (offset 0x40105D) for the CreateSolidBrush function called at offset 0x401087.

Now, let's analyze the mysterious function sub_401477.

sub_401477

Here is the code of the function:

.text:00401477 f_patch_memory  proc near
.text:00401477                 mov     GetCurrentProcessId_0, offset GetCurrentProcessId
.text:00401481                 sub     GetCurrentProcessId_0, 4
.text:00401488                 add     OpenProcess_0, 4
.text:0040148F                 add     OpenProcess_0, offset OpenProcess
.text:00401499                 add     GetCurrentProcessId_0, 4
.text:004014A0                 mov     WriteProcessMemory_0, offset loc_4016D0
.text:004014AA                 inc     WriteProcessMemory_0
.text:004014B0                 call    GetCurrentProcessId_0
.text:004014B6                 sub     OpenProcess_0, 4
.text:004014BD                 push    eax             ; dwProcessId: handle to process
.text:004014BE                 push    0               ; bInheritHandle
.text:004014C0                 push    1F0FFFh         ; dwDesiredAccess: PROCESS_ALL_ACCESS
.text:004014C5                 call    OpenProcess_0
.text:004014CB                 mov     hObject, eax    ; hObject = handle to process
.text:004014D0                 dec     WriteProcessMemory_0
.text:004014D6                 push    dword_403CA0    ; *lpNumberOfBytesWritten
.text:004014DC                 push    2               ; nSize
.text:004014DE                 push    offset push_10h ; lpBuffer
.text:004014E3                 push    offset loc_40103C ; lpBaseAddress
.text:004014E8                 push    hObject         ; hProcess
.text:004014EE                 call    WriteProcessMemory_0
.text:004014F4                 mov     dword_403CA0, 0
.text:004014FE                 push    dword_403CA0    ; *lpNumberOfBytesWritten
.text:00401504                 push    6               ; nSize
.text:00401506                 push    offset nag      ; lpBuffer
.text:0040150B                 push    offset loc_40103E ; lpBaseAddress
.text:00401510                 push    hObject         ; hProcess
.text:00401516                 call    WriteProcessMemory_0
.text:0040151C                 mov     dword_403CA0, 0
.text:00401526                 push    dword_403CA0    ; *lpNumberOfBytesWritten
.text:0040152C                 push    6               ; nSize
.text:0040152E                 push    offset patch_me_if_you_can ; lpBuffer
.text:00401533                 push    offset loc_401043 ; lpBaseAddress
.text:00401538                 push    hObject         ; hProcess
.text:0040153E                 call    WriteProcessMemory_0
.text:00401544                 mov     dword_403CA0, 0
.text:0040154E                 push    dword_403CA0    ; *lpNumberOfBytesWritten
.text:00401554                 push    2               ; nSize
.text:00401556                 push    offset push_0   ; lpBuffer
.text:0040155B                 push    offset loc_401048 ; lpBaseAddress
.text:00401560                 push    hObject         ; hProcess
.text:00401566                 call    WriteProcessMemory_0
.text:0040156C                 mov     dword_403CA0, 0
.text:00401576                 push    dword_403CA0    ; *lpNumberOfBytesWritten
.text:0040157C                 push    5               ; nSize
.text:0040157E                 push    offset call_sub_4026CF ; lpBuffer
.text:00401583                 push    offset loc_40104A ; lpBaseAddress
.text:00401588                 mov     my_serial_field, offset ReadProcessMemory
.text:00401592                 push    hObject         ; hProcess
.text:00401598                 call    WriteProcessMemory_0
.text:0040159E                 sub     my_serial_field, 2
.text:004015A5                 push    hObject         ; hObject
.text:004015AB                 call    CloseHandle
.text:004015B0                 xor     eax, eax
.text:004015B2                 retn
.text:004015B2 f_patch_memory  endp

We immediately notice calls to GetCurrentProcessId, OpenProcess and WriteProcessMemory, which make us think of memory injection, which is likely to trigger alerts from the antivirus. Now, an inspection of the lpBuffer and lpBaseAddress shows that this function is simply overwriting the content of the nag screen (arguments pushed to the stack and call to the MessageBoxA function). This trick ensures that even if the nag screen has been removed by NOP'ing the instructions, it will overwrite it in memory. Clever, but not enough to be enough for us :) Let's keep that in mind when we will patch the program.

sub_40113E

Code overview

The function layout is as follows:

Check username length

Starting from offset 0x401264, the code checks that the username is 5 to 7 characters long:

.text:00401264                 mov     lstrlenA_0, offset lstrlenA
.text:0040126E                 dec     lstrlenA_0
.text:00401274                 mov     GetWindowTextLengthA_0, offset GetWindowTextLengthA
.text:0040127E                 inc     GetWindowTextLengthA_0
.text:00401284                 push    my_user_field
.text:0040128A                 dec     GetWindowTextLengthA_0
.text:00401290                 call    GetWindowTextLengthA_0
.text:00401296                 cmp     eax, 5          ; len(my_username) >= 5
.text:00401299                 jl      exit_func
.text:0040129F                 cmp     eax, 8          ; len(my_username) < 8
.text:004012A2                 jge     exit_func

Serial 2nd part

Starting from offset 0x4012A8, the code is building the 2nd part of the serial, based on the username:

.text:004012A8                 mov     ecx, eax        ; ecx = len(my_username)
.text:004012AA                 mov     len_my_username, ecx
.text:004012B0                 xor     ebx, ebx        ; ebx = 0
.text:004012B2                 lea     esi, my_name
.text:004012B8                 lea     edi, expected_serial_2
.text:004012BE
.text:004012BE loc_4012BE:
.text:004012BE                 mov     al, [ecx+esi-1] ; my_username[i]
.text:004012BE                                         ; with i starting from the end of string
.text:004012C2                 cmp     cl, 4
.text:004012C5                 jz      short loc_4012DA
.text:004012C7                 cmp     al, 4Dh         ; 'M'
.text:004012C9                 jl      short loc_4012CF
.text:004012CB                 sub     al, 11h
.text:004012CD                 jmp     short loc_4012D1
.text:004012CF ; ---------------------------------------------------------------------------
.text:004012CF
.text:004012CF loc_4012CF:
.text:004012CF                 add     al, 15h
.text:004012D1
.text:004012D1 loc_4012D1:
.text:004012D1                 xor     al, cl          ; AL ^= cl
.text:004012D3                 xor     al, 2           ; AL ^= 2
.text:004012D5                 mov     [ebx+edi], al
.text:004012D8                 jmp     short loc_4012DE
.text:004012DA ; ---------------------------------------------------------------------------
.text:004012DA
.text:004012DA loc_4012DA:
.text:004012DA                 mov     byte ptr [ebx+edi], 2Dh
.text:004012DE
.text:004012DE loc_4012DE:
.text:004012DE                 inc     ebx
.text:004012DF                 loopne  loc_4012BE      ; ecx -= 1

The algorithm is simple. It saves the username length in ecx at offset 0x4012A8 and decrements it each time the loop ends (instruction loopne at offset 0x4012DF). It reads the username in reverse order and performs the following operations for each character:

  • if ecx is equal to 4, the current character for the expected 2nd part of the serial will be '-' (0x2D in hex).
  • Else (ecx != 4):
    • If the current character of the username is lower than M (0x4D in hex), it adds 0x15 to the character
    • Else, it substracts 0x11 from the character
    • It then XORs the result with CL (remember how ECX evolves in the loop)
    • It XORs the result with 2

This results in an array saved at memory location 0x403254 which is the second part of the expected serial number.

Serial 1st part

Starting from offset 0x4012E1, the first part of the expected serial is computed, using exactly the same technique as for the 2nd part, but this time using the result of the previous computation (second part of the serial) instead of the username.

.text:004012E1                 lea     esi, expected_serial_2 ; 0x403254 - this time, the algo will process
                                                              ; the expected serial part 2 instead of username
.text:004012E7                 lea     edi, expected_serial_1
.text:004012ED                 mov     ecx, len_my_username
.text:004012F3                 xor     ebx, ebx
.text:004012F5                 mov     dword_403C5C, offset lstrcatA
.text:004012FF                 sub     dword_403C5C, 2
.text:00401306                 mov     smiley, offset asc_40207F ; ":-)"
.text:00401310                 mov     Correct_Serial, offset aCorrectSerial ; "Correct Serial"
.text:0040131A                 sub     smiley, 5
.text:00401321                 sub     Correct_Serial, 5
.text:00401328
.text:00401328 loc_401328:
.text:00401328                 cmp     cl, 4
.text:0040132B                 jz      short loc_401344
.text:0040132D                 mov     al, [ecx+esi-1]
.text:00401331                 cmp     al, 4Dh         ; 'M'
.text:00401333                 jl      short loc_401339
.text:00401335                 sub     al, 11h
.text:00401337                 jmp     short loc_40133B
.text:00401339 ; ---------------------------------------------------------------------------
.text:00401339
.text:00401339 loc_401339:
.text:00401339                 add     al, 15h
.text:0040133B
.text:0040133B loc_40133B:
.text:0040133B                 xor     al, cl
.text:0040133D                 xor     al, 2
.text:0040133F                 mov     [ebx+edi], al
.text:00401342                 jmp     short loc_401348
.text:00401344 ; ---------------------------------------------------------------------------
.text:00401344
.text:00401344 loc_401344:
.text:00401344                 mov     byte ptr [ebx+edi], 2Dh
.text:00401348
.text:00401348 loc_401348:
.text:00401348                 inc     ebx
.text:00401349                 loopne  loc_401328      ; ecx -= 1

Final checks

There are 2 final checks:

  • the code calls lstrlen at offset 0x40138E to get the size of my_serial, pushed to the stack at offset 0x401389. It then compares the value against the expected serial length at offset 0x401394 and exits if the values don't match.
  • it checks that the provided serial matches the expected one (performed by sub_401450, renamed f_check_serial in the below extract.
.text:0040134B                 mov     MessageBoxA_0, offset MessageBoxA
.text:00401355                 sub     MessageBoxA_0, 7
.text:0040135C                 push    offset expected_serial_2 ; _DWORD
.text:00401361                 push    offset expected_serial_1 ; _DWORD
.text:00401366                 add     dword_403C5C, 2
.text:0040136D                 call    dword_403C5C
.text:00401373                 push    offset expected_serial_1 ; _DWORD
.text:00401378                 inc     lstrlenA_0
.text:0040137E                 call    lstrlenA_0
.text:00401384                 mov     expected_serial_length, eax
.text:00401389                 push    offset my_serial ; _DWORD
.text:0040138E                 call    lstrlenA_0                  ; eax = len(my_serial)
.text:00401394                 cmp     eax, expected_serial_length ; \ if len(my_serial) != len(expected_serial)
.text:0040139A                 jnz     short exit_func             ; / exit
.text:0040139C                 push    len_my_username
.text:004013A2                 push    offset expected_serial_1    ; \
.text:004013A7                 push    offset my_serial            ; | check that my_serial = expected_serial
.text:004013AC                 call    f_check_serial              ; / sub_401450
.text:004013B1                 cmp     eax, 1                      ; \
.text:004013B4                 jz      short exit_func             ; / function should return 0 to win

If both tests pass, the success popup is displayed:

.text:00401306                 mov     smiley, offset asc_40207F             ; ":-)"
.text:00401310                 mov     Correct_Serial, offset aCorrectSerial ; "Correct Serial"
               ; ... [SNIP] ...
.text:004013B6                 add     Correct_Serial, 5
.text:004013BD                 add     smiley, 5
.text:004013C4                 push    40h             ; _DWORD
.text:004013C6                 push    smiley          ; _DWORD
.text:004013CC                 push    Correct_Serial  ; _DWORD
.text:004013D2                 push    [ebp+hWndParent] ; _DWORD
.text:004013D5                 add     MessageBoxA_0, 7
.text:004013DC                 call    MessageBoxA_0

Integrity check

At the end of the function, there is a call to sub_401408 (renamed f_integrity_check).

.text:004013E2 exit_func:
.text:004013E2
.text:004013E2                 call    f_integrity_ckeck ; should be NOP'ed
.text:004013E7                 leave
.text:004013E8                 retn    10h

Without going in the details, sub_401408 calls another function, sub_4015B3, at offset 0401449, that performs an integrity check. At the end of the function, we can see that the code is exited if the byte at memory location 0x403C94 is not equal to 0xC0:

Let's also keep that in mind when we will patch the code.

Solution

Patching

Now that we have completely analyzed the code and have identified the parts that need to be patched, we can NOP the following instructions:

.text:00401037 E8 3B 04 00 00                                call    f_patch_memory  ; patch with NOP (0x90)
.text:0040103C 6A 10                                         push    10h             ; patch with NOP (0x90)
.text:0040103E 68 35 30 40 00                                push    offset Caption  ; patch with NOP (0x90)
.text:00401043 68 21 30 40 00                                push    offset Text     ; patch with NOP (0x90)
.text:00401048 6A 00                                         push    0               ; patch with NOP (0x90)
.text:0040104A E8 33 06 00 00                                call    MessageBoxA     ; patch with NOP (0x90)
                                  ; ... [SNIP] ...
.text:00401449 E8 65 01 00 00                                call    sub_4015B3      ; patch with NOP (0x90)

You can use hiew to patch the code.

Keygen and solution

Below is the keygen I wrote:

#!/usr/bin/env python
import sys

def make_serial_part(s):
    serial2 = []
    len_user = len(s)
    for c, l in enumerate(s[::-1]):
        if int(len_user - c) == 4:
            serial2.append(0x2D)
        else:
            if ord(l) < 0x4D:
                l_ = ord(l) + 0x15
            else:
                l_ = ord(l) - 0x11
            serial2.append(l_ ^ int(len_user-c) ^ 2)

    return ''.join([chr(i) for i in serial2])

def make_serial(my_username):
    serial_part2 = make_serial_part(my_username)
    return "%s%s" % (make_serial_part(serial_part2), serial_part2)

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print "Usage: %s <username>" % sys.argv[0]
        sys.exit(1)

    my_username = sys.argv[1]
    if len(my_username) < 5 or len(my_username) >= 8:
        print "Username should be 5 to 7 characters long"
        sys.exit(2)
    
    print make_serial(my_username)

Let's check:

$ ./my_keygen.py aldeid
FM-CNEW_-R[S

Comments

Keywords: assembly x86 reverse-engineering crackme DaXXoR_101 crackmes.de