Thursday, December 22, 2011

Solution to Virw's xc2 keygenme

About one year ago, I submitted on crackmes.de this solution to the keygenme xc2 made by Virw. Unfortunately, crackmes.de has been down for months by now, so I guess there is no harm to put both the challenge and the solution on this blog.

Keywords: keygen, windows, crc32.

The difficulty of this keygenme does not reside in its protection scheme, which is actually quite straightforward, but rather in the length of the key and the multiple algorithms used. This goes from a simple char comparison against hardcoded values, to more advanced algos such as crc32, which is probably the most interesting thing about this chall.

To give you an idea, this chall was rated as 3-Getting Harder on crackmes.de.

Downloads:

Keygenme: virw-s-xc2-keygenme.zip
MD5: 877adf1fcd123faf67acd02707de9316
SHA-256: 4623a2c7cfbb28138eae9fd925a08754f2c1af1820fc921fc56be8b9b9e3febf

Solution: virw-s-xc2-solution.zip
MD5: 9d6e3d3023a5b293570e4ca8797a7d55
SHA-256: e47745f8309145316f676805e0acfa0b9396fe4f45ca3c7fd21ed3323078bc9a

Have fun. Any comments are appreciated.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Tools used  :  OllyDbg
~~~~~~~~~~~~~~~~~~~~~~

Statical analysis
~~~~~~~~~~~~~~~~~

A search for referenced text strings leads us to the block :

004014D8  push 40                            ; /Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL
004014DA  push xc2.00405130                  ; |Title = "GREAT CRACKER"
004014DF  push xc2.0040511C                  ; |Text = "Successfully code!"
004014E4  push 0                             ; |hOwner = NULL
004014E6  call near [<&USER32.MessageBoxA>]  ; \MessageBoxA

Looking up we got the following :

004014CC    .  E8 AFFDFFFF       call    xc2.00401280
004014D1    .  83C4 04           add     esp, 4
004014D4    .  85C0              test    eax, eax
004014D6    .  74 43             je      short xc2.0040151B

Call to 401280 (at 4014CC) looks important : its return value is tested to
decide whether to display the message box or not. It must return something != 0.
Let s take a quick look at this routine :

It calls 2 times GetDlgItemTextA to get user input. (40129D and 4012BE)
Then execute something that looks like verification code or key generation.
(from 4012CF to 401301)
Then a call to wsprintfA at 401354.
And then again some verification code until the end of the routine (ends at 401404)
We notice many conditional jumps to 4013FC : it makes the routine return 0,
this is the bad boy jump.


Runtime analysis
~~~~~~~~~~~~~~~~

The key point in this crackme is to understand in details the routine at 401280 :

00401280   /$  83EC 40           sub     esp, 40
00401283   |.  8D4424 0C         lea     eax, [esp+C]
00401287   |.  53                push    ebx
00401288   |.  56                push    esi
00401289   |.  8B7424 4C         mov     esi, [esp+4C]
0040128D   |.  57                push    edi
0040128E   |.  8B3D 9C404000     mov     edi, [<&USER32.GetDlgItemTextA>] ;  USER32.GetDlgItemTextA
00401294   |.  6A 14             push    14                               ; /Count = 14 (20.)
00401296   |.  50                push    eax                              ; |Buffer
00401297   |.  68 E8030000       push    3E8                              ; |ControlID = 3E8 (1000.)
0040129C   |.  56                push    esi                              ; |hWnd
0040129D   |.  FFD7              call    near edi                         ; \GetDlgItemTextA
0040129F   |.  83F8 02           cmp     eax, 2
004012A2   |.  0F8C 54010000     jl      xc2.004013FC
004012A8   |.  83F8 14           cmp     eax, 14
004012AB   |.  0F8F 4B010000     jg      xc2.004013FC
004012B1   |.  8D4C24 2C         lea     ecx, [esp+2C]
004012B5   |.  6A 20             push    20                               ; /Count = 20 (32.)
004012B7   |.  51                push    ecx                              ; |Buffer
004012B8   |.  68 E9030000       push    3E9                              ; |ControlID = 3E9 (1001.)
004012BD   |.  56                push    esi                              ; |hWnd
004012BE   |.  FFD7              call    near edi                         ; \GetDlgItemTextA
004012C0   |.  83F8 1D           cmp     eax, 1D
004012C3   |.  0F85 33010000     jnz     xc2.004013FC

