CoDe_InSiDe Crackme #10

By MiNoS

Bon, on perd pas de temps, on remplit les champs nom / sérial, on pose un bpx GetDlgItemTextA, et SI break dès que l'edit du nom ou du sérial n'est plus actif. Un petit coup de F12, et on est dans le crackme, on retient l'adresse (401068), et on sort de SI. A peine sorti, ça break à nouveau. Après un F12, on arrive en 40841B (c'est juste après le call qui récupère le sérial). On trace, et là aussi, on retient l'adresse (401185). Avant d'aller plus loin, on se décide à aller faire un tour sous W32Dasm pour voir ça de plus près.

Malheureusement, W32Dasm n'apprécie guère les plaisanteries de CoDe InSiDe, et c'est avec un "Cannot Allocate File Buffer Memory" que l'on est accueilli !
Pas de panique, on ouvre le crackme avec un éditeur hexa, et on regarde de plus près le PE File Header :
00000080  5045 0000 4C01 0500 0000 0000 0000 0000 PE..L...........
00000090  0000 0000 F000 0F01                     ........

C'est pas beau tout ça ! Le 00F0 représente la taille de l'Optional Header. Habituellement, cette taille est de 224 octets (00E0h), mais ici, elle est de 240 octets (00F0h). Il y a donc 10h octets de trop. Bien que ceci ne dérange pas Windows (du moment que la taille indiquée est égale à la taille réelle), W32Dasm n'a pas l'air d'aimer ce genre de modifications. On va donc corriger ceci.
Toujours avec un éditeur hexa, on remplace F000 par E000. Maintenant, il va falloir enlever les 10h octets de trop du PE Optional Header, sinon c'est le plantage assuré. Comme celui-ci commence à l'offset 98h, il doit finir en 98h + E0h soit en 178h. On se positionne en 178h, et on supprime 10h octets (jusqu'au Section Header). Mais, si on enlève 10h octets, il va bien falloir en rajoutter 10h quelque part, sinon, là aussi on risque d'avoir quelques surprises ! On va dont les rajouter juste avant l'entry point (ici, on peut donc les rajouter autour de l'offset 900h). Voilà, maintenant que l'on a fini de s'amuser, on s'attaque au crackme ;-).

Maintenant que l'on peut utiliser W32Dasm, on va pas s'en priver :

:00401050  push 00000100
:00401055  push 0040A000
:0040105A  push 000003E8
:0040105F  push [ebp+08]

* Reference To: USER32.GetDlgItemTextA, Ord:0000h
                                  |
:00401062  Call dword ptr [00401D60]
:00401068  pop ebp
:00401069  mov dword ptr [00401F00], eax

Ici, on a la routine qui récupère notre nom. Celui-ci sera donc stocké en 40A000 et le nombre de caractères du nom sera stocké en 401F00. Allons voir ce qui se trouve en 401185 :

:00401185  mov eax, dword ptr [00401F10]	< nbre de char du sérial
:0040118A  cmp al, 00			< on a entré un sérial ?
:0040118C  jne 0040118F			< non,
:0040118E  ret				< au revoir
								< sinon, on continue


* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040118C(C)
|
:0040118F  push eax						
:00401190  pop ecx			< ecx = nbre de char du sérial
:00401191  mov eax, dword ptr [00401F00]	< eax = nbre de char du nom
:00401196  cmp ecx, eax			< le sérial est - long 
						  	                 que le nom ?	
:00401198  jb 0040119C			< oui ---> bye bye
:0040119A  jmp 0040119D			< non, on continue

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401198(C)
|
:0040119C  ret

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040119A(U)
|
:0040119D  mov edi, 0040A100		< edi pointe sur le sérial
:004011A2  mov ebx, 00408000		< tien tien
:004011A7  xor eax, eax			< les registres sont 
:004011A9  xor ecx, ecx		          remis à 0
:004011AB  xor edx, edx

