DEV Community

ddupard
ddupard

Posted on

CrackMe Level 6

1. Introduction

Level 6 crackmes are the most difficult ones you can find on the crackmes.one website. If you search the site for Assembly crackmes with a difficulty of 6, you will only find 3:

For this first example, we will take chili_crackme_11_by_witeg by witeg.

As usual, when approaching a crackme, we start with a hexdump -C CrackMe11_wg.exe:

00000000  4d 5a 00 00 00 00 00 00  00 00 00 00 50 45 00 00  |MZ..........PE..|
00000010  4c 01 02 00 46 53 47 21  00 00 00 00 00 00 00 00  |L...FSG!........|
00000020  e0 00 0f 01 0b 01 00 00  00 16 00 00 00 f0 00 00  |................|
00000030  00 00 00 00 54 01 00 00  00 10 00 00 0c 00 00 00  |....T...........|
00000040  00 00 40 00 00 10 00 00  00 02 00 00 04 00 00 00  |..@.............|
00000050  00 00 00 00 04 00 00 00  00 00 00 00 00 90 01 00  |................|
00000060  00 02 00 00 00 00 00 00  02 00 00 00 00 00 10 00  |................|
00000070  00 10 00 00 00 00 10 00  00 10 00 00 00 00 00 00  |................|
00000080  10 00 00 00 00 00 00 00  00 00 00 00 c4 8a 01 00  |................|
00000090  84 00 00 00 00 50 01 00  fc 09 00 00 00 00 00 00  |.....P..........|
000000a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000100  00 00 00 00 00 00 00 00  00 00 00 00 00 40 01 00  |.............@..|
00000110  00 10 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000120  00 00 00 00 00 00 00 00  e0 00 00 c0 00 00 00 00  |................|
00000130  00 00 00 00 00 40 00 00  00 50 01 00 45 3b 00 00  |.....@...P..E;..|

...

00003cb0  51 6d 9c 0a 28 c8 91 da  14 c0 ce 5c 6c 14 01 bb  |Qm..(......\l...|
00003cc0  fe f9 80 00 1c 8b 01 00  00 00 00 00 00 00 00 00  |................|
00003cd0  f2 01 00 00 1c 8b 01 00  00 00 00 00 00 00 00 00  |................|
00003ce0  00 00 00 00 00 00 00 00  00 00 00 00 00 10 40 00  |..............@.|
00003cf0  fc 59 41 00 70 30 40 00  00 00 00 00 0c 8b 41 00  |.YA.p0@.......A.|
00003d00  80 00 00 00 00 7d 00 00  ec 8a 41 00 e8 01 40 00  |.....}....A...@.|
00003d10  dc 01 40 00 de 01 40 00  00 10 40 00 26 8b 01 00  |..@...@...@.&...|
00003d20  34 8b 01 00 00 00 00 00  4c 6f 61 64 4c 69 62 72  |4.......LoadLibr|
00003d30  61 72 79 41 00 00 47 65  74 50 72 6f 63 41 64 64  |aryA..GetProcAdd|
00003d40  72 65 73 73 00                                    |ress.|
00003d45

Enter fullscreen mode Exit fullscreen mode

Seeing immediately that it is a Windows executable, we run readpe CrackMe11_wg.exe:

DOS Header
    Magic number:                    0x5a4d (MZ)
    Bytes in last page:              0
    Pages in file:                   0
    Relocations:                     0
    Size of header in paragraphs:    0
    Minimum extra paragraphs:        0
    Maximum extra paragraphs:        17744
    Initial (relative) SS value:     0
    Initial SP value:                0x14c
    Initial IP value:                0x5346
    Initial (relative) CS value:     0x2147
    Address of relocation table:     0
    Overlay number:                  0
    OEM identifier:                  0x10b
    OEM information:                 0
    PE header offset:                0xc