Put a breakpoint at 401280 and run the prog.
The first call to GetDlgItemTextA puts your name into the stack at 12FA48.
  Returned result (length of your name) must be between 2 and 20. (0x14) chars
The second one put your key at 12FA5C.
  The length of the key must be 0x1D (29. chars)

Retry with valid length name/key, or simply bypass the jump by changing ZF.

We arrive at the following block :
004012C9   |.  32DB              xor     bl, bl
004012CB   |.  885C24 50         mov     [esp+50], bl
004012CF   |> /E8 2CFDFFFF       /call    xc2.00401000
004012D4   |. |8B7424 50         |mov     esi, [esp+50]
004012D8   |. |81E6 FF000000     |and     esi, 0FF
004012DE   |. |8A5434 2C         |mov     dl, [esp+esi+2C]
004012E2   |. |52                |push    edx
004012E3   |. |E8 28FDFFFF       |call    xc2.00401010
004012E8   |. |83C4 04           |add     esp, 4
004012EB   |. |E8 60FDFFFF       |call    xc2.00401050
004012F0   |. |3A86 D8594000     |cmp     al, [esi+4059D8]
004012F6   |. |0F85 00010000     |jnz     xc2.004013FC
004012FC   |. |FEC3              |inc     bl
004012FE   |. |80FB 04           |cmp     bl, 4
00401301   |. |885C24 50         |mov     [esp+50], bl
00401305   |.^\72 C8             \jb      short xc2.004012CF

It is a loop with 4 iterations (bl is the counter), it uses the 4 first chars
  of your key
First call to 401000 sets the byte at 4055D4 to 0
Last call to 401050 just put back the byte at 4055D4 into al
Middle call to 401010 is more complex :
  it takes as parameter a char of your key
  we can assume it modifies the byte at 4055D4
  After returning two bytes are compared (4055D4 and 4059D8) : it must match or
  you will take the bad boy path.

Let's see what happens in the middle call :

00401010   /$  8A5424 04         mov     dl, [esp+4]
00401014   |.  8A0D D4554000     mov     cl, [4055D4]
0040101A   |.  56                push    esi
0040101B   |.  BE 08000000       mov     esi, 8
00401020   |>  8AC1              /mov     al, cl
00401022   |.  32C2              |xor     al, dl
00401024   |.  24 01             |and     al, 1
00401026   |.  3C 01             |cmp     al, 1
00401028   |.  75 03             |jnz     short xc2.0040102D
0040102A   |.  80F1 18           |xor     cl, 18
0040102D   |>  D0E9              |shr     cl, 1
0040102F   |.  3C 01             |cmp     al, 1
00401031   |.  75 03             |jnz     short xc2.00401036
00401033   |.  80C9 80           |or      cl, 80
00401036   |>  D0EA              |shr     dl, 1
00401038   |.  4E                |dec     esi
00401039   |.^ 75 E5             \jnz     short xc2.00401020
0040103B   |.  880D D4554000     mov     [4055D4], cl
00401041   |.  5E                pop     esi
00401042   \.  C3                ret

It performs some stuff on the parameter, and then stores the result at 4055D4
Looks like a kind of hashing stuff. No need to fully understand it, we will
just rip it when needed. All you need to keep in mind is that is takes a char
of your key as input, and returns a byte compared to a hardcoded one.

Right after the 4 iterations loop we got the following :

