Cible :      jB Crypto KeygenMe #1
Outils :      - Softice ( ou autre debugger )
       - Icedump ( juste pour moi pour les captures d'écran )
       - IDA / W32Dasm
       - RSA Tool v2.7 de the Egoiste!
       - CryptoCal v1.2 de Christal

NB
:
J'espère que la police " Courier " est installé sur votre PC !


1 - Approche en douceur...

Déjà, je regarde le binaire du KeygenMe avec un éditeur hexa et je trouve une chose intéressante :

Ce "#Eg" est un élément très significatif d'un HASH.... mais le mieux reste à venir en regardant le désassemblage :

Vous le voyiez rapidement sur W32Dasm, car ce sont les premières lignes du désassemblage.

CODE:00401000 sub_401000 proc near ; CODE XREF: sub_402795+4
CODE:00401000 mov ds:dword_404524,
67452301h
CODE:0040100A mov ds:dword_404528,
0EFCDAB89h
CODE:00401014 mov ds:dword_40452C,
98BADCFEh
CODE:0040101E mov ds:dword_404530,
10325476h
CODE:00401028 retn
CODE:00401028 sub_401000 endp

Regardez via Internet pour ces valeurs ou dans le tut de Christal ou dans votre mémoire de crypto-Keygenneur, vous aurez le même résultat :
Initialisation d'un HASH de type MD5, RipeMD ou SHA1.
On présume déjà de l'utilisation d'un HASH.


2 - Identification du premier ennemi...

Le Keygen est écrit avec TASM et on reconnait sans peine d'ailleurs la structuration interne.
Je rentre mes informations (Lisegrim/321654), je pose bpx GetDlgItemTextA et je mets mon curseur sur l'une des EditBox (nom/serial) pour activer la gestion de celle-ci.

015F:0040335D 6890404000   PUSH 00404090               <-- buffer pour stocker le nom
015F:00403362 68E8030000   PUSH 000003E8               
<-- ID de l'editBox
015F:00403367 FF7508       PUSH DWORD PTR [EBP+08]     
<-- handle
015F:0040336A E8F6000000   CALL USER32!GetDlgItemTextA
<-- récupére le nom
015F:0040336F 83F802       CMP EAX,02                  
<-- nom < 2 lettres ?
015F:00403372 0F826FFFFFFF JB 004032E7                 
<-- oui = pas bon
015F:00403378 50           PUSH EAX                    
<-- nombre de caractére du nom
015F:00403379 6890404000   PUSH 00404090               
<-- nom = "Lisegrim"
015F:0040337E E812F4FFFF   CALL 00402795               
<-- un call mystérieux

Ok, alors, eax a été modifié après ce call mystérieux, je regarde vers quoi il pointe :

0167:00404524 56 52 8D 24 F1 F2 E1 69-7B EB 94 AB 1E 30 AF 2D VR.$...i{....0.-
0167:00404534
1D 86 18 D1 7B D8 E9 D5-41 9D 7E A2 8D 1A 18 F9 ....{...A.~.....
0167:00404544 66 FB 0E D8 E8 10 4A CA-A2 34 80 F0 8C 09 41 92 f.....J..4....A.
0167:00404554
40 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 @...............

D'accord, donc notre "Lisegrim" a été transformé...
Je regarde les arguments de ce call :

push taille_nom
push adresse_nom
call Transfo

C'est plutôt faible en informations cryptographique ! ( pas de clé, d'adresse d'une table de caractéres, etc )
Je pencherai pour le HASH :)

Je décide de jeter un BREF regard dans ce call :

015F:00402795 C8000000            ENTER     0000,00            <-- on arrive ici
015F:00402799 E862E8FFFF          CALL      00401000           
<-- tiens ! on déjà vu cette adresse !
015F:0040279E FF750C              PUSH      DWORD PTR [EBP+0C]
015F:004027A1 FF7508              PUSH      DWORD PTR [EBP+08]

On a déjà vu cette adresse 401000 en désassemblant :)

015F:00401000 C7052445400001234567 MOV       DWORD PTR [00404524],67452301 <-- notre HASH !!!!
015F:0040100A C7052845400089ABCDEF MOV       DWORD PTR [00404528],EFCDAB89
015F:00401014 C7052C454000FEDCBA98 MOV       DWORD PTR [0040452C],98BADCFE
015F:0040101E C7053045400076543210 MOV       DWORD PTR [00404530],10325476
015F:00401028 C3                   RET

Alors, on a bien l'utilisation d'un HASH :)
Sortons du call du HASH pour revenir à la routine.