J'espère que les commentaires sont assez explicites et que tout le monde comprendra. Ici, ça commence à se compliquer, car en 408000, on trouve une chaine qui est dérivée du nom.
Pour le savoir, prenez le loader de SI, une fois que vous êtes à l'entry point, posez un bpm 408000 w. Une fois revenu dans le crackme, enregistrez-vous, et voici ce que l'on trouve au break :

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004010D1(U)
|
:0040109F  mov al, byte ptr [edi+00002000] < prend un char du nom
:004010A5  cmp al, 00			< il reste des chars ?
:004010A7  je 004010D3			< non, on s'en va
:004010A9  imul eax, 00000043		< sinon, eax = eax * 43h
:004010AC  lea edx, dword ptr [eax+4*eax]< edx = 5 * eax
:004010AF  add edx, eax			< edx = edx + eax
:004010B1  push eax			< save eax
:004010B2  xor eax, eax			< eax = 0
:004010B4  push edx			< save edx
:004010B5  xchg eax,edx			< échange eax et edx
:004010B6  xor ecx, ecx			< ecx = 0
:004010B8  mov cl, 0A			< ecx = 0Ah
:004010BA  idiv ecx			< edx = eax mod ecx
:004010BC  pop eax			< eax = ancienne valeur de edx
:004010BD  imul eax, edx			< eax = eax * edx 
:004010C0  push eax			< save eax
:004010C1  xor eax, eax			< eax = 0
:004010C3  imul edx, esi			< edx = edx * esi
:004010C6  xchg eax,edx			< échange eax et edx
:004010C7  pop edx			< edx = ancienne valeur de eax
:004010C8  xor dl, al			< dl = dl xor al
:004010CA  add edx, 00000070		< edx = edx + 70h
:004010CD  mov byte ptr [edi], dl	< on écrit un char
:004010CF  dec esi			< esi = esi - 1
:004010D0  inc edi			< char suivant
:004010D1  jmp 0040109F			< retour au début

Je sais pas si vous avez remarqué, mais ici, on a 3 push, et seulement 2 pop. On verra plus loin ce que deviennent les valeurs d'eax stockées dans la pile. Je crois que le keygen va pas être des plus facile ! Et comme si on avait pas assez de problèmes comme ça, il faut qu'un esi se pointe dans cette routine. Hé oui, il est bien gentil, mais il nous a pas dit sa valeur ;-). On va donc chercher un peu avant voir si on trouve quelque chose :

:00401092  call 004010DA			< on rentre dans le call
	:004010DA  xor eax, eax		< eax = 0
	:004010DC  mov edi, 00408000
	:004010E1  mov ecx, 000003C0
	
	* Referenced by a (U)nconditional or (C)onditional Jump at Address:
	|:004010EC(C)		 	< on rajoute 4 * 3C0h octets (00h)
	|					à partir de l'@ 408000
	:004010E6  mov dword ptr [edi], eax
	:004010E8  add edi, 00000004
	:004010EB  dec ecx
	:004010EC  ne 004010E6
	:004010EE  mov eax, dword ptr [00401F00]< enfin !
	:004010F3  ret
:00401097  lea edi, dword ptr [00408000]
:0040109D  mov esi, eax			< esi = eax

Esi sera donc égal au nombre de caractères du nom (contenu en 401F00). On remarque en passant que des 0 sont ajoutés a partir de 408000.
Voilà, maintenant que l'on sait comment est formée cette chaine (celle issue du nom), on peut retourner là où on s'était arrété :



* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004011C7(U)
|
:004011AD  mov cl, byte ptr [edi]	< prend 1 char du sérial
:004011AF  cmp ecx, 00000000		< il reste des char ?
:004011B2  je 004011C9			< non, alors on saute

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004011BC(U)
|
:004011B4  mov al, byte ptr [ebx]	< prend 1 char du nom modifié
:004011B6  cmp al, 00			< il reste des char ?
:004011B8  jne 004011BE			< oui, on saute
:004011BA  xor bl, bl			< non, on remet bl à 0
:004011BC  jmp 004011B4

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004011B8(C)
|
:004011BE  imul eax, ecx			< eax = eax * ecx
:004011C1  add edx, eax			< edx = edx + eax
:004011C3  inc ebx			< char suivant (de la chaine)
:004011C4  inc edi			< char suivant (du sérial)
:004011C5  xor eax, eax			< eax = 0
:004011C7  jmp 004011AD			< on revient

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004011B2(C)
|
:004011C9  push edx			
:004011CA  pop eax			< eax = edx
:004011CB  imul eax, 65446F43		< eax = eax * 65446F43h
:004011D1  xor edx, edx			< edx = 0
:004011D3  push eax			
:004011D4  mov eax, dword ptr [00408300]	< c'est quoi ça encore ?
:004011D9  pop ecx			< ecx = ancienne valeur de eax
:004011DA  xor eax, ecx			< eax = ecx ?
:004011DC  je 00401234			< oui, on saute
:004011DE  pushad			< save des registres