00401307   |.  8A4424 18         mov     al, [esp+18]
0040130B   |.  8D4C24 2C         lea     ecx, [esp+2C]
0040130F   |.  24 0F             and     al, 0F
00401311   |.  51                push    ecx
00401312   |.  83E0 0F           and     eax, 0F
00401315   |.  50                push    eax
00401316   |.  E8 75FEFFFF       call    xc2.00401190
0040131B   |.  83C4 08           add     esp, 8
0040131E   |.  85C0              test    eax, eax
00401320   |.  0F84 D6000000     je      xc2.004013FC

Another procedure is called (401190) and must return something != 0. It takes
your key as 2nd parameter (401311: push ecx), and the 4 Less Significant Bits
of the first char of your name (401312: and eax, 0F  ;401315: push eax).

00401190   /$  8B4424 08         mov     eax, [esp+8]
00401194   |.  8B4C24 04         mov     ecx, [esp+4]
00401198   |.  83C0 06           add     eax, 6
0040119B   |.  8B148D 60504000   mov     edx, [ecx*4+405060]
004011A2   |.  50                push    eax                 ; /Arg2
004011A3   |.  52                push    edx                 ; |Arg1
004011A4   |.  E8 97FFFFFF       call    xc2.00401140        ; \xc2.00401140
004011A9   |.  83C4 08           add     esp, 8
004011AC   |.  F7D8              neg     eax
004011AE   |.  1BC0              sbb     eax, eax
004011B0   |.  40                inc     eax
004011B1   \.  C3                ret

We can see that the return value of 401190 is directly linked to another call
(401140 see below), meaning that the procedure at 401140 must return 0 so that
neg,sbb,inc instructions produces effectively something different from 0.

At 40119B, it uses some kind of a hash map located at 405060 : the offset used
(ecx*4) is taken as 1st parameter (401194: mov ecx, [esp+4]). It means the
4 LSBits of the first char of your name are actually used as an offset into
the following table :

00405060  0C 51 40 00 04 51 40 00 FC 50 40 00 F4 50 40 00  .Q@..Q@..P@..P@.
00405070  EC 50 40 00 E4 50 40 00 DC 50 40 00 D4 50 40 00  .P@..P@..P@..P@.
00405080  CC 50 40 00 C4 50 40 00 BC 50 40 00 B4 50 40 00  .P@..P@..P@..P@.
00405090  AC 50 40 00 A4 50 40 00 9C 50 40 00 58 50 51 34  .P@..P@..P@.XPQ4
004050A0  47 00 00 00 36 43 52 43 43 00 00 00 46 52 54 39  G...6CRCC...FRT9
004050B0  4A 00 00 00 42 39 54 4B 59 00 00 00 44 47 38 46  J...B9TKY...DG8F
004050C0  56 00 00 00 43 52 48 39 51 00 00 00 50 38 52 44  V...CRH9Q...P8RD
004050D0  34 00 00 00 43 43 48 42 32 00 00 00 56 46 47 47  4...CCHB2...VFGG
004050E0  38 00 00 00 51 33 52 48 36 00 00 00 36 39 32 48  8...Q3RH6...692H
004050F0  57 00 00 00 33 59 39 48 59 00 00 00 4B 46 48 42  W...3Y9HY...KFHB
00405100  59 00 00 00 44 46 54 59 56 00 00 00 4B 39 32 47  Y...DFTYV...K92G
00405110  4A 00 00 00                                      J...

In my case I had ecx==1, so the offset was 4, leading to 405060+4. Dwords
located in the first part of the table are actually pointers to the 2nd part.
And the 2nd part contains strings of 5chars length. In my example it gave
[405064] == 405104, dereferenced to "DFTYV".

Details for procedure at 401140 : takes as parameter your key beginning at the
7th char, and the string taken from the hash table above.

