iC!tEaM Trial crackme - Tutoriel par Canterwood

Introduction

J'ai enfin décidé de m'attaquer au fameux Trial crackme !

Lorque Toniard m'avait fait une première proposition il y a quelques mois (je ne sais pas s'il s'en souvient), je lui avais répondu que je débutais, et j'ai juste survolé le crackme de Fnaax pour voir de quoi il ressortait. J'ai rapidement constaté que c'était hors de ma portée et je n'ai pas insisté... D'autant que le challenge était réputé pour être plutôt ardu (selon un message du forum); alors naturellement, j'ai abandonné.

Ce n'est que récemment (le 5 Juin 2003) qu'un autre membre de la team, RocketSpawn, m'a relancé et m'a encouragé à tenter ma chance. Il m'a finalement convaincu et m'a donné quelques conseils pour aborder le programme, notamment en ce qui concerne la technique d'unpacking.
Je me suis donc lancé à l'aventure pour finalement aboutir à ce tutoriel, qui je l'espère, vous plaira et s'avérera suffisamment complet.

Voilà pour cette introduction, je vous propose dès maintenant de passer à l'étude du crackme, si vous le voulez bien !

Démarrage

On va commencer par lancer le programme "à vide" pour voir à quoi l'on a affaire.

Trois champs de texte sont affichés : "Name", "Key" et "Code", en bas nous avons un label qui fait office de status bar.
Si l'on clique sur "Go!", un vilain nag apparaît, si on le supprime, des message box s'affichent en boucle infinie... On est obligé de faire un Ctrl + Alt + Suppr et de mettre fin au processus pour quitter !

Le programme paraît donc d'emblée assez vicieux car au moins deux clés sont calculées ("Key" et "Code"), et d'autre part il y a ce nag qui nous limite dans notre champ d'action, puisque l'on a le droit de ne faire qu'un seul essai à chaque fois que l'on lance l'éxécutable.

Bon, nous allons regarder ce que cela donne avec un debugger.

Pour ma part, j'ai utilisé OllyDbg et WinDbg, mais les manipulations sont aussi valables sous SoftIce ou tout autre outil du genre.

On charge donc l'application, on va à l'entry point et l'on trouve quelque chose qui ressemble à ceci.

0041009C  JMP crkmetri.00410147
004100A1  DD crkmetri.00411CAC
004100A5  DD <&KERNEL32.LoadLibraryA>
004100A9  DD <&KERNEL32.GetProcAddress>
004100AD  DD 00000000
004100B1  DD 000028AC
004100B5  DD crkmetri.0041015E
004100B9  ASCII "NeoLite Executab"
004100C9  ASCII "le File Compress"
004100D9  ASCII "or Copyright (c"
004100E9  ASCII ") 1998,1999 NeoW"
004100F9  ASCII "orx Inc Portion"
00410109  ASCII "s Copyright (c) "
00410119  ASCII "1997-1999 Lee Ha"
00410129  ASCII "siuk All Rights"
00410139  ASCII " Reserved.",0
00410146  DB 00
00410147  MOV EAX,DWORD PTR SS:[ESP+4]
0041014B  AND EAX,DWORD PTR DS:[4100AD]
00410151  CALL crkmetri.00410643
00410156  INC BYTE PTR DS:[410146]
0041015C  JMP EAX
0041015E  CMP BYTE PTR DS:[410146],0
00410165  JNZ SHORT crkmetri.0041017A
00410167  NOP
00410168  NOP
00410169  NOP
0041016A  NOP
0041016B  PUSH EAX
0041016C  SUB EAX,EAX
0041016E  CALL crkmetri.00410643
00410173  POP EAX
00410174  INC BYTE PTR DS:[410146]
0041017A  RETN
    

Bref, on constate que le PE a été packé (avec Neolite, mais peu importe).

Plusieurs choix s'offrent alors à nous quand à la technique à utiliser.

Laquelle est la plus intéressante ?

Faire un dump est la manière la plus facile de s'en tirer mais ce n'est pas forcément la plus judicieuse.
C'est la première chose que j'ai essayé, mais l'éxécutable unpacké ne se réagissait pas tout à fait pareil que son équivalent packé. Peut-être que c'était un mauvais dump, ok, mais dans tous les cas il faut avouer que ce n'est pas très propre, bien que l'on puisse arriver à quelque chose de concluant : le cracking s'effectue ensuite sans trop de problèmes (inversion de sauts, patch de certains octets... la routine quoi !).