COFF/File header
    Machine:                         0x14c IMAGE_FILE_MACHINE_I386
    Number of sections:              2
    Date/time stamp:                 558322502 (Fri, 11 Sep 1987 01:35:02 UTC)
    Symbol Table offset:             0
    Number of symbols:               0
    Size of optional header:         0xe0
    Characteristics:                 0x10f
    Characteristics names
                                         IMAGE_FILE_RELOCS_STRIPPED
                                         IMAGE_FILE_EXECUTABLE_IMAGE
                                         IMAGE_FILE_LINE_NUMS_STRIPPED
                                         IMAGE_FILE_LOCAL_SYMS_STRIPPED
                                         IMAGE_FILE_32BIT_MACHINE
Optional/Image header
    Magic number:                    0x10b (PE32)
    Linker major version:            0
    Linker minor version:            0
    Size of .text section:           0x1600
    Size of .data section:           0xf000
    Size of .bss section:            0
    Entrypoint:                      0x154
    Address of .text section:        0x1000
    Address of .data section:        0xc
    ImageBase:                       0x400000
    Alignment of sections:           0x1000
    Alignment factor:                0x200
    Major version of required OS:    4
    Minor version of required OS:    0
    Major version of image:          0
    Minor version of image:          0
    Major version of subsystem:      4
    Minor version of subsystem:      0
    Size of image:                   0x19000
    Size of headers:                 0x200
    Checksum:                        0
    Subsystem required:              0x2 (IMAGE_SUBSYSTEM_WINDOWS_GUI)
    DLL characteristics:             0
    DLL characteristics names
    Size of stack to reserve:        0x100000
    Size of stack to commit:         0x1000
    Size of heap space to reserve:   0x100000
    Size of heap space to commit:    0x1000

Enter fullscreen mode Exit fullscreen mode

Then we launch the debugger winedbg --gdb CrackMe11_wg.exe:

We immediately look for where the program is loaded and where its Entry Point is located:

info sharedlibrary

From        To          Syms Read   Shared Object Library
0x5abb1000  0x5abb5010  Yes ()     /usr/lib/wine/wine
0xf74b6000  0xf76f08bc  Yes ()     /lib/i386-linux-gnu/libc.so.6
0xf7718000  0xf774ca18  Yes ()     /lib/ld-linux.so.2
0xf7401000  0xf74b5854  Yes ()     /usr/lib/wine/../i386-linux-gnu/wine/i386-unix/ntdll.so
0x00401000  0x00418b45  Yes (*)     /home/daniel/Desktop/LABO/CRACKME/Windows/6/1/CrackMe11_wg.exe
0x7bd11000  0x7bdbced0  Yes         /usr/lib/i386-linux-gnu/wine/i386-windows/ntdll.dll
0x7bb91000  0x7bbf43d8  Yes         /usr/lib/i386-linux-gnu/wine/i386-windows/kernel32.dll
0x7b651000  0x7b8e7cc0  Yes         /usr/lib/i386-linux-gnu/wine/i386-windows/kernelbase.dll

Enter fullscreen mode Exit fullscreen mode

info file

