Solution-Thunder-cls-Sticky-Crackme

From aldeid
Jump to navigation Jump to search

Description

Download

Very funny crackme (especially the way to validate the serial) where you will have to bypass several anti-debugging tricks.

Download link: http://crackmes.de/users/thunder_cls/sticky_crackme/download

  • Type: PE32 executable (GUI) Intel 80386, for MS Windows
  • MD5: 46609f011a7ccda9e382e8dcc22ea6c1
  • SHA1: 0cdfe3695d802d6e4d7a5b782f969ca18ddf6eca

Running the crackme

When you run the program without argument, nothing happens. Providing an argument would help, provided you know what to write :)

Analysis

Basic static analysis

Analyzing this crackme requires some organization because they are many functions. A good start is to proceed with a basic static analysis to find information that we could then confirm with a detailed static analysis and a dynamic approach. For this purpose, I recommend the strings command as well as the pescanner.py script.

Here are some interesting information:

Information Description
The program is likely to be developped in Borland C++

Presence of following strings:

Borland C++ - Copyright 2002 Borland Corporation
Software\Borland\Delphi\Locales
SOFTWARE\Borland\Delphi\RTL
Software\Borland\Locales
The program is likely to use notepad.exe

Presence of following strings:

Notepad
notepad.exe
Software\Microsoft\Notepad
Interesting banners

Seen in the strings:

 Thunder Sticky Crackme v1.0   
                                               
  Autor...: Thunder    Protection....: Funny   
                                               
-=[ Cuban Cracking Project 2010 ]=-  
                                               
Serial: 
 Thunder Sticky Crackme v1.0   
                                               
  Autor...: Thunder    Protection....: Funny   
                                               
        HEY!!!...YOU FOUND ME DUDE ;)          
          Here's some info for you             
                                               
  Release info..:                              
   - Coded in C++ (BC++ 6)                     
   - Some lame & home-made antidbg (threads)   
   - Just write on notepad your "serial"       
   - Tip1: 6C656E6768742873657269616C293D3D    
           3078354500616E20                    
   - Tip2: 7469746C653D3D757365722B6461792D    
           6D6F6E74682D28796561722B31352900    
   - Tip3: 6E6F206D6F72652C2069742069732065    
           617379206D616E00                    
                                               
   - Make a working keygen and tut             
                                               
-=[ Cuban Cracking Project 2010 ]=-  
Anti-debugging tricks?
debugger
disassembler
decompiler
w32dasm
pe explorer
ollydbg
olly...
Possible argument?
-noSound
Interesting imports

At this stage, we have gathered several information that could enable to assess the following hypothesis:

  • the program is probably expecting an argument (-noSound)?
  • the combination of strings related to "notepad" and the IAT WinExec could be used to open notepad automatically
  • The content of a process will be read in memory
  • The crackme has keylogging features (GetAsyncKeyState)
  • The crackme is likely to have anti-debugging capabilities

Also one of the banners provides additional information:

hex Translation Description
6C656E6768742873657269616C293D3D3078354500616E20 lenght(serial)==0x5E The serial should have a length of 94
7469746C653D3D757365722B6461792D6D6F6E74682D28796561722B31352900 title==user+day-month-(year+15) The title (we don't know what title yet) should be the concatenation of the username and datetime info
6E6F206D6F72652C2069742069732065617379206D616E00 no more, it is easy man OK :)

Arguments

Graph overview

Starting our analysis with the WinMain function, here is the graph overview:

As you have noticed, there are 3 possible arguments, 1 of which being invalid, as we will detail in the next sections.

noSound

Starting from offset 0x4014A4, the length of the argument provided to the program is computed and saved to EAX. Possible values are 11 and 16. However, a test is performed at offset 0x4014C9 to check whether the argument is -noSound, which has an inconsistent length as compared to previous tests. This argument will never be accepted by the program.

That said, it gives us a valuable information. Is there any sound in the program? Indeed, we won't detail it here, but there is a call to sub_402BF4 at offset 0x4014E1, a function that reads a wav and loops over it.

.text:004014A4                 call    _strlen
.text:004014A9                 pop     ecx
.text:004014AA                 mov     [ebp+len_arg], ax
.text:004014AE                 cmp     [ebp+len_arg], 0Bh ; \ len(arg) = 11?
.text:004014B3                 jz      short loc_4014C0 ;   /
.text:004014B5                 cmp     [ebp+len_arg], 10h ; \ len(arg) = 16?
.text:004014BA                 jnz     loc_4015CD      ;    /
.text:004014C0
.text:004014C0 loc_4014C0:
.text:004014C0                 push    offset s2       ; '-noSound'
.text:004014C5                 lea     eax, [ebp+s1]
.text:004014C8                 push    eax             ; s1
.text:004014C9                 call    _strcmp
.text:004014CE                 add     esp, 8
.text:004014D1                 test    eax, eax
.text:004014D3                 jz      short loc_4014E6
.text:004014D5                 push    1
.text:004014D7                 push    0F6Eh
.text:004014DC                 push    offset aExtendedModuleBl ; "Extended Module: blinded (loop)      \x"...
.text:004014E1                 call    f_play_music

LetUsStick

Starting from offset 0x4014E6, tests are performed against each character of the provided argument to check whether it is -LetUsStick, as detailed below:

.text:004014E6 loc_4014E6:
.text:004014E6                 cmp     [ebp+arg], 2Dh  ;
.text:004014E6                                         ; arg[0] = '-'
.text:004014EA                 jnz     loc_401573
.text:004014F0                 cmp     byte ptr [ebp-47h], 4Ch ; arg[1] = 'L'
.text:004014F4                 jnz     short loc_401573
.text:004014F6                 xor     eax, eax
.text:004014F8                 mov     al, [ebp-46h]   ; arg[2] = 0x4E + 0x14 ^ 0x7 = 'e'
.text:004014FB                 xor     eax, 7
.text:004014FE                 sub     eax, 14h
.text:00401501                 cmp     eax, 4Eh
.text:00401504                 jnz     short loc_401573
.text:00401506                 cmp     byte ptr [ebp-45h], 74h ; arg[3] = 't'
.text:0040150A                 jnz     short loc_401573
.text:0040150C                 xor     eax, eax
.text:0040150E                 mov     al, [ebp-44h]   ; arg[4] = 0x59 - 0x18 ^ 0x14 = 'U'
.text:00401511                 xor     eax, 14h
.text:00401514                 add     eax, 18h
.text:00401517                 cmp     eax, 59h
.text:0040151A                 jnz     short loc_401573
.text:0040151C                 cmp     byte ptr [ebp-43h], 73h ; arg[5] = 's'
.text:00401520                 jnz     short loc_401573
.text:00401522                 xor     eax, eax
.text:00401524                 mov     al, [ebp-42h]   ; arg[6] = 0x28E / 0x3 - 0x87 = 'S'
.text:00401527                 add     eax, 87h
.text:0040152C                 imul    eax, 3
.text:0040152F                 cmp     eax, 28Eh
.text:00401534                 jnz     short loc_401573
.text:00401536                 cmp     byte ptr [ebp-41h], 74h ; arg[7] = 't'
.text:0040153A                 jnz     short loc_401573
.text:0040153C                 xor     eax, eax
.text:0040153E                 mov     al, [ebp-40h]   ; arg[8] = (0x3E ^ 0x7) + 0x30 = 'i'
.text:00401541                 sub     eax, 30h
.text:00401544                 xor     eax, 7
.text:00401547                 cmp     eax, 3Eh
.text:0040154A                 jnz     short loc_401573
.text:0040154C                 cmp     byte ptr [ebp-3Fh], 63h ; arg[9] = 'c'
.text:00401550                 jnz     short loc_401573
.text:00401552                 xor     eax, eax
.text:00401554                 mov     al, [ebp-3Eh]   ; arg[10] = (0xF1ACA - 0xA517A) / 0x1987 + 0x3B = 'k'
.text:00401557                 sub     eax, 3Bh
.text:0040155A                 imul    eax, 1987h
.text:00401560                 add     eax, 0A517Ah
.text:00401565                 cmp     eax, 0F1ACAh
.text:0040156A                 jnz     short loc_401573
.text:0040156C                 call    f_open_notepad  ; Function called with
.text:0040156C                                         ; '-LetUsStick' arg
.text:00401571                 jmp     short loc_4015CB
Note
Notice that some letters need to be reversed, following the inverse logic of the encryption flow.