Ici, les ennuis continuent. En effet, le mov eax, dword ptr [00408300] pose problème. On revient donc sous W32Dasm, et on fait une recherche sur [00408300], on remonte un peu dans le listing, et voici ce que l'on obtient :

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004010A7(C)
|
:004010D3  mov eax, dword ptr [00401F00]	< eax = nbre de char du nom
:004010D8  jmp 004010F4
|
|	{coupé}
|
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004010D8(U)
|
:004010F4  mov edi, 00408200		< il n'y a que des 0 à 
					partir de 408200

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004010FF(C)
|
:004010F9  pop dword ptr [edi]
:004010FB  add edi, 00000004
:004010FE  dec eax	
:004010FF  jne 004010F9

Je vais expliquer le bloc en rouge plus en détail. Vous vous souvenez des 3 push et des 2 pop ? Ben ici, on va récupérer les valeur qui sont encore dans la pile. On a n push de retard avec n le nbre de char du nom. Je vous donne un petit exemple avec mon nom :

0030:00408200  B9 15 00 00 0D 1D 00 00 6A 14 00 00 7B 1B 00 00  ;.......j...{...
0030:00408210  27 14  				    .

En rouge, les valeurs qui étaient stockées dans la pile. On continue donc :

:00401101  mov eax, dword ptr [00401F00]	< eax = nbre de char du nom
:00401106  push eax
:00401107  imul eax, 00000004		< eax = eax * 4
:0040110A  sub edi, eax			< on revient au début des valeurs
:0040110C  pop eax
:0040110D  mov esi, eax			< esi = nbre de char du nom
:0040110F  xor eax, eax			< eax = 0

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401117(C)
|
:00401111  add eax, dword ptr [edi]	< ajoute la valeur contenue 
								  dans edi
:00401113  add edi, 00000004		< valeur suivante
:00401116  dec esi			< il reste des valeurs ?		
:00401117  jne 00401111			< oui, on boucle
:00401119  imul eax, 436F4465		< eax = eax * 436F4465h
:0040111F  mov dword ptr [00408300], eax	< enfin !

Bon, maintenant que l'on sait ce qui se trouve à l'adresse 408300, on peut continuer là où on s'était arrété :


* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401232(U)
|
:004011DF  call 004011E4

* Referenced by a CALL at Address:
|:004011DF   
|						< cette routine vérifie si l'on 
						a patché ou pas le je 00401234
						de l'@ 4011DC
:004011E4  pop ebp
:004011E5  cmp byte ptr [ebp-08], 75
:004011E9  je 004011F9
:004011EB  cmp byte ptr [ebp-08], EB
:004011EF  je 004011F9
:004011F1  cmp byte ptr [ebp-08], 74
:004011F5  jne 004011F9
:004011F7  jmp 0040121D			< aucun patch, on continue

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:004011E9(C), :004011EF(C), :004011F5(C)
|
:004011F9  popad				< sinon, bad boy

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0040123F(U), :00401298(U)
|
:004011FA  push ebp
:004011FB  mov ebp, esp
:004011FD  push 00000000

* Possible StringData Ref from Code Obj ->"Please"
                                  |
:004011FF  push 00401ED0

* Possible StringData Ref from Code Obj ->"Don't patch the file =)"
                                  |
:00401204  push 00401C00
:00401209  push [ebp+08]

* Reference To: USER32.MessageBoxA, Ord:0000h
                                  |
:0040120C  Call dword ptr [00401D5C]
:00401212  pop ebp
:00401213  ret



* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040121E(U)
|
:00401214  mov eax, dword ptr [00401F20]	< registered flag dans eax
:00401219  test al, al			< c'est bon ou pas ?
:0040121B  jmp 00401220			< la suite après le saut ;-)

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004011F7(U)
|
:0040121D  popad				< restaure les registres
:0040121E  jmp 00401214

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040121B(U)
|
:00401220  test al, al			< et si on retestait ?
:00401222  jne 00401225			< si al = 1, c'est bon
:00401224  ret				< sinon au revoir



* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401222(C)
|
:00401225  jmp 00401240			< un petit peu de CCA ! ;-)

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040123D(U)
|
:00401227  xor eax, eax			< eax = 0
:00401229  inc eax			< eax = 1
:0040122A  mov dword ptr [00401F20], eax	< flag registered à true ;-)
:0040122F  xor eax, eax			< eax = 0
:00401231  pushad			< save des registres
:00401232  jmp 004011DF			< et on va vers les vérifs

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004011DC(C)
|
:00401234  mov eax, dword ptr [00401F20]	< eax = flag registered 
:00401239  cmp al, 00			< al = 0 ?
:0040123B  jne 0040123F			< oui, on continue
:0040123D  jmp 00401227			< sinon ---> bad boy

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040123B(C)
|							< code polymorphe
:0040123F  jmp 004011FA
:00401241  lock
:00401242  push ds
:00401243  inc eax
:00401244  BYTE 0


:00401245  push ebp
:00401246  mov ebp, esp
:00401248  push 00000000
:0040124A  push ecx			< ecx pointe sur "Greet !!!"

* Possible StringData Ref from Code Obj ->"You did it !!! =)"
                                  |
:0040124B  push 00401C18
:00401250  push [ebp+08]

* Reference To: USER32.MessageBoxA, Ord:0000h
                                  |
:00401253  Call dword ptr [00401D5C]

Vive le code spaghetti ; je viens de passer 1/4 d'heure pour comprendre comment ça fonctionnait ! J'espère que les couleurs et les commentaires vous aiderons une fois de plus à comprendre, car ce passage n'est pas des plus simples.
Je vais juste détailler le passage sur le code polymorphe (le bloc rouge) :

on avait :				on aura (après le jmp 00401240) :
:0040123F EBB9     jmp 004011FA	  	:0040123F EB              BYTE EB
:00401241 F0       lock			:00401240 B9F01E4000      mov eax, 00401EF0
:00401242 1E       push ds
:00401243 40       inc eax
:00401244 00       BYTE 0

On va résumer un peu tout ça pour y voir plus clair :

  1. Une chaine est créée à partir de notre nom.
  2. Après quelques calculs avec cette chaine, notre sérial, et une autre valeur issue de notre nom, on effectue un test (xor eax, ecx).
  3. Si eax = ecx, le flag registered est mis à true, sinon, il reste à false.
  4. Un test est fait pour savoir si le flag registered est à true ou à false, ce qui nous entrainera vers good boy ou bad boy.

Ce résumé et bien entendu très simplifié, mais je pense qu'il contient l'essenciel.

Bon, maintenant qu'on est lancé, on va s'attaquer au keygen. Autant vous le dire tout de suite, si vous n'avez pas tout compris à ce que l'on vient de faire, relisez, car la partie keygen n'est vraiment pas facile.

La première idée que j'ai eu était de calculer la valeur correspondant au eax du xor eax, ecx (donc en passant par imul eax, 65446F43), et ensuite de remonter au ecx du xor eax, ecx (en divisant la valeur obtenue par 436F4465). C'était même pas la peine d'y penser ! Si la valeur correspondant à notre nom dépasse seulement 2, eax dépassera FFFFFFFFh, et donc va être tronqué. Si l'on obtient 0012451Fh à la place de 12E0012451Fh, on va quand même pas considérer que c'est pareil ! Donc, on oublie ...
On va plutôt chercher des valeurs i, j et x telles que :

	| i * 65446F43h = x
	| j * 436F4465h = x

Encore une chose avant de coder ce premier bruteforce, si on regarde bien la routine de génération du eax du xor eax, ecx, on trouve en 004010A9 imul eax, 00000043, c'est à dire que j devra être divisible par 43h. On va donc faire un brute forcer 2 en 1 (comme la lessive ;-) !

procedure TForm1.Button1Click(Sender: TObject);
var i, j, x : LongInt;
begin
for j := 1 to 1000000 do
     if (j mod $43 = 0) then
     begin
     x := j * $436F4465;
     for i := 1 to 1000000 do
          begin
	  if i * $65446F43 = x then
	       Memo1.Lines.Add(IntToHex(i, 8) + '  ' 
	       + IntToHex(j, 8) + '  ' + IntToHex(x, 8));
	  end;
     end;
end;

Bon, attendez-moi là, je vais prendre une douche. Pompompommm..... ...... ..... Voilà, un petit coup d'oeil sur la gauge que j'ai rajouté au brute forcer : 27%. Bon, on a déjà deux x, on va s'arréter là (un petit exit sous SI devrait suffir). Voilà ce que j'ai :