00401140   /$  55                push    ebp
00401141   |.  8BEC              mov     ebp, esp
00401143   |.  51                push    ecx
00401144   |.  8B45 0C           mov     eax, [ebp+C]
00401147   |.  33C9              xor     ecx, ecx
00401149   |.  56                push    esi
0040114A   |.  57                push    edi
0040114B   |.  8945 FC           mov     [ebp-4], eax
0040114E   |.  8848 05           mov     [eax+5], cl
00401151   |.  894D 0C           mov     [ebp+C], ecx
00401154   |.  57                push    edi
00401155   |.  56                push    esi
00401156   |.  8B55 08           mov     edx, [ebp+8]
00401159   |.  8B7D 08           mov     edi, [ebp+8]
0040115C   |.  8B75 FC           mov     esi, [ebp-4]
0040115F   |.  B9 FFFFFFFF       mov     ecx, -1
00401164   |.  33C0              xor     eax, eax
00401166   |.  F2:AE             repne   scas byte ptr es:[edi]
00401168   |.  F7D1              not     ecx
0040116A   |.  8BFA              mov     edi, edx
0040116C   |.  33D2              xor     edx, edx
0040116E   |.  F3:A6             repe    cmps byte ptr es:[edi], byte ptr [esi]
00401170   |.  8A46 FF           mov     al, [esi-1]
00401173   |.  8A57 FF           mov     dl, [edi-1]
00401176   |.  2BC2              sub     eax, edx
00401178   |.  8945 0C           mov     [ebp+C], eax
0040117B   |.  5E                pop     esi
0040117C   |.  5F                pop     edi
0040117D   |.  8B45 0C           mov     eax, [ebp+C]
00401180   |.  5F                pop     edi
00401181   |.  5E                pop     esi
00401182   |.  8BE5              mov     esp, ebp
00401184   |.  5D                pop     ebp
00401185   \.  C3                ret

This routine actually compares its first parameter (5chars string from the
hash table) with a 5chars chunk of your key (from 7th to 11th char) : they
must match.

To sum up : to crack it we will need to dump the table and use the offset to 
retrieve the value of the 7-11th chars chunk.

Back at the big testing routine :

00401326   |.  68 2083B8ED       push    EDB88320
0040132B   |.  E8 70FDFFFF       call    xc2.004010A0

It is a call to 4010A0 that takes a constant as parameter :
004010A0   /$  56                push    esi
004010A1   |.  57                push    edi
004010A2   |.  8B7C24 0C         mov     edi, [esp+C]
004010A6   |.  33D2              xor     edx, edx
004010A8   |.  B9 D8554000       mov     ecx, xc2.004055D8
004010AD   |>  8BC2              /mov     eax, edx
004010AF   |.  BE 08000000       |mov     esi, 8
004010B4   |>  A8 01             |/test    al, 1
004010B6   |.  74 06             ||je      short xc2.004010BE
004010B8   |.  D1E8              ||shr     eax, 1
004010BA   |.  33C7              ||xor     eax, edi
004010BC   |.  EB 02             ||jmp     short xc2.004010C0
004010BE   |>  D1E8              ||shr     eax, 1
004010C0   |>  4E                ||dec     esi
004010C1   |.^ 75 F1             |\jnz     short xc2.004010B4
004010C3   |.  8901              |mov     [ecx], eax
004010C5   |.  83C1 04           |add     ecx, 4
004010C8   |.  42                |inc     edx
004010C9   |.  81F9 D8594000     |cmp     ecx, xc2.004059D8
004010CF   |.^ 7C DC             \jl      short xc2.004010AD
004010D1   |.  5F                pop     edi
004010D2   |.  5E                pop     esi
004010D3   \.  C3                ret

This routine actually fills a memory array from 4055D8 to 4059D7.
This turns out to be a CRC32 table. We can even recognize the constant used to
generate the lookup table : 0xEDB88320 is a common used value in this case.

Back at testing routine again :