When this argument is provided, the program calls sub_40168C (renamed f_open_notepad) at offset 0x40156C.

TellMeSomeThing

A similar mechanism is implemented later to test whether the argument is -TellMeSomeThing:

.text:00401573 loc_401573:
.text:00401573                 cmp     [ebp+arg], 2Dh  ; arg[0] = '-'
.text:00401577                 jnz     short loc_4015C4
.text:00401579                 xor     eax, eax
.text:0040157B                 mov     eax, [ebp-47h]  ; arg[1] = 0x6C6CAF9E ^ 0xCACA = 0x6c6c6554 ('Tell')
.text:0040157E                 xor     eax, 0CACAh
.text:00401583                 cmp     eax, 6C6CAF9Eh
.text:00401588                 jnz     short loc_4015C4
.text:0040158A                 xor     eax, eax
.text:0040158C                 mov     eax, [ebp-43h]  ; arg[5] = 0xC0C0 ^ 0x6F53A58D = 0x6f53654d ('MeSo')
.text:0040158F                 xor     eax, 0C0C0h
.text:00401594                 cmp     eax, 6F53A58Dh
.text:00401599                 jnz     short loc_4015C4
.text:0040159B                 xor     eax, eax
.text:0040159D                 mov     eax, [ebp-3Fh]  ; arg[9] = 0xC0CA ^ 0x6854A5A7 = 0x6854656d ('meTh')
.text:004015A0                 xor     eax, 0C0CAh
.text:004015A5                 cmp     eax, 6854A5A7h
.text:004015AA                 jnz     short loc_4015C4
.text:004015AC                 xor     eax, eax
.text:004015AE                 mov     eax, [ebp-3Bh]  ; arg[13] = 0xCBCB ^ 0x67A5A2 = 0x676e69 ('ing')
.text:004015B1                 xor     eax, 0CBCBh
.text:004015B6                 cmp     eax, 67A5A2h
.text:004015BB                 jnz     short loc_4015C4
.text:004015BD                 call    f_notepad_help  ; function called with
.text:004015BD                                         ; '-TellMeSomeThing' arg

When this argument is provided, the program calls sub_401988 (renamed f_notepad_help) at offset 0x4015BD. It displays the help banner detailed in the introduction:

f_open_notepad (sub_40168C)

Notepad banner

As we noticed in the strings, there is a banner loaded to ESI at offset 0x40169E and copied to lparam with a rep movsd at offset 0x4016AE:

Later in the code at offset 0x4017D7, the notepad will be opened with a call to WinExec:

.text:004017D0 loc_4017D0:             ; uCmdShow
.text:004017D0 push    3
.text:004017D2 push    offset CmdLine  ; "notepad.exe"
.text:004017D7 call    WinExec
.text:004017DC call    GetCurrentProcessId
.text:004017E1 push    eax             ; dwProcessId
.text:004017E2 push    0               ; bInheritHandle
.text:004017E4 push    1F0FFFh         ; dwDesiredAccess
.text:004017E9 call    OpenProcess
.text:004017EE mov     [ebp+hProcess], eax
.text:004017F4 push    offset ProcName ; "FindWindowA"
.text:004017F9 push    offset ModuleName ; "user32.dll"
.text:004017FE call    GetModuleHandleA
.text:00401803 push    eax             ; hModule
.text:00401804 call    GetProcAddress
.text:00401809 mov     [ebp+lpBaseAddress], eax
.text:0040180F cmp     [ebp+lpBaseAddress], 0
.text:00401816 jz      short loc_401876

At offset 0x4018EB, the banner will be displayed in the notepad window with a call to SendMessageA with the Msg parameter set to WM_SETTEXT (0xC):

.text:004018D7 lea     edx, [ebp+lParam]
.text:004018DD push    edx             ; lParam
.text:004018DE push    1F1h            ; wParam
.text:004018E3 push    WM_SETTEXT      ; Msg
.text:004018E5 push    notepad_window  ; hWnd
.text:004018EB call    SendMessageA    ; WM_SETTEXT
.text:004018EB                         ; Write banner to notepad

This is where you will have to enter the serial:

Anti-debug trick

There is an anti-debug trick based on calls to GetTickCount at 2 different positions in the code:

.text:004016EF call    GetTickCount
.text:004016F4 mov     [ebp+gettickcount], eax                   ; 1st call to GetTickCount

; .... [SNIP] ...

.text:004018A9                 call    GetTickCount              ; 2nd call to GetTickCount
.text:004018AE                 sub     eax, [ebp+gettickcount]
.text:004018B4                 mov     [ebp+_gettickcount], eax
.text:004018BA                 cmp     notepad_window, 0
.text:004018C1                 jz      loc_401980
.text:004018C7                 cmp     [ebp+_gettickcount], 1F4h ; \ check whether prgm
.text:004018D1                 ja      loc_401978      ;           / being debugged
                                                                 ; [PATCH] NOP
Note
You can patch the code to simplify the debugging of the program, by NOP'ing the instruction at offset 0x4018D1.

Call StartAddress

At the end of this function, StartAddress is called via CreateThread:

.text:00401958 lea     eax, [ebp+ThreadId]
.text:0040195E push    eax             ; lpThreadId
.text:0040195F push    0               ; dwCreationFlags
.text:00401961 push    0               ; lpParameter
.text:00401963 push    offset StartAddress ; lpStartAddress
.text:00401968 push    0               ; dwStackSize
.text:0040196A push    0               ; lpThreadAttributes
.text:0040196C call    CreateThread
.text:00401971 mov     dword_4220CC, eax
.text:00401976 jmp     short loc_401980

StartAddress (sub_401BE0)

Check key length

Starting from offset 0x401CD3, there is a loop that gets the serial entered in the notepad and saves it to src (renamed key):