x               i               j
29FA9645        377D7           23C61
53F52C8A        6EFAE           478C2

Maintenant que l'on a nos "passages", on va pouvoir continuer. On va prendre le premier x (29FA9645h), et les valeurs de i et j correspondantes.
On récapitule : on sait maintenant que pour l'instruction
xor eax, ecx, on devra avoir eax = 23C61h et ecx = 377D7h

Comme il faut bien commencer quelque part, on va s'arranger pour avoir eax = 23C61h. Je remet l'essenciel de l'algo qui traite de cette partie de la génération :

:0040109F  mov al, byte ptr [edi+00002000] < prend un char du nom
:004010A5  cmp al, 00			< il reste des chars ?
:004010A7  je 004010D3			< non, on s'en va
:004010A9  imul eax, 00000043		< sinon, eax = eax * 43h
:004010AC  lea edx, dword ptr [eax+4*eax]< edx = 5 * eax
:004010AF  add edx, eax			< edx = edx + eax
:004010B1  push eax
{coupé}
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401117(C)
|
:00401111  add eax, dword ptr [edi]	< ajoute la valeur contenue dans edi
:00401113  add edi, 00000004		< valeur suivante
:00401116  dec esi			< il reste des valeurs ?
:00401117  jne 00401111			< oui, on boucle
:00401119  imul eax, 436F4465		< eax = eax * 436F4465h
:0040111F  mov dword ptr [00408300], eax	< enfin !

On va tirer de ceci une petite formule, pour un nom à 5 caractères (je ne compte pas les chars qui seront issus de Cte ; a, b, c, d, e sont les valeur ascii des caractères du nom), cette égalité devra-être vérifiée :

     a * 43h + b * 43h + c * 43h + d * 43h + e * 43h + Cte * 43h = 23C61h
<=>  43h * (a + b + c + d + e + Cte) = 23C61h
<=>  a + b + c + d + e + Cte = 88Bh

Vous aurez bien compris que l'on arrive au résultat avec autant de caractères que l'on veut (du moment que la somme de leur valeur ascii + cte est égale à 88Bh). Cte est une constante. Elle nous sert à atteindre 88Bh. On la décomposera en caractères pour faire le keygen.

Maintenant, on va s'arranger pour que ecx = 377D7h, et là ça va être un peu plus dur ... Encore une fois, voici la partie de l'algo qui nous interresse :

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004010D1(U)
|
:0040109F  mov al, byte ptr [edi+00002000] < prend un char du nom
:004010A5  cmp al, 00			< il reste des chars ?
:004010A7  je 004010D3			< non, on s'en va
:004010A9  imul eax, 00000043		< sinon, eax = eax * 43h
:004010AC  lea edx, dword ptr [eax+4*eax]< edx = 5 * eax
:004010AF  add edx, eax			< edx = edx + eax
:004010B1  push eax			< save eax
:004010B2  xor eax, eax			< eax = 0
:004010B4  push edx			< save edx
:004010B5  xchg eax,edx			< échange eax et edx
:004010B6  xor ecx, ecx			< ecx = 0
:004010B8  mov cl, 0A			< ecx = 0Ah
:004010BA  idiv ecx			< edx = eax mod ecx
:004010BC  pop eax			< eax = ancienne valeur de edx
:004010BD  imul eax, edx			< eax = eax * edx 
:004010C0  push eax			< save eax
:004010C1  xor eax, eax			< eax = 0
:004010C3  imul edx, esi			< edx = edx * esi
:004010C6  xchg eax,edx			< échange eax et edx
:004010C7  pop edx			< edx = ancienne valeur de eax
:004010C8  xor dl, al			< dl = dl xor al
:004010CA  add edx, 00000070		< edx = edx + 70h
:004010CD  mov byte ptr [edi], dl	< on écrit un char
:004010CF  dec esi			< esi = esi - 1
:004010D0  inc edi			< char suivant
:004010D1  jmp 0040109F			< retour au début
{coupé}
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004011C7(U)
|
:004011AD  mov cl, byte ptr [edi]	< prend 1 char du sérial
:004011AF  cmp ecx, 00000000		< il reste des char ?
:004011B2  je 004011C9			< non, alors on saute

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004011BC(U)
|
:004011B4  mov al, byte ptr [ebx]	< prend 1 char du nom modifié
:004011B6  cmp al, 00			< il reste des char ?
:004011B8  jne 004011BE			< oui, on saute
:004011BA  xor bl, bl			< non, on remet bl à 0
:004011BC  jmp 004011B4

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004011B8(C)
|
:004011BE  imul eax, ecx			< eax = eax * ecx
:004011C1  add edx, eax			< edx = edx + eax
:004011C3  inc ebx			< char suivant (de la chaine)
:004011C4  inc edi			< char suivant (du sérial)
:004011C5  xor eax, eax			< eax = 0
:004011C7  jmp 004011AD			< on revient