00401330   |.  8D7C24 1C         lea     edi, [esp+1C]
00401334   |.  83C9 FF           or      ecx, FFFFFFFF
00401337   |.  33C0              xor     eax, eax
00401339   |.  8D5424 1C         lea     edx, [esp+1C]
0040133D   |.  F2:AE             repne   scas byte ptr es:[edi]
0040133F   |.  F7D1              not     ecx
00401341   |.  49                dec     ecx
00401342   |.  51                push    ecx
00401343   |.  52                push    edx
00401344   |.  E8 17FDFFFF       call    xc2.00401060
00401349   |.  50                push    eax                         ; /<%02X>
0040134A   |.  8D4424 1C         lea     eax, [esp+1C]               ; |
0040134E   |.  68 14514000       push    xc2.00405114                ; |Format = "%02X"
00401353   |.  50                push    eax                         ; |s
00401354   |.  FF15 A0404000     call    near [<&USER32.wsprintfA>]  ; \wsprintfA
0040135A   |.  83C4 18           add     esp, 18

At that time (401330) esp+1C contains your name, its length is computed (repne).
Both your name and its length are passed as parameters to 401060.
This function returns a dword that is converted to a string using wsprintfA :

00401060   /$  56                push    esi
00401061   |.  8B7424 0C         mov     esi, [esp+C]
00401065   |.  83C8 FF           or      eax, FFFFFFFF
00401068   |.  85F6              test    esi, esi
0040106A   |.  76 24             jbe     short xc2.00401090
0040106C   |.  8B4C24 08         mov     ecx, [esp+8]
00401070   |.  53                push    ebx
00401071   |>  8BD0              /mov     edx, eax
00401073   |.  33DB              |xor     ebx, ebx
00401075   |.  8A19              |mov     bl, [ecx]
00401077   |.  81E2 FF000000     |and     edx, 0FF
0040107D   |.  33D3              |xor     edx, ebx
0040107F   |.  C1E8 08           |shr     eax, 8
00401082   |.  8B1495 D8554000   |mov     edx, [edx*4+4055D8]
00401089   |.  33C2              |xor     eax, edx
0040108B   |.  41                |inc     ecx
0040108C   |.  4E                |dec     esi
0040108D   |.^ 75 E2             \jnz     short xc2.00401071
0040108F   |.  5B                pop     ebx
00401090   |>  F7D0              not     eax
00401092   |.  5E                pop     esi
00401093   \.  C3                ret

It uses the table at 4055D8; we recognize the CRC computing routine.
Length passed as argument is put into esi (401061: mov esi, [esp+C]) and we
ensure that it is non zero. Then a loop for each char of your name, each time
updating the CRC signature (in eax). The signature is eventually returned.

If you are not familiar with CRC, here are the basics :
http://www.gamedev.net/reference/programming/features/crc32/

Back at main routine :

0040135D   |.  33C0              xor     eax, eax
0040135F   |.  8D4C24 38         lea     ecx, [esp+38]
00401363   |>  0FBE5404 0C       /movsx   edx, byte ptr [esp+eax+C]
00401368   |.  0FBE31            |movsx   esi, byte ptr [ecx]
0040136B   |.  2BD6              |sub     edx, esi
0040136D   |.  0F85 89000000     |jnz     xc2.004013FC
00401373   |.  83C0 02           |add     eax, 2
00401376   |.  41                |inc     ecx
00401377   |.  83F8 08           |cmp     eax, 8
0040137A   |.^ 7C E7             \jl      short xc2.00401363

Your key (starting from 13th char) and pointed to by ecx is compared with the
even chars of the signature string created by wsprintfA (pointed to by esp+eax+C).
It loops 4 times, meaning 4 chars of your key (13th to 16th) are tested.

To crack this part, we will need to calculate the CRC32 signature of Name, then
to take only the even chars : this will constitute chars from 13 to 16 of Key.