.text:00401CD3 loc_401CD3:
.text:00401CD3                 lea     eax, [ebp+lParam] ; [BP] Enter serial
.text:00401CD9                 push    eax             ; lParam
.text:00401CDA                 push    252h            ; wParam
.text:00401CDF                 push    WM_GETTEXT      ; Msg
.text:00401CE1                 push    notepad_window  ; hWnd
.text:00401CE7                 call    SendMessageA    ; WM_GETTEXT (get serial)
.text:00401CEC                 call    GetTickCount
.text:00401CF1                 mov     [ebp+gettickcount], eax
.text:00401CF4                 mov     [ebp+counter_k], 1F0h ; k = 496
.text:00401CF4                                         ; (offset in notepad to get serial)
.text:00401CFB                 xor     edx, edx
.text:00401CFD                 mov     [ebp+counter_j], edx ; j = 0
.text:00401D00                 jmp     short loc_401D1B
.text:00401D02 ; ---------------------------------------------------------------------------
.text:00401D02
.text:00401D02 loc_401D02:
.text:00401D02                 mov     ecx, [ebp+counter_k] ;
.text:00401D02                                         ; ecx = k
.text:00401D05                 mov     al, byte ptr [ebp+ecx+lParam]
.text:00401D0C                 mov     edx, [ebp+counter_j]
.text:00401D0F                 mov     serial[edx], al ; src = serial (memory location 0x423470)
.text:00401D15                 inc     [ebp+counter_k]
.text:00401D18                 inc     [ebp+counter_j]
.text:00401D1B
.text:00401D1B loc_401D1B:
.text:00401D1B                 mov     ecx, [ebp+counter_k] ;
.text:00401D1B                                         ; ecx = k
.text:00401D1E                 cmp     byte ptr [ebp+ecx+lParam], 0
.text:00401D26                 jz      short loc_401D2E
.text:00401D28                 cmp     [ebp+counter_j], 5Eh ; while(j<94)
.text:00401D28                                         ; len(serial) = 94
.text:00401D2C                 jle     short loc_401D02
.text:00401D2E
.text:00401D2E loc_401D2E:
.text:00401D2E                 mov     eax, [ebp+counter_j]
.text:00401D31                 mov     serial[eax], 0  ; add trailing null at the end of the serial

Later in the code, there is a check of this key length to ensure it is 94 characters long:

.text:00401D50 loc_401D50:
.text:00401D50 popa
.text:00401D51 xor     [ebp+counter_j], 0FE0h ; j = 0x5E ^ 0xFE0 = 0xfbe
.text:00401D58 sub     [ebp+counter_j], 87h ; j = 0xfbe - 0x87 = 0xf37
.text:00401D5F mov     edx, [ebp+counter_j] ; edx = 0xf37
.text:00401D62 mov     ecx, edx        ; ecx = 0xf37
.text:00401D64 shl     edx, 3          ; edx = 0xf37 << 0x3 = 0x79b8
.text:00401D67 sub     edx, ecx        ; edx = 0x79b8 - 0xf37 = 0x6a81
.text:00401D69 mov     [ebp+counter_j], edx ; j = 0x6a81
.text:00401D6C cmp     [ebp+counter_j], 6A81h ; \
.text:00401D73 jnz     loc_401E24      ;        / ensure that length(serial) = 0x5E

Validate the title of notepad

Two functions are used to validate the title of the notepad window:

  • At offset 0x401D8B, sub_4020D4 (renamed f_notepad_title) is called. This function computes a string (expected notepad title) based on the Windows user name and the system time, encrypts and saves it.
  • At offset 0x401DC6, sub_4023C0 (renamed f_validate_title) is called. This function encrypts the title of the notepad window where the serial is entered, using the same algorithm, and check whether both encrypted strings match.
.text:00401D86                 push    offset Buffer   ; username
.text:00401D8B                 call    f_notepad_title ; encrypt notepad title

; ...[SNIP]...

.text:00401DC6                 call    f_validate_title
.text:00401DCB                 cmp     al, 1           ; f_validate_title
.text:00401DCB                                         ; should return 1
.text:00401DCD                 jnz     short loc_401E24

Check spaces in serial

There is a call to sub_401E3C (renamed f_serial_spaces) at offset 0x401D79. This function checks that the serial contains spaces at given positions, as we will see later.

.text:00401D79 call    f_serial_spaces
.text:00401D7E test    al, al          ; \ f_serial_spaces should
.text:00401D80 jz      loc_401E24      ; / return 1

Check other characters in serial

There are 2 functions to check that the serial characters (non space) are correct:

  • sub_401FDC (renamed f_serial_nonspace) called at offset 0x401DAD
  • sub_40235C (renamed f_validate_serial_chars) called at offset 0x401DBD
.text:00401DAC loc_401DAC:
.text:00401DAC                 popa
.text:00401DAD                 call    f_serial_nonspace
.text:00401DB2                 call    GetTickCount
.text:00401DB7                 sub     eax, [ebp+gettickcount]
.text:00401DBA                 mov     [ebp+_gettickcount], eax
.text:00401DBD                 call    f_validate_serial_chars
.text:00401DC2                 cmp     al, 1           ; f_validate_serial_chars
.text:00401DC2                                         ; should return 1
.text:00401DC4                 jnz     short loc_401E24

Anti-debug tricks

Starting from offset 0x401D2E, there is another anti-debug trick, based on PEB.NtGlobalFlag:

.text:00401D2E loc_401D2E:
.text:00401D2E                 mov     eax, [ebp+counter_j]
.text:00401D31                 mov     serial[eax], 0
.text:00401D38                 pusha
.text:00401D39                 mov     eax, large fs:30h ; eax = PEB
.text:00401D40                 db      3Eh
.text:00401D40                 mov     eax, [eax+68h]  ; eax = PEB.NtGlobalFlag
.text:00401D44                 and     eax, 70h        ;  \ check whether prgm
.text:00401D47                 jz      short loc_401D50 ; / is being debugged
.text:00401D47                                         ; [PATCH] NOP
.text:00401D49                 mov     is_key_valid, 1

There is something interesting here that you should begin to understand. Each time there is an anti-debug trick, the program checks whether it is being debugged. In this case, it will terminate. Otherwise, it will set a flag to 1. This is what I've renamed is_key_valid.

Another anti-debug trick starts at offset 0x401D92. This time, it only checks whether the FLG_HEAP_ENABLE_TAIL_CHECK flag is set.

.text:00401D92                 mov     eax, large fs:30h ; eax = PEB
.text:00401D99                 db      3Eh
.text:00401D99                 mov     eax, [eax+18h]  ; eax = PEB.NtGlobalFlag
.text:00401D9D                 db      3Eh
.text:00401D9D                 mov     eax, [eax+10h]  ; FLG_HEAP_ENABLE_TAIL_CHECK flag set?
.text:00401DA1                 test    eax, eax        ; \ check whether prgm
.text:00401DA3                 jz      short loc_401DAC ; / being debugged
.text:00401DA3                                         ; [PATCH] NOP
.text:00401DA5                 mov     is_key_valid, 1

And to finish the anti-debug tricks seen in this function, there is also a GetTickCount test:

.text:00401CEC                 call    GetTickCount
.text:00401CF1                 mov     [ebp+gettickcount], eax

; ... [SNIP] ...

.text:00401DB2                 call    GetTickCount
.text:00401DB7                 sub     eax, [ebp+gettickcount]
.text:00401DBA                 mov     [ebp+_gettickcount], eax

; ... [SNIP] ...

.text:00401DCF                 cmp     [ebp+_gettickcount], 200h
.text:00401DD6                 ja      short loc_401E24 ; [PATCH] Patch with NOP
Note
I've patched the program with NOP instructions to get rid of these anti-debug tricks.