Créer un loader se rapproche un peu du patching, ce serait d'ailleurs une méthode plus appropriée dans le ce cas-là.
Mais le problème réside dans le fait que c'est que c'est assez dur à réaliser, et puis ce n'est pas toujours pratique à utiliser, puisque le programme est ensuite dépendant de son loader.

La meilleure solution je pense, c'est quand même d'étudier le listing en profondeur pour comprendre le crackme.
Bien entendu, c'est aussi la méthode qui demande le plus de travail mais où les résultats seront les plus concluants !

C'est ce que nous allons faire : tout décortiquer, afin que lorsque nous serons arrivés au bout, plus rien ne reste obscur.

On en revient donc à notre debugger. On trace un peu dans le code jusqu'à se trouver ici.

00410146  DB 00
00410147  MOV EAX,DWORD PTR SS:[ESP+4]
0041014B  AND EAX,DWORD PTR DS:[4100AD]
00410151  CALL crkmetri.00410643         <-- procédure de décompression en mémoire
00410156  INC BYTE PTR DS:[410146]
0041015C  JMP EAX                        <-- saut vers le "vrai" entry point
0041015E  CMP BYTE PTR DS:[410146],0
00410165  JNZ SHORT crkmetri.0041017A
00410167  NOP
    

On suit donc le saut (de toute façon on n'a pas trop le choix) pour se retrouver au début du programme :

00401000  PUSH crkmetri.004022DD         <-- pose l'adresse d'un buffer
00401005  CALL crkmetri.004016E4         <-- GetLocaleTime : met la date courante dans ce buffer
0040100A  CALL crkmetri.00401620         <-- procédure anti-debugger (!)
0040100F  PUSH crkmetri.0040101E
00401014  CALL crkmetri.004016DE
00401019  JMP crkmetri.00401591          <-- saut vers des routines d'anti-debugging (!)
0040101E  NOP
0040101F  PUSH 0
00401021  CALL crkmetri.004016EA
00401026  JMP crkmetri.00401657
0040102B  PUSH 0
0040102D  CALL crkmetri.004016D2
00401032  MOV DWORD PTR DS:[402268],EAX
00401037  PUSH 0
00401039  PUSH crkmetri.004012F0
0040103E  PUSH 0
00401040  PUSH 3E8
00401045  PUSH DWORD PTR DS:[402268]
0040104B  CALL crkmetri.004016AE         <-- DialogBoxParamA : affiche la boîte de dialogue
00401050  PUSH 0
00401052  CALL crkmetri.004016EA
    

C'est assez bizarre, n'est-ce pas ? Je vais vous faire un petit résumé.

À ce stade, on peut se demander ce que nous allons faire.

On ne peut pas tracer à cause des protections.
La première idée serait d'explorer et de patcher les routines, ce qui est tout à fait faisable (j'ai expérimenté ça au début), mais il existe une manipulation beaucoup plus simple :
on remarque en effet que si l'on démarre l'application avec le debugger mais sans tracer, la fenêtre s'affiche normalement.
On redémarre donc simplement le programme et l'on fait "Run" ou équivalent dans le debugger. La boîte de dialogue apparaît comme par magie !

Nous allons maintenant rentrer dans le vif du sujet.

Tout d'abord, on pose un breakpoint sur la fonction API GetDlgItemTextA, qui sert à récupérer le contenu des champs. Cela se fait généralement en tapant la commande bp GetDlgItemTextA ou similaire dans le debugger.
On remplit ensuite les champs de texte avec des trucs bidon, par exemple 12345 pour la clé et 54321 pour le code. Je vous laisse choisir les valeurs de votre choix, mais pensez à utilisez quelque chose de facile à retenir pour la suite.
Notez au passage que seuls les chiffres sont acceptés pour écrire le code et que les champs de clé et de code sont bridés à un nombre maximal de caractères (respectivement 10 et 31). Juste pour nous donner une petite idée de ce qui nous attend par la suite...

Le programme breake aussitôt dans user32.dll, en revenant dans le thread principal on arrive ici.