0040137C   |.  33F6              xor     esi, esi
0040137E   |>  E8 7DFCFFFF       /call    xc2.00401000
00401383   |.  8A4434 18         |mov     al, [esp+esi+18]
00401387   |.  50                |push    eax
00401388   |.  E8 83FCFFFF       |call    xc2.00401010
0040138D   |.  83C4 04           |add     esp, 4
00401390   |.  E8 BBFCFFFF       |call    xc2.00401050
00401395   |.  8AC8              |mov     cl, al
00401397   |.  80F9 1A           |cmp     cl, 1A
0040139A   |.  884C24 50         |mov     [esp+50], cl
0040139E   |.  72 25             |jb      short xc2.004013C5
004013A0   |.  80F9 17           |cmp     cl, 17
004013A3   |.  76 20             |jbe     short xc2.004013C5
004013A5   |.  8B5424 50         |mov     edx, [esp+50]
004013A9   |.  B8 ABAAAAAA       |mov     eax, AAAAAAAB
004013AE   |.  81E2 FF000000     |and     edx, 0FF
004013B4   |.  83EA 15           |sub     edx, 15
004013B7   |.  F7E2              |mul     edx
004013B9   |.  D1EA              |shr     edx, 1
004013BB   |>  80C1 FD           |/add     cl, 0FD
004013BE   |.  4A                ||dec     edx
004013BF   |.^ 75 FA             |\jnz     short xc2.004013BB
004013C1   |.  884C24 50         |mov     [esp+50], cl
004013C5   |>  8B4424 50         |mov     eax, [esp+50]
004013C9   |.  25 FF000000       |and     eax, 0FF
004013CE   |.  8A88 44504000     |mov     cl, [eax+405044]
004013D4   |.  8A4434 3E         |mov     al, [esp+esi+3E]
004013D8   |.  3AC8              |cmp     cl, al
004013DA   |.  75 20             |jnz     short xc2.004013FC
004013DC   |.  46                |inc     esi
004013DD   |.  83FE 05           |cmp     esi, 5
004013E0   |.^ 7C 9C             \jl      short xc2.0040137E

Here we recognize the same pattern as before (call to 401000, 401010, 401050)
The first char of your name is hashed. Then the hash is modified from 401395
to 4013C9, and it seems that the result is always below 0x1A (26.).
This modified value is used as an offset into a table at 405044 containing
the 26 alpha letters (it explains why offset must be below or equal to 26.).
The pointed letter must equals the 19th letter of your key.

5 chars of your key are tested this way (19th to 23rd) respectively using the
modified hash produced by the 1st to 5th chars of your name.
Edit (thx cyclops): As you can see name should be of length >= 5 to work
properly. If it's not (e.g, 2 <= length < 5) the result may vary depending
on the operating system you are running, meaning that for a given name the key
may differ on two different systems. In the keygen I simply drop your name when
length < 5.

To crack this part we will need to rip the pattern 401000,401010,401050,
(hashing stuff) and the 26 alpha letters table at 405044.
We will also need the hash-modification code (401395 to 4013C1)

Let s move on to next block :
004013E2   |.  8D5424 2C         lea     edx, [esp+2C]
004013E6   |.  52                push    edx
004013E7   |.  E8 D4FDFFFF       call    xc2.004011C0
004013EC   |.  83C4 04           add     esp, 4
004013EF   |.  F7D8              neg     eax
004013F1   |.  1BC0              sbb     eax, eax
004013F3   |.  5F                pop     edi
004013F4   |.  5E                pop     esi
004013F5   |.  5B                pop     ebx
004013F6   |.  F7D8              neg     eax
004013F8   |.  83C4 40           add     esp, 40
004013FB   |.  C3                ret

A call is made to 4011C0, passing your key as parameter. Return result of
4011C0 is not returned directly but modified with neg, sbb, neg.
This call is a pretty long routine compared to what we ve faced so far. Lets
split it into small pieces :

004011C0   /$  51                push    ecx
004011C1   |.  53                push    ebx
004011C2   |.  55                push    ebp
004011C3   |.  33ED              xor     ebp, ebp
004011C5   |.  56                push    esi
004011C6   |.  8B7424 14         mov     esi, [esp+14]
004011CA   |.  57                push    edi
004011CB   |.  896C24 10         mov     [esp+10], ebp

This constitutes the header of the routine, nothing special here.

