Making a Keygen for CrackHead by Muad'Dib
 
 
Tools needed:

IDA or W32DASM (I use IDA)
A Windows Assembler
1/2 of a brain

Disassemble the crackme with IDA or W32DASM (I use IDA) and go to the string references.  Look through the strings, the following one looks interesting to us:

"Now write a keygen and tut and you're done."

Go to that section of the code and you see this:
 
0040135F                         loc_40135F:                             ; CODE XREF: .text:00401331j
0040135F 6A 00                        push 0
00401361 68 0F 30 40 00               push offset aCruddSCrackHea        ; "Crudd's Crack Head"
00401366 68 33 13 40 00               push offset aNowWriteAKeyge        ; "Now write a keygen and tut
0040136B FF 75 08                     push dword ptr [ebp+8]
0040136E E8 19 01 00 00               call j_MessageBoxA
00401373
00401373                         loc_401373:                             ; CODE XREF: .text:00401306j

 

Let's take a look at the cross reference at 401331:
 
00401316 6A 28                        push 28h
00401318 68 C4 33 40 00               push offset unk_4033C4
0040131D FF 35 90 31 40 00            push dword_403190
00401323 E8 4C 01 00 00               call j_GetWindowTextA
00401328 E8 A5 00 00 00               call sub_4013D2
0040132D 3B C6                        cmp eax, esi
0040132F 75 42                        jnz short loc_401373
00401331 EB 2C                        jmp short loc_40135F

Well, the easiest thing to do would be to NOP the JNZ, but that's
not the goal of the crackme, so we'll take a look at sub_4013D2.
We know that the serial that the user enters is in 4033C4h (because
we know our windows API functions well, right? ;)  so here's the
code in sub_4013D2:
 
004013D2                         sub_4013D2 proc near                    ; CODE XREF: .text:00401328
004013D2 56                           push esi
004013D3 33 C0                        xor eax, eax
004013D5 8D 35 C4 33 40 00            lea esi, ds:4033C4h
004013DB 33 C9                        xor ecx, ecx
004013DD 33 D2                        xor edx, edx
004013DF 8A 06                        mov al, [esi]
004013E1 46                           inc esi
004013E2 3C 2D                        cmp al, 2Dh
004013E4 75 08                        jnz short loc_4013EE
004013E6 BA FF FF FF FF               mov edx, 0FFFFFFFFh
004013EB 8A 06                        mov al, [esi]
004013ED 46                           inc esi
004013EE
004013EE                         loc_4013EE:                             ; CODE XREF: sub_4013D2+12j
004013EE EB 0B                        jmp short loc_4013FB
004013F0                         ; ------------------------------------------------------------------
004013F0
004013F0                         loc_4013F0:                             ; CODE XREF: sub_4013D2+2Bj
004013F0 2C 30                        sub al, 30h
004013F2 8D 0C 89                     lea ecx, [ecx+ecx*4]
004013F5 8D 0C 48                     lea ecx, [eax+ecx*2]
004013F8 8A 06                        mov al, [esi]
004013FA 46                           inc esi
004013FB
004013FB                         loc_4013FB:                             ; CODE XREF: sub_4013D2+1Cj
004013FB 0A C0                        or al, al
004013FD 75 F1                        jnz short loc_4013F0
004013FF 8D 04 0A                     lea eax, [edx+ecx]
00401402 33 C2                        xor eax, edx
00401404 5E                           pop esi
00401405 81 F6 53 75 7A 79            xor esi, 797A7553h
0040140B C3                           retn
0040140B                         sub_4013D2 endp

Well, it moves the address of the user's serial into ESI and then compares byte by byte to 2Dh or '-'.  If it finds one then it jumps to 4013EE which skips moving a new byte of the user serial into AL and the moving of a value into EDX.  No matter what it jumps to 4013FB.  At 4013FB we see some code that checks if AL is zero.  If not, it jumps to 4013F0 which does some multiplication.  If so, it adds EDX and ECX into EAX, XORs eax with EDX, and then XORs ESI with that weird number.  Then it returns.  But what's in ESI after the POP?

Time to look back a bit.  Return from the procedure and just before the GetWindowTextA call, we see this:
 
.text:00401310 8B 35 9C 33 40 00            mov esi, dword_40339C

What's in 40339C?  Well, go to it and check the cross references...
 
 r .text:00401310   mov esi, dword_40339C
 o .text:00401425   push offset dword_40339C
 w .text:0040144E   mov dword_40339C, edi

Number three seems to be the one we're interested in because that is where it is being written to.  Go to the location and you land in a procedure.  Here is the procedure:
 
 
0040140C                         sub_40140C proc near                    ; CODE XREF: start+11p
0040140C 60                           pusha
0040140D 6A 00                        push 0
0040140F E8 B4 00 00 00               call j_GetDriveTypeA
00401414 A2 EC 33 40 00               mov byte_4033EC, al

 