00401436  PUSH 32
00401438  PUSH crkmetri.0040226C
0040143D  PUSH 6E
0040143F  PUSH DWORD PTR SS:[EBP+8]
00401442  CALL crkmetri.004016C0         <-- GetDlgItemTextA : récupère le nom
00401447  TEST EAX,EAX                   <-- teste si un nom a été rentré
00401449  JE SHORT crkmetri.004014B1     <-- si ce n'est pas le cas, affiche un message dans la status bar
0040144B  CALL crkmetri.00401620         <-- protection anti-debugger
00401450  PUSH 0A
00401452  PUSH crkmetri.0040229F
00401457  PUSH 6F
00401459  PUSH DWORD PTR SS:[EBP+8]
0040145C  CALL crkmetri.004016C0         <-- GetDlgItemTextA : récupère la clé
00401461  TEST EAX,EAX                   <-- ...
00401463  JE SHORT crkmetri.004014B1     <-- ...
00401465  PUSH 32
00401467  PUSH crkmetri.004022AA
0040146C  PUSH 70
0040146E  PUSH DWORD PTR SS:[EBP+8]
00401471  CALL crkmetri.004016C0         <-- GetDlgItemTextA : récupère le code
00401476  TEST EAX,EAX                   <-- ...
00401478  JE SHORT crkmetri.004014B1     <-- ...
0040147A  CMP EAX,1F                     <-- compare la longueur du code à 1F (31 caractères en décimal)
0040147D  JL crkmetri.004015DB           <-- si elle est inférieure, affiche le nag
00401483  JMP crkmetri.00401057          <-- saut vers la routine de vérification
    

Le crackme vérifie donc que tous les champs sont remplis, et teste la longueur du code, lequel doit faire obligatoirement 31 caractères.

Méfiez-vous de la protection anti-debugger, pour ce faire nopez le call ou passez directement du premier breakpoint au deuxième sans tracer. Lorsque vous reverrez cette protection, n'hésitez pas à noper l'appel sur le champ ;-).

Redémarrez le programme, entrez cette fois-ci un code possédant la bonne longueur et vous arriverez normalement jusqu'au saut qui mène à la routine de vérification.
Cette routine est relativement longue et se divise en plusieurs sous-parties, concernant le keyfile et le code.

1. Le keyfile

On se rend vite compte que la "clé" que l'on rentre est en fait le nom d'un keyfile dans lequel le programme va récupérer des informations.

Le nom du keyfile

Au tout début de la routine, le crackme va tester si le nom du keyfile est valide.

00401057  MOV EAX,crkmetri.0040229F      <-- eax = le nom du keyfile
0040105C  ADD EAX,5
0040105F  CMP BYTE PTR DS:[EAX],2E       <-- teste si le 6ème caractère est un '.' (2E)
00401062  JNZ crkmetri.004015DB          <-- si ce n'est pas le cas, affiche le nag
00401068  INC EAX
00401069  CMP BYTE PTR DS:[EAX],6B       <-- teste si le 7ème caractère est un 'k' (6B)
0040106C  JNZ crkmetri.004015DB          <-- ...
00401072  INC EAX
00401073  CMP BYTE PTR DS:[EAX],65       <-- teste si le 8ème caractère est un 'e' (65)
00401076  JNZ crkmetri.004015DB          <-- ...
0040107C  INC EAX
0040107D  CMP BYTE PTR DS:[EAX],79       <--  teste si le 9ème caractère est un 'y' (79)
00401080  JNZ crkmetri.004015DB          <-- ...
00401086  INC EAX
00401087  MOV EAX,crkmetri.0040229F
0040108C  MOVZX EDX,BYTE PTR DS:[EAX]    <-- edx = 1er caractère
0040108F  XOR DL,BYTE PTR DS:[EAX+1]     <-- edx = edx xor 2ème caractère
00401092  XOR DL,BYTE PTR DS:[EAX+2]     <-- edx = edx xor 3ème caractère
00401095  XOR DL,BYTE PTR DS:[EAX+3]     <-- ...
00401098  XOR DL,BYTE PTR DS:[EAX+4]     <-- ...
0040109B  CMP DL,62                      <-- teste si edx est égal à 62 (en héxadécimal)
0040109E  JNZ crkmetri.004015DB          <-- si ce n'est pas le cas, affiche le nag
004010A4  CMP BYTE PTR DS:[EAX+1],72     <-- teste si le deuxième caractère est un 'r' (72)
004010A8  JNZ crkmetri.004015DB          <-- ...
    