Notepad title validation

f_notepad_title (sub_4020D4)

This function takes the username (Windows username of logged in user) as argument. The code gathers the system time to build a string that will be concatenated to the username. The resulting string is then copied to dest at offset 0x4022DB.

.text:004020D4 f_notepad_title proc near
.text:004020D4
.text:004020D4 SystemTime      = _SYSTEMTIME ptr -78h
.text:004020D4 counter_i       = dword ptr -68h
.text:004020D4 strlen_title    = dword ptr -64h
.text:004020D4 var_60          = dword ptr -60h
.text:004020D4 gettickcount    = dword ptr -5Ch
.text:004020D4 var_58          = dword ptr -58h
.text:004020D4 var_48          = word ptr -48h
.text:004020D4 index_i         = dword ptr -3Ch
.text:004020D4 var_34          = byte ptr -34h
.text:004020D4 var_30          = byte ptr -30h
.text:004020D4 var_2C          = byte ptr -2Ch
.text:004020D4 var_28          = byte ptr -28h
.text:004020D4 var_24          = byte ptr -24h
.text:004020D4 var_20          = byte ptr -20h
.text:004020D4 var_1C          = byte ptr -1Ch
.text:004020D4 var_18          = byte ptr -18h
.text:004020D4 var_14          = byte ptr -14h
.text:004020D4 var_10          = byte ptr -10h
.text:004020D4 var_C           = byte ptr -0Ch
.text:004020D4 var_8           = byte ptr -8
.text:004020D4 var_4           = byte ptr -4
.text:004020D4 windows_username= dword ptr  8
.text:004020D4
.text:004020D4                 push    ebp
.text:004020D5                 mov     ebp, esp
.text:004020D7                 add     esp, 0FFFFFF88h
.text:004020DA                 mov     eax, offset stru_424E84
.text:004020DF                 call    @__InitExceptBlockLDTC
.text:004020E4                 mov     [ebp+var_48], 8
.text:004020EA                 lea     eax, [ebp+var_4]
.text:004020ED                 call    unknown_libname_34 ; Borland Visual Component Library & Packages
.text:004020F2                 inc     [ebp+index_i]   ; index_i = 1
.text:004020F5                 mov     [ebp+var_48], 14h
.text:004020FB                 lea     edx, [ebp+SystemTime]
.text:004020FE                 push    edx             ; lpSystemTime
.text:004020FF                 call    GetLocalTime    ; get datetime
.text:00402104                 call    GetTickCount
.text:00402109                 mov     [ebp+gettickcount], eax
.text:0040210C                 mov     [ebp+var_48], 20h
.text:00402112                 lea     eax, [ebp+var_14]
.text:00402115                 call    unknown_libname_34 ; Borland Visual Component Library & Packages
.text:0040211A                 push    eax
.text:0040211B                 inc     [ebp+index_i]   ; index_i = 2
.text:0040211E                 mov     edx, offset asc_424D16 ; "-"
.text:00402123                 lea     eax, [ebp+var_10]
.text:00402126                 call    sub_421050
.text:0040212B                 inc     [ebp+index_i]   ; index_i = 3
.text:0040212E                 lea     edx, [ebp+var_10]
.text:00402131                 push    edx
.text:00402132                 lea     eax, [ebp+var_C] ; this
.text:00402135                 mov     dx, [ebp+SystemTime.wDay] ; dx = current day
.text:00402139                 call    @System@AnsiString@$bctr$qqrus ; System::AnsiString::AnsiString(ushort)
.text:0040213E                 inc     [ebp+index_i]   ; index_i = 4
.text:00402141                 pop     edx
.text:00402142                 pop     ecx
.text:00402143                 call    @System@AnsiString@$badd$xqqrrx17System@AnsiString ; System::AnsiString::operator+(System::AnsiString &)
.text:00402148                 lea     eax, [ebp+var_14]
.text:0040214B                 push    eax
.text:0040214C                 lea     eax, [ebp+var_1C]
.text:0040214F                 call    unknown_libname_34 ; Borland Visual Component Library & Packages
.text:00402154                 push    eax
.text:00402155                 inc     [ebp+index_i]   ; index_i = 5
.text:00402158                 lea     eax, [ebp+var_18] ; this
.text:0040215B                 mov     dx, [ebp+SystemTime.wMonth] ; dx = current month
.text:0040215F                 call    @System@AnsiString@$bctr$qqrus ; System::AnsiString::AnsiString(ushort)
.text:00402164                 mov     edx, eax
.text:00402166                 inc     [ebp+index_i]   ; index_i = 6
.text:00402169                 pop     ecx
.text:0040216A                 pop     eax
.text:0040216B                 call    @System@AnsiString@$badd$xqqrrx17System@AnsiString ; System::AnsiString::operator+(System::AnsiString &)
.text:00402170                 lea     eax, [ebp+var_1C]
.text:00402173                 push    eax
.text:00402174                 lea     eax, [ebp+var_24]
.text:00402177                 call    unknown_libname_34 ; Borland Visual Component Library & Packages
.text:0040217C                 push    eax
.text:0040217D                 inc     [ebp+index_i]   ; index_i = 7
.text:00402180                 mov     edx, offset asc_424D18 ; edx = '-' (separator)
.text:00402185                 lea     eax, [ebp+var_20]
.text:00402188                 call    sub_421050
.text:0040218D                 inc     [ebp+index_i]   ; index_i = 8
.text:00402190                 lea     edx, [ebp+var_20]
.text:00402193                 pop     ecx
.text:00402194                 pop     eax
.text:00402195                 call    @System@AnsiString@$badd$xqqrrx17System@AnsiString ; System::AnsiString::operator+(System::AnsiString &)
.text:0040219A                 lea     edx, [ebp+var_24]
.text:0040219D                 push    edx
.text:0040219E                 lea     eax, [ebp+var_2C]
.text:004021A1                 call    unknown_libname_34 ; Borland Visual Component Library & Packages
.text:004021A6                 push    eax
.text:004021A7                 inc     [ebp+index_i]   ; index_i = 9
.text:004021AA                 movzx   edx, [ebp+SystemTime.wYear] ; edx = current year
.text:004021AE                 add     edx, 0Fh        ; Add 15 to current year
.text:004021B1                 lea     eax, [ebp+var_28] ; this
.text:004021B4                 call    @System@WideString@$bctr$qqrul ; System::WideString::WideString(ulong)
.text:004021B9                 mov     edx, eax
.text:004021BB                 inc     [ebp+index_i]   ; index_i = 10
.text:004021BE                 pop     ecx
.text:004021BF                 pop     eax
.text:004021C0                 call    @System@AnsiString@$badd$xqqrrx17System@AnsiString ; System::AnsiString::operator+(System::AnsiString &)
.text:004021C5                 lea     edx, [ebp+var_2C]
.text:004021C8                 lea     eax, [ebp+var_4]
.text:004021CB                 call    sub_421180
.text:004021D0                 dec     [ebp+index_i]   ; index_i = 9
.text:004021D3                 lea     eax, [ebp+var_2C]
.text:004021D6                 mov     edx, 2
.text:004021DB                 call    sub_421150
.text:004021E0                 dec     [ebp+index_i]   ; index_i = 8
.text:004021E3                 lea     eax, [ebp+var_28]
.text:004021E6                 mov     edx, 2
.text:004021EB                 call    sub_421150
.text:004021F0                 dec     [ebp+index_i]   ; index_i = 7
.text:004021F3                 lea     eax, [ebp+var_24]
.text:004021F6                 mov     edx, 2
.text:004021FB                 call    sub_421150
.text:00402200                 dec     [ebp+index_i]   ; index_i = 6
.text:00402203                 lea     eax, [ebp+var_20]
.text:00402206                 mov     edx, 2
.text:0040220B                 call    sub_421150
.text:00402210                 dec     [ebp+index_i]   ; index_i = 5
.text:00402213                 lea     eax, [ebp+var_1C]
.text:00402216                 mov     edx, 2
.text:0040221B                 call    sub_421150
.text:00402220                 dec     [ebp+index_i]   ; index_i = 4
.text:00402223                 lea     eax, [ebp+var_18]
.text:00402226                 mov     edx, 2
.text:0040222B                 call    sub_421150
.text:00402230                 dec     [ebp+index_i]   ; index_i = 3
.text:00402233                 lea     eax, [ebp+var_14]
.text:00402236                 mov     edx, 2
.text:0040223B                 call    sub_421150
.text:00402240                 dec     [ebp+index_i]   ; index_i = 2
.text:00402243                 lea     eax, [ebp+var_10]
.text:00402246                 mov     edx, 2
.text:0040224B                 call    sub_421150
.text:00402250                 dec     [ebp+index_i]   ; index_i = 1
.text:00402253                 lea     eax, [ebp+var_C]
.text:00402256                 mov     edx, 2
.text:0040225B                 call    sub_421150
.text:00402260                 mov     [ebp+var_48], 2Ch
.text:00402266                 lea     eax, [ebp+var_34]
.text:00402269                 call    unknown_libname_34 ; Borland Visual Component Library & Packages
.text:0040226E                 push    eax
.text:0040226F                 inc     [ebp+index_i]   ; index_i = 2
.text:00402272                 mov     edx, offset asc_424D1A ; edx = ' - ' (separator)
.text:00402277                 lea     eax, [ebp+var_30]
.text:0040227A                 call    sub_421050
.text:0040227F                 mov     edx, eax
.text:00402281                 inc     [ebp+index_i]   ; index_i = 3
.text:00402284                 mov     eax, [ebp+windows_username]
.text:00402287                 pop     ecx
.text:00402288                 call    @System@$badd$qqrpxcrx17System@AnsiString ; System::operator+(char *,System::AnsiString &)
.text:0040228D                 lea     edx, [ebp+var_34]
.text:00402290                 push    edx
.text:00402291                 lea     eax, [ebp+var_8]
.text:00402294                 call    unknown_libname_34 ; Borland Visual Component Library & Packages
.text:00402299                 mov     ecx, eax
.text:0040229B                 inc     [ebp+index_i]   ; index_i = 4
.text:0040229E                 lea     edx, [ebp+var_4]
.text:004022A1                 pop     eax
.text:004022A2                 call    @System@AnsiString@$badd$xqqrrx17System@AnsiString ; System::AnsiString::operator+(System::AnsiString &)
.text:004022A7                 dec     [ebp+index_i]   ; index_i = 3
.text:004022AA                 lea     eax, [ebp+var_34]
.text:004022AD                 mov     edx, 2
.text:004022B2                 call    sub_421150
.text:004022B7                 dec     [ebp+index_i]   ; index_i = 2
.text:004022BA                 lea     eax, [ebp+var_30]
.text:004022BD                 mov     edx, 2
.text:004022C2                 call    sub_421150
.text:004022C7                 mov     [ebp+var_48], 14h
.text:004022CD                 lea     eax, [ebp+var_8] ; this
.text:004022D0                 call    @System@AnsiString@c_str$xqqrv ; System::AnsiString::c_str(void)
.text:004022D5                 push    eax             ; src
.text:004022D6                 push    offset dest     ; title = username + ' - ' + day + '-' + month + '-' + (year+15)
.text:004022DB                 call    _strcpy