Lets take a look at the API for GetDriveTypeA:
 
 
The GetDriveType function determines whether a disk drive is a removable,
fixed, CD-ROM, RAM disk, or network drive.

UINT GetDriveType(

    LPCTSTR lpRootPathName  // address of root path
   );
Parameters

lpRootPathName

Points to a null-terminated string that specifies the root directory of the
disk to return information about. If lpRootPathName is NULL, the function uses the
root of the current directory.


 

Well, it gets the drive that the crackme is on and then moves the type into 4033EC.  Then it does some stuff with GetVolumeInformationA.
 
 
00401419 6A 00                        push 0
0040141B 6A 00                        push 0
0040141D 6A 00                        push 0
0040141F 6A 00                        push 0
00401421 6A 00                        push 0
00401423 6A 0B                        push 0Bh
00401425 68 9C 33 40 00               push offset dword_40339C
0040142A 6A 00                        push 0
0040142C E8 A3 00 00 00               call j_GetVolumeInformationA

 
 
 
BOOL GetVolumeInformation(

    LPCTSTR lpRootPathName, // address of root directory of the file system
    LPTSTR lpVolumeNameBuffer, // address of name of the volume
    DWORD nVolumeNameSize, // length of lpVolumeNameBuffer
    LPDWORD lpVolumeSerialNumber, // address of volume serial number
    LPDWORD lpMaximumComponentLength, // address of system’s maximum filename length
    LPDWORD lpFileSystemFlags, // address of file system flags
    LPTSTR lpFileSystemNameBuffer, // address of name of file system
    DWORD nFileSystemNameSize  // length of lpFileSystemNameBuffer
   );

Well, it seems that he only wants the first 11 characters of the name into 40339C.
 
00401431 8D 35 9C 33 40 00            lea esi, ds:40339Ch
00401437 0F B6 0D EC 33 40 00         movzx ecx, byte_4033EC
0040143E 33 FF                        xor edi, edi
00401440
00401440                         loc_401440:                             ; CODE XREF: sub_40140C+40j
00401440 8B C1                        mov eax, ecx
00401442 8B 1E                        mov ebx, [esi]
00401444 F7 E3                        mul ebx
00401446 03 F8                        add edi, eax
00401448 49                           dec ecx
00401449 83 F9 00                     cmp ecx, 0
0040144C 75 F2                        jnz short loc_401440
0040144E 89 3D 9C 33 40 00            mov dword_40339C, edi
00401454 61                           popa
00401455 C3                           retn
00401455                         sub_40140C endp

Next, it starts manipluating the data of the volume name with the type of drive doing multiplications, additions, and uses the number retrieved with GetDriveTypeA as the counter.  Well, now that we've got the code, we can make a keygen.  Remember that we have to XOR the result with 797A7553h because of the check.
 
 
.386
.model flat,stdcall
option casemap:none

include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc

includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib

.data

decspec     db  "%lu",0
strspec     db  "%s",0
_title      db  "Your serial is:",0

.data?

drivename   db  0Ch dup(?)
serial      db  0Ch dup(?)
drivetype   db  ?

.code
main:
    invoke  GetModuleHandle, 0
    invoke  GetDriveType, 0
    mov     drivetype,al
    invoke  GetVolumeInformation, 0, OFFSET drivename, 0Bh, 0, 0, 0, 0, 0
    lea     esi, drivename
    movzx   ecx, drivetype
    xor     edi, edi
@loop:
    mov     eax,ecx
    mov     ebx,[esi]
    mul     ebx
    add     edi,eax
    dec     ecx
    jnz     @loop
    xor     edi, 797A7553h
    invoke  wsprintf,OFFSET serial, OFFSET decspec, edi
    invoke  wsprintf,OFFSET serial, OFFSET strspec, OFFSET serial
    invoke  MessageBox, 0, OFFSET serial, OFFSET _title, 0
    invoke  ExitProcess, 0

end main


 

There you have it.  The keygen.  Easy enough, right?  Well, this was an interesting crackme (I like HD serial stuff, very interesting :) but I'd only rate it about 2/10 for difficulty.  The complete newbie couldn't do it beacause the generation spawns in two different locations (kind of) but it was very easy.  Hope you enjoyed it! :)

If you have any questions mail me at muaddib(at)immortaldescendants(dot)org.  Please do NOT send me crack requests or try to recruit me for any group.  I will NOT fulfill the requests and the E-Mail will be forwarded to the company who made the program you requested a crack for.

http://crackmes.cjb.net/
http://muad.cjb.net/