Solution-andrewl-us-Crackme-1

From aldeid
Jump to navigation Jump to search

Introduction

Description

This crackme available here is relatively easy and is an excellent crackme for beginners.

When run, the crackme shows a dialog box with 2 fields: Name and Serial. If the serial is incorrect, a popup with the message "NOPE" is displayed.

Code overview

As shown below, the cross references from the start function show a call to the DialogFunc function, which then checks for the serial.

Let's analyze DialogFunc.

Code analysis

DialogFunc

Branches

The interesting part of this function is shown below:

.text:004011B2                 push    20h             ; cchMax
.text:004011B4                 lea     eax, [ebp+my_serial]
.text:004011B7                 push    eax             ; lpString
.text:004011B8                 push    69h             ; nIDDlgItem
.text:004011BA                 mov     ecx, [ebp+hWnd]
.text:004011BD                 push    ecx             ; hDlg
.text:004011BE                 call    ds:GetDlgItemTextA
.text:004011C4                 lea     edx, [ebp+my_serial]
.text:004011C7                 push    edx
.text:004011C8                 call    f_check
.text:004011CD                 add     esp, 4
.text:004011D0                 test    eax, eax
.text:004011D2                 jz      short loc_4011DD
.text:004011D4                 mov     [ebp+lpText], offset unk_403000
.text:004011DB                 jmp     short loc_4011E4
.text:004011DD ; ---------------------------------------------------------------------------
.text:004011DD
.text:004011DD loc_4011DD:
.text:004011DD                 mov     [ebp+lpText], offset unk_403008
.text:004011E4
.text:004011E4 loc_4011E4:
.text:004011E4                 push    7                 ; string length
.text:004011E6                 mov     eax, [ebp+lpText]
.text:004011E9                 push    eax               ; encrypted string
.text:004011EA                 call    sub_401000        ; decryption rountine
.text:004011EF                 add     esp, 8
.text:004011F2                 push    0               ; uType
.text:004011F4                 mov     ecx, [ebp+lpText]
.text:004011F7                 push    ecx             ; lpCaption
.text:004011F8                 mov     edx, [ebp+lpText]
.text:004011FB                 push    edx             ; lpText
.text:004011FC                 push    0               ; hWnd
.text:004011FE                 call    ds:MessageBoxA
.text:00401204                 push    7
.text:00401206                 mov     eax, [ebp+lpText]
.text:00401209                 push    eax
.text:0040120A                 call    sub_401000
.text:0040120F                 add     esp, 8
.text:00401212                 jmp     short loc_40121B

Starting from offset 0x4011BE, the program gets the serial entered in the Serial field and saves it to edx. It is used as argument to the f_check function called at offset 0x4011C8. The return value is tested at offset 0x4011D0. If the function returns 0, the program jumps to 0x4011DD. If on the contrary, the value 1 is returned, the program continues to 0x4011D4. Depending on the branch, a different value is saved to lpText, the message that will be displayed at offset 0x4011FE via a call to MessageBoxA.

Decoding messages

Now, we see that both messages are crypted:

.data:00403000 unk_403000      db  28h ; (
.data:00403001                 db  36h ; 6
.data:00403002                 db  31h ; 1
.data:00403003                 db  31h ; 1
.data:00403004                 db  3Ah ; :
.data:00403005                 db  2Dh ; -
.data:00403006                 db  7Fh ; 
.data:00403007                 db    0
.data:00403008 unk_403008      db  31h ; 1
.data:00403009                 db  30h ; 0
.data:0040300A                 db  2Fh ; /
.data:0040300B                 db  3Ah ; :
.data:0040300C                 db  7Fh ; 
.data:0040300D                 db  7Fh ; 
.data:0040300E                 db  7Fh ; 
.data:0040300F                 db    0

As lpText is pushed to the stack just before a call to sub_401000 at offset 0x4011EA, we can guess that sub_401000 is the decryption routine and we rename the function accordingly.

The decryption routine is easy to understand. It simply XORs bytes of the encrypted string sent as parameter to the function, with 0x7F:

.text:00401000                               decrypt_string  proc near
.text:00401000
.text:00401000                               encrypted_string= dword ptr  8
.text:00401000                               len_string      = byte ptr  0Ch
.text:00401000
.text:00401000 55                                            push    ebp
.text:00401001 8B EC                                         mov     ebp, esp
.text:00401003
.text:00401003                               loc_401003:
.text:00401003 0F BE 45 0C                                   movsx   eax, [ebp+len_string]
.text:00401007 8A 4D 0C                                      mov     cl, [ebp+len_string]
.text:0040100A 80 E9 01                                      sub     cl, 1
.text:0040100D 88 4D 0C                                      mov     [ebp+len_string], cl
.text:00401010 85 C0                                         test    eax, eax
.text:00401012 74 19                                         jz      short loc_40102D
.text:00401014 8B 55 08                                      mov     edx, [ebp+encrypted_string]
.text:00401017 0F BE 02                                      movsx   eax, byte ptr [edx]
.text:0040101A 83 F0 7F                                      xor     eax, 7Fh        ; encrypted_string[i] ^ 0x7F
.text:0040101D 8B 4D 08                                      mov     ecx, [ebp+encrypted_string]
.text:00401020 88 01                                         mov     [ecx], al
.text:00401022 8B 55 08                                      mov     edx, [ebp+encrypted_string]
.text:00401025 83 C2 01                                      add     edx, 1
.text:00401028 89 55 08                                      mov     [ebp+encrypted_string], edx
.text:0040102B EB D6                                         jmp     short loc_401003
.text:0040102D                               ; ---------------------------------------------------------------------------
.text:0040102D
.text:0040102D                               loc_40102D:
.text:0040102D 5D                                            pop     ebp
.text:0040102E C3                                            retn
.text:0040102E                               decrypt_string  endp

We can write a python script and execute it in IDA-Pro to decrypt the strings:

def decrypt_string(loc):
    i = 0
    while True:
        b = Byte(loc+i)
        if b == 0x0:
            break
        PatchByte(loc+i, b ^ 0x7F)
        i += 1

# Decrypt WINNER
decrypt(0x403000)

# Decrypt NOPE
decrypt(0x403008)

Once run (Alt+F7), the script decrypts the strings:

Now, we know that the f_check function at offset 0x4011C8 should return 1 to have a valid serial.

f_check()

Once again, we have to deal with encryption. As shown below, the program is building an hard-coded array starting from offset 0x4010A6.

.text:004010A0                               f_check         proc near
.text:004010A0
.text:004010A0                               hardcoded_8272386= byte ptr -8
.text:004010A0                               var_7           = byte ptr -7
.text:004010A0                               var_6           = byte ptr -6
.text:004010A0                               var_5           = byte ptr -5
.text:004010A0                               var_4           = byte ptr -4
.text:004010A0                               var_3           = byte ptr -3
.text:004010A0                               var_2           = byte ptr -2
.text:004010A0                               var_1           = byte ptr -1
.text:004010A0                               my_serial       = dword ptr  8
.text:004010A0
.text:004010A0 55                                            push    ebp
.text:004010A1 8B EC                                         mov     ebp, esp
.text:004010A3 83 EC 08                                      sub     esp, 8
.text:004010A6 C6 45 F8 47                                   mov     [ebp+hardcoded_8272386], 47h
.text:004010AA C6 45 F9 4D                                   mov     [ebp+var_7], 4Dh
.text:004010AE C6 45 FA 48                                   mov     [ebp+var_6], 48h
.text:004010B2 C6 45 FB 4D                                   mov     [ebp+var_5], 4Dh
.text:004010B6 C6 45 FC 4C                                   mov     [ebp+var_4], 4Ch
.text:004010BA C6 45 FD 47                                   mov     [ebp+var_3], 47h
.text:004010BE C6 45 FE 49                                   mov     [ebp+var_2], 49h
.text:004010C2 C6 45 FF 00                                   mov     [ebp+var_1], 0
.text:004010C6 6A 07                                         push    7                    ; len(encrypted_string)
.text:004010C8 8D 45 F8                                      lea     eax, [ebp+hardcoded_8272386]
.text:004010CB 50                                            push    eax
.text:004010CC E8 2F FF FF FF                                call    decrypt_string       ; decrypted content: '8272386'
.text:004010D1 83 C4 08                                      add     esp, 8
.text:004010D4 8B 4D 08                                      mov     ecx, [ebp+my_serial] ; ecx = my_serial
.text:004010D7 51                                            push    ecx
.text:004010D8 8D 55 F8                                      lea     edx, [ebp+hardcoded_8272386]
.text:004010DB 52                                            push    edx
.text:004010DC E8 4F FF FF FF                                call    f_make_serial
.text:004010E1 83 C4 08                                      add     esp, 8
.text:004010E4 85 C0                                         test    eax, eax        ; \ f_make_serial() should return 1
.text:004010E6 74 07                                         jz      short FAIL      ; / to win
.text:004010E8 B8 01 00 00 00                                mov     eax, 1
.text:004010ED EB 10                                         jmp     short loc_4010FF
.text:004010EF                               ; ---------------------------------------------------------------------------
.text:004010EF
.text:004010EF                               FAIL:
.text:004010EF 6A 07                                         push    7
.text:004010F1 8D 45 F8                                      lea     eax, [ebp+hardcoded_8272386]
.text:004010F4 50                                            push    eax
.text:004010F5 E8 06 FF FF FF                                call    decrypt_string
.text:004010FA 83 C4 08                                      add     esp, 8
.text:004010FD 33 C0                                         xor     eax, eax
.text:004010FF
.text:004010FF                               loc_4010FF:
.text:004010FF 8B E5                                         mov     esp, ebp
.text:00401101 5D                                            pop     ebp
.text:00401102 C3                                            retn
.text:00401102                               f_check         endp

Here again, we can see that the decryption routine is the same as previously (bytes XOR'ed with 0xF7). Let's use the following python script to decode the hard-coded string and display it as a comment at location 0x4010CC:

# Decrypt var_8
loc = 0x4010A6
loc_comm = 0x4010CC
MakeComm(loc_comm, ''.join([chr(Byte(loc+(i+1)*4-1) ^ 0x7F) for i in range(7)]))

We can see that the hard-coded string ('8272386') and the serial entered by the user are provided as arguments to the f_make_serial function at offset 0x4010DC which should return 1 to win. Let's analyze this function.

f_make_serial()

This function is a bit longer but very easy to understand. We immediately notice a loop that will end when the trailing null character of the hard-coded string will be met (offset 0x40103A). Inside the loop, here is what happens:

  • bytes of the serial are read with an index and the current character is saved to edx at offset 0x40104B
  • bytes of the hard-coded values are read with an index and the current character is saved to ecx at offset 0x401051
  • ecx and edx are compared at offset 0x401066. If they are the same, the loop continues with the next bytes but otherwise, the loop exits prematurely (fail)
  • Both the serial and the hard-coded value are shifted 1 byte so that the first byte correspond to the current position of the index. At the end of the loop, they will be completely overwritten with the trailing null character
.text:00401030                               f_make_serial   proc near
.text:00401030
.text:00401030                               var_4           = dword ptr -4
.text:00401030                               hardcoded_8272386= dword ptr  8
.text:00401030                               my_serial       = dword ptr  0Ch
.text:00401030
.text:00401030 55                                            push    ebp
.text:00401031 8B EC                                         mov     ebp, esp
.text:00401033 51                                            push    ecx
.text:00401034
.text:00401034                               loop:
.text:00401034 8B 45 08                                      mov     eax, [ebp+hardcoded_8272386]
.text:00401037 0F BE 08                                      movsx   ecx, byte ptr [eax] ; ecx = hardcoded_8272386[i]
.text:0040103A 85 C9                                         test    ecx, ecx        ; loop 7 times until all chars are read
.text:0040103C 74 2E                                         jz      short exit_loop
.text:0040103E 8B 55 0C                                      mov     edx, [ebp+my_serial]
.text:00401041 0F BE 02                                      movsx   eax, byte ptr [edx] ; eax = my_serial[i]
.text:00401044 85 C0                                         test    eax, eax
.text:00401046 74 24                                         jz      short exit_loop
.text:00401048 8B 4D 0C                                      mov     ecx, [ebp+my_serial]
.text:0040104B 0F BE 11                                      movsx   edx, byte ptr [ecx] ; edx = my_serial[i]
.text:0040104E 8B 45 08                                      mov     eax, [ebp+hardcoded_8272386]
.text:00401051 0F BE 08                                      movsx   ecx, byte ptr [eax] ; ecx = hardcoded_8272386[i]
.text:00401054 8B 45 0C                                      mov     eax, [ebp+my_serial] ; eax = my_serial
.text:00401057 83 C0 01                                      add     eax, 1
.text:0040105A 89 45 0C                                      mov     [ebp+my_serial], eax ; my_serial = my_serial[i+1]
.text:0040105D 8B 45 08                                      mov     eax, [ebp+hardcoded_8272386] ; eax = hardcoded_8272386
.text:00401060 83 C0 01                                      add     eax, 1
.text:00401063 89 45 08                                      mov     [ebp+hardcoded_8272386], eax ; hardcoded_8272386 = hardcoded_8272386[i+1]
.text:00401066 3B CA                                         cmp     ecx, edx        ; \ if hardcoded_8272386[i] != my_serial[i]
.text:00401068 75 02                                         jnz     short exit_loop ; / exit_loop (FAIL)
.text:0040106A EB C8                                         jmp     short loop
.text:0040106C                               ; ---------------------------------------------------------------------------
.text:0040106C
.text:0040106C                               exit_loop:
.text:0040106C 8B 4D 08                                      mov     ecx, [ebp+hardcoded_8272386]
.text:0040106F 0F BE 11                                      movsx   edx, byte ptr [ecx]
.text:00401072 85 D2                                         test    edx, edx        ; hardcoded_8272386 = 0?
.text:00401074 75 13                                         jnz     short FAIL
.text:00401076 8B 45 0C                                      mov     eax, [ebp+my_serial]
.text:00401079 0F BE 08                                      movsx   ecx, byte ptr [eax]
.text:0040107C 85 C9                                         test    ecx, ecx
.text:0040107E 75 09                                         jnz     short FAIL
.text:00401080 C7 45 FC 01 00 00 00                          mov     [ebp+var_4], 1
.text:00401087 EB 07                                         jmp     short loc_401090
.text:00401089                               ; ---------------------------------------------------------------------------
.text:00401089
.text:00401089                               FAIL:
.text:00401089 C7 45 FC 00 00 00 00                          mov     [ebp+var_4], 0
.text:00401090
.text:00401090                               loc_401090:
.text:00401090 8B 45 FC                                      mov     eax, [ebp+var_4]
.text:00401093 8B E5                                         mov     esp, ebp
.text:00401095 5D                                            pop     ebp
.text:00401096 C3                                            retn
.text:00401096                               f_make_serial   endp

Starting from offset 0x40106C, the program checks that the initial hardcoded value is overwritten with the null character (which should be the case if the loop hasn't exited prematurely) and the same test is performed against the serial, starting from offset 0x401076.

Conclusion and solution

As a conclusion, the program is only checking that the serial is 8272386 and doesn't care of the username. Let's check:

Comments

Keywords: assembly x86 reverse-engineering crackme andrewl.us crackmes.de