Ici non plus, une petite formule ne nous ferais pas de mal. On sait que la chaine formée à partir de notre nom aura le même nombre de caractères que celui-ci, dont les valeurs ascii seront respectivement a', b', c', d', e' (on garde notre nom de 5 lettres), et on garde la constante. On aura donc :

                     algo
a, b, c, d, e, Cte --------> a', b', c', d', e', Cte'

Comme notre sérial devra avoir au moins le même nombre de caractères que notre nom, on va aussi prendre un sérial à 5 caractères (dont les valeurs ascii seront respectivement v, w, x, y, z) plus une constante Cte2. Cte2 devra contenir le même nombre de caractères que Cte'
Si on regarde la deuxième partie de l'algo, on voit que l'on devra obtenir cette relation :

 (Cte' * Cte2) + (a' * v) + (b' * w) + (c' * x) + (d' * y) + (e' * z) = 377D7h  (1)

Le problème c'est que ça va nous donner quelque chose de très compliqué à résoudre ! On pourrait chercher un diviseur de 377D7h, j'ai essayé, et c'est pas la meilleure solution ! On va donc prendre un sérial de n caractères, dont (n-1) caractères sont égaux, et le dernier servira à atteindre 377D7h :

(1) <=> v * (a' + b' + c' + d' + char(1) + char(2) + .. + char(n)) + z * e' = 377D7h

Les chars de char(1) à char(n) sont les caractères qui étaient contenus dans Cte'. En fait comme Cte nous servait à atteindre 88Bh, on aurait pu le remplacer par n * (FFh) + Reste (le reste étant une valeur ascii valide). Si vous ne comprenez pas, essayez de comprendre la source du keygen (tout en bas). On en tire donc (XXh et Reste' étant des valeurs ascii valides) :

(1) <=> v * (a' + b' + c' + d' + n * (XXh) + Reste') + z + e' = 377D7h 

On revient à nos moutons ;-)
L'idéal serait que e' = 1, comme ça, seul z (la valeur ascii du dernier caractère du sérial) permettrait d'atteindre 377D7h. J'ai donc fait un autre petit brute forcer pour voir s'il est possible d'obtenir un e' = 1 :

procedure TForm1.Button1Click(Sender: TObject);
var eax, edx, save_eax, save_edx, esi, i : LongInt;
begin
for esi := 1 to $FF do
    begin
    for i := 1 to $FF do
        begin
        eax := i * $43;
        edx := eax * 6;
        save_edx := edx;
        eax := edx;
        edx := eax mod $A;
        eax := save_edx;
        eax := eax * edx;
        save_eax := eax;
        edx := edx * esi;
        eax := edx;
        edx := save_eax;
        edx := StrToInt('$' + Copy(IntToHex(edx,8), 7, 2));
        eax := StrToInt('$' + Copy(IntToHex(eax,8), 7, 2));
        edx := edx xor eax;
        edx := edx + $70;
        edx := StrToInt('$' + Copy(IntToHex(edx,8), 7, 2));
        if edx = 1 then
            Memo1.Lines.Add(IntToStr(esi) + '     ' + IntToStr(i));
        end;
    end;
end;

On lance le prog, et .... non ! Il a rien trouvé le c.. ! Bon, c'est pas très grave, on a qu'à chercher e' = 2 (on remplace if edx =1 then par if edx = 2 then), ça compliquera pas beaucoup plus. Voici ce que l'on obtient :

position         val. ascii
1                63
1                196
3                61
.                .
.                .
255              131
255              238

On va prendre la première ligne :
position = 1 signifie que ce caractère sera le dernier caractère du nom (et non le premier). Le dernier caractère du nom sera donc un '?'.

