Crackme solution: CRACKME.58DD2D69 by zudas

I’ve finally managed to pull my act together and solve a crackme. It was a really simple one (its author rated it 1/10 on the difficulty scale), but I feel it was adequate to my skills. The purpose of this crackme was creating a general key generator, not modifying the executable to accept any key. I’m going to explain how I accomplished that. The only tool I’ve used was OllyDbg.

Warning: it will be all pretty basic stuff, so if you’re an experienced reverse engineer, you’ll be bored out of your mind.

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.

Picture 1. Main window.

When we run the program, we’ll see a simple window with two editboxes and three buttons, as can be seen in Picture 1. What’s interesting for us is code executed when we press the Enter button. There are many ways to find place in the executable where this button is handled — I chose what was, in my opinion, the easiest way: trace usage of error messages. To achieve that, I clicked the Enter button without entering any username or registration number beforehand. Just as expected, I was rewarded with an error message.

Picture 2. Error message: too short username.

Now I needed to find that string in the executable. I had no idea whether it was a wide string or an ansi string, but luck was on my side and it was easy enough to spot at the very top of the data segment (at the address 40308Ch):

Picture 3. Data segment containing the error message.

Having the address, I could try to find all occurences of it being pushed onto the stack (in order to call procedure creating the error messagebox) and put breakpoints on them.

Picture 4. Search

Thankfully, there’s only one occurence, and I am further ensured about that when execution indeed breaks on my breakpoint.

CPU Disasm
Address   Hex dump          Command                                  Comments
00401590  /.  6A FF         PUSH -1
00401592  |.  68 B81C4000   PUSH 00401CB8                            ; Entry point
00401597  |.  64:A1 0000000 MOV EAX,DWORD PTR FS:[0]
0040159D  |.  50            PUSH EAX
0040159E  |.  64:8925 00000 MOV DWORD PTR FS:[0],ESP                 ; Installs SE handler 401CB8
004015A5  |.  83EC 10       SUB ESP,10
004015A8  |.  53            PUSH EBX
004015A9  |.  57            PUSH EDI
004015AA  |.  8BF9          MOV EDI,ECX
004015AC  |.  8D4C24 08     LEA ECX,[LOCAL.6]
004015B0  |.  E8 C5030000   CALL <JMP.&MFC42.#540>                   ; Jump to MFC42.#540
004015B5  |.  33DB          XOR EBX,EBX
004015B7  |.  8D4C24 10     LEA ECX,[ESP+10]
004015BB  |.  895C24 20     MOV DWORD PTR SS:[ESP+20],EBX
004015BF  |.  E8 B6030000   CALL <JMP.&MFC42.#540>                   ; Jump to MFC42.#540
004015C4  |.  8D4C24 0C     LEA ECX,[ESP+0C]
004015C8  |.  C64424 20 01  MOV BYTE PTR SS:[ESP+20],1
004015CD  |.  E8 A8030000   CALL <JMP.&MFC42.#540>                   ; Jump to MFC42.#540
004015D2  |.  8D4424 08     LEA EAX,[ESP+8]
004015D6  |.  8D8F A0000000 LEA ECX,[EDI+0A0]
004015DC  |.  50            PUSH EAX
004015DD  |.  C64424 24 02  MOV BYTE PTR SS:[ESP+24],2
004015E2  |.  E8 BD030000   CALL <JMP.&MFC42.#3874>                  ; Jump to MFC42.#3874
004015E7  |.  8B4424 08     MOV EAX,DWORD PTR SS:[ESP+8]
004015EB  |.  8B48 F8       MOV ECX,DWORD PTR DS:[EAX-8]
004015EE  |.  83F9 0A       CMP ECX,0A
004015F1  |.  7D 0C         JGE SHORT 004015FF
004015F3  |.  53            PUSH EBX
004015F4  |.  53            PUSH EBX
004015F5  |.  68 8C304000   PUSH OFFSET 0040308C                     ; ASCII "Your Username must be at least 10 characters"
004015FA  |.  E9 8E000000   JMP 0040168D
004015FF  |>  56            PUSH ESI
00401600  |.  33F6          XOR ESI,ESI
00401602  |.  49            DEC ECX
00401603  |.  85C9          TEST ECX,ECX
00401605  |.  7E 42         JLE SHORT 00401649

Thanks to Olly, finding the entry point is trivial (just look up), so all that’s left is finding the actual code needed to recreate and reverse whatever algorithm’s used. Again, it’s not a hard task, considering we can see CMP ECX,0A just four lines above our error message. After following the jump below(JGE SHORT 004015FF), we’ll be granted with view of loop that probably takes care of hashing the username (we can safely ignore jump 00401605 |. /7E 42 JLE SHORT 00401649 because there’s no chance it’ll ever going to occur).