The expected format of the title is as follows:

┌──────┬───────┬─────┬─────┬───────┬─────┬─────────┐
│ User │ ' - ' │ day │ '-' │ month │ '-' │ year+15 │
└──────┴───────┴─────┴─────┴───────┴─────┴─────────┘
           ▲            ▲             ▲
           └────────────┼─────────────┘
                        │
                    separators
    (notice that the 1st one contains spaces)

Starting from offset 0x4022E3, the length of the resulting string is computed and characters are XOR'ed with 0xE.

.text:004022E3                 push    offset dest     ; s
.text:004022E8                 call    _strlen
.text:004022ED                 pop     ecx
.text:004022EE                 mov     [ebp+strlen_title], eax
.text:004022F1                 xor     edx, edx
.text:004022F3                 mov     [ebp+counter_i], edx ; i = 0
.text:004022F6                 mov     ecx, [ebp+counter_i]
.text:004022F9                 cmp     ecx, [ebp+strlen_title] ; \ while (i < len(title))
.text:004022FC                 jge     short loc_402313 ;        /
.text:004022FE
.text:004022FE loc_4022FE:
.text:004022FE                 mov     eax, [ebp+counter_i]
.text:00402301                 xor     dest[eax], 0Eh  ; title[i] ^= 0xE
.text:00402308                 inc     [ebp+counter_i]
.text:0040230B                 mov     edx, [ebp+counter_i]
.text:0040230E                 cmp     edx, [ebp+strlen_title]
.text:00402311                 jl      short loc_4022FE

This encrypted string will be used later by f_validate_title (sub_4023C0).

Notice that there is also an anti-debug trick in this function, based on GetTickCount:

.text:00402104                 call    GetTickCount
.text:00402109                 mov     [ebp+gettickcount], eax
; ...[SNIP]...
.text:00402313 loc_402313:
.text:00402313                 call    GetTickCount    ; antidebug trick
.text:00402318                 sub     eax, [ebp+gettickcount]
.text:0040231B                 mov     [ebp+tickcount], eax
.text:0040231E                 cmp     [ebp+tickcount], 20h
.text:00402322                 jbe     short loc_40232C ; [PATCH] patch with unconditional jump (EB 08)
.text:00402324                 push    0
.text:00402326                 call    f_terminate
Note
To get rid of this anti-debug trick, I've patched the code by replacing the conditional jump to an unconditional one.

f_validate_title (sub_4023C0)

This function encrypts the title of the notepad window with the same algorithm as the one used in f_notepad_title (sub_4020D4) and checks whether the resulting string matches the one previously computed.

Starting from offset 0x4023CE, the length of the previously encrypted title is computed and the current title of the notepad window is gathered and saved to lParam:

.text:004023CE push    offset dest     ; dest = encrypted title
.text:004023D3 call    _strlen
.text:004023D8 pop     ecx
.text:004023D9 mov     [ebp+len_encrypted_title], eax
.text:004023DC lea     eax, [ebp+lParam]
.text:004023DF push    eax             ; lParam
.text:004023E0 push    32h             ; wParam
.text:004023E2 push    WM_GETTEXT      ; Msg
.text:004023E4 push    hWnd            ; hWnd
.text:004023EA call    SendMessageA    ; WM_GETTEXT
                                       ; Gather title of notepad window and save it in lParam