Là aussi, un petit récapitulatif s'impose pour mettre les choses au clair.

Ça y est, on a notre nom ! Si on rentre srqpb.key, on ne saute pas vers le nag !

Notez que l'on aurait pu faire un "key generator" permettant de trouver tous les noms possibles par brute force. Je vous laisse essayer, c'est assez rapide à mettre en oeuvre.

La clé

Maintenant qu'il a ce nom de fichier, le programme va logiquement essayer de l'ouvrir pour y chercher une clé.

Si l'on regarde la suite du code, on remarque qu'il y a deux appels de fonction.

004010AE  PUSH 0
004010B0  PUSH crkmetri.0040229F         <-- pose le nom du fichier
004010B5  CALL crkmetri.004016CC         <-- ouvre le fichier
004010BA  CMP EAX,-1                     <-- teste si le handle est valide (présence du fichier)
004010BD  JE crkmetri.004015DB           <-- si ce n'est pas le cas, affiche le nag
004010C3  PUSH 0A                        <-- pose la longueur de la clé (A, 10 en décimal)
004010C5  PUSH crkmetri.0040229F         <-- pose le nom du fichier
004010CA  PUSH EAX                       <-- pose le handle
004010CB  CALL crkmetri.004016D8         <-- lit la clé et la stocke à la place du nom de fichier
    

Je pense que c'est assez clair. La clé est récupérée et elle est placée dans le buffer qui contenait auparavant le nom du fichier.

C'est à ce moment-là que les choses deviennent intéressantes.
Le crackme va en effet générer une clé et la comparer avec celle contenue dans le fichier. Voyons cela plus en détail.

004010D0  XOR EAX,EAX
004010D2  XOR EBX,EBX
004010D4  XOR ECX,ECX
004010D6  XOR EDX,EDX
004010D8  MOV EAX,crkmetri.004022DD      <-- eax = la date courante (cf. plus haut)
004010DD  MOV EBX,crkmetri.0040226C      <-- ebx = le nom
004010E2  MOV DX,WORD PTR DS:[EAX]       <-- edx = l'année
004010E5  SUB DL,BYTE PTR DS:[EBX]       <-- edx = edx - 1er caractère du nom
004010E7  ADD ECX,EDX                    <-- ecx = ecx + edx
004010E9  MOV DX,WORD PTR DS:[EAX+2]     <-- edx = le mois
004010ED  SUB DL,BYTE PTR DS:[EBX+1]     <-- edx = edx - le deuxième byte du nom
004010F0  ADD ECX,EDX                    <-- ...
004010F2  MOV DX,WORD PTR DS:[EAX+4]     <-- edx = le jour de la semaine
004010F6  SUB DL,BYTE PTR DS:[EBX+3]     <-- ...
004010F9  ADD ECX,EDX                    <-- ...
004010FB  MOV DX,WORD PTR DS:[EAX+6]     <-- edx = le jour
004010FF  SUB DL,BYTE PTR DS:[EBX+5]     <-- ...
00401102  ADD ECX,EDX                    <-- ...
00401104  MOV DX,WORD PTR DS:[EAX+8]     <-- edx = l'heure
00401108  SUB DL,BYTE PTR DS:[EBX+7]     <-- ...
0040110B  ADD ECX,EDX                    <-- ...
0040110D  IMUL ECX,ECX,100000            <-- décale la somme de 5 bytes vers la gauche
00401113  PUSH ECX                       <-- pose la somme
00401114  PUSH crkmetri.00402000
00401119  PUSH crkmetri.004022F3         <-- pose le buffer
0040111E  CALL crkmetri.00401696         <-- wsprintfA : convertit le nombre en string
00401123  ADD ESP,0C
00401126  CALL crkmetri.00401620         <-- protection anti-debugger
0040112B  PUSH crkmetri.004022F3         <-- pose la clé créée
00401130  PUSH crkmetri.0040229F         <-- pose la clé du fichier
00401135  CALL crkmetri.004016C6         <-- lstrcmpA : compare les deux clés
0040113A  TEST EAX,EAX                   <-- teste si elles sont égales
0040113C  JNZ crkmetri.004015DB          <-- si ce n'est pas le cas, affiche le nag
    