CPU Disasm
Address   Hex dump          Command                                  Comments
00401607  |> /8A0C06        /MOV CL,BYTE PTR DS:[EAX+ESI]
0040160A  |. |66:0FBE4406 0 |MOVSX AX,BYTE PTR DS:[EAX+ESI+1]
00401610  |. |66:0FBEC9     |MOVSX CX,CL
00401614  |. |0FAFC1        |IMUL EAX,ECX
00401617  |. |25 FFFF0000   |AND EAX,0000FFFF
0040161C  |. |25 1F000080   |AND EAX,8000001F
00401621  |. |79 05         |JNS SHORT 00401628
00401623  |. |48            |DEC EAX
00401624  |. |83C8 E0       |OR EAX,FFFFFFE0
00401627  |. |40            |INC EAX
00401628  |> |04 41         |ADD AL,41
0040162A  |. |8D4C24 10     |LEA ECX,[ESP+10]
0040162E  |. |884424 18     |MOV BYTE PTR SS:[ESP+18],AL
00401632  |. |8B5424 18     |MOV EDX,DWORD PTR SS:[ESP+18]
00401636  |. |52            |PUSH EDX
00401637  |. |E8 62030000   |CALL <JMP.&MFC42.#940>                  ; Jump to MFC42.#940
0040163C  |. |8B4424 0C     |MOV EAX,DWORD PTR SS:[ESP+0C]
00401640  |. |46            |INC ESI
00401641  |. |8B48 F8       |MOV ECX,DWORD PTR DS:[EAX-8]
00401644  |. |49            |DEC ECX
00401645  |. |3BF1          |CMP ESI,ECX
00401647  |.^\7C BE         \JL SHORT 00401607

Except for the MFC call, everything is trivial in this loop. Fortunately, the only thing MFC42.#940 function does is appending DL to the output string. With that knowledge we can write down what happens in above loop using human understandable language.
For ESI = 0 to length-1 :

  1. Move values from username+ESI and username+ESI+1th character to CX and AX, preserving their signs
  2. Perform signed integer multiplication over EAX and ECX
  3. Perform and operation on EAX and FFFFh
  4. Increment AL by 41h
  5. Append AL to the output string

I’ve ignored 00401621 |. |79 05 |JNS SHORT 00401628 because its AND EAX,8000001Fh will never be signed and thereby jump will always be taken.

We can now implement name hashing, C++ is perfect for that task. I’m going to use an union to abstract over a register:

namespace assembly{
 
typedef union
{
	uint64_t rx;
	uint32_t ex;
	uint16_t x;
	struct {uint8_t l,h;}b;
} Register;
 
}

And here’s the implementation of the name hashing loop:

std::string hash_name(std::string const & name)
{
	using namespace assembly;
	Register a,c;
 
	auto MaxSize = name.size();
	if(MaxSize-- < 10) throw "Username must be at least 10 characters long";
 
	std::string hash;
 
	for(auto i = 0u; i < MaxSize; ++i){
		c.b.l = name[i];
		a.x = name[i+1];
		(int16_t&)c.x = (int8_t)c.b.l;
		(int32_t&)a.ex *= (int32_t)c.ex;
		a.ex &= 0xFFFF;
		a.ex &= 0x1F;
		a.b.l += 0x41;
		hash += a.b.l;
	}
	return hash;
}

We can now check if my implementation is correct (it is), by comparing its output to program’s output. They should be identical. With that done we can look further… and see that the only operation separating us from “Good job cracker!Now make a keygen” messagebox creation is a call to mbscmp.

CPU Disasm
Address   Hex dump          Command                                  Comments
00401649  |> \8D4424 14     LEA EAX,[ESP+14]
0040164D  |.  8D4F 60       LEA ECX,[EDI+60]
00401650  |.  50            PUSH EAX
00401651  |.  E8 4E030000   CALL <JMP.&MFC42.#3874>                  ; Jump to MFC42.#3874
00401656  |.  8B4424 14     MOV EAX,DWORD PTR SS:[ESP+14]
0040165A  |.  5E            POP ESI
0040165B  |.  3958 F8       CMP DWORD PTR DS:[EAX-8],EBX
0040165E  |.  75 09         JNE SHORT 00401669
00401660  |.  53            PUSH EBX
00401661  |.  53            PUSH EBX
00401662  |.  68 64304000   PUSH OFFSET 00403064                     ; ASCII "You have to enter a Registration Number"
00401667  |.  EB 24         JMP SHORT 0040168D
00401669  |>  8B4C24 0C     MOV ECX,DWORD PTR SS:[ESP+0C]
0040166D  |.  50            PUSH EAX                                 ; /string2
0040166E  |.  51            PUSH ECX                                 ; |string1
0040166F  |.  FF15 D0214000 CALL DWORD PTR DS:[<&MSVCRT._mbscmp>]    ; \MSVCRT._mbscmp
00401675  |.  83C4 08       ADD ESP,8
00401678  |.  85C0          TEST EAX,EAX
0040167A  |.  53            PUSH EBX
0040167B  |.  75 09         JNE SHORT 00401686
0040167D  |.  6A 40         PUSH 40
0040167F  |.  68 40304000   PUSH OFFSET 00403040                     ; ASCII "Good job cracker!
Now make a keygen"
00401684  |.  EB 07         JMP SHORT 0040168D

A quick foray into google will tell us (if we didn’t guess it already) that it’s a string comparing function. What’s more, once we step to it, we can see that it’s comparing hashed username with key inserted by user, so our reproduction of name hashing loop is already a working keygen. Probably that’s the reason why this crackme was rated only 1/10.

At this point, we already have a working keygen, so the crackme should be considered solved. Have fun, here’s a sample username with key:
krzaqrocks
GU[RSONBR
After entering these we should be rewarded with success message.

Picture 5. Success!

Leave a Reply

Your email address will not be published.