015F:00403383 B910000000          MOV       ECX,00000010    <-- ecx = 10h = 16 bytes à convertir
015F:00403388 8BF0                MOV       ESI,EAX         
<-- esi = adresse de mon nom hashé
015F:0040338A BFBC414000          MOV       EDI,004041BC    
<-- adresse du buffer
015F:0040338F E855F4FFFF          CALL      004027E9        
<-- call de transformation ASCII

Alors ici, pour comprendre rapidement le call, il faut juste mettre toute la chaine ASCII en 004041BC à 00 hexa.
Après passage du call, l'espace mémoire 004041BC est rempli de la chaine ASCII.

Le buffer qui va récupérer la transformation ASCII n'est pas nettoyé à chaque passage...
On avait donc la précédente transformation ASCII.

Le call convertit les bytes du HASH en String ASCII.

56h, 52h, 8Dh, 24h, F1h, F2h, E1h, 69h, 7Bh, EBh, 94h, ABh, 1Eh, 30h, AFh, 2Dh
--> String "56528D24F1F2E1697BEB94AB1E30AF2D"

Ok, fesons une pause pour faire le point :

HASH ( Lisegrim ) =
56528D24F1F2E1697BEB94AB1E30AF2D

Mettons un nom sur ce HASH !
Pour cela, je prends Cryptocalc de Christal et je vais dans la section HASH et coche RipeMd, MD, SHA.


Ok, Ripe MD 128...


3 - Identification du deuxième ennemi...

Continuons notre analyse de la routine.

015F:00403394 6A32                PUSH      32                       <-- sur 50 bytes
015F:00403396 6858414000          PUSH      00404158                 
<-- du buffer où est stocké le serial
015F:0040339B E8A7000000          CALL      KERNEL32!RtlZeroMemory   
<-- remet tout à 00 hexa

jB s'assure juste d'avoir un buffer propre avant de récupérer le serial.

015F:004033A0 6A32                PUSH      32                       <-- 50 caractéres
015F:004033A2 6858414000          PUSH      00404158                 
<-- le buffer serial
015F:004033A7 68E9030000          PUSH      000003E9                 
<-- ID de l'EditBox du serial
015F:004033AC FF7508              PUSH      DWORD PTR [EBP+08]       
<-- handle
015F:004033AF E8B1000000          CALL      USER32!GetDlgItemTextA   
<-- récupére le serial

Passons à la suite...

015F:004033B4 BE58414000          MOV       ESI,00404158   <-- "321654 " mon serial
015F:004033B9 BF8A414000          MOV       EDI,0040418A   
<-- buffer
015F:004033BE E88AFEFFFF          CALL      0040324D       
<-- transforme la chaine ASCII en bytes hexa.

Ok, pareil qu'avant, pour voir le rôle du call, il faut passer tout à 00 hexa en 0040418A, car le keygenme ne nettoie pas cette zone après utilisation.
On a donc :

"321654" --> 32h,16h,54h (= 3 bytes utilisés)

015F:004033C3 52                  PUSH      EDX           <-- nombre de bytes utilisés
015F:004033C4 68EE414000          PUSH      004041EE      
<-- buffer de stockage
015F:004033C9 688A414000          PUSH      0040418A      
<-- serial en bytes
015F:004033CE 6887444000          PUSH      00404487      
<-- (1)
015F:004033D3 687F444000          PUSH      0040447F      
<-- (2)
015F:004033D8 E84EF4FFFF          CALL      0040282B      
<-- un mystérieux call

Les datas donnent en mémoire :

(1)
0167:00404487
0A 00 86 AD BC FB C2 3A 91 65 3F 04 8B 24 A6 4A
0167:00404497
20 BE 04 18 E2 79 00 00

(2)
0167:0040447F
02 00 DA 20 2C 91 00 00

Donc là, on a visiblement le passage de notre nom dans un truc qui utilise 2 data.
Là, on réfléchit quelques secondes pour faire une supposition :)

Constatation n°1 :
On sait que le crackme est écrit en ASM... pas trop propice pour trouver des sources crypto à priori...
On mise sur le fait que jB n'a pas pu nous trouver une crypto "exotique" :)
RSA est en vogue...

Constatation n°2 :
Hum... d'autant que RSA demande 2 valeurs ( une clé publique et un Modulus ) pour crypter une information...
Cela devient fortement plausible !