Symbols from "/home/daniel/Desktop/LABO/CRACKME/Windows/6/1/CrackMe11_wg.exe".
Remote target using gdb-specific protocol:
  `/home/daniel/Desktop/LABO/CRACKME/Windows/6/1/CrackMe11_wg.exe', file type pei-i386.
warning: Cannot find section for the entry point of /home/daniel/Desktop/LABO/CRACKME/Windows/6/1/CrackMe11_wg.exe.
  Entry point: 0x400154
  0x00401000 - 0x00415000 is 
  0x00415000 - 0x00418b45 is 

Enter fullscreen mode Exit fullscreen mode

Seeing that the Entry Point is at 0x400154, we set a breakpoint:

break *0x400154
continue

Enter fullscreen mode Exit fullscreen mode

Which gives us the following instructions:

   0x400154    xchg   dword ptr [0x418b08], esp
   0x40015a    popal 
   0x40015b    xchg   esp, eax                              ESP => 0x31ff54, EAX => 0x418b0c
   0x40015c    push   ebp
   0x40015d    movsb  byte ptr es:[edi], byte ptr [esi]
   0x40015e    mov    dh, 0x80                              DH => 0x80
   0x400160    call   dword ptr [ebx]

   0x400162  ? jae    0x40015d                    <0x40015d>

   0x400164    xor    ecx, ecx            ECX => 0
   0x400166    call   dword ptr [ebx]

   0x400168  ? jae    0x400180                    <0x400180>

   0x40016a    xor    eax, eax            EAX => 0
   0x40016c    call   dword ptr [ebx]

   0x40016e  ? jae    0x40018f                    <0x40018f>

   0x400170    mov    dh, 0x80            DH => 0x80
   0x400172    inc    ecx
   0x400173    mov    al, 0x10            AL => 0x10
   0x400175    call   dword ptr [ebx]

   0x400177    adc    al, al
   0x400179  ? jae    0x400175                    <0x400175>

   0x40017b  ? jne    0x4001b7                    <0x4001b7>

Enter fullscreen mode Exit fullscreen mode

We could step through the instructions one by one, but there is often a better approach, which starts by running the program completely to see what happens.

continue

Continuing.
0138:013c: loads DLL C:\windows\system32\GDI32.dll @7A640000 (2355200<7390>)
0138:013c: loads DLL C:\windows\system32\advapi32.dll @7B550000 (958464<7400>)
0138:013c: loads DLL C:\windows\system32\msvcrt.dll @7B2A0000 (2715648<8214>)
0138:013c: loads DLL C:\windows\system32\sechost.dll @7B200000 (565248<3163>)
0138:013c: loads DLL C:\windows\system32\ucrtbase.dll @7AEE0000 (3198976<11541>)
0138:013c: loads DLL C:\windows\system32\user32.dll @7A160000 (5005312<14159>)
0138:013c: loads DLL C:\windows\system32\zlib1.dll @7A120000 (135680<0>)
0138:013c: loads DLL C:\windows\system32\win32u.dll @7A0D0000 (221184<1966>)
0138:013c: loads DLL C:\windows\system32\imm32.dll @78010000 (401408<2996>)
0138:013c: loads DLL C:\windows\system32\COMCTL32.dll @77480000 (4870144<13692>)
0138:013c: loads DLL C:\windows\system32\winex11.drv @77E00000 (307200<4314>)
0138:013c: loads DLL C:\windows\system32\uxtheme.dll @77D40000 (671744<3646>)
0138:013c: loads DLL C:\windows\system32\ole32.dll @7A9F0000 (4440064<14323>)
0138:013c: loads DLL C:\windows\system32\combase.dll @7A890000 (1314816<7562>)
0138:013c: loads DLL C:\windows\system32\rpcrt4.dll @79EA0000 (2162688<9274>)
0138:013c: loads DLL C:\windows\system32\coml2.dll @79E40000 (262144<3883>)
0138:013c: loads DLL C:\windows\system32\msimg32.dll @78C20000 (81920<608>)

Enter fullscreen mode Exit fullscreen mode

We see that the program loads libraries, and then the window below appears:

Since the libraries are not accessible when the program loads, it means they are loaded after the program starts. Furthermore, because these names do not appear in plain text in hexdump -C, this indicates that an unpacking mechanism occurs prior to executing LoadLibraryA. Looking at the hexdump, we see FSG! at offset 0x00000014, and the very small header size confirms the use of the FSG v2.0 packer.

2. The FSG v2.0 Packer

We will therefore look for calls to LoadLibraryA:

info functions LoadLibraryA

All functions matching regular expression "LoadLibraryA":

File dlls/kernelbase/loader.c:
499:  HMODULE LoadLibraryA@4(LPCSTR);

Non-debugging symbols:
0x7bb9cfa8  __wine_spec_imp_LoadLibraryA
0x7bbbd038  LoadLibraryA@4

Enter fullscreen mode Exit fullscreen mode

And we set breakpoints:

break *0x7bb9cfa8

At the first breakpoint, we examine the call stack:

bt
#0  0x7bb9cfa8 in __wine_spec_imp_LoadLibraryA () from /usr/lib/i386-linux-gnu/wine/i386-windows/kernel32.dll
#1  0x004001c9 in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)

Enter fullscreen mode Exit fullscreen mode

What is interesting is that it gives us an address inside our program's code: 0x004001c9.
In fact, we find ourselves right in the middle of the decompression loop. The code of an FSG stub decompression loop typically performs dynamic API resolution (LoadLibraryA and GetProcAddress) to rebuild the IAT (Import Address Table) in memory, and then ends with an unconditional jump (jmp or ret) to the original, uncompressed code.

We will disassemble around this address 0x004001c9:

disass 0x4001b0, 0x4001e0

   0x004001b0:  inc    ecx
   0x004001b1:  inc    ecx
   0x004001b2:  xchg   ebp,eax
   0x004001b3:  mov    eax,ebp
   0x004001b5:  mov    dh,0x0
   0x004001b7:  push   esi
   0x004001b8:  mov    esi,edi
   0x004001ba:  sub    esi,eax
   0x004001bc:  rep movs BYTE PTR es:[edi],BYTE PTR ds:[esi]
   0x004001be:  pop    esi
   0x004001bf:  jmp    0x400160


   0x004001c1:  pop    esi
   0x004001c2:  lods   eax,DWORD PTR ds:[esi]
   0x004001c3:  xchg   edi,eax
   0x004001c4:  lods   eax,DWORD PTR ds:[esi]
   0x004001c5:  push   eax
   0x004001c6:  call   DWORD PTR [ebx+0x10]     # call to LoadLibraryA
   0x004001c9:  xchg   ebp,eax
   0x004001ca:  mov    eax,DWORD PTR [edi]
   0x004001cc:  inc    eax
   0x004001cd:  js     0x4001c2
   0x004001cf:  jne    0x4001d4


   0x004001d1:  jmp    DWORD PTR [ebx+0xc]    # jump to the program's entry point


   0x004001d4:  push   eax
   0x004001d5:  push   ebp
   0x004001d6:  call   DWORD PTR [ebx+0x14]   # call to GetProcAddress
   0x004001d9:  stos   DWORD PTR es:[edi],eax
   0x004001da:  jmp    0x4001ca
   0x004001dc:  xor    ecx,ecx
   0x004001de:  inc    ecx
   0x004001df:  call   DWORD PTR [ebx]

Enter fullscreen mode Exit fullscreen mode

Instruction Analysis:

  • LZMA/LZ77 Decompression Loop (0x004001b0 - 0x004001bf): These instructions copy decompressed data blocks via rep movsb (0x004001bc). Once the block is processed, an unconditional jump loops back to the core algorithm (jmp 0x400160).
  • Dynamic IAT Resolution (0x004001c1 - 0x004001da):
  • 0x004001c6 (call DWORD PTR [ebx+0x10]): This is the call to LoadLibraryA (where the return address 0x004001c9 was captured by our initial breakpoint).
  • 0x004001d6 (call DWORD PTR [ebx+0x14]): This is the call to GetProcAddress. The packer iterates through the imported function names and writes their resolved addresses into the real IAT using stos dword ptr es:[edi], eax (0x004001d9).

  • Jump to OEP (0x004001d1):

  • jmp DWORD PTR [ebx+0xc]: When the import pointer reaches its end condition (jne 0x4001d4 is not taken), the packer executes an indirect jump via the ebx+0xc register. This is precisely the instruction that transfers execution to the OEP (Original Entry Point).

We simply need to set a breakpoint on the jmp and run to get the disassembled code that will execute the rest of the program:

break *0x004001d1
continue

Enter fullscreen mode Exit fullscreen mode

3. The Disassembled Code

   0x4001d1    jmp    dword ptr [ebx + 0xc]       <0x401000>
    ↓
   0x401000    push   0x4010c6
   0x401005    push   1
   0x401007    push   0
   0x401009    call   0x4014f2                    <0x4014f2>

   0x40100e    call   0x4014fe                    <0x4014fe>

   0x401013    sub    eax, 0xb7
   0x401018  ? je     0x401037                    <0x401037>

   0x40101a    push   0
   0x40101c    call   0x401504                    <0x401504>

   0x401021    mov    dword ptr [0x404160], eax
   0x401026    push   0
   0x401028    push   0x4011cb
   0x40102d    push   0
   0x40102f    push   0x64
   0x401031    push   eax
   0x401032    call   0x4014b6                    <0x4014b6>

   0x401037    push   0
   0x401039    call   0x4014f8                    <0x4014f8>

   0x40103e    call   0x4014aa                    <0x4014aa>

   0x401043    fadd   qword ptr [eax]


   0x401045:  add    BYTE PTR [eax],al
   0x401047:  (bad)
   0x401048:  (bad)
   0x401049:  (bad)

Enter fullscreen mode Exit fullscreen mode

Now that the program is unpacked, the straightforward idea is to search for the character strings visible on the window when running the executable:

find 0x400000, +1000000, "Name"
find 0x400000, +1000000, "Serial"
find 0x400000, +1000000, "Witeg"

Enter fullscreen mode Exit fullscreen mode

Unfortunately, this returns nothing. Why?

The absence of these strings in a simple memory search is due to two technical reasons:

  1. Windows Resources (Dialog Templates): In standard Win32 applications, dialog labels ("Name", "Serial", etc.) are not stored as direct ASCII/UTF-8 strings in the code section (.text) or data section (.data), but in the resource section (.rsrc). Furthermore, these strings are encoded in UTF-16LE (Wide Characters, 2 bytes per character: N\x00a\x00m\x00e\x00).
  2. Memory Encoding: A classic search like find "Name" searches for the ASCII byte sequence (4e 61 6d 65). In UTF-16LE, the sequence becomes 4e 00 61 00 6d 00 65 00, explaining why the search fails if the format is not specified.

To search for our strings, we need to run the following commands:

search -w "Name"
search -w "Serial"
search -w "Witeg"

Enter fullscreen mode Exit fullscreen mode

However, this yields no results either. In the end, it doesn't matter; what is important is retrieving the user input. To do this, we will set breakpoints on the Windows API functions that capture keyboard inputs, first finding their exact signatures and then placing the breakpoints:

info functions GetDlgItemText
info functions GetWindowText

hbreak GetDlgItemTextA@16
hbreak GetDlgItemTextW@16
hbreak GetWindowTextA@12
hbreak GetWindowTextW@12

Enter fullscreen mode Exit fullscreen mode

Now that this is done, all we have to do is resume execution. However, no hbreak is triggered, which means the programmer wrote a custom Windows message handling routine:

info functions DispatchMessage

hbreak DispatchMessageA@4
hbreak DispatchMessageW@4

Enter fullscreen mode Exit fullscreen mode

We finally hit our breakpoints with the following call stack:

0 0x7a1a8665 DispatchMessageW@4+5
   1 0x7a186559 IsDialogMessageW@8+425
   2 0x7a186dff DIALOG_DoDialogBox+319
   3 0x7a186f5e DialogBoxParamA@20+142
   4 0x401037
   5 0x3
   6 0x4
   7 0xffff

Enter fullscreen mode Exit fullscreen mode

We just need to disassemble around the return address 0x401037, for instance using disass 0x401010, 0x401038:

disass 0x401010, 0x401039
Dump of assembler code from 0x401010 to 0x401039:
   0x00401010:  add    al,0x0
   0x00401012:  add    BYTE PTR ds:0xb7,ch
   0x00401018:  je     0x401037
   0x0040101a:  push   0x0
   0x0040101c:  call   0x401504
   0x00401021:  mov    ds:0x404160,eax
   0x00401026:  push   0x0
   0x00401028:  push   0x4011cb
   0x0040102d:  push   0x0
   0x0040102f:  push   0x64
   0x00401031:  push   eax
   0x00401032:  call   0x4014b6
   0x00401037:  push   0x0

Enter fullscreen mode Exit fullscreen mode

4. The Windows Message Handler Routine

The prototype of the function is:

INT_PTR DialogBoxParamA(
  HINSTANCE hInstance,
  LPCSTR    lpTemplateName,
  HWND      hWndParent,
  DLGPROC   lpDialogFunc,  // <--- The CrackMe routine address
  LPARAM    dwInitParam
);

Enter fullscreen mode Exit fullscreen mode

We set a breakpoint on 0x4011cb via:

break *0x4011cb

And we get the following disassembled code, which is a standard Windows message handler routine (DialogProc). We can recognize the switch-case structure comparing ax against various message types, and we just need to identify the one that interests us from the list:

   0x4011cb:  push   ebp
   0x4011cc:  mov    ebp,esp
   0x4011ce:  mov    eax,DWORD PTR [ebp+0xc]
   0x4011d1:  cmp    ax,0x2
   0x4011d5:  je     0x4012a4
   0x4011db:  cmp    ax,0x10
   0x4011df:  je     0x4012a4

   0x4011e5:  cmp    ax,0x111  # This is the one we want; we set a breakpoint on 0x4012b2
   0x4011e9:  je     0x4012b2


   0x4011ef:  cmp    ax,0x110
   0x4011f3:  je     0x4012e1
   0x4011f9:  cmp    ax,0x200
   0x4011fd:  je     0x401248
   0x4011ff:  cmp    ax,0x202
   0x401203:  je     0x401221
   0x401205:  cmp    ax,0x201
   0x401209:  je     0x40122a
   0x40120b:  cmp    ax,0x214
   0x40120f:  je     0x401221
   0x401211:  cmp    ax,0x133
   0x401215:  je     0x4013fd
   0x40121b:  xor    eax,eax
   0x40121d:  leave
   0x40121e:  ret    0x10

Enter fullscreen mode Exit fullscreen mode

Analyzing this switch-case shows the typical structure of a DialogProc function. The uMsg argument ([ebp+0xc]) is compared against several Win32 system constants:

  • 0x110 (WM_INITDIALOG) $\rightarrow$ Redirects to 0x004012e1 (interface initialization, loading images, or hiding/displaying controls).
  • 0x111 (WM_COMMAND) $\rightarrow$ Redirects to 0x004012b2. This is precisely the target branch.
  • 0x02 (WM_DESTROY) and 0x10 (WM_CLOSE) $\rightarrow$ Redirect to 0x004012a4 to close the application.
  • 0x200 (WM_MOUSEMOVE), 0x201 (WM_LBUTTONDOWN), 0x202 (WM_LBUTTONUP) $\rightarrow$ Mouse event handlers (often used to move a borderless window or handle the visible hyperlink on the image).
  • 0x133 (WM_CTLCOLOREDIT) $\rightarrow$ Customization of text field colors.

Below is the code found at 0x4012b2:

   0x4012b2:  mov    eax,DWORD PTR [ebp+0x10]
   0x4012b5:  cmp    ax,0x3ea
   0x4012b9:  je     0x4012cd
   0x4012bb:  cmp    ax,0x3e9
   0x4012bf:  je     0x4012cd
   0x4012c1:  cmp    ax,0x2
   0x4012c5:  je     0x4012a4
   0x4012c7:  xor    eax,eax
   0x4012c9:  leave
   0x4012ca:  ret    0x10
   0x4012cd:  shr    eax,0x10
   0x4012d0:  cmp    eax,0x300
   0x4012d5:  je     0x40141f
   0x4012db:  xor    eax,eax
   0x4012dd:  leave
   0x4012de:  ret    0x10
   0x4012e1:  push   0x0
   0x4012e3:  push   0x0
   0x4012e5:  push   0x31
   0x4012e7:  push   DWORD PTR [ebp+0x8]
   0x4012ea:  call   0x4014e6
   0x4012ef:  push   0x404120
   0x4012f4:  push   0x3c
   0x4012f6:  push   eax
   0x4012f7:  call   0x40148c
   0x4012fc:  mov    DWORD PTR ds:0x404130,0x2bc
   0x401306:  push   0x404120
   0x40130b:  call   0x401486
   0x401310:  mov    ds:0x404164,eax
   0x401315:  push   0x3e9
   0x40131a:  push   DWORD PTR [ebp+0x8]
   0x40131d:  call   0x4014c8
   0x401322:  push   0x1
   0x401324:  push   DWORD PTR ds:0x404164
   0x40132a:  push   0x30
   0x40132c:  push   eax
   0x40132d:  call   0x4014e6
   0x401332:  push   0x401094
   0x401337:  push   0x3e9
   0x40133c:  push   DWORD PTR [ebp+0x8]
   0x40133f:  call   0x4010d7
   0x401344:  push   0x3ea
   0x401349:  push   DWORD PTR [ebp+0x8]
   0x40134c:  call   0x4014c8
   0x401351:  push   0x1
   0x401353:  push   DWORD PTR ds:0x404164
   0x401359:  push   0x30
   0x40135b:  push   eax
   0x40135c:  call   0x4014e6
   0x401361:  push   0x4010ac
   0x401366:  push   0x3ea
   0x40136b:  push   DWORD PTR [ebp+0x8]
   0x40136e:  call   0x4010d7
   0x401373:  push   0x0
   0x401375:  push   0x40
   0x401377:  push   0xc5
   0x40137c:  push   0x3e9
   0x401381:  push   DWORD PTR [ebp+0x8]
   0x401384:  call   0x4014e0
   0x401389:  push   0x0
   0x40138b:  push   0x18
   0x40138d:  push   0xc5
   0x401392:  push   0x3ea
   0x401397:  push   DWORD PTR [ebp+0x8]
   0x40139a:  call   0x4014e0
   0x40139f:  push   0x3e8
   0x4013a4:  push   DWORD PTR [ebp+0x8]
   0x4013a7:  push   0x40105d
   0x4013ac:  push   0x50000000
   0x4013b1:  call   0x4014a4
   0x4013b6:  mov    ds:0x404168,eax
   0x4013bb:  push   0x401043
   0x4013c0:  push   0x2
   0x4013c2:  push   0x404
   0x4013c7:  push   eax
   0x4013c8:  call   0x4014e6
   0x4013cd:  push   0x1
   0x4013cf:  push   DWORD PTR ds:0x404164
   0x4013d5:  push   0x30
   0x4013d7:  push   DWORD PTR ds:0x404168
   0x4013dd:  call   0x4014e6
   0x4013e2:  push   0x4010c6
   0x4013e7:  push   0x1
   0x4013e9:  push   0x401
   0x4013ee:  push   DWORD PTR ds:0x404168
   0x4013f4:  call   0x4014e6
   0x4013f9:  leave
   0x4013fa:  ret    0x10
   0x4013fd:  push   0x1f1fff
   0x401402:  push   DWORD PTR [ebp+0x10]
   0x401405:  call   0x40149e
   0x40140a:  push   0x2
   0x40140c:  push   DWORD PTR [ebp+0x10]
   0x40140f:  call   0x401498
   0x401414:  push   0x0
   0x401416:  call   0x401492
   0x40141b:  leave
   0x40141c:  ret    0x10
   0x40141f:  pusha
   0x401420:  mov    ebx,0x401081
   0x401425:  mov    esi,0x4041b8
   0x40142a:  push   esi
   0x40142b:  push   0x19
   0x40142d:  push   0xd
   0x40142f:  push   0x3ea
   0x401434:  push   DWORD PTR [ebp+0x8]
   0x401437:  call   0x4014e0
   0x40143c:  cmp    eax,0x18
   0x40143f:  jne    0x40146e
   0x401441:  mov    edi,0x404170
   0x401446:  push   edi
   0x401447:  push   0x41
   0x401449:  push   0xd
   0x40144b:  push   0x3e9
   0x401450:  push   DWORD PTR [ebp+0x8]
   0x401453:  call   0x4014e0
   0x401458:  cmp    eax,0x5
   0x40145b:  jl     0x40146e
   0x40145d:  push   esi
   0x40145e:  push   eax
   0x40145f:  push   edi
   0x401460:  call   0x401510
   0x401465:  test   eax,eax
   0x401467:  je     0x40146e
   0x401469:  mov    ebx,0x401064
   0x40146e:  push   ebx
   0x40146f:  push   0x0
   0x401471:  push   0x401
   0x401476:  push   DWORD PTR ds:0x404168
   0x40147c:  call   0x4014e6
   0x401481:  popa
   0x401482:  leave
   0x401483:  ret    0x10

Enter fullscreen mode Exit fullscreen mode

This piece of assembly is the core of the CrackMe. The entire application behavior resides here, from keystroke detection to the validation algorithm.

First, we find:

1. Filtering the WM_COMMAND Message (0x004012b2 - 0x004012d5)

0x4012b2: mov eax, DWORD PTR [ebp+0x10] ; eax = wParam
0x4012b5: cmp ax, 0x3ea                 ; Check ID 0x3EA (Serial field) ?
0x4012b9: je  0x4012cd
0x4012bb: cmp ax, 0x3e9                 ; Check ID 0x3E9 (Name field) ?
0x4012bf: je  0x4012cd

Enter fullscreen mode Exit fullscreen mode

The program checks if the message comes from:

  • The Serial field (ID 0x3EA / 1002)
  • The Name field (ID 0x3E9 / 1001)

If so, it extracts the notification code:

0x4012cd: shr eax, 0x10                 ; HIWORD(wParam) = notification code
0x4012d0: cmp eax, 0x300                ; 0x300 = EN_CHANGE
0x4012d5: je  0x40141f                 ; Jump to check!

Enter fullscreen mode Exit fullscreen mode

0x0300 corresponds to the Win32 constant EN_CHANGE. This confirms that validation runs dynamically on every character typed or deleted in either field, without waiting for the Enter key to be pressed.

2. Serial Verification Routine (0x0040141f - 0x00401483)

This is where the application checks the entered data:

Step A: Extracting and checking the Serial (0x3EA)

0x401425: mov esi, 0x4041b8             ; Destination buffer for Serial
0x40142a: push esi
0x40142b: push 0x19                     ; Max size (25 bytes)
0x40142d: push 0xd                      ; Message 0x0D = WM_GETTEXT
0x40142f: push 0x3ea                    ; Serial field ID
0x401434: push DWORD PTR [ebp+0x8]      ; hwnd
0x401437: call 0x4014e0                 ; Wrapper around SendDlgItemMessageA
0x40143c: cmp eax, 0x18                 ; Serial length == 24 chars (0x18) ?
0x40143f: jne 0x40146e                  ; If != 24, immediate failure

Enter fullscreen mode Exit fullscreen mode

Step B: Extracting and checking the Name (0x3E9)

0x401441: mov edi, 0x404170             ; Destination buffer for Name
0x401446: push edi
0x401447: push 0x41                     ; Max size (65 bytes)
0x401449: push 0xd                      ; WM_GETTEXT
0x40144b: push 0x3e9                    ; Name field ID
0x401450: push DWORD PTR [ebp+0x8]      ; hwnd
0x401453: call 0x4014e0
0x401458: cmp eax, 0x5                  ; Name length >= 5 chars ?
0x40145b: jl  0x40146e                  ; If < 5, failure

Enter fullscreen mode Exit fullscreen mode

Step C: Comparison Algorithm (0x00401510)

0x40145d: push esi                      ; Pointer to entered Serial (0x4041b8)
0x40145e: push eax                      ; Length of Name
0x40145f: push edi                      ; Pointer to entered Name (0x404170)
0x401460: call 0x401510                 ; <--- VALIDATION ALGORITHM
0x401465: test eax, eax                 ; Returns 1 (Success) or 0 (Failure)
0x401467: je  0x40146e                  ; Jumps to failure
0x401469: mov ebx, 0x401064             ; Pointer to SUCCESS message

Enter fullscreen mode Exit fullscreen mode

Now, it is very simple: if the goal is to pass the check without looking into the routine, simply replacing je 0x40146e with two nop instructions will do the trick. However, if the goal is to provide a valid Name and correct Serial, we will need to reverse-engineer the verification routine.

5. Epilogue

In this first article on Level 6 crackmes, we have smoothly progressed up to the routine that validates the Serial against the Name.
Nothing overly complicated so far—just a packer and a message loop. When the main difficulty doesn't lie in obfuscation, it resides within the routine that verifies the Serial against the Name. That is what we will explore in a future article.

Top comments (0)