On rend compte que la clé est créée à partir du nom et de la date, date qui a été récupérée au début du programme (souvenez-vous, juste après l'entry point).

Mais la date varie ;-), ce qui fait que l'on a une nouvelle clé à chaque heure (puisque les opérations se font au maximum avec l'heure) !
Pour que ça fonctionne, on pourrait évidemment regarder la clé générée à partir du debugger, la recopier dans le keyfile puis relancer le crackme, mais c'est une manipulation qui fait perdre beaucoup de temps.
C'est pourquoi je pense que créer un keygen n'est pas de trop.

J'ai mis ci-dessous un petit keygen en JavaScript pour vous montrer à quoi cela ressemblerait (vous pouvez jeter un oeil à la source de cette page pout voir l'algo).

Génération de la clé :
Nom :
Clé :


Il ne reste plus qu'à copier la clé dans le keyfile, et lancer le crackme avant expiration !

2. Le code

On va maintenant s'attaquer à la deuxième grande partie, à savoir la vérification du code.

Comme celui-ci fait 31 caractères, ce sera assez long. En fait, la vérification est divisée en plusieurs petites routines que nous allons détailler ci-après.

Du 1er au 4ème caractère

Ces 4 premiers caractères font l'objet de quelques 70 lignes de code à eux seuls ! Mais ne nous laissons pas impressionner par le nombre car c'est bien connu, avec l'ASM on obtient vite des listings phénoménaux pour finalement pas grand chose...

Voyons voir ce que cela donne.

00401142  XOR EAX,EAX
00401144  XOR EBX,EBX
00401146  XOR ECX,ECX
00401148  XOR EDX,EDX
0040114A  MOV EDX,crkmetri.004022AA      <-- edx = le code
0040114F  MOV BYTE PTR DS:[EDX+4],0      <-- isole les 4 premiers caractères

00401153  MOVZX EAX,BYTE PTR DS:[EDX]    <-- boucle : convertit chaque caractère en entier
00401156  CMP AL,0
00401158  JE SHORT crkmetri.00401164
0040115A  SUB AL,30
0040115C  ROL ECX,8
0040115F  MOV CL,AL
00401161  INC EDX
00401162  JMP SHORT crkmetri.00401153

00401164  ROL ECX,8
00401167  MOVZX EBX,CL
0040116A  IMUL EBX,EBX,10
0040116D  MOV AL,BL
0040116F  ROL ECX,8
00401172  ADD AL,CL
00401174  ROL EAX,8
00401177  ROL ECX,8
0040117A  MOVZX EBX,CL
0040117D  IMUL EBX,EBX,10
00401180  MOV AL,BL
00401182  ROL ECX,8
00401185  ADD AL,CL                      <-- eax = les 4 premiers chiffres du code
    

Vous vous demandez peut-être à quoi servent ces quelques instructions.

En fait elles effectuent juste un formatage des données.
Si les 4 premiers caractères sont '1', '2', '3' et '4', on obtient quelque chose comme cela en hexadécimal : 31323334, ce qui correspond au code des caractères. Eh bien ce code est transformé pour arriver à ceci : 00001234. Facile, non ?

Nous pouvons maintenant nous intéresser à la suite.