On va tenter la piste RSA...
Si on s'est trompé, on testera sur une autre crypto.
Déjà, j'exclue El Gamal pour le manque de data pushés.

------------------------------ Note ------------------------------------------------
Au moment où j'ai attaqué ce KeygenMe, c'était Dimanche soir...
Je n'ai pas accés à Internet et j'utilise des cybercafés pour y avoir accès.

Vous avez donc sur moi, un avantage ! Vous pouvez aller chercher sur Google... pas moi !

Ce n'est pas de la flatterie intellectuelle que je me fais, mais c'est pour expliquer que maintenant je vais trouver la voie et la compréhension dans un souvenir.
Vous, vous pourrez le faire en fesant une recherche sur Google sur les sources RSA et sur des tuts de cracking sur RSA :)
( Rien ne sort du chapeau ! )
-----------------------------------------------------------------------------------------

J'avais entendu que The Egoiste! avait sorti une source ASM pour RSA sur son site, malheureusement disparu depuis un moment :(
Aussi, j'ai un grand nombre de Crackme à disposition sur mon PC et j'ai retrouvé son "Trial TMG KeygenMe n°2" utilisant RSA...
Je me suis mise à désassembler le crackme sans vraiment d'espoir...

Et c'est là que les choses sérieuses commencent !

jb Keygen Me

tE! Trial TMG KeygenMe n°2

Ok, alors déjà on repére des similarités... qui ne cessent de s'accumuler en avançant dans les 2 désassemblages ! ( je n'ai pas tout mis ! )
Il y a quelques différences qui viennent d'une altération de la source de tE! pour son Trial KeygenMe afin de déstabiliser le compétiteur :)
Cette réponse me vient de crypto4newbie 1 de Christal qui a fait l'étude de ce KeygenMe !

D'accord, alors non seulement on sait quel est la crypto, mais en plus on connait la provenance de la source et son auteur !
C'est royal pour la Keygennisation Crypto :)

Reprenons les informations trouvées :

015F:004033C3 52                  PUSH      EDX           <-- nombre de bytes utilisés
015F:004033C4 68EE414000          PUSH      004041EE      
<-- buffer du résultat
015F:004033C9 688A414000          PUSH      0040418A      <-- serial en bytes
015F:004033CE 6887444000          PUSH      00404487      
<-- (1)
015F:004033D3 687F444000          PUSH      0040447F      
<-- (2)
015F:004033D8 E84EF4FFFF          CALL      0040282B      
<-- un mystérieux call

(1)
0167:00404487
0A 00 86 AD BC FB C2 3A 91 65 3F 04 8B 24 A6 4A
0167:00404497
20 BE 04 18 E2 79 00 00

(2)
0167:0040447F
02 00 DA 20 2C 91 00 00

Ok alors pour RSA, on doit avoir un Modulus + une clé publique.
Hi hi, à votre avis, (1) et (2), cela ne pourrait pas être cela ? :)

Le truc aussi que l'on apprend via cette source, c'est que tE! formate ses data de la façon suivante : ( information issue de Crypto4Newbie de Christal, toujours et encore :) )

Formatage des data :

XX 00
YY YY YY YY YY YY .. .. 00 00

XX = multipliant pour obtenir la taille de la data
YY = bytes de la data

Ce qui donne :

(1)

0A 00 86 AD BC FB C2 3A 91 65 3F 04 8B 24 A6 4A
20 BE 04 18 E2 79 00 00

0Ah = 10 = multipliant pour connaitre la taille de la data
Nous sommes en hexadécimal donc base = 16.
Son formatage dit à sa routine :
Taille = 10 * base = 10 * 16 = 160 bits


(2)
02 00 DA 20 2C 91 00 00
Pareil ici :
taille = 02 * base = 02 * 16 = 32 bits

------------------------------ Note ------------------------------------------------
C'est ce formatage qui m'a poussé à releaser ma solution plutôt que de vous laisser continuer à chercher.
-----------------------------------------------------------------------------------------

Ok, maintenant, on a trouvé nos informations :

Modulus = N = 86ADBCFBC23A91653F048B24A64A20BE0418E279
Clé Publique = E = DA202C91

Le modulus étant le garant de la sécurité du RSA, autant qu'il soit grand ! ( Ce qui donne l'indice des ordres par rapport à la Clé publique. )

Vous voulez connaitre la valeur de notre RSA ?
Et bien je vous l'ai déjà donné 160 bits ! Donc RSA 160...

