Cruehead CrackMe v1.0/v2.0/v3.0 - Tutorial

Cruehead CrackMe v1.0/v2.0 & v3.0 - CrackMe Archive (180k).

I always recommend that you try your hand at these CrackMe's, this example by Cruehead will introduce the simplest concept of encryption, specifically the logic operation XOR. So start up the CrackMe and insert some details into the dialog boxes and >bpx Hmemcpy or GetDlgItemTextA, just start stepping with F10 or use F12 to reach the following code (you'll go through User & Kernel32):

:00401223 CMP EAX,00000000
:00401226 JE 004011E6 <-- No jump.
:00401228 PUSH 0040218E <-- Name entered.
:0040122D CALL 0040137E <-- Function to trace.
:00401232 PUSH EAX <-- Save XOR result of name on stack.
:00401233 PUSH 0040217E <-- Serial # entered.
:00401238 CALL 004013D8 <-- Function to trace.
:0040123D ADD ESP,04 <-- Stack Tidy.
:00401240 POP EAX
:00401241 CMP EAX,EBX <-- Compare.
:00401243 JZ 0040124C <-- Jump_good_code.

So its very easy to see how this scheme can be beaten, however the purpose of a CrackMe is to fully investigate how the protection works. So lets have a trace of 0040137E & 004013D8.

:0040137E MOV ESI,[ESP+04] <-- Name entered.
:00401382 PUSH ESI <-- Stack it.
:00401383 MOV AL,[ESI] <-- AL = 1st letter of name.
:00401385 TEST AL,AL <-- Was it 0.
:00401387 JZ 0040139C <-- Jump_if_it_was.
:00401389 CMP AL,41 <-- Compare it with 41 or 'A'.
:0040138B JB 004013AC <-- Jump_below_'A'.
:0040138D CMP AL,5A <-- Compare it with 5A or 'Z'.
:0040138F JNB 00401394 <-- Jump_if_not_below_'Z'.
:00401391 INC ESI <-- Name counter.
:00401392 JMP 00401383 <-- Repeat test.
:00401394 CALL 004013D2 <-- Jump here if below 'Z'.
:00401399 INC ESI <-- Name counter.
:0040139A JMP 00401383 <-- Repeat test.

So this portion of code will check whether all of the names characters were indeed letters between A and Z, if the letter is lower case the CALL at 00401394 is executed, that consists of the classic SUB AL,20 i.e. uppercasing routine. So at the end our name is completely uppercased, we continue (still inside CALL 0040137E) with a CALL to function 004013C2.

:004013C2 XOR EDI,EDI <-- EDI=0.
:004013C4 XOR EBX,EBX <-- EBX=0.
:004013C6 MOV BL, BYTE PTR [ESI] <-- Pointer to name.
:004013C8 TEST BL,BL <-- End_of_name_check.
:004013CA JZ 004013D1 <-- Jump_loop_finished.
:004013CC ADD EDI,EBX <-- Add value to EDI which was zero.
:004013CE INC ESI <-- Counter.
:004013CF JMP 004013C6 <-- Loop_again.

So this function will sum up all of the letters in your name and place the result in EDI. In my case:

CRACKZ = 43 + 52 + 41 + 43 + 4B + 5A = 19Eh.

At the end, we have our critical operation:

:004013A2 XOR EDI,00005678 <-- XOR total of name characters.
:004013A8 MOV EAX,EDI <-- Move it to EAX.

In my case the result of this XOR operation is 57E6h, you should then see that this result is saved away on the stack. Now lets trace 004013D8:

:004013D8 XOR EAX,EAX <-- EAX=0.
:004013DA XOR EDI,EDI <-- EDI=0.
:004013DC XOR EBX,EBX <-- EBX=0.
:004013DE MOV ESI,[ESP+04] <-- Serial # entered.
:004013E2 MOV AL,0A <-- AL=10.
:004013E4 MOV BL, BYTE PTR [ESI] <-- Pointer to ESI.
:004013E6 TEST BL,BL <-- Check.
:004013E8 JZ 004013F5 <-- Finished_loop.
:004013EA SUB BL,30
:004013F0 ADD EDI,EBX <-- EDI used for result storage (again).
:004013F2 INC ESI <-- Name counter.
:004013F3 JMP 004013E2 <-- Repeat.
:004013F5 XOR EDI,00001234 <-- Critical XOR.
:004013FB MOV EBX,EDI <-- Place it in EBX and return for the compare.

Again we have a loop operation, this time upon our entered serial #, actually all this does is convert our decimal number into HEX, so with 123456 I get the result 1e240h which is then XOR-ed with 00001234 to give 1f074h. So for a correct serial # this result must actually equal 57e6h (the result obtained from the name).

So lets work out my good code:

57E6h XOR 1234h = 45D2h = 17874

XOR - Notes

Just a side note for those of you unfamiliar with logic operations, below I've given you the XOR gate truth table, where a and b are inputs and x is the result obtained by XORing them:

a b x
0 0 0
0 1 1
1 0 1
1 1 0

So let me show you how I calculated my result:

5 7 E 6 = (binary) 0101 0111 1110 0110
1 2 3 4 = (binary) 0001 0010 0011 0100

> Result of XOR = 0100 0101 1101 0010 = 45D2h = 17874 decimal.

Cruehead CrackMe v2.0 - Tutorial

Another Cruehead CrackMe (this time v2.0), which yet again demonstrates the use of the XOR logic operation. Launch the CrackMe and enter a password, I used CrackZ, as with CrackMe v1.0 you should easily trace to the following code, just >bpx GetDlgItemTextA or Hmemcpy and step (the pattern is almost identical to CrackMe v1.0):

:00401228 PUSH 0040217E <-- Password entered pushed to stack.
:0040122D CALL 00401365 <-- Trace this.
:00401232 PUSH 0040217E <-- Result of XOR-ing password entered.
:00401237 CALL 004013B8 <-- Trace this. (Compare XOR results).
:0040123C ADD ESP,04 <-- Tidy stack.
:0040123F TEST CL,CL <-- CX-low test for 0.
:00401241 JZ 0040124A <-- Jump_good_password.

Tracing the first part of 00401365 will take you through the same uppercasing routine which we saw in CrackMe v1.0. At address 00401390 each character in our entered password will be XOR-ed with the string Messing_in_bytes, this should immediately tell you that our good password must be the same length as the XOR key i.e. 16. The result of Xor-ing our entered password is then placed at address 0040217E before being compared with the correct XOR result in function 004013B8.

So you should be able to read from the data window the desired XOR result which we should get when we XOR the correct password with Messing_in_bytes. Therefore you should deduce that to work out the correct password we need to XOR Messing_in_bytes with the desired result which we read from EDI in function call 004013B8. I've shown the method below.

Messing_in_bytes = 4D 65 73 73 69 6E 67 5F 69 6E 5F 62 79 74 65 73
Correct password XOR result = 1F 2C 37 36 3B 3D 28 19 3D 26 1A 31 2D 3B 37 3E

So the first letter of the good password would be:


0100 1101 <-- 4D
0001 1111 <-- 1F

0101 0010 <-- 52 = R

If you continue to XOR the remaining 15 characters you'll find that the good password is RIDERSOFTHESTORM and as we know the program will uppercase the password before XOR-ing, it is not case-sensitive.

Cruehead CrackMe v3.0 - Tutorial

Welcome to what would seem to be the final Cruehead CrackMe. This CrackMe presents a different challenge to the previous versions in that our task is to create a working keyfile for the program. The first thing you should find out is that the name of our missing file is crackme3.key, use a file monitoring tool or just disassemble if you want to verify this.

So lets create an empty crackme3.key file. Now we need to use SoftICE to intercept the reading of the key file, you can use ReadFile however I strongly recommend CreateFileA which is used unsurprisingly for creating and opening files (check the disassembly also). So lets >bpx CreateFileA and launch the CrackMe. You should break on this code:

:00401032 CMP EAX,-01 <-- If EAX is returned -1 then the file doesn't exist.
:00401035 JNZ 00401043 <-- Jump_crackme3.key_exists.
:00401043 MOV [004020F5],EAX
:00401048 MOV EAX,00000012 <-- Number of bytes to read from file.
:0040104D MOV EBX,00402008
:00401052 PUSH 00 <-- Now the program is pushing all the parameters for ReadFile.
:00401054 PUSH 004021A0
:00401059 PUSH EAX
:0040105A PUSH EBX
:0040105B PUSH DWORD PTR [004020F5]
:00401061 CALL KERNEL32!ReadFile <-- API call to read file.
:00401066 CMP DWORD PTR [004021A0],12 <-- Compare for 12h (18 decimal).
:0040106D JNZ 00401037 <-- Jump_crackme3.key_was_wrong_length.

Now lets modify our key file so that it is 18 bytes in length, it will now pass the 2 validity checks above. After the length check you should proceed to trace CALL 00401311:

:00401315 MOV ESI,[ESP+04] <-- ESI holds crackme3.key contents.
:00401319 MOV BL,41 <-- BX-low moved to 41h.
:0040131B MOV AL,[ESI] <-- AX-low moved to byte from crackme3.key.
:0040131D XOR AL,BL <-- XOR.
:0040131F MOV [ESI], AL <-- Save XOR result.
:00401321 INC ESI <-- Increment ESI to next byte.
:00401322 INC BL <-- Increment BL from 41h to 42h for next byte.
:00401324 ADD DWORD PTR [004020F9], EAX <-- checksum.
:0040132A CMP AL,00 <-- Finished decrypting.
:0040132C JZ 00401335 <-- Jump_finished_decrypt.
:0040132E INC CL <-- Increment CX-low.
:00401330 CMP BL,4F <-- Is BX-low 4F. (i.e. 4F-41 = 14).
:00401333 JNZ 0040131B <-- Loop_again_if_not.
:00401335 MOV [00402149],ECX

After this simple decryption you'll return from the function at this code:

:00401079 XOR DWORD PTR [004020F9],12345678 <-- Sum of decrypted bytes XORed with 12345678.
:0040108B CALL 0040133C <-- Last 4 bytes of keyfile.
:00401093 CMP EAX, [004020F9] <-- Compare.
:00401099 SETE AL <-- Set AX-low to 1 if the compare was good.
:0040109C PUSH EAX
:0040109D TEST AL,AL <-- Test AX=low for 0.
:0040109F JZ 00401037 <-- No_jump_and_we've_cracked_it.

Now that we have examined the code lets start to construct our valid keyfile, in this case for the name CrackZ. We know that the first 14 bytes determine the name and that it works on a simple character by character XOR starting with 41h. So for CrackZ our keyfile must look like the following:

CrackZ = 43 72 65 63 6B 5A
XOR with: 41 42 43 44 45 46
Result: 02 30 22 27 2E 1C <-- Use SoftICE if you want to quickly work out this result.


72h XOR 42h = 30h

0111 0010 = 72h (114 decimal)
0100 0010 = 42h (66 decimal)
0011 0000 = 30h (48 decimal)

We now need to terminate our name by ensuring that the next character produces an XOR result of zero, so our next value must be 47 i.e. XOR 47,47=0, the remaining 7 bytes can be left blank as they will not be checked (If you want to do this easily I recommend using Hiew in HEX mode). The last 4 digits however must be the sum of the decrypted values XOR-ed with 12345678.

So the sum of our decrypt = 23E (Tip: Obtain this value from DS:004020F9 from the SoftICE register window).

Now we need to XOR this result 23E with 12345678.


So the last 4 bytes of the keyfile for me are 46 54 34 12.

CrackMe 3.0 Registered Picture

Back to CrackMe's

© 1998,1999, 2000 CrackZ. June 1998.