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 !