Crackme solution: Crypto 4 Newbies – #1 by Zuma

Another simple crackme I’ve managed to solve. The crackme’s author didn’t rate it explicitly, but as it’s supposed to be for newbies, I think it’s safe to assume that it’s somewhere around 1/10. The task was again to create a keygen, not a modified executable.

Picture 1. Main window.

First of all, this crackme can be downloaded from If you’re going to try to solve it by yourself, I suggest you avoid reading the rest of this post, as it’ll just spoil the fun.

Our first point of order is locating the procedure handling the Validate button. Fortunately, that’s quite easy, because the dialog is created right from the entry point, using the DialogBoxParamA WinApi function. If we look at its declaration, we can easily spot the interesting part in its argument list: __in_opt DLGPROC lpDialogFunc. It’s basically an (optional) address of procedure handling this control’s events.

CPU Disasm
Address   Hex dump          Command                                  Comments
00401000  /.  E8 69030000   CALL <JMP.&comctl32.InitCommonControls>  ; Jump to comctl32.InitCommonControls
00401005  |.  6A 00         PUSH 0                                   ; /ModuleName = NULL
00401007  |.  E8 56030000   CALL <JMP.&kernel32.GetModuleHandleA>    ; \KERNEL32.GetModuleHandleA
0040100C  |.  A3 E0324000   MOV DWORD PTR DS:[4032E0],EAX
00401011  |.  6A 00         PUSH 0                                   ; /InitParam = 0
00401013  |.  68 30104000   PUSH 00401030                            ; |DialogProc = cryptokg1_fixedecx.401030
00401018  |.  6A 00         PUSH 0                                   ; |hParent = NULL
0040101A  |.  68 E9030000   PUSH 3E9                                 ; |TemplateName = 3E9
0040101F  |.  FF35 E0324000 PUSH DWORD PTR DS:[4032E0]               ; |hInst = NULL
00401025  |.  E8 08030000   CALL <JMP.&user32.DialogBoxParamA>       ; \USER32.DialogBoxParamA
0040102A  |.  50            PUSH EAX                                 ; /ExitCode
0040102B  |.  E8 2C030000   CALL <JMP.&kernel32.ExitProcess>         ; \KERNEL32.ExitProcess

Getting the address is trivial now: PUSH 00401030 ; |DialogProc = cryptokg1_fixedecx.401030. This procedure is slightly larger, but what we really need to know is the declaration it has to fit:

  __in  HWND hwndDlg,
  __in  UINT uMsg,
  __in  WPARAM wParam,
  __in  LPARAM lParam

Arguments that are interesting to us are __in UINT uMsg and __in WPARAM wParam. The first one will contain ids of window messages — including the most interesting one: WM_COMMAND (expands to 0x111), and the second one will contain the id of the control receiving the message. We’d be all set for finding the procedure handling the Validate button, but we don’t know its id. The simplest way of finding it is setting a breakpoint on WM_COMMAND handling code in the DialogProc callback, clicking the right button and checking the uMsg parameter’s value.

Since uMsg is the second argument, we know it’s located at EBP+0Ch, so all we have to do is look for cmp instruction on that address against 111h. First check we can see is the following:

CPU Disasm
Address   Hex dump             Command                                  Comments
00401033  |.  817D 0C 10010000 CMP DWORD PTR SS:[EBP+0C],110
0040103A  |.  75 46            JNE SHORT 00401082

It’s a check for 0x110WM_INITDIALOG. We’re close. Once we follow the conditional jump, we can see what we’ve been looking for:

CPU Disasm
Address   Hex dump             Command                                  Comments
00401082  |> \817D 0C 11010000 CMP DWORD PTR SS:[EBP+0C],111
00401089  |.  75 34            JNE SHORT 004010BF
0040108B  |.  8B45 10          MOV EAX,DWORD PTR SS:[EBP+10]

Once we put a breakpoint on 0040108B and click the right button, we will get its id from EBP+10wParam.

Picture 2. Success! Control id found.

Okay, so the id is 0x3EB, and as our luck would have it, there’s a check for it right below.

CPU Disasm
Address   Hex dump             Command                                  Comments
0040108B  |.  8B45 10          MOV EAX,DWORD PTR SS:[EBP+10]
0040108E  |.  3D EB030000      CMP EAX,3EB
00401093  |.  75 0A            JNE SHORT 0040109F
00401095  |.  FF75 08          PUSH DWORD PTR SS:[EBP+8]                ; /Arg1
00401098  |.  E8 3F000000      CALL 004010DC                            ; \cryptokg1_fixedecx.004010DC
0040109D  |.  EB 30            JMP SHORT 004010CF

All we have to do now is follow the CALL 004010DC and we’ll see the juicy part of this crackme. Finally.

First thing we notice are two calls to GetDlgItemTextA WinAPI function. Finding out what they do is as simple as letting the program run and breaking right after. Olly is even nice enough to update the string values.

Picture 3. Situation right after GetDlgItemA calls.

After that, we can see a long list of instructions that are only there for purposes of obfuscation.

CPU Disasm
Address   Hex dump             Command                                  Comments
00401107  |.  33C0             XOR EAX,EAX
00401109  |.  33DB             XOR EBX,EBX
0040110B  |.  33C9             XOR ECX,ECX
0040110D  |.  33D2             XOR EDX,EDX
0040110F  |.  05 DEC0EFBE      ADD EAX,BEEFC0DE
00401114  |.  03C8             ADD ECX,EAX
00401116  |.  0FC8             BSWAP EAX
0040118E  |.  0FC8             BSWAP EAX
00401190  |.  68 E8324000      PUSH OFFSET 004032E8                     ; /String = "username"
00401195  |.  E8 CE010000      CALL <JMP.&kernel32.lstrlenA>            ; \KERNEL32.lstrlen
0040119A  |.  8BD0             MOV EDX,EAX
0040119C  |.  BB 00000000      MOV EBX,0
004011A1  |.  B9 F6BD807C      MOV ECX,7C80BDF6
004011A6  |.  E9 D5010000      JMP 00401380

The call to lstrlenA overwrites EAX with its return — all that byteswapping was just for show. After that, EAX (length of username) is copied to EDX, EBX is zeroed and ECX set to 0x7C80BDF6 and off to the hashing procedure we jump!

CPU Disasm
Address   Hex dump             Command                                  Comments
00401380  /> >0283 E8324000    /ADD AL,BYTE PTR DS:[EBX+4032E8]
00401386  |. |03C1             |ADD EAX,ECX
00401388  |. |0FAFC2           |IMUL EAX,EDX
0040138B  |. |03C0             |ADD EAX,EAX
0040138D  |. |43               |INC EBX
0040138E  |. |4A               |DEC EDX
0040138F  |.^\75 EF            \JNE SHORT 00401380
00401391  \.^ E9 15FEFFFF      JMP 004011AB

It’s self-explanatory and pretty easy to recreate — unless we need to understand exactly what’s happening here, but for now we do not. After the name is hashed, the result is stored in EAX and we jump back right after we left. The first thing we can notice is that only the low byte of EAXAL is used, and everything else is discarded. What we should also notice is overwriting first letter of the username with its hash.

CPU Disasm
Address   Hex dump             Command                                  Comments
004011B2  |.  A2 E8324000      MOV BYTE PTR DS:[4032E8],AL
004011B7  |.  A2 E8344000      MOV BYTE PTR DS:[4034E8],AL
004011BC  |.  B8 00000000      MOV EAX,0
004011C1  |.  33C0             XOR EAX,EAX

Right after that, we can see two copy loops — first broken after encountering 0x2D — the hyphen character in ASCII, and the second after encountering NULL. From that we can partially deduce the proper format of the key string — it has to contain two substrings separated by a hyphen.

CPU Disasm
Address   Hex dump             Command                                  Comments
004011C3  |> /8A98 E8334000    /MOV BL,BYTE PTR DS:[EAX+4033E8]
004011C9  |. |80FB 2D          |CMP BL,2D
004011CC  |. |74 09            |JE SHORT 004011D7
004011CE  |. |8898 68334000    |MOV BYTE PTR DS:[EAX+403368],BL
004011D4  |. |40               |INC EAX
004011D5  |.^\EB EC            \JMP SHORT 004011C3
004011D7  |>  33C9             XOR ECX,ECX
004011D9  |>  8A98 E9334000    /MOV BL,BYTE PTR DS:[EAX+4033E9]
004011DF  |.  80FB 00          |CMP BL,0
004011E2  |.  74 0A            |JE SHORT 004011EE
004011E4  |.  8899 68344000    |MOV BYTE PTR DS:[ECX+403468],BL
004011EA  |.  41               |INC ECX
004011EB  |.  40               |INC EAX
004011EC  |.^ EB EB            \JMP SHORT 004011D9

Just a few instructions later, we can learn even more about the supposed format. This is how the first part of the key string is processed:

CPU Disasm
Address   Hex dump             Command                                  Comments
004011EE  |> \68 68334000      PUSH OFFSET 00403368                     ; /String
004011F3  |.  E8 70010000      CALL <JMP.&kernel32.lstrlenA>            ; \KERNEL32.lstrlen
0040120C  |>  8A99 68334000    /MOV BL,BYTE PTR DS:[ECX+403368]
00401212  |.  33D2             |XOR EDX,EDX
00401214  |.  4A               |DEC EDX
00401215  |>  42               |/INC EDX
00401216  |.  3A9A D5324000    ||CMP BL,BYTE PTR DS:[EDX+4032D5]        ; ASCII "XZMAJK"
0040121C  |.^ 75 F7            |\JNE SHORT 00401215
0040121E  |.  8891 68334000    |MOV BYTE PTR DS:[ECX+403368],DL
00401224  |.  41               |INC ECX
00401225  |.  48               |DEC EAX
00401226  |.^ 75 E4            \JNE SHORT 0040120C

And after translation to C++ pseudo-code:

// key - first part of the key string
// allowed - ASCII "XZMAJK"
for(int i = 0; i < key.size(); i++){
	int j = 0;
		if(key[i] == allowed[j]){
			buf[i] = j;

Basically, it will replace characters with their index in “XZMAJK” string, or — if we use a character outside of the allowed range — potentially cause memory access violation. After that, we copy back string length to CL and proceed with such loop:

CPU Disasm
Address   Hex dump             Command                                  Comments
00401245  |.  8A0D E9324000    MOV CL,BYTE PTR DS:[4032E9]
0040124B  |.  49               DEC ECX
0040124C  |.  0FBE05 68334000  MOVSX EAX,BYTE PTR DS:[403368]
00401253  |>  BA 06000000      /MOV EDX,6
00401258  |.  0FAFD0           |IMUL EDX,EAX
0040125B  |.  0FBEBB 69334000  |MOVSX EDI,BYTE PTR DS:[EBX+403369]
00401262  |.  03D7             |ADD EDX,EDI
00401264  |.  43               |INC EBX
00401265  |.  8BC2             |MOV EAX,EDX
00401267  |.  49               |DEC ECX
00401268  |.^ 75 E9            \JNE SHORT 00401253

Which is equal to the following C++ code:

// key - first part of the key string transformed to indexes of "XZMAJK" string
eax = key[0];
for(int i = 1; i < key_size; i++){
	eax = eax * 6 + key[i];

Now, we need to remember that “XZMAJK” contains six characters and that we posited that it’d be unwise to use any others. Combined with code above, it’s clear that the first part of the key string is treated as a base six number with decimal characters 0,1,2,3,4,5 substituted by X,Z,M,A,J,K in that order.

Fortunately for us, the exact same thing happens to the second part of the key string, so I’ll leave that out and jump straight to the next step — the last one separating us from the success messagebox.

CPU Disasm
Address   Hex dump             Command                                  Comments
004012F2  |.  A1 E8324000      MOV EAX,DWORD PTR DS:[4032E8]
004012F7  |.  69C0 F20A0000    IMUL EAX,EAX,0AF2
004012FD  |.  8B1D EC324000    MOV EBX,DWORD PTR DS:[4032EC]
00401303  |.  69DB 30010000    IMUL EBX,EBX,130
00401309  |.  2BD8             SUB EBX,EAX
0040130B  |.  33D2             XOR EDX,EDX
0040130D  |.  8A15 E8344000    MOV DL,BYTE PTR DS:[4034E8]
00401313  |.  6BD2 02          IMUL EDX,EDX,2
00401316  |.  3BDA             CMP EBX,EDX
00401318  |.  75 14            JNE SHORT 0040132E

This is fairly simple. We move decoded value of first part of the key string, multiply it by 0xAF2 and store in EAX. Then we take decoded value of the second part, multiply it by 0x130, store in EBX and subtract EAX from EBX. After that, we retrieve stored value of hashed username, multiply it by two and compare against EBX. We need them to be equal.

It’s fairly simple to achieve, but first we need to write this down as an equation:
A — username hash
B — first decoded part of key string
C — second decoded part of key string
A*2 = C*0x130 – B*0xAF2
Assuming this crackme is solvable, there at least two ways to get the correct results:

  1. Extended Euclid Algorithm*
  2. Brute Force

I’ve proceeded with the first — analytical — option, but both should be fast enough on modern computers. I won’t bore anyone with the details, but the result of my calculations was the following:
A*2 = C*0x130 – B*0xAF2 will always be true if C = 23*A and B = 212*A

It seems to be done, let’s check if we can use that to write a key generator:

u8 username_hash(std::string const &name);
std::string make_XZMAJK_string_base_six(i32 val);
std::string keygen(std::string const &name);
std::string keygen(const std::string &name)
	using namespace assembly;
	u8 nameHash = username_hash(name);
	return make_XZMAJK_string_base_six(23*nameHash)+"-"+make_XZMAJK_string_base_six(212*nameHash);
u8 username_hash(std::string const & name)
	using namespace assembly;
	i32 slen = name.size();
	Register val = {slen};
	for(u32 i(0); i < slen; i++){
		val.b.l += (u8)name[i];
		val.ex += 0x7C80BDF6;
		(i32&)val.ex *= (i32)(slen-i);
		val.ex += val.ex;
	return val.b.l;
std::string make_XZMAJK_string_base_six(i32 val)
	const char allowed[] = "XZMAJK";
	std::list<char> temp;
		val /= 6;
	return std::string(temp.begin(),temp.end());

For username krzaq we get key MMZAJ-AAKAXJ. If we check it, we’ll get the following result:

Picture 4. Success!.

Succcess! We’ve managed to solve the crackme and create a key generator for it.

Working source for the keygen — link.

*thanks to Zordon for suggesting that.

3 thoughts on “Crackme solution: Crypto 4 Newbies – #1 by Zuma

Leave a Reply

Your email address will not be published.