• Cours appronfondie n°4

    Mon objectif : programmer un keygen pour Start Clean 1.2

     

    1/ Les logiciels utiles pour ce cours

    -> Le programme à craquer     :   Start Clean v1.2
    -> Un désassembleur             :   W32dasm 8.93
    -> Un compilateur C++           :   Dev C++

     

    2/ Fabriquer un keygen pour Start Clean v 1.2.

    Dans ce cours nous allons apprendre à fabriquer un keygen pour Start Clean 1.2.Pour cela on va procéder par étapes.

    -> Analyse de l'enchaînement des étapes dans la vérification du serial par Start Clean
    -> Localisation de la routine de calcul du serial valide dans Start Clean
    -> Etude détaillée de la routine de calcul
    -> Programmation d'un algorithme en C++ qui reproduit le calcul du serial

    Avant de commencer la leçon petit rappel pour ceux qui ne connaissent pas la signification de ce terme : un keygen (abbréviation en anglais de key generator) est un générateur de clés valides pour déverrouiller un logiciel en version d'évaluation. Il permet moyennant la donnée d'un nom de calculer un numéro de série correspondant puis d'enregister un logiciel et de l'utiliser dans sa version complète. Un numéro de série en général est une suite de chiffres séparés par des - par exemple 1598-15026-2421-469 (c'est le numéro de série (on utilise aussi le terme serial) associé au nom pifoman dans Start Clean). Cette opération de keygenning n'est bien sûre possible que si bien entendu le logiciel le logiciel à keygenner possède une boîte d'enregistrement. Dans le cas de Start clean c'est le cas puisque si vous cliquez sur le bouton Register vous voyez apparaître un 2 ième écran où vous avez la possibilité de vous enregistrer.

     

    <script type="text/javascript">document.write("")</script>

     

    <script type="text/javascript">document.write("")</script>

     

    3/ Analyse de l'enchaînement des étapes dans la vérification du serial par Start Clean

    Cette première étape permet de réfléchir à la manière dont est conçu le mécanisme de vérification. Je ne m'étendrais pas sur le sujet puisque le cours traite déja le sujet en détail. Ce que l'on peut dire en revanche pour résumer c'est que

    -> L' utilisateur saisi le couple name / code dans la boîte d'enregistrement du dessus
    -> Le programme récupère le nom et le code entrés grâce à l'API User32.GetDlgItemTextA
    -> Le programme calcule le vrai code à partir nom entré
    -> Le programme compare avec l'API Kernel32.lstrcmpA le code calculé au code entré
        -> Si ce n'est pas la même -> le programme n'est pas enregistré.
        -> Si c'est le même le programme est enregistré (version complète).

     

    4/ Localisation de la routine de calcul du serial dans Start Clean

    Pour localiser la routine de calcul du serial dans Start Clean il faut procéder par étapes :

    -> On désassemble Start Clean dans W32dasm avec la commande Open File To Disassemble -> STARTCLN.EXE
    -> On charge le programme Start Clean en mémoire par la commande CTRL L de w32dasm
    -> On démarre dans le débugger de w32dasm le programme Start Clean avec F9 puis on saisi le couple name / code suivant pifoman / 123456 dans la boîte d'enregistrement sans faire OK.
    -> On clique sur le bouton Imp Fn (Imported Functions) dans w32dasm pour voir les fonctions API importées dans Start Clean.
    -> Dans cette liste on répère l'API Kernel32.lstrcmpA (qui est utilisée pour la comparaison des chaîne de caractères).
    -> On clique sur l'API Kernel32.lstrcmpA qu'on a vient de repérer.
    -> On arrive au total à 5 endroits dans le code (en continuant de cliquer sur l'API Kernel32.lstrcmpA). Les voici.

    :00401131     FF1520924000         Call dword ptr [00409220]
    :004011E3     FF1520924000         Call dword ptr [00409220]
    :0040154B    8B2D20924000         mov ebp, dword ptr [00409220]
    :00401A32    8B2D20924000         mov ebp, dword ptr [00409220]
    :00401F1F    FF1520924000         Call dword ptr [00409220]

    -> Ce que l'on va faire maintenant c'est mettre un bp (breakpoint; en français un point d'arrêt) sur chacune des 5 lignes trouvées comme ça dès que le programme va faire appel à l'API Kernel32.lstrcmpA le débugger de w32dasm va s'arrêter sur la ligne où l'on a mis le bp. Il suffit pour mettre un bp de sélectionner la ligne de code dans w32dasm puis de faire F2.

    -> On clique sur le bouton OK de la boîte d'enregistrement de Start Clean et on tombe à cet endroit

                                    :004011E3     FF1520924000          Call dword ptr [00409220]

    Si on fait F9 dans w32dasm , Start Clean nous affiche ensuite le message d'erreur.

    Conclusion : Start Clean ne fait appel qu'une seule fois à la fonction lstrcmpA de l'API Kernel32. C'est à cet endroit à l'adresse 004011E3 que Start Clean effectue une comparaison de chaîne (entre notre code 123456 et le code calculé).

     

    Ecran 0

    <script type="text/javascript">document.write("")</script>

     

    Pour rappel (cf milieu de ce cours) en 004011DD le registre eax contient notre serial 123456 et en 004011DE l'adresse 0040630 contient le vrai serial calculé par Start Clean 1598-15026-2421-469 (pour le nom pifoman).Le vrai serial est calculé juste avant dans le call 00401280 surligné en bleu dans la photo du dessus. On va donc entrer dans ce call (sélectionnez la ligne d'adresse 004011D1 puis flèche doite du clavier).

     

    5/ Etude détaillée de la routine de calcul

    C'est le point le plus difficile du cours car il fait appel à l'interprétation de l'assembleur. L'assembleur est un langage assez hermétique pour celui qui n'y est pas initié. Je vais essayer quand même de vous expliquer en détail chaque ligne de code et de me placer à un niveau débutant en assembleur pour que vous puissiez comprendre ce qui est écrit dans le code du call 00401280 à l'adresse 004011D1.Il va vous falloir pour cela de la concentration et une attention continue jusqu'à la fin. Sachez en effet qu'un keygen est beaucoup plus difficile à faire qu'un crack car il ne fait pas qu'annuler la protection d'enregistrement mais il reproduit l'algorithme de calcul. Ok c'est parti.

     

     a/ Avant de commencer petit rappel d'asm (asm = assembleur)

    -> Passage d'arguments à une fonction en asm.

        On utilise la zone de pile du programme (le stack).Par exemple si vous voulez passer les arguments suivants arg1,arg2,arg3 dans cet ordre à la fonction f (c'est à dire appeler f(arg1,arg2,arg3)) on écrira en assembleur

        push adresse_arg3
        push adresse_arg2
        push adresse_arg1
        call adresse_fonction_f

      -> Affectation d'un valeur à un registre

        :004012BB  8B9C241C010000 mov ebx, dword ptr [esp+0000011C] -> mettre dans ebx le contenu de l'adresse esp+0000011C
        :004012C5  8BC3                    mov eax, ebx  -> mettre l'adresse du registre ebx dans le registre eax

     

     b/ Note importante

       Dans les écrans suivants (écran 1 à écran 10) quand je dis qu'un registre ou une adresse contient telle valeur je m'appuie sur le résultat donné par le débugger w32dasm au cours de la session de debugging de Start Clean. Pour lancer une session de debugging dans w32dasm il faut suivre les étapes suivantes (en référence à l'écran 0 plus haut) :

    -> Désassembler Start Clean dans w32dasm par Open File To disassemble -> STARTCLN.EXE
    -> Dans w32dasm CTRL L suivi de F9 puis on entre le couple name/code pifoman / 123456 dans la boîte d'enregistrement de Start Clean (que l'on vient de lancer avec w32dasm) sans faire OK.
    -> On sélectionne dans w32dasm la ligne de code 004011D1  E8AA000000  call 00401280 et on met un bp dessus avec F2.
    -> On fait OK dans Start Clean
    -> w32dasm break sur 004011D1 (vous entendez un bing).
    -> On fait F7 pour descendre dans ce call 00401280
    -> Ensuite avec des F8 on exécute le programme ligne de code par ligne de code et dès qu'une adresse ou un registre nous intéresse (ie on veut examiner ce qu'il y a dedans) on fait comme ça par exemple si on est sur la ligne de code d'adresse 004012C2 et qu'on veut voir le contenu de l'adresse contenue dans le registre ebx et calculée en 004012BB
     

       :004012BB 8B9C241C010000          mov ebx, dword ptr [esp+0000011C]
       :004012C2 83C408                         add esp, 00000008

     

    <script type="text/javascript">document.write("")</script>

     

    J'explique au cas où certains n'auraient pas compris. Quand le débugger arrive sur l'adresse 004012C2 on voit dans la zone regs (regs pour registers => registres en français) que le registre ebx vaut 00406130 (il est écrit en capitale => ça veut dire qu'il vient d'être mis à jour). Tout ce qui commence par 00 suivi d'un chiffre non nul comme 4 par exemple cela signifie que c'est une adresse dans le programme en cours de debugging (ici STARTCLN.EXE). Donc 00406130 c'est une adresse.Nous l'adresse 00406130 ça nous intéresse pas c'est ce qu'elle contient qui nous intéresse.Pour le savoir on clique sur la case sous User Addr 2 et on tape l'adresse dont on veut voir le contenu à savoir 00406130.On clique sur le bouton UA2 à côté à droite pour constater qu'en 00406130  commence une chaîne qui contient le nom entré dans la boîte d'enregistrement de Start CLean à savoir pifoman. En 00406130 on a la lettre p de pifoman dont le code hexadécimal est 70 (on lit les codes hexadécimaux en partant de la droite) ensuite à l'adresse 00406131 (00406130 + 00000001) on a la lettre i de pifoman dont le code hexadécimal est 69 et caetera pour le reste des lettres de la chaîne pifoman.

     

     c/ Rôle de 2 API utilisées dans le programme

    Dans le corps de la routine de calcul du serial de Start Clean vous aller voir qu'il y a des appels à l'API USER32.wsprintfA et à l'API KERNEL32.lstrcatA. La première convertit notre nombre calculé (ebp) en chaîne, et la deuxième permet de concaténer deux chaînes, c'est à dire les coller ensemble (la deuxième derrière la première).

    -> wsprintfA(buffer, "%d-", ebp);  // où buffer est un élément de type char va recevoir la valeur du registre ebp converti en entier suivi d'un -.Le d dans %d signifie decimal.

    -> lstrcatA(serial, buffer);  //où serial est un char * qui va recevoir la valeur du serial actuel suivi du contenu de la variable buffer c'est à dire en prenant pour symbole de concaténation de chaîne le signe + on a serial = serial + buffer.

     

     d/ Analyse de la routine de calcul

    - Tous les écrans suivants s'enchaînent les un aux autres de haut en bas.
    - Pour les calculs prenez la calculatrice de windows (démarrer->exécuter->calc) -> affichage -> scientifique -> bouton hex coché.

    Ecran 1

    <script type="text/javascript">document.write("")</script>

    Quand on break avec w32dasm sur le bp posé en 004011D1 (là où il y a notre call 00401280) et qu'on F7 on arrive à l'adresse 00401280 (c'est la ligne surlignée en bleu).

    Dans cette partie la chose intéressante à remarquer c'est l'initialisation du registre ebp à la valeur hexadécimale 0000006A (instruction mov ebp, 0000006A). C'est cette variable ebp qui contiendra le serial qui sera progressivement calculé dans la routine de calcul du serial pour Start Clean.

     ---------------------------------------------

    Ecran 2

    <script type="text/javascript">document.write("")</script>

    L'appel à la fonction wsprintfA en 004012B5 ici ne donne rien d'intéressant.

    Ce qui par contre est intéressant c'est le code asm à l'adresse 004012BB où l'on met dans ebx le contenu du registre esp augmenté de 0000011C (instruction mov ebx, dword ptr [esp+0000011C]). En effet ebx vaut [esp+0000011C] = 00406130 et à cette adresse (en 00406130) on a la chaîne pifoman qui va être utilisée pour générer  le vrai serial.

    Ensuite on met en 004012C5 la valeur de ebx dans eax soit eax = 00406130.

     

    En 004012CD on compare le byte (octet en français) à l'adresse 00406130 (car [ebx] = contenu de ebx = 00406130) avec le byte 00. Comme en 00406130 on a 70 (code hexadécimal pour le p dans la chaîne pifoman) qui est différent du byte 00 le saut en 004012D0 (qui saute si les 2 termes de la comparaison sont précédente sont égaux) ne va pas s'exécuter. On va donc exécuter l'API charNextA qui va balayer tous les lettre du nom pifoman

    NB : vous vous demander certainement pourquoi la comparaison se fait à 00. Eh bien c'est simple 00 c'est le caractère NUL qui termine toute chaîne de texte et qui va dire au programme qu'on a atteint la fin de chaîne. Pour ceux qui programme en C le 00 ça correspond au '\0' qu'on ajoute pour terminer une chaîne.

      ---------------------------------------------

    Ecran 3

    <script type="text/javascript">document.write("")</script>

     

     

     

    Boucle de calcul de la première partie du serial.

    En 004012D2 on met le byte à l'adresse 00406130 (00406130 = contenu de eax  = [eax]) dans ecx soit ecx = 00000070 (70 c'est le code hexadécimal pour la lettre p de pifoman).

    Ensuite on empile eax (qui vaut 00406130) avec l'instruction push eax

    En 004012D6 on effectue le calcul de la première partie du serial. On prend ebp que l'on avait initialisé à 6A à l'adresse 004012A2 et on lui ajoute 2 * ecx. On met ensuite le résulat du calcul dans ebp (lea a1,a2 met a2 dans a1).Le calcul se fait en hexadécimal sur 4 octets (c'est pour ça que c'est écrit dword : 1 byte = 1 octet, 1 word = 2 bytes; 1 dword (1 double word) = 4 bytes) soit ebp = 0000006A + 2 * 00000070 = 0000014A (prenez la calculatrice de windows avec démarrer->exécuter->calc en prenant l'affichage scientifique et en cochant le bouton hex et tapez  6A + 2* 70).

    En 004012DA on appelle l'API User32.charNextA en passant en paramètre à cette fonction la variable eax (c'est le push eax en 004012D5). Le registre edi vaut à ce moment 77D172EC. 77D172EC c'est l'adresse calculée en 004012C7de la fonction charNextA dans C:\windows\system32\user32.dll. En effet si vous désassemblez user32.dll dans w32dasm et que vous faites SHIFT F12 -> 77D172EC vous arrivez sur le code de la fonction charNextA (Exported fn(): CharNextA - Ord:002Bh...). Vous comprenez maintenant un peu mieux comment fonctionne la communication entre le programme et le système ...

    Arrivé en 004012DC notre API User32.charNextA a mis à jour eax qui est passé de 00406130 à 00406131.C'est normal car User32.charNextA a pour fonction de passer au caractère suivant dans une chaîne (ici c'est la chaîne pifoman dont il s'agit). 00406131 contient le i de pifoman. En  004012DC que se passe-t-il ? Le programme va comparer le byte contenu à l'adresse de eax (l'adresse de eax vaut 00406131) à 00 et comme ce byte vaut  69 (c'est le code hexadécimal de la lettre i de pifoman) le jne (jump if not equal to zero) de la ligne de code suivante va provoquer un saut vers 004012D2.

    En conclusion entre les adresses 004012D2 et 004012DF nous avons une boucle qui lit tous les caractères du nom tapé dans la boîte d'enregistrement de Start Clean et qui effectue un calcul sur chacune des lettres du nom. Le résultat du calcul est mis dans le registre ebp comme ceci :

         Algorithme

         ebp = ebp + (BYTE)name[i] *2    // pour i variant de 0 à longueur du name -1.
                                                         // (BYTE)name[i] siginifie que la lettre name[i] est convertie en hexadécimal.

         Paramètres

                    Ici les variables de l'algorithme ont les valeurs suivantes :
                    name="pifoman".
                    name[0]="p", name[1]="i", name[2]="f", name[3]="o", name[4]="m", name[5]="a", name[6]="n".
                    Longueur de name=7.

         Soit

         ebp  =   0000006A  +  00000070  * 2 =  0000014A       // 70 code hexadécimal de la lettre p dans pifoman  (1 ière boucle)
         ebp  =   0000014A  +  00000069  * 2 =  0000021C       // 69 code hexadécimal de la lettre  i dans pifoman  (2 ième boucle)
         ebp  =   0000021C  +  00000066  * 2  =  000002E8      // 66 code hexadécimal de la lettre f dans pifoman   (3 ième boucle)
         ebp  =   000002E8  +   0000006F * 2 =  000003C6       // 6F code hexadécimal de la lettre o dans pifoman (4 ième boucle)
         ebp  =   000003C6  +  0000006D * 2  =  000004A0      // 6D code hexadécimal de la lettre m dans pifoman (5 ième boucle)
         ebp  =   000004A0  +  00000061  * 2  =  00000562      // 61 code hexadécimal de la lettre a dans pifoman  (6 ième boucle)
         ebp  =   00000562  +  0000006E  * 2  =  0000063E     // 6E code hexadécimal de la lettre n dans pifoman  (7 ième boucle)

         Sortie de boucle : ebp = 0000063E

     ---------------------------------------------

    Ecran 4

    <script type="text/javascript">document.write("")</script>

    Dans cette partie on va invoquer l'API user32.wsprintfA qui va prendre dans cet ordre les paramètres eax , 00406274, ebp et convertir en nombre décimal ebp en lui ajoutant un - et en mettant le résultat obtenu dans eax. L'adresse 00406274 contient la chaîne %d-.

    En 004012EC on a simplement écrit wsprintfA(eax,"%d-",ebp) qui traduit ce qu'on vient de dire soit eax = 1598-.

    En 004012F2 on réinitialise eax qui passe de 00406137 à 00406130.

    En 004012FB on concatène esi qui vaut 00000000 à eax et  on met le résulat dans esi. On a simplement écrit lstrcatA(esi,eax).

    En 00401301 on réinitialise eax à 00406130.

    En 00401303 comme le byte contenu à l'adresse de ebx vaut 70 (code hexadécimal de la lettre p de pifoman)  et que c'est différent de 00 le je (jump if equal to zero) ne s'exécute pas en 00401306.

      ---------------------------------------------

    Ecran 5

    <script type="text/javascript">document.write("")</script>

     

     

     

    Boucle de calcul de la deuxième partie du serial.


       1 ière boucle du calcul

       00401308  -> on met le byte à l'adresse de eax (00406130) dans ecx soit ecx = 70 (70 = hexadecimal('p') dans pifoman)
       0040130B -> ecx = ecx + ecx <=> ecx = 2 *ecx soit ecx = 000000E0
       0040130D -> on empile le paramètre eax qui va être passé à l'API User32.charNextA en 00401313
       0040130E -> edx = ecx + 8 ecx soit edx = 000007E0
       00401311 -> ebp = ebp + edx soit ebp = 0000063E + 000007E0 = 00000E1E
       00401313 -> charNextA(edi)
       00401315 -> eax vaut 00406130 + 00000001 = 00406131. byte ptr [eax] = 69 != 00
       00401318 -> saut vers 00401308 tant que la fin de la chaîne pifoman non atteinte (code 00)

       Algorithme

       ebp = ebp + (BYTE)name[i] *18    // pour i variant de 0 à longueur du name -1.
                                                         // (BYTE)name[i] siginifie que la lettre name[i] est convertie en hexadécimal.

       Sortie de boucle : ebp = 00003AB2

     ---------------------------------------------

    Ecran 6

    <script type="text/javascript">document.write("")</script>

     

     

     

     

     

     

    En 00401325 on appelle wsprintfA(eax,"%d-",ebp) qui converti ebp = 00003AB2 (calculé dans la boucle de calcul précédente) en décimal et le met dans eax. Soit eax = 15026-

    En 00401334 on appelle lstrcatA(esi,eax) qui ajoute eax à esi = 1598- soit esi = 1598-15026-

    En 0040133C comme byte ptr [ebx] = 70 != 00 le je (jump if equal to zero) en 0040133F ne provoque pas de saut vers 00401359.

       ---------------------------------------------

    Ecran 7

    <script type="text/javascript">document.write("")</script>

     

     

     

    Boucle de calcul de la troisième partie du serial.


       1 ière boucle du calcul

       00401341  -> on met le byte à l'adresse de eax (00406130) dans ecx soit ecx = 70 (code hexadécimal de p dans pifoman)
       00401344 -> on empile le paramètre eax qui va être passé à l'API User32.charNextA en 00401352
       00401345 -> ebp = ecx + 4 * ecx <=> ebp = 5 *ecx soit ebp = 00000230
       00401348 -> ecx = ecx + 2 ebp soit ecx = 000004D0
       0040134B -> ebp = 2 * ecx + 1 soit ebp = 000009A0 + 00000001 = 000009A1
       00401352 -> charNextA(edi)
       00401354 -> eax vaut 00406130 + 00000001 = 00406131. byte ptr [eax] = 69 != 00
       00401357 -> saut vers 00401341 tant que la fin de la chaîne pifoman non atteinte (code 00)

       Algorithme

       ebp =  (BYTE)name[L-1] *22 + 1    // avec L = longueur du name -1
                                                                 // (BYTE)name[L-1] siginifie que la lettre name[L-1] est convertie en hexadécimal.

       Sortie de boucle : ebp = 00000975

       ---------------------------------------------

    Ecran 8

    <script type="text/javascript">document.write("")</script>

     

     

     

     

     

     

    En 00401364 on appelle wsprintfA(eax,"%d-",ebp) qui converti ebp = 00000975 (calculé dans la boucle de calcul précédente) en décimal et le met dans eax. Soit eax = 2421-

    En 00401373 on appelle lstrcatA(esi,eax) qui ajoute eax à esi = 1598-15026- soit esi = 1598-15026-2421-

    En 0040137B comme byte ptr [ebx] = 70 != 00 le je (jump if equal to zero) en 0040137E ne provoque pas de saut vers 00401392.

       ---------------------------------------------

    Ecran 9

    <script type="text/javascript">document.write("")</script>

     

     

     

    Boucle de calcul de la quatrième partie du serial.


       1 ière boucle du calcul

       00401380  -> on met le byte à l'adresse de eax (00406130) dans ecx soit ecx = 70 (code hexadécimal de p dans pifoman)
       00401383 -> on empile le paramètre eax qui va être passé à l'API User32.charNextA en 0040138B
       0040138B -> ebp = 4 * ecx + 1D soit ebp = 000001DD
       00401390 -> charNextA(edi)
       0040138D -> eax vaut 00406130 + 00000001 = 00406131. byte ptr [eax] = 69 != 00
       00401390 -> saut vers 00401380 tant que la fin de la chaîne pifoman non atteinte (code 00)

       Algorithme

       ebp =  (BYTE)name[L-1] * 4 + 1D    // avec L = longueur du name -1
                                                                 // (BYTE)name[L-1] siginifie que la lettre name[L-1] est convertie en hexadécimal.

       Sortie de boucle : ebp = 000001D5

    ---------------------------------------------

    Ecran 10

    <script type="text/javascript">document.write("")</script>

     

     

     

     

     

     

    En 0040139D on appelle wsprintfA(eax,"%d-",ebp) qui converti ebp = 000001D5 (calculé dans la boucle de calcul précédente) en décimal et le met dans eax. Soit eax = 469

    En 004013AC on appelle lstrcatA(esi,eax) qui ajoute eax à esi = 1598-15026-2421- soit esi = 1598-15026-2421-469

    En 004013BC on quitte la boucle de calcul pour retourner à l'adresse qui suit le call 00401280 soit l'adresse 004011D6.

     

    Voila j'espère que vous avez tenu jusque là ! On est enfin arrivé à la fin de la routine de calcul ! C'était long (pour moi aussi) mais nous avons calculé notre serial pour le name pifoman qui est de la forme X-Y-Z-T où X = 1598, Y = 15026, Z = 2421, T = 469. On peut donc maintenant élaborer un algorithme pour calculer le bon serial en C++.

     

     

    6/ Programmation d'un algorithme en C++ qui reproduit le calcul du serial

     

      a/ Description de la marche à suivre

      Voici les étapes à suivre pour générer le bon serial connaissant le nom

      -> 1/ On demande un nom que l'on met dans la variable name.
      -> 2/ On initialise une variable ebp à 6A.
      -> 3/ 1 ière boucle   :  ebp = ebp + (BYTE)name[i] *2 // i variant de 0 à la longueur de name -1.
      -> 4/ On ajoute un - derrière ebp
      -> 5/ 2 ième boucle :  ebp = ebp + (BYTE)name[i] *18 // i variant de 0 à la longueur de name -1.
      -> 6/ On ajoute un - derrière ebp et on ajoute la chaîne calulée en 5/ à celle calculée en 4/
      -> 7/ 3 ième boucle : ebp =  (BYTE)name[L-1] *22 + 1  // L = longueur de name -1.
      -> 8/ On ajoute un - derrière ebp et on ajoute la chaîne calulée en 7/ à celle calculée en 6/
      -> 9/ 3 ième boucle : ebp =  (BYTE)name[L-1] *4 + 1D   // L = longueur de name -1.
      -> 10/ On ajoute un - derrière ebp et on ajoute la chaîne calulée en 9/ à celle calculée en 8/

    Cet algorithme fonctionne aussi quand le name est une chaîne vide auquel cas le serial est 106-106-106-106 (les 106 viennent de la conversion de 6A en décimal avec la fonction wsprintfA(eax, "%d-", ebp)). On obtient cette chaîne car les étapes 3/ 5/ 7/ 9/ sont évitées à cause de l'instruction cmp byte ptr [eax], 00 aux adresses 004012CD, 00401303, 0040133C, 0040137B car à ce moment là byte ptr [eax] = 00 et le saut conditionnel qui suit (je = jump if equal to zero dans l'intruction précédente) s'exécute.

     

       b/ Interface graphique du keygen

    <script type="text/javascript">document.write("")</script>

     

       c/ Code C++ du keygen

     

    Le code suivant entre Début du code et Fin du code est à copier dans dev C++.Pour le compiler il faut procéder ainsi

    -> Installez dev C++ sur votre machine
    -> Lancez devcpp.exe
    -> File -> New Project -> Application console -> OK -> Project 1 -> Enregistrer
    -> Insérez le code entre Début du code et Fin du code dans la page devant vous en effaçant le code que dev c++ a mis.
    -> CTRL F10 (execute -> compile and run)

     

    Remarque :

    -> Les commentaires sont en vert et précédé de //
    -> L'espace de travail (code source ,exécutable) est ici

     

    --------------------------------------------------------------------------------- Début du code ---------------------------------------------------------------------------

    #include <windows.h>        //Accès aux API
    #include <stdio.h>               //Minimum vital
    #include <stdlib.h>              //Pour avoir system
    
    //Variables globales
    
    //Serial est un tableau de 100 cases vides de type chaîne
    char serial[100]="";
    
    
    
    
    void afficher_banniere()
    {
        printf("\n\n\n");
        printf("                   ²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²\n");
        printf("                   ²                                      ²\n");
        printf("                   ²      Keygen pour Start Clean 1.2     ²\n");
        printf("                   ²                                      ²\n");
        printf("                   ²       Par pifoman le 10/01/2005      ²\n");
        printf("                   ²                                      ²\n");
        printf("                   ²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²\n\n\n\n");
    }
    
    
    
    char * get_serial(char * name)
    {
    
                  //Déclaration et initialisation des variables
                  int EBP=0x6A;
                  char *buffer;
                  serial[0]='\0';
    
    
                  //On calcule la longueur de la chaîne name avec la fonction strlen
                  //En C un tableau est une chaine et vice-versa
                  long longueur=strlen(name);
    
    
                  //1 ière partie du serial à calculer (cf 1ière boucle)
                  for(int i=0;i<longueur;i++) EBP+=(BYTE)name[i]*2;
    
                  //On ajoute un - derrière le morceau de serial calculé EBP et on le met dans serial
                  //%d converti EBP en nombre décimal
                  wsprintfA(buffer, "%d-", EBP);
                  lstrcatA(serial, buffer);
    
                  //2 ième partie du serial à calculer (cf 2ième boucle)
                  //On converti chaque caractère du name en entier,on les multiplie par 18,on les somme
                  //entre eux puis on ajoute la valeur de EBP calulée dans le 1ère boucle
                  for(int i=0;i<longueur;i++) EBP+=(BYTE)name[i]*18;
    
                  //On ajoute un - derrière le morceau de serial EBP calculé dans la 2ième boucle
                  //et on le colle derrière la variable serial calculée dans la 1ère boucle
                  //%s converti serial en string et %d converti EBP en nombre décimal
                  wsprintfA(buffer, "%d-", EBP);
                  lstrcatA(serial, buffer);
    
                  //3 ième partie du serial à calculer (cf 3ième boucle)
                  //On prend la dernière lettre de la variable name.
                  //On la converti en byte et on la multiplie par 22 puis on lui ajoute 1
                  //On ne fait pas appel au serial calulé dans les boucles précédentes
                  //Si name est vide sa longueur est donc 0.Il faut donc vérifier que la longueur est > à 1
                  if(longueur>0) EBP=(BYTE)name[longueur-1]*22+1;
                  wsprintfA(buffer, "%d-", EBP);
                  lstrcatA(serial, buffer);
    
                  //4 ième partie du serial à calculer (cf 4ième boucle)
                  //On prend la dernière lettre de la variable name.
                  //On la converti en byte ,on la multiplie par 4 puis on lui ajoute 1D
                  //On ne fait pas appel au serial calulé dans les boucles précédentes
                  //Si name est vide sa longueur est donc 0.Il faut donc vérifier que la longueur est > à 1
                  if(longueur>0) EBP = (BYTE)name[longueur-1] * 4 + 0x1D;
                  wsprintfA(buffer, "%d", EBP);
                  lstrcatA(serial, buffer);
    
                  return serial;
    }
    
    
    //Point d'entrée du programme
    int main(int argc, char *argv[])
    {
                   long a;
                   char *name;
                   char continuer='o';
                   char C;
    
                   while(continuer=='o')
                  {
                                  int j=0;
         
                                  //On vide l'écran
                                  system("cls");
    
                                  afficher_banniere();
    
                                  printf("                      Votre nom    : ");
    
                                  //On met chaque lettre du nom dans le tableau name
                                  //jusqu'à ce qu'on tape sur la touche entrée symbolisée par '\n'
                                  while((C=getchar())!= '\n')  name[j++]=C;
    
                                  //On termine la chaîne saisie par le caratère de fin de chaîne '\0'
                                  //Si on ne l'ajoute pas la lecture de la chaîne saisie continuera au dela de la
                                  //longueur de la chaine et la fonction get_serial rendra un serial erroné.
                                  name[j++]='\0';
    
                                  //On affiche "Votre serial :" suivi du serial calculé avec la fonction get_serial
                                  //%s converti en string le résultat de get_serial(name)
                                  printf("\n                      Votre serial : ");
                                  printf("%s\n\n",get_serial(name));
    
                                 //On demande à l'utilisateur s'il veut continuer o pour oui n pour non
                                  printf("\n                      Recommencer [o/n] ?");
                                  continuer=getchar();
    
                                  //On vide le tampon d'entrée du clavier
                                  rewind(stdin);
    
                  }
    
        return 0;
    
    }
    
    

    --------------------------------------------------------------------------------- Fin du code ---------------------------------------------------------------------------

     

    Voila vous avez entre les main un keygen fonctionnel.Il ne fonctionne cependant après des tests poussés pas avec les caractères dont le code ascii est supérieur à 127 (ascii étendu) par exemple les caractères £µù§àoôöûüéèêëîï. Cela vient du compilateur C++ qui ne reconnais pas les caractères accentués (ISO-LATIN-1) qui ne font pas partie de l'aphabet anglo-saxon. Le compilateur est en effet incapable de convertir correctement les caractères spéciaux en codes hexadécimaux corrects (vous aurez un serial mais il ne sera pas valide).

    Par exemple vous pouvez tester ces combinaisons name /code correctes (les caractères du name ont un code ascii <127)
    -> &"#[('15 %;     1120-10246-1299-265
    -> &*/-+$<>?./!    1230-11346-727-161

    Pour vous éviter de chercher sur le net une table ascii compatible avec le keygen je vous encode la table ascii étendue (256 caractères -> elle inclue les caractères spéciaux) en javascript.Sur chacune de ses lignes vous avez le code décimal qui correspond à chaque caractère.

     

     

    7/ Conclusion

    Ce tutorial est assez long à expliquer car il fait appel à beaucoup de mécanismes dans le langage d'assemblage puis le langage C qui ne sont pas forcément simples à comprendre du premier coup par exemple la conversion d'un caractère en code hexadécimal. Néanmoins la procédure de génération du serial reste assez simple à comprendre et à retranscrire en C++ pour celui qui a quelques notions suffisantes en assembleur et qui a la volonté d'apprendre.

    Nous avons vu aussi dans la leçon comment trouver la procédure de calcul d'un serial puis nous avons appris à interpréter le code dans cette procédure en constatant le rôle joué par les API wsprintfA, lstrcatA, CharNextA dans la construction pas à pas du serial

     


  • Commentaires

    Aucun commentaire pour le moment

    Suivre le flux RSS des commentaires


    Ajouter un commentaire

    Nom / Pseudo :

    E-mail (facultatif) :

    Site Web (facultatif) :

    Commentaire :