Attendez la suite, je vous donnerai le moyen de l'obtenir via RSA Tool.


Reprenons la routine...

015F:004033DD B910000000          MOV       ECX,00000010   <-- ecx = 10h = 16 bytes à convertir
015F:004033E2 BEF0414000          MOV       ESI,004041F0   
<-- mon serial crypté
015F:004033E7 BF2A424000          MOV       EDI,0040422A   
<-- buffer pour recevoir le résultat
015F:004033EC E8F8F3FFFF          CALL      004027E9       
<-- converti le en String ASCII

Mon serial crypté, qui est en bytes en mémoire, est transformé en chaine ASCII.

3Ah, 5Bh, A0h, C3h, F8h, 61h, 50h, ABh, EAh, 2Dh, ADh, 73h, 17h, B5h, 0Ah, 10h
--> "3A5BA0C3F86150ABEA2DAD7317B50A10"


Et maintenant le grand final :

015F:004033F1 682A424000          PUSH      0040422A       <-- mon nom hashé     "56528D24F1F2E1697BEB94AB1E30AF2D"
015F:004033F6 68BC414000
          PUSH      004041BC       <-- mon serial crypté "3A5BA0C3F86150ABEA2DAD7317B50A10"
015F:004033FB E83B000000
          CALL      0040343B       <-- (lstrcmpA) sont-ils égaux ?
015F:00403400 85C0
                TEST      EAX,EAX        <-- non ?
015F:00403402 0F85DFFEFFFF
        JNZ       004032E7       <-- alors bye bye

Ok, alors pour résumer les tests :

Ripe-MD_128 ( Nom ) = CRYPT_RSA_160 ( Serial )

On a terminé l'analyse !
Exception faite du truc du formatage de tE!, avouez que cela n'est pas compliqué ?


4 - Renverser la vapeur.


Alors, le truc qu'il faudrait, c'est :

DECRYPT_RSA_160 [ Ripe-MD_128 ( Nom ) ] = Serial

Et bien.... C'EST FESABLE !
La valeur du RSA est 160 bits qui est fesable par RSA Tool ( du même auteur en plus : tE! ).
On aurait 1024 bits par exemple, on n'aurait pas pu !

RSA nécessite une clé de cryptage et un modulus pour crypter...
On les a !
Modulus = N = 86ADBCFBC23A91653F048B24A64A20BE0418E279
Clé Publique = E(ncrypt) = DA202C91


RSA nécessite une clé de décryptage et un modulus pour décrypter...
On n'en a qu'un !
Modulus = N = 86ADBCFBC23A91653F048B24A64A20BE0418E279
Clé Privée = D(ecrypt) = ????????????????

Utilisons RSA Tool pour calculer D !
1 - Remplissez les champs E et N par les valeurs que l'on vient de trouver.
2 - Clickez sur "Exact Size" pour connaitre la taille ( pour retrouver 160 bits )
3 - Clickez sur " Factor N " pour factoriser le Modulus.


Le résultat arrive vite :

Le résultat arrive en un éclair :

C'est parfait, on a tous les éléments pour le RSA en décryptage :)

NB :
C'est ici que notre supposition ( que la crypto est RSA ) est vérifié.
En effet, les valeurs que vous entrez ( le modulus, etc ) respectent à des régles de formatage pour lier à la mathématique de RSA.
Si on s'était trompé de Crypto, on n'aurait ( sauf si on n'a pas de chance ) pas pu retrouver la clé de décryptage D.


5 - Le Keygen

On a tout maintenant, il reste à trouver les sources de RSA et Ripe MD 128 :)
Comme je vous l'ai marqué, au moment où j'ai attaqué ce KeygenMe, je n'avais pas accès à Internet... alors j'ai fait avec les moyens du bord !

Cryptocalc de Christal a ( c'est un miracle ) : 3 DLL...
* ghirirsa.dll
* md.dll
* ripemd.dll

On laisse tomber md.dll et on garde juste les 2 autres !
En désassemblant les DLL, on a les noms des fonctions et en traçant l'utilisation de Cryptocalc, on a les paramétres :

DLL : ripemd.dll
Crypto : Ripe MD 128
Fonction : RIPEMD128
Paramétres push    offset String_Entree
push
    nombre_caractere
push
    offset String_Sortie
call
     DLL_RIPE
DLL : ghirirsa.dll
Crypto : RSA
Fonction : ghRSAexp
Paramétres push    offset String_Sortie
push
    offset String_Entree
push
    BASERSA ( 010h )
push
    offset RSA_N
push
    offset RSA_D
call
     DLL_RSA

Ces DLL sont bien pratiques car elles donnent le résultat directement en String ASCII :)

Le coeur de mon Keygen ASM :

push  32h
push  offset BUF_NOM          
; nom
push  IDC_NOMBOX
push  hWnd
call  GetDlgItemTextA
mov   dword ptr [NB_NOM], eax
; nombre de caractére nom

cmp   eax, 2                  
; nom < 2 lettres ?
jb    Nom_court               
; oui = exit

cmp   eax, 30                 
; nom > 30 lettres ? (sécurité)
jg    Nom_long                
; oui = exit

; On Hashe le Nom en Ripe-MD128 via la DLL ripemd.dll
push  offset BUF_NOM          
; nom
push  eax                     
; le nombre de caractére
push  offset TMP_BUF1         
; le buffer pour le résultat
call  lpRipe                  
; appel de la DLL

; Conversion ASCII et affichage ( rip du Keygen )
mov  ecx, 10h                 
; nombre de caractére
mov  esi, offset TMP_BUF1     
; chaine entrée
mov  edi, offset RIPE         
; chaine sortie
call sub_4027E9

; Décryptage RSA 160 via la DLL ghirirsa.dll
push  offset SERIAL           
; sortie = serial
push  offset RIPE             
; nom hashé
push  BASERSA                 
; base RSA = 10h
push  offset RSA_n            
; modulus
push  offset RSA_d            
; clé de décryptage
call  lpRSA                   
; appel de la DLL

invoke SetDlgItemText, hWnd, IDC_SERIALBOX, offset SERIAL

..................................
..................................
; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; RIP DU CONVERTISSEUR ASCII ( 100 % IDA et non modifié)
; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

sub_4027E9 proc near ; CODE XREF: DialogFunc+CC p
; DialogFunc+129 p

var_8 = dword ptr -8
var_4 = dword ptr -4

enter 8, 0
mov [ebp+var_4], ecx
xor ecx, ecx
xor eax, eax
mov [ebp+var_8], eax

loc_4027F7: ; CODE XREF: sub_4027E9+3E j
mov al, [esi+ecx]
mov ebx, eax
shr eax, 4
add al, 90h
daa
adc al, 40h
daa
mov edx, [ebp+var_8]
add edx, ecx
mov [edi+edx], al
inc [ebp+var_8]
mov al, bl
and eax, 0Fh
add al, 90h
daa
adc al, 40h
daa
mov edx, [ebp+var_8]
add edx, ecx
mov [edi+edx], al
inc ecx
cmp ecx, [ebp+var_4]
jnz short loc_4027F7
leave
retn
sub_4027E9 endp

Après plusieurs tests sur ce Keygen, j'ai fini par trouver au moins 2 possibilités où le résultat Nom/Serial est mauvais...
J'ai tracé pour retrouver l'erreur, mais je ne la trouve pas...
Mon Keygen est imparfait :(
Le rippage complet des routines lui devrait tenir le choc sans problème :)


6 - Exemple de schéma Inkeygenable

Nous avons vu que la faille dans ce schéma est le RSA qui est sur un nombre de bits trop faibles.
On peut facilement retrouver la clé de décryptage via une factorisation rapide...

Imaginons que nous ayons eu un RSA 1024...

Ripe-MD_128 ( Nom ) = CRYPT_RSA_1024 ( Serial )


RSA Tool est dans les choux !

En gros, le Keygen aurait été impossible car on n'aurait pas pu retrouver la clé de décryptage dans un temps raisonnable... sauf si vous êtes prêts à faire tourner votre PC pendant plusieurs années pour factoriser le Modulus de 1024 bits !

On pourrait tenter une attaque inverse : à savoir, trouver le nom à partir d'un serial ( que l'on donne )...
Le problème est que le HASH est à sens unique :)
Pour retrouver ce qui a été HASHé, il faut faire un bruteforce !
On génére une String dont la taille va augmenter d'un caractére quand toutes les combinaisons de lettres auront été faites....
Cette string sera HASHée et on comparera le résultat avec celui du KeygenMe... Autant dire que cela risque d'être LONG !

C'est inimaginable pour un Keygen...
Obtenir un couple Serial + Nom valide par cette méthode est envisageable... encore faut-il en avoir le temps et la puissance de calcul.