004011CF   |>  BF 30504000       /mov     edi, xc2.00405030  ;  ASCII "1234567890ABCDEF"
004011D4   |.  83C9 FF           |or      ecx, FFFFFFFF
004011D7   |.  33C0              |xor     eax, eax
004011D9   |.  33D2              |xor     edx, edx
004011DB   |.  F2:AE             |repne   scas byte ptr es:[edi]
004011DD   |.  8A5C2E 18         |mov     bl, [esi+ebp+18]
004011E1   |.  F7D1              |not     ecx
004011E3   |.  49                |dec     ecx
004011E4   |.  74 22             |je      short xc2.00401208

A hardcoded string "1234567890ABCDEF" is loaded into edi, and its length (16.)
is computed into ecx, the 25th char of your key is put into bl.

004011E6   |>  3A9A 30504000     |/cmp     bl, [edx+405030]
004011EC   |.  74 16             ||je      short xc2.00401204
004011EE   |.  BF 30504000       ||mov     edi, xc2.00405030  ;  ASCII "1234567890ABCDEF"
004011F3   |.  83C9 FF           ||or      ecx, FFFFFFFF
004011F6   |.  33C0              ||xor     eax, eax
004011F8   |.  42                ||inc     edx
004011F9   |.  F2:AE             ||repne   scas byte ptr es:[edi]
004011FB   |.  F7D1              ||not     ecx
004011FD   |.  49                ||dec     ecx
004011FE   |.  3BD1              ||cmp     edx, ecx
00401200   |.^ 72 E4             |\jb      short xc2.004011E6
00401202   |.  EB 04             |jmp     short xc2.00401208

Here the char in bl is compared to each char of "1234567890ABCDEF", in other
words we search if bl is present into the hardcoded string. If it is, we jump
at 401204, otherwise we just continue until 401202 and jump at 401208.

00401204   |>  FF4424 10         |inc     dword ptr [esp+10]
00401208   |>  45                |inc     ebp
00401209   |.  83FD 05           |cmp     ebp, 5
0040120C   |.^ 7C C1             \jl      short xc2.004011CF

If bl was found, we increment [esp+10]. Fortunately we remember putting 0 into
[esp+10] (Look at 4011CB). So we have 2 counters :
   ebp counting the number of loops (5 iterations for 5 letters of your key, 
      e.g. we test chars from 25 to 29)
   [esp+10] counting the number of chars matching one of "1234567890ABCDEF"

0040120E   |.  837C24 10 05      cmp     dword ptr [esp+10], 5
00401213   |.  74 08             je      short xc2.0040121D
00401215   |.  5F                pop     edi
00401216   |.  5E                pop     esi
00401217   |.  5D                pop     ebp
00401218   |.  33C0              xor     eax, eax
0040121A   |.  5B                pop     ebx
0040121B   |.  59                pop     ecx
0040121C   |.  C3                ret

If each char matched one of "1234567890ABCDEF" ([esp+10] == 5), we jump to
40121D. Otherwise the routine terminates and returns 0 (Obviously it means
that the check failed)

So we know that chars from 25 to 29 in key must be among "1234567890ABCDEF"