Then, each character of the notepad window title is XOR'ed with 0xE and a test ensures that each character match the ones of the encrypted string computed previously.

.text:004023F7                 xor     ecx, ecx
.text:004023F9                 mov     [ebp+counter_i], ecx ; i = 0
.text:004023FC                 jmp     short loc_402401
.text:004023FE ; ---------------------------------------------------------------------------
.text:004023FE
.text:004023FE loc_4023FE:
.text:004023FE                 inc     [ebp+counter_i]
.text:00402401
.text:00402401 loc_402401:
.text:00402401                 mov     eax, [ebp+counter_i]
.text:00402404                 movsx   edx, byte ptr [ebp+eax+lParam]
.text:00402409                 xor     edx, 0Eh
.text:0040240C                 mov     ecx, [ebp+counter_i]
.text:0040240F                 movsx   eax, dest[ecx]  ; eax = encrypted_title[i]
.text:00402416                 cmp     edx, eax        ; title of notepad window matches?
.text:00402418                 jnz     short loc_402424
.text:0040241A                 mov     edx, [ebp+counter_i]
.text:0040241D                 cmp     byte ptr [ebp+edx+lParam], 0
.text:00402422                 jnz     short loc_4023FE

Here again, there is an anti-debug trick based on GetTickCount:

.text:004023C6                 call    GetTickCount               ; 1st call to GetTickCount
.text:004023CB                 mov     [ebp+gettickcount], eax
; ...[SNIP]...
.text:00402424                 call    GetTickCount               ; 2nd call to GetTickCount
.text:00402429                 sub     eax, [ebp+gettickcount]    ; compute difference between both calls
.text:0040242C                 mov     [ebp+tickcount], eax
; ...[SNIP]...
.text:00402437                 cmp     [ebp+tickcount], 20h
.text:0040243B                 jnb     short loc_402441 ; [PATCH] patch with NOP
Note
You can patch the code by replacing the conditional jump with a NOP to get rid of this anti-debug trick.

Serial validation

f_serial_spaces (sub_401E3C)

This function checks that there are 15 spaces in the serial, and that they are placed at the expected positions.

