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 |
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 :
|
|
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.