Si on cherche une valeur e pour e' = {3, 5, 7, 9 ....}, on ne trouvera rien, car e' est toujours pair, et ça c'est pas très bon. Je m'explique, on doit avoir :

     v * (a' + b' + c' + d' + Char(1) + .. + Char(n)) + z * e' = 377D7h
     ************************************************   ******   ******
                            pair                         pair    impair

Chez moi, la somme de deux nombres pairs donne un nombre pair, et apparemment, ici, ça risque de poser problème. On reprend donc ce que l'on avait trouvé avec le premier brute forcer :

x               i               j
29FA9645        377D7           23C61
53F52C8A        6EFAE           478C2

Ce coup-ci, on va prendre la deuxième ligne, et voilà ce qu'on devra obtenir :

     | a + b + c + d + e + Cte = 1116h
     | v * (a' + b' + c' + d' + Char(1) + .. + Char(n)) + z * e' = 6EFAEh

Bon, maintenant, on en assez pour coder un joli petit keygen :

procedure TForm1.Button1Click(Sender: TObject);
var Ascii, Reste, Manque, eax, ecx, edx, save_eax, save_edx : LongInt;
    i, esi, NBFF, Coeff, a, b : Integer;
    chaine : string;
begin
for a := 1 to $FF do
    for b := 1 to $FF do
        begin
        Edit1.Text := 'MiNoS-' + Chr(a) + Chr(b);
        Ascii := 0;
        for i := 1 to length(Edit1.Text) do
            Ascii := Ascii + Ord(Edit1.Text[i]);
        Manque := $1116 - Ascii;
        NbFF := Manque div $FF;
        Reste := Manque mod $FF;
        Edit1.Text := Edit1.Text + Chr(Reste);
        for i := 1 to NbFF - 1 do
            Edit1.Text := Edit1.Text + Chr($FF);
        Edit1.Text := Edit1.Text + Chr(192) + Chr(63);
        chaine := '';
        esi := Length(Edit1.Text);
        for i := 1 to Length(Edit1.Text) do
            begin
            eax := Ord(Edit1.Text[i]);
            eax := eax * $43;
            edx := eax * 6;
            save_edx := edx;
            eax := edx;
            edx := 0;
            edx := eax mod $A;
            eax := save_edx;
            eax := eax * edx;
            save_eax := eax;
            edx := edx * esi;
            eax := edx;
            edx := save_eax;
            edx := StrToInt('$' + Copy(IntToHex(edx,8), 7, 2));
            eax := StrToInt('$' + Copy(IntToHex(eax,8), 7, 2));
            edx := edx xor eax;
            edx := edx + $70;
            edx := StrToInt('$' + Copy(IntToHex(edx,8), 7, 2));
            Chaine := Chaine + Chr(edx);
            esi := esi - 1;
            end;
        Edit2.Text := Chaine;
        Ascii := 0;
        for i := 1 to (Length(Chaine)-1) do
            Ascii := Ascii + Ord(Chaine[i]);
        Coeff := $6EFAE div Ascii;
        Reste := $6EFAE mod Ascii;
        if (Reste <= 255) and (reste mod 2 = 0) then
            begin
            for i := 1 to (Length(Edit2.Text) - 1) do
                Edit3.Text := Edit3.Text + Chr(Coeff);
            Edit3.Text := Edit3.Text + Chr(Reste div 2);
            Memo1.Lines.Add(Edit1.Text + ' ------> '+ Edit3.text);
            Edit3.Text := '';
            end;
        end;
end;

Désolé, mais j'ai pas le courage de commenter, j'ai mis en rouge les points principaux que l'on a abordé précédemment. J'espère que cela vous aidera à comprendre ce keygen (qui est d'ailleur très mal codé en Delphi).
Si vous n'avez rien compris à ce tut, relisez, et si vous ne comprennez toujours rien ... et bien mail-me ;-)
Si vous trouvez des erreurs (certainement), mail-me too.

Quelques greets pour :

Christal : rien qu'a la présentation du tut, on voit déjà que je te dois beaucoup ;-), merci pour tout
YoLeJedi : merci pour tes explications sur Asprotect, et.. ton patcheur est vraiment génial
MrPhilex : héhé ;-)
CoDe_InSiDe : thx for this good crackme, and ... cya...

 

C'est fini pour aujourd'hui !

MiNoS