Starting from offset 0x401E48, a list of 15 values is loaded from memory location 0x4242AC (serial_spaces:

.text:00401E48                 mov     esi, offset serial_spaces
.text:00401E4D                 lea     edi, [ebp+serial_spaces]
.text:00401E50                 mov     ecx, 0Fh        ; ecx = 15
.text:00401E55                 rep movsd

;...[SNIP]...

.data:004242AC serial_spaces   dd 5, 0Bh, 0Fh, 12h, 19h, 22h, 27h, 2Ah, 2Dh, 35h, 40h
.data:004242AC                 dd 46h, 4Ch, 50h, 56h

Then we can see a loop that checks whether characters of the serial at the offsets of the array are all equal:

.text:00401EA1 loc_401EA1:
.text:00401EA1                 movsx   eax, [ebp+counter_i] ; eax = i
.text:00401EA5                 mov     edx, [ebp+eax*4+serial_spaces] ; edx = serial_spaces[i*4]
.text:00401EA9                 mov     [ebp+var_54], edx ; var_54 = serial_spaces[i*4]
.text:00401EAC                 movsx   ecx, [ebp+counter_i] ; ecx = i
.text:00401EB0                 mov     eax, [ebp+ecx*4+var_38] ; var_38 = serial_spaces[i*4+4]
.text:00401EB4                 mov     [ebp+var_58], eax ; var_58 = serial_spaces[i*4+4]
.text:00401EB7                 xor     eax, eax        ; eax = 0
.text:00401EB9                 mov     ebx, [ebp+var_54] ; ebx = serial_spaces[i*4]
.text:00401EBC                 mov     edx, [ebp+var_58] ; edx = serial_spaces[i*4+4]
.text:00401EBF                 mov     al, [edx+ebp-0B8h] ; al = serial[edx]
.text:00401EC6                 cmp     [ebx+ebp-0B8h], al ; serial[ebx] = serial[edx]?
.text:00401ECD                 jnz     short loc_401ED7

This code ensures that the following condition is met:

serial[5] = serial[11] = serial[15] = serial[18] = serial[25] = serial[34] = serial[39] = serial[42] = serial[45]
= serial[53] = serial[64] = serial[70] = serial[76] = serial[80] = serial[86]

Later in the code, the variable should_be_0x11D6, initialized to 0 at offset 0x401E65, is manipulated as follows:

.text:00401F0B loc_401F0B:
.text:00401F0B                 popa
.text:00401F0C                 xor     eax, eax
.text:00401F0E                 xor     ecx, ecx
.text:00401F10                 mov     al, [ebp+var_B3] ; al = serial[5]
.text:00401F16                 mov     cl, [ebp+var_AD] ; cl = serial[11]
.text:00401F1C                 add     al, cl          ; al = serial[5] + serial[11]
.text:00401F1E                 movzx   eax, al
.text:00401F21                 add     [ebp+should_be_0x11D6], eax ; should_be_0x11D6 = serial[5] * 2
.text:00401F24                 mov     al, [ebp+var_A9] ; al = serial[15]
.text:00401F2A                 mov     cl, [ebp+var_A6] ; cl = serial[18]
.text:00401F30                 add     al, cl          ; al = serial[15] + serial[18]
.text:00401F32                 movzx   eax, al
.text:00401F35                 add     [ebp+should_be_0x11D6], eax ; should_be_0x11D6 = serial[5] * 4
.text:00401F38                 mov     al, [ebp+var_9F] ; al = serial[25]
.text:00401F3E                 mov     cl, [ebp+var_96] ; cl = serial[34]
.text:00401F44                 add     al, cl          ; al = serial[25] + serial[34]
.text:00401F46                 movzx   eax, al
.text:00401F49                 add     [ebp+should_be_0x11D6], eax ; should_be_0x11D6 = serial[5] * 6
.text:00401F4C                 mov     al, [ebp+var_91] ; al = serial[39]
.text:00401F52                 mov     cl, [ebp+var_8E] ; cl = serial[42]
.text:00401F58                 add     al, cl          ; al = serial[39] + serial[42]
.text:00401F5A                 movzx   eax, al
.text:00401F5D                 add     [ebp+should_be_0x11D6], eax ; should_be_0x11D6 = serial[5] * 8
.text:00401F60                 mov     al, [ebp+var_8B] ; serial[45]
.text:00401F66                 mov     cl, [ebp+var_83] ; serial[53]
.text:00401F6C                 add     al, cl
.text:00401F6E                 movzx   eax, al
.text:00401F71                 add     [ebp+should_be_0x11D6], eax ; should_be_0x11D6 = serial[5] * 10
.text:00401F74                 mov     al, [ebp-120]   ; serial[64]
.text:00401F77                 mov     cl, [ebp+var_72] ; serial[70]
.text:00401F7A                 add     al, cl
.text:00401F7C                 movzx   eax, al
.text:00401F7F                 add     [ebp+should_be_0x11D6], eax ; should_be_0x11D6 = serial[5] * 12
.text:00401F82                 xor     edx, edx
.text:00401F84                 mov     al, [ebp+var_6C] ; serial[76]
.text:00401F87                 mov     cl, [ebp+var_68] ; serial[80]
.text:00401F8A                 mov     dl, [ebp+var_62] ; serial[86]
.text:00401F8D                 add     al, cl
.text:00401F8F                 add     al, dl
.text:00401F91                 movzx   eax, al
.text:00401F94                 add     [ebp+should_be_0x11D6], eax ; should_be_0x11D6 = serial[5] * 15
.text:00401F97                 xor     [ebp+should_be_0x11D6], 2010h ; should_be_0x11D6 ^= 0x2010
.text:00401F9E                 add     [ebp+should_be_0x11D6], 0B0B0h ; should be 0x11D6 += 0B0B0h
.text:00401FA5                 sub     [ebp+should_be_0x11D6], 0C0CAh ; should_be_0x11D6 -= 0xC0CA
.text:00401FAC
.text:00401FAC loc_401FAC:
.text:00401FAC                 call    GetTickCount
.text:00401FB1                 sub     eax, [ebp+gettickcount]
.text:00401FB4                 mov     [ebp+_gettickcount], eax
.text:00401FB7                 cmp     [ebp+should_be_1], 0
.text:00401FBC                 jz      short loc_401FD1
.text:00401FBE                 cmp     [ebp+should_be_0x11D6], 11D6h ; should_be_0x11D6 = 0x11D6 ?
.text:00401FC5                 jnz     short loc_401FD1 ; serial[5] = ((0x11D6 + 0xC0CA - 0xB0B0) ^ 0x2010) / 15
.text:00401FC5                                         ; = 0x20 (SPACE character)

This algorithm is as follows:

(serial[5] * 15 ^ 0x2010) + 0xB0B0 - 0xC0CA = 0x11D6

We can reverse it to find the value of serial[5]:

serial[5] = ((0x11D6 + 0xC0CA - 0xB0B0) ^ 0x2010) / 15 = 0x20

As we know that 0x20 is the space character, we conclude that the serial should have 15 spaces, located at offsets pointed to by the array.

At thi stage, the serial should look like this:

 0                   1                   2                   3                   4                   5                   6                   7                   8                   9
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
┌─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┐ ┌─┬─┬─┐ ┌─┬─┐ ┌─┬─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┐ ┌─┬─┐ ┌─┬─┐ ┌─┬─┬─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┐ ┌─┬─┬─┐ ┌─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┬─┬─┐
│?│?│?│?│?│ │?│?│?│?│?│ │?│?│?│ │?│?│ │?│?│?│?│?│?│ │?│?│?│?│?│?│?│?│ │?│?│?│?│ │?│?│ │?│?│ │?│?│?│?│?│?│?│ │?│?│?│?│?│?│?│?│?│?│ │?│?│?│?│?│ │?│?│?│?│?│ │?│?│?│ │?│?│?│?│?│ │?│?│?│?│?│?│?│
└─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┘ └─┴─┴─┘ └─┴─┘ └─┴─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┴─┴─┴─┘ └─┴─┴─┴─┘ └─┴─┘ └─┴─┘ └─┴─┴─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┘ └─┴─┴─┘ └─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┴─┴─┘

Notice that we also have in this function. The 1st one checks whether PEB.BeingDebugged is set:

.text:00401EF5                 mov     eax, large fs:30h ; PEB
.text:00401EFC                 db      3Eh
.text:00401EFC                 mov     eax, [eax+2]    ; PEB.BeingDebugged
.text:00401F00                 test    al, al          ; \ check whether prgm
.text:00401F02                 jz      short loc_401F0B ; / being debugged
.text:00401F02                                         ; [PATCH] NOP

And the 2nd one one is based on GetTickCount:

.text:00401E83                 call    GetTickCount
.text:00401E88                 mov     [ebp+gettickcount], eax

;...[SNIP]...

.text:00401FAC                 call    GetTickCount
.text:00401FB1                 sub     eax, [ebp+gettickcount]
.text:00401FB4                 mov     [ebp+tickcount], eax

;...[SNIP]...

.text:00401FC7                 cmp     [ebp+tickcount], 20h ;   \ check whether prgm
.text:00401FCB                 jnb     short loc_401FD1     ;   / being debugged
.text:00401FCB                                              ; [PATCH] NOP

f_serial_nonspace (sub_401FDC)

This function checks the remaining characters of the serial (the ones that are not spaces).

It starts by counting how many non-space characters are present in the serial provided in the notepad window.

.text:00401FE7                 mov     [ebp+gettickcount], eax
.text:00401FEA                 push    offset serial   ; src
.text:00401FEF                 lea     eax, [ebp+dest] ; dest = serial
.text:00401FF2                 push    eax             ; dest
.text:00401FF3                 call    _strcpy
.text:00401FF8                 add     esp, 8
.text:00401FFB                 xor     edx, edx
.text:00401FFD                 mov     [ebp+counter_j], edx ; j = 0
.text:00402000                 xor     ecx, ecx
.text:00402002                 mov     [ebp+counter_k], ecx ; k = 0
.text:00402005
.text:00402005 loc_402005:
.text:00402005                 cmp     [ebp+counter_j], 5 ;
.text:00402005                                         ; skip space at byte #5
.text:00402009                 jz      short loc_402071
.text:0040200B                 cmp     [ebp+counter_j], 0Bh ; skip space at byte #11
.text:0040200F                 jz      short loc_402071
.text:00402011                 cmp     [ebp+counter_j], 0Fh ; skip space at byte #15
.text:00402015                 jz      short loc_402071
.text:00402017                 cmp     [ebp+counter_j], 12h
.text:0040201B                 jz      short loc_402071
.text:0040201D                 cmp     [ebp+counter_j], 19h
.text:00402021                 jz      short loc_402071
.text:00402023                 cmp     [ebp+counter_j], 22h
.text:00402027                 jz      short loc_402071
.text:00402029                 cmp     [ebp+counter_j], 27h
.text:0040202D                 jz      short loc_402071
.text:0040202F                 cmp     [ebp+counter_j], 2Ah
.text:00402033                 jz      short loc_402071
.text:00402035                 cmp     [ebp+counter_j], 2Dh
.text:00402039                 jz      short loc_402071
.text:0040203B                 cmp     [ebp+counter_j], 35h
.text:0040203F                 jz      short loc_402071
.text:00402041                 cmp     [ebp+counter_j], 40h
.text:00402045                 jz      short loc_402071
.text:00402047                 cmp     [ebp+counter_j], 46h
.text:0040204B                 jz      short loc_402071
.text:0040204D                 cmp     [ebp+counter_j], 4Ch
.text:00402051                 jz      short loc_402071
.text:00402053                 cmp     [ebp+counter_j], 50h
.text:00402057                 jz      short loc_402071
.text:00402059                 cmp     [ebp+counter_j], 56h
.text:0040205D                 jz      short loc_402071
.text:0040205F                 mov     eax, [ebp+counter_j] ; if char is not a SPACE
.text:00402062                 mov     dl, [ebp+eax+dest]
.text:00402066                 mov     ecx, [ebp+counter_k]
.text:00402069                 mov     serial_chars[ecx], dl ; if char not space serial_char[i] = serial[i]
.text:00402069                                         ; serial_char at 0x428B84
.text:0040206F                 jmp     short loc_402074
.text:00402071 ; ---------------------------------------------------------------------------
.text:00402071
.text:00402071 loc_402071:
.text:00402071                 dec     [ebp+counter_k] ; if char = SPACE
.text:00402074
.text:00402074 loc_402074:
.text:00402074                 inc     [ebp+counter_j]
.text:00402077                 inc     [ebp+counter_k]
.text:0040207A                 cmp     [ebp+counter_k], 4Fh ; \ while (k < 79)
.text:0040207E                 jl      short loc_402005 ;     /

All characters of the serial entered in the notepad window are XOR'ed with 0xF:

.text:0040209B                 popa
.text:0040209C                 xor     eax, eax
.text:0040209E                 mov     [ebp+counter_i], eax ; i = 0
.text:004020A1 loc_4020A1:
.text:004020A1                 mov     edx, [ebp+counter_i]
.text:004020A4                 xor     serial_chars[edx], 0Fh ; serial_chars[i] ^= 0xF
.text:004020A4                                         ; serial_chars at 0x428B84
.text:004020AB                 inc     [ebp+counter_i] ; i++
.text:004020AE                 cmp     [ebp+counter_i], 4Fh ; while(i<79)
.text:004020B2                 jl      short loc_4020A1

Notice that there are 2 anti-debug tricks in this function. The 1st one is based on PEB.ProcessHead.ForceFlags:

.text:00402081                 mov     eax, large fs:30h ; eax = PEB
.text:00402088                 db      3Eh
.text:00402088                 mov     eax, [eax+18h]    ; eax = PEB.ProcessHeap
.text:0040208C                 db      3Eh
.text:0040208C                 mov     eax, [eax+10h]    ; ForceFlags (Windows XP only)
.text:00402090                 test    eax, eax          ; Check whether ForceFlags field (offset 0x10 relative to ProcessHeap) is 0
.text:00402092                 jz      short loc_40209B  ; [PATCH] NOP

The 2nd one is base on GetTickCount:

.text:00401FE2                 call    GetTickCount
.text:00401FE7                 mov     [ebp+gettickcount], eax
; ...[SNIP]...
.text:004020B4                 call    GetTickCount
.text:004020B9                 sub     eax, [ebp+gettickcount]
.text:004020BC                 mov     [ebp+tickcount], eax
.text:004020BF                 cmp     [ebp+tickcount], 20h
.text:004020C3                 jbe     short loc_4020CD ; [PATCH] patch with unconditional jump (EB 08)
.text:004020C5                 push    0
.text:004020C7                 call    f_terminate

f_validate_serial_chars (sub_40235C)

This function checks whether the serial is valid. It compares the encrypted characters of the serial (see f_serial_nonspace (sub_401FDC)) with bytes saved at memory location 0x4242E8.

.text:00402380                 mov     [ebp+counter_i], eax ; i = 0
.text:00402383                 jmp     short loc_402388
.text:00402385 ; ---------------------------------------------------------------------------
.text:00402385
.text:00402385 loc_402385:
.text:00402385                 inc     [ebp+counter_i]
.text:00402388
.text:00402388 loc_402388:
.text:00402388                 mov     edx, [ebp+counter_i]
.text:0040238B                 mov     cl, serial_chars[edx] ; cl = serial_chars[i]
.text:00402391                 mov     eax, [ebp+counter_i]
.text:00402394                 cmp     cl, [ebp+eax+encrypted_serial] ; serial_chars[i] = encrypted_serial[i]?
.text:00402398                 jz      short loc_402385

; ...[SNIP]...

.data:004242E8 encrypted_serial db 41h, 7Ah, 61h, 6Ch, 2 dup(6Eh), 61h, 6Bh, 6Ah, 7Ch
.data:004242E8                 db 7Fh, 60h, 7Dh, 6Ah, 63h, 6Ch, 6Eh, 62h, 66h, 61h, 60h
.data:004242E8                 db 7Bh, 7Dh, 6Eh, 75h, 6Eh, 6Bh, 60h, 23h, 7Fh, 7Ah, 6Ah
.data:004242E8                 db 7Ch, 0E6h, 63h, 7Bh, 6Ah, 6Ch, 60h, 61h, 6Bh, 7Ah, 6Ch
.data:004242E8                 db 6Ah, 0F5h, 61h, 66h, 6Ch, 6Eh, 62h, 6Ah, 61h, 7Bh, 6Ah
.data:004242E8                 db 67h, 6Eh, 6Ch, 66h, 6Eh, 6Bh, 60h, 61h, 6Bh, 6Ah, 63h
.data:004242E8                 db 60h, 7Ch, 60h, 7Bh, 7Dh, 60h, 7Ch, 69h, 7Ah, 6Ah, 7Dh
.data:004242E8                 db 60h, 61h, 21h

Here again, there is an anti-debug trick:

.text:00402376                 call    GetTickCount
.text:0040237B                 mov     [ebp+gettickcount], eax
; ...[SNIP]...
.text:0040239A                 call    GetTickCount
.text:0040239F                 sub     eax, [ebp+gettickcount]
.text:004023A2                 mov     [ebp+tickcount], eax
.text:004023A5                 cmp     [ebp+counter_i], 4Fh ; i = 79?
.text:004023A9                 jnz     short loc_4023B5

As we know the expected bytes as well as the algorithm (XOR with 0xF), it's easy to find the expected password (see script).

Solution

Title

As seen previously, the notepad window should be saved with the following name:

 WindowsUserName - day-month-(year+15)

Example:

Crack Me - 7-2-2031

Serial

The serial can be found with the following python script:

#!/usr/bin/env python

serial_spaces = [0x05, 0x0B, 0x0F, 0x12, 0x19, 0x22, 0x27, 0x2A,
    0x2D, 0x35, 0x40, 0x46, 0x4C, 0x50, 0x56]

encrypted_serial = [0x41, 0x7A, 0x61, 0x6C, 0x6E, 0x6E, 0x61, 0x6B,
    0x6A, 0x7C, 0x7F, 0x60, 0x7D, 0x6A, 0x63, 0x6C,
    0x6E, 0x62, 0x66, 0x61, 0x60, 0x7B, 0x7D, 0x6E,
    0x75, 0x6E, 0x6B, 0x60, 0x23, 0x7F, 0x7A, 0x6A,
    0x7C, 0xE6, 0x63, 0x7B, 0x6A, 0x6C, 0x60, 0x61,
    0x6B, 0x7A, 0x6C, 0x6A, 0xF5, 0x61, 0x66, 0x6C,
    0x6E, 0x62, 0x6A, 0x61, 0x7B, 0x6A, 0x67, 0x6E,
    0x6C, 0x66, 0x6E, 0x6B, 0x60, 0x61, 0x6B, 0x6A,
    0x63, 0x60, 0x7C, 0x60, 0x7B, 0x7D, 0x60, 0x7C,
    0x69, 0x7A, 0x6A, 0x7D, 0x60, 0x61, 0x21]

secret = []

c = 0
for i in encrypted_serial:
    if c in serial_spaces:
        secret.append(0x20)
        c += 1
    secret.append(i ^ 0xF)
    c += 1

print ''.join([chr(i) for i in secret])

It provides us with the following output:

Nunca andes por el camino trazado, pues él te conduce únicamente hacia donde los otros fueron.

As soon as this serial has been entered, the content is changed to:

Comments

Keywords: crackme assembly reverse-engineering Thunder_cls Sticky