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
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
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
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
Seeing that the Entry Point is at 0x400154, we set a breakpoint:
break *0x400154
continue
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>
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>)
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
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?)
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]
Instruction Analysis:
-
LZMA/LZ77 Decompression Loop (
0x004001b0-0x004001bf): These instructions copy decompressed data blocks viarep 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 toLoadLibraryA(where the return address0x004001c9was captured by our initial breakpoint). 0x004001d6(call DWORD PTR [ebx+0x14]): This is the call toGetProcAddress. The packer iterates through the imported function names and writes their resolved addresses into the real IAT usingstos dword ptr es:[edi], eax(0x004001d9).Jump to OEP (
0x004001d1):jmp DWORD PTR [ebx+0xc]: When the import pointer reaches its end condition (jne 0x4001d4is not taken), the packer executes an indirect jump via theebx+0xcregister. 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
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)
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"
Unfortunately, this returns nothing. Why?
The absence of these strings in a simple memory search is due to two technical reasons:
-
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). -
Memory Encoding: A classic search like
find "Name"searches for the ASCII byte sequence (4e 61 6d 65). In UTF-16LE, the sequence becomes4e 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"
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
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
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
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
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
);
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
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 to0x004012e1(interface initialization, loading images, or hiding/displaying controls). -
0x111(WM_COMMAND) $\rightarrow$ Redirects to0x004012b2. This is precisely the target branch. -
0x02(WM_DESTROY) and0x10(WM_CLOSE) $\rightarrow$ Redirect to0x004012a4to 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
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
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!
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
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
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
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)