00401187  XOR EBX,EBX
00401189  XOR ECX,ECX
0040118B  XOR EDX,EDX
0040118D  MOV DL,AL                      <-- prend les 3ème et 4ème chiffres du code
0040118F  MOV AL,0
00401191  CMP DL,10
00401194  JL SHORT crkmetri.0040119D
00401196  SUB DL,10                      <-- boucle : garde uniquement le chiffre des unités
00401199  ADD AL,0A                      <-- augmente al d'autant de fois A que l'on a d'itérations nécessaires
0040119B  JMP SHORT crkmetri.00401191
0040119D  ADD AL,DL                      <-- ajoute le chiffre des unités à al
0040119F  ADD BL,AL                      <-- ebx = al (on conserve ce résultat pour plus tard)
004011A1  MOV AL,0
004011A3  ROL EAX,8
004011A6  ROL EAX,8
004011A9  ROL EAX,8
004011AC  MOV DL,AL                      <-- prend les 1er et 2ème chiffres du code
004011AE  MOV AL,0
004011B0  CMP DL,10
004011B3  JL SHORT crkmetri.004011BC
004011B5  SUB DL,10
004011B8  ADD AL,0A
004011BA  JMP SHORT crkmetri.004011B0
004011BC  ADD AL,DL                      <-- procède de la même manière que précédemment
004011BE  IMUL EAX,EAX,64                <-- mutiplie ce résultat par 64
004011C1  ADD BX,AX                      <-- ajoute le résultat à ebx
004011C4  XOR EAX,EAX
004011C6  IMUL EBX,EBX,5                 <-- multiplie le tout par 5
004011C9  PUSH EBX                       <-- pose le résultat
004011CA  PUSH crkmetri.00402000
004011CF  PUSH crkmetri.004022EE         <-- pose un buffer
004011D4  CALL crkmetri.00401696         <-- wsprintfA : formate ce nombre en chaîne de caractères
004011D9  ADD ESP,0C
004011DC  CALL crkmetri.00401620         <-- protection anti-debugger
004011E1  XOR EAX,EAX
004011E3  XOR EBX,EBX
004011E5  XOR ECX,ECX
004011E7  XOR EDX,EDX
004011E9  MOV EAX,crkmetri.004022AA      <-- eax = le code
004011EE  MOV EDX,crkmetri.004022EE      <-- edx = le nombre créé jute avant
004011F3  MOV CL,BYTE PTR DS:[EAX+2]     <-- ecx = le 3eme caractère du code
004011F6  CMP CL,BYTE PTR DS:[EDX+1]     <-- teste si le 3eme caractère est égal au 2ème caractère du nombre créé
004011F9  JNZ crkmetri.004015DB          <-- si ce n'est pas le cas, affiche le nag
    

Même si le nombre de lignes peut paraître impressionnant au premier abord, nous allons comme d'habitude essayer d'en tirer l'essentiel.

Visiblement, un nombre est créé à partir des 4 premiers chiffres du code, puis un test est effectué à partir de ce nombre, une fois que celui-ci a été formaté en caractères.

Intéressons-nous tout d'abord à la manière dont le nombre est généré.

Si l'on suit attentivement les instructions, on arrive à cette opération-là :
nombre = ((A * 1er chiffre + 2ème chiffre) * 64 + A * 3ème chiffre + 4ème chiffre) * 5.
Ceci peut être déroutant au premier abord, mais essayons donc de simplifier... Passons tout déjà en décimal pour que ce soit plus clair :
nombre = ((10 * 1er chiffre + 2ème chiffre) * 100 + 10 * 3ème chiffre + 4ème chiffre) * 5.
Si l'on applique ensuite la distributivité :
nombre = (1000 * 1er chiffre + 100 * 2ème chiffre + 10 * 3ème chiffre + 4ème chiffre) * 5.
Au final, on obtient tout simplement :
nombre = 4 premiers chiffres * 5
Eh oui, il s'agit d'une bête multiplication par 5 ! Tout ce travail pour en arriver là !

On en déduit que l'auteur (Fnaax) possède un certain sens de l'humour...

Il ne nous reste plus qu'un détail à régler : le test conditionnel à la fin, qui exige que le 3ème caractère du code soit égal au deuxième caractère du nombre, donc de sa multiplication par 5 si vous avez bien suivi !

Aucun problème, il y a des tonnes de possibilités : prenez 1000 par exemple, multipliez-le par 5, on arrive à 5000, les conditions sont bien remplies !
Pour ma part j'ai essayé 2003 et ça marchait !

On peut faire un keygen brute force, puisque je ne vous en ai pas montré pour l'instant.
Ne vous inquiétez pas si quelques secondes s'écoulent avant l'affichage des chiffres, c'est normal ;-)

Génération des 4 premiers caractères du code :

Donc pour récapituler nous avons actuellement un code possédant ce format :

2003xxxxxxxxxxxxxxxxxxxxxxxxxxx.

C'est déjà pas mal mais on est encore loin du compte, puisque 27 caractères restent encore inconnus !

Du 6ème au 10ème caractère

On se retrouve ensuite dans une petite routine qui va effectuer des opérations sur 5 autres caractères.