0040121D   |>  8A46 18           mov     al, [esi+18]
00401220   |.  B1 46             mov     cl, 46
00401222   |.  3AC1              cmp     al, cl
00401224   |.  75 0F             jnz     short xc2.00401235
00401226   |.  384E 19           cmp     [esi+19], cl
00401229   |.  75 0A             jnz     short xc2.00401235
0040122B   |.  384E 20           cmp     [esi+20], cl
0040122E   |.  75 05             jnz     short xc2.00401235
00401230   |.  384E 21           cmp     [esi+21], cl
00401233   |.  74 3F             je      short xc2.00401274
00401235   |>  B1 41             mov     cl, 41
00401237   |.  3AC1              cmp     al, cl
00401239   |.  75 0F             jnz     short xc2.0040124A
0040123B   |.  384E 19           cmp     [esi+19], cl
0040123E   |.  75 0A             jnz     short xc2.0040124A
00401240   |.  384E 20           cmp     [esi+20], cl
00401243   |.  75 05             jnz     short xc2.0040124A
00401245   |.  384E 21           cmp     [esi+21], cl
00401248   |.  74 2A             je      short xc2.00401274
0040124A   |>  3C 42             cmp     al, 42
0040124C   |.  75 26             jnz     short xc2.00401274
0040124E   |.  384E 19           cmp     [esi+19], cl
00401251   |.  75 0E             jnz     short xc2.00401261
00401253   |.  8A56 20           mov     dl, [esi+20]
00401256   |.  B1 44             mov     cl, 44
00401258   |.  3AD1              cmp     dl, cl
0040125A   |.  75 05             jnz     short xc2.00401261
0040125C   |.  384E 21           cmp     [esi+21], cl
0040125F   |.  74 13             je      short xc2.00401274
00401261   |>  3C 42             cmp     al, 42
00401263   |.  75 0F             jnz     short xc2.00401274
00401265   |.  807E 19 31        cmp     byte ptr [esi+19], 31
00401269   |.  75 09             jnz     short xc2.00401274
0040126B   |.  807E 20 30        cmp     byte ptr [esi+20], 30
0040126F   |.  75 03             jnz     short xc2.00401274
00401271   |.  8A46 21           mov     al, [esi+21]
00401274   |>  5F                pop     edi
00401275   |.  5E                pop     esi
00401276   |.  5D                pop     ebp
00401277   |.  B8 01000000       mov     eax, 1
0040127C   |.  5B                pop     ebx
0040127D   |.  59                pop     ecx
0040127E   \.  C3                ret

Something funny about this code, it always returns 1 no matter what tests
are performed. If you manage to reach it, you win because returning 1 here
leads to the good boy. (Follow the `ret' instructions back to 4014D1)


Sum up
~~~~~~

Name : 2.-20. chars
Key  : 29. chars split using this pattern :
       1-4  5  6   7-11   12   13-16   17   18   19-23   24   25-29

5, 6, 12, 17, 18, 24 are never tested, e.g. you can put whatever you want.
 (I will use '-' dash)

1-4 : respectively hashed and compared to 38, 7C, C5, 63, (DA). We can deduce
 by testing all possible letters as input that 38, 7C, C5, 63, DA correspond to
 J8G6I (see reverse simple table attached)
 Last value (DA) is never tested, so putting 'J8G6I' is actually the same as
 putting 'J8G6-'

7-11  : compared to a 5chars length string from a hashtable at 405060.
 String is chosen in the hashtable using the 4 LSB from the 1st char of your
 name. (Table has 15 entries, but there are 2^4 == 16 possible LSB, meaning
 ascii chars ending with 4 bits set to 1 have no valid value in the table.
 In fact if your name starts with one of the following chars : '/', '?', 'O',
 '_', 'o', then you have no valid key but a segfault^^)

13-17 : CRC is used to generate a 8chars length string from Name, then even
 chars are tested against 13-17.

19-23 : chars 1-5 from Name are hashed (using the same routine as 1-4), then
 the hash modified to be below 26, then used as an offset in a table of alpha
 letters at 405044.

25-29 : any char from "1234567890ABCDEF" (405030)


What you need to do
~~~~~~~~~~~~~~~~~~~~

Rip the simple hash routine at 401010 (used for 1-4 and 19-23).
Rip the hashtable at 405060. (used for 7-11)
Rip the CRC generation routine, or just dump the CRC table. (for 13-17)
Rip the letter table at 405044, and the modification hash stuff. (for 19-23)
Choose 5chars from "1234567890ABCDEF" (for 25-29)

You now have all the clues to write a working keygen. Lets get to work !
A C keygen (for gcc) is attached.


Greetings
~~~~~~~~~

To all reversers from crackmes.de
Special thanks to virw for this great crackme !
posted by po.