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.
First of all, this crackme can be downloaded from crackmes.us. 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:
INT_PTR CALLBACK DialogProc( __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 0x110 — WM_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+10 — wParam.
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.
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 EAX – AL 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; while(true){ if(key[i] == allowed[j]){ buf[i] = j; break; } 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:
- Extended Euclid Algorithm*
- 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; while(val){ temp.push_front(allowed[val%6]); 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:
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.
If you wrote an atricle about life we’d all reach enlightenment.
If you wrote an article about life we’d all be Polish.
So, yeah. I commented here. ;)