004011FF  MOV EAX,crkmetri.004022AF
00401204  MOV DL,BYTE PTR DS:[EAX]       <-- dl = 6ème caractère
00401206  SUB DL,BYTE PTR DS:[EAX+4]     <-- dl = dl - 10ème caractère
00401209  ADD DL,BYTE PTR DS:[EAX+1]     <-- dl = dl + 7ème caractère
0040120C  CMP DL,BYTE PTR DS:[EAX+3]     <-- compare dl au 9ème caractère
0040120F  JNZ crkmetri.004015DB          <-- si ils sont différents, affiche le nag
00401215  MOV DL,BYTE PTR DS:[EAX]       <-- dl = 6ème caractère
00401217  SUB DL,2                       <-- dl = dl - 2
0040121A  MOV CL,BYTE PTR DS:[EAX+2]     <-- cl = 8eme caractère
0040121D  ADD CL,2                       <-- cl = cl + 2
00401220  CMP CL,DL                      <-- compare dl et cl
00401222  JNZ crkmetri.004015DB          <-- si ils sont différents, affiche le nag
00401228  CMP CL,BYTE PTR DS:[EAX+4]     <-- compare cl et le 10ème caractère
0040122B  JNZ crkmetri.004015DB          <-- si ils sont différents, affiche le nag
    

On peut en tirer plusieurs informations.

Au niveau du premier test, on trouve :
9ème caractère = 6ème caractère - 10ème caractère + 7ème caractère.
De plus, les égalités :
8ème caractère = 6ème caractère - 4
et :
10ème caractère = 8ème caractère + 2
doivent être vraies.
Compliqué tout ça, n'est-ce pas ?

On va effectuer quelques remplacements :
9ème caractère = 6ème caractère - ((6ème caractère - 4) + 2) + 7ème caractère.

On peut à présent trouver des valeurs, si nous fixons le 6ème caractère avec la valeur '6' et le 7ème avec la valeur '7' (pour faire simple), on arrive à cela :
9ème caractère = 6 - ((6 - 4) + 2) + 7 = 6 - 4 + 7 = 9 (en plus le chiffre correspond à sa position ;-)),
8ème caractère = 6 - 4 = 2, et
10ème caractère = 2 + 2 = 4.

Nous pouvons donc affiner notre shéma de code comme cela :

2003x67294xxxxxxxxxxxxxxxxxxxxx.

Du 13ème au 19ème caractère

Là on commence à avoir l'habitude, hein ;-). Regardons donc le code.

00401231  ADD EAX,7
00401234  MOV BYTE PTR DS:[EAX-1],0
00401238  MOV BYTE PTR DS:[EAX+7],0      <-- isole la chaîne

0040123C  MOVZX EDX,BYTE PTR DS:[EAX]    <-- boucle : effectue des opérations sur chacun des caractères (edx)
0040123F  TEST EDX,EDX
00401241  JE SHORT crkmetri.00401259
00401243  ADD EDX,3                      <-- edx = edx + 3
00401246  XOR EDX,3                      <-- edx = edx xor 3
00401249  SUB EDX,3                      <-- edx = edx - 3
0040124C  MOV BYTE PTR DS:[EAX],DL       <-- on remet le caractère modifié dans son buffer
0040124E  INC EAX
0040124F  JMP SHORT crkmetri.0040123C

00401251  CMP BYTE PTR SS:[ECX],DH       <-- inutile
00401254  XOR EAX,363734                 <-- ...
00401259  SUB EAX,7
0040125C  PUSH EAX
0040125D  PUSH EAX                       <-- pose le nombre généré
0040125E  PUSH crkmetri.00401251         <-- pose une constante ("6815476")
00401263  CALL crkmetri.004016C6         <-- lstrcmpA : compare le nombre à la constante !
00401268  TEST EAX,EAX
0040126A  JNZ crkmetri.004015DB          <-- si ils sont différents, affiche le nag
    

Cette partie est très simple, puisque l'on prend chaque caractère de la chaîne, sur lesquels on effectue un petit cryptage en xor, et on compare ensuite le tout avec une constante.

Pour retrouver la bonne combinaison, il nous suffit d'effectuer l'opération inverse à partir de la constante.

Voici l'équivalent JavaScript de cette opération :
(((6 + 3) ^ 3) - 3).toString() + (((8 + 3) ^ 3) - 3).toString() + (((1 + 3) ^ 3) - 3).toString() + (((5 + 3) ^ 3) - 3).toString() + (((4 + 3) ^ 3) - 3).toString() + (((7 + 3) ^ 3) - 3).toString() + (((6 + 3) ^ 3) - 3).toString().

On obtient au final la chaîne 7548167, ce qui nous permet de continuer notre code :

2003x67294xx7548167xxxxxxxxxxxx.

Du 22ème au 31ème caractère

Allez courage, on est bientôt à la fin !

00401270  CALL crkmetri.00401620         <-- protection anti-debugger
00401275  POP EAX
00401276  XOR ECX,ECX
00401278  ADD EAX,9                      <-- eax = la suite du code

0040127B  MOVZX EBX,BYTE PTR DS:[EAX]    <-- boucle : fait la somme des 10 derniers caractères
0040127E  ADD CX,BX
00401281  INC EAX
00401282  CMP BYTE PTR DS:[EAX],0
00401285  JNZ SHORT crkmetri.0040127B

00401287  CMP ECX,207                    <-- teste si cette somme est égale à 207
0040128D  JNZ crkmetri.004015DB          <-- si ce n'est pas le cas, affiche le nag
    

Très bien, il suffit que la somme des 10 derniers bytes soit égale à 207 en héxadécimal !

Après quelques essais, j'ai remarqué que la chaîne 4444444443 convenait parfaitement !

On va compléter de suite notre code :

2003x67294xx7548167xx4444444443.

L'étape finale

Notre code est-il terminé ? Non, car il reste encore des blancs (représentés par la lettre 'x' dans notre shéma).

Comme nous ne savons pas quoi en faire, on va regarder la suite (et fin) du listing, bien entendu !

00401293  PUSH 32
00401295  PUSH crkmetri.004022AA         <-- pose le buffer de code
0040129A  PUSH 70
0040129C  PUSH DWORD PTR SS:[EBP+8]
0040129F  CALL crkmetri.004016C0         <-- GetDlgItemTextA : récupère le code
004012A4  MOV EAX,crkmetri.004022AA
004012A9  XOR ECX,ECX

004012AB  MOVZX EBX,BYTE PTR DS:[EAX]    <-- boucle : fait la somme de tous les caractères
004012AE  ADD CX,BX
004012B1  INC EAX
004012B2  CMP BYTE PTR DS:[EAX],0
004012B5  JNZ SHORT crkmetri.004012AB

004012B7  CMP ECX,65B                    <-- teste si ette somme est égale à 65B
004012BD  JNZ crkmetri.004015DB          <-- si ce n'est pas le cas, affiche le nag
004012C3  JMP SHORT crkmetri.004012DC    <-- sinon saute vers good boy !!
    

Ceci ressemble fortement à l'étape précédente, mais cette fois-ci c'est la somme de tous les caractères du code qui doit être égale à un nombre, en l'occurrence 65B.

Il faut en fait ajuster les blancs pour que le compte soit bon.
Après un petit moment de réflexion (merci JavaScript et la calculatrice Windows !), je suis arrivé à cela :

2003567294557548167594444444443.

C'est le code final et il fonctionne, vous pouvez le tester, le message Key and Code are OK :) s'affiche !
On y est arrivés !!

Bien sûr, il y a plein d'autres codes possibles mais maintenant que vous connaissez le principe, cela ne devrait pas trop poser de problèmes pour en trouver !
On pourrait aussi faire un keygen (ou plutôt un codegen ;-)), mais là je vous laisse vous débrouiller !

Vous avez toutes les clés en main !

Conclusion

Le crackme de Fnaax est effectivement long, mais comme vous l'avez vu, c'est possible !
Quand je pense que le 8 Juin j'étais sur le forum de la team en croyant que l'on ne pouvait pas trouver de code valide tellement il y avait d'ambigüités !
Il faut avoir la volonté de réussir et c'est ceci le plus important.

Voilà, c'était mon premier tuto sur le cracking et j'espère que vous avez apprécié !

Je remercie particulièrement RocketSpawn pour son accueil, ses encouragements, et ses conseils. Sans lui, je ne serais peut-être pas en train d'écrire ces mots !
Merci aussi à Eeddy31 qui m'a tout de suite répondu sur le forum quand j'avais posé ma question au sujet de Trial crackme.
Et enfin, je passe le bonjour à tous les membres de la Iciteam !

Bonne journée,

Canterwood - 11 Juin 2003 (modifié le 23 Décembre 2003 pour publication)