Niveau : Débutant [x] - Intermédiaire [ ] - Avancé [ ] - Expert [ ]
Notez que les manipulations sont susceptibles de fonctionner avec d'autres versions des logiciels précédemment cités, il se peut toutefois qu'il y ait quelques variantes.
Quant aux connaissances en programmation que vous devez avoir, cela se limite à la mise en oeuvre d'un algorithme simple (boucles, opérations sur des variables, etc...).
Lorsqu'un logiciel présente une boîte d'enregistrement dans laquelle on peut indiquer un nom et un numéro de série, il peut être judicieux de coder un key generator pour un venir à bout. En effet, lorsque c'est possible, quoi de plus idéal pour déplomber une application que de l'attaquer sur son propre terrain ?
Certes, fabriquer un keygen est en général plus difficile que de procéder à un patching standard, mais cette différence se ressent aussi dans le résultat obtenu. Ainsi, les avantages d'un tel générateur sont multiples : il laisse le programme dans son état d'origine, il offre à l'utilisateur la possibilité de personnaliser son enregistrement et, cerise sur le gâteau, les serials qu'il produit sont parfois compatibles avec les versions ultérieures du logiciel (c'est le cas de WinZip, par exemple).
Bref, dans cet essai, nous verrons comment keygenner le programme Dialog Builder, un petit outil qui se révèle parfois bien pratique pour les programmeurs...
Ainsi, Notre étude passera par :
Les deux dernières étapes s'établieront plus ou moins en parallèle selon les besoins.
La première chose à faire est, en toute logique, de lancer Dialog Builder "à vide" pour faire un premier état des lieux.
Immédiatement, un nag-scren apparaît, nous proposant d'enregistrer le programme. Cela tombe bien, car c'est justement ce que nous recherchions ! On clique donc sur Enter Registration Number, un formulaire s'affiche (cf. ci-contre, remarquez au passage l'ignoble faute de frappe dans le titre ;-)). On entre des valeurs au hasard pour le nom et le serial, on appuie sur OK (tiens, un custom control plutôt inhabituel !)... et la fenêtre se ferme, au lieu d'obtenir un message d'erreur comme on pourrait s'y attendre.
Problématique, n'est-ce pas ? Nous ne pourrons donc pas rechercher des données dans les références de chaînes de caractère, ou bien poser un breakpoint sur la fonction API MessageBoxA... Mais il en faut plus pour nous arrêter, et d'autres indices devraient nous conduire sur la bonne piste.
Au hasard, on ouvre l'éxécutable avec un éditeur héxadécimal, et surprise, on s'aperçoit qu'il est packé avec UPX.
À ce stade, on réfléchit quelques secondes : le programme a été compressé par ce qu'il était assez gros, et d'autre part, nous avons repéré tout à l'heure que le bouton OK avait quelque chose de bizarre... Non, vous ne voyez pas ? En fait, le curseur utilisé par ce contrôle est intégré par défaut dans l'outil de développement d'application Borland Delphi. Et nous nous rappelons aussi que les éxécutables Delphi utilisant la VCL (la librairie principale) sont plutôt imposants au niveau de leur taille, d'où la nécessité de les packer... Bref je crois que vous voyez où je veux en venir : c'est un logiciel Delphi ! Normal, non ? On pouvait le savoir dès le début, avec le titre de l'application : Dialog Builder for Delphi, mais j'ai préféré vous donner ici d'autres techniques d'identification, histoire que cela vous resserve plus tard...
Passons maintenant aux choses sérieuses. On fait d'abord un petit upx -d DlgBuilder.exe à partir du répertoire de Dialog Builder pour décompresser le programme (ne me parlez pas de manual unpacking, ce n'est pas l'objet de ce tutorial, mais je ne vous empêche pas de le faire, bien entendu :-)).
Ensuite, nous allons directement désassembler le fichier avec DeDe : cliquez sur l'icône Open, choisissez l'éxécutable et faites Process. Patientez quelques instants le temps de la décompilation, et faites l'Extended analysis lorqu'on vous le demande.
C'est bon ? Nous allons procéder à la phase de localisation de code à proprement parler.
Tâchons tout d'abord de retrouver la fonction appelée lors du clic sur le bouton OK. Pour cela, on se rend dans Procedures, puis on choisit l'unité Registration pour faire apparaître les gestionnaires d'évènements et procédures associés. Cela saute au yeux, on a deux fonctions gsLabel1Click et gsLabel2Click, qui doivent correspondre respectivement aux actions à effectuer lors du clic sur les boutons OK et Cancel. Pour vous en assurer, vous pouvez aller voir le formulaire en question (Forms dans DeDe) et jeter un oeil à la définition du contrôle...
Sans plus attendre, on affiche le code de la première procédure en double-cliquant dessus, on explore un peu en rentrant dans le call du début... Malheureusement, nous nous apercevons qu'il n'y a rien de ce que l'on pourrait espérer, à savoir des fonctions de manipulations de chaîne de caractère, par exemple. On se dit que l'on s'est peut-être trompé et l'on regarde ce que renferme gsLabel2Click : on a le même code à quelque chose près. La fonction d'en dessous ne propose également rien d'intéressant...
Alors, sommes-nous sur la mauvaise voie ? Que faire à présent, puisqu'il n'y a rien ?
Eh bien, un détail nous a peut-être échappé. Lorsque nous avons exploré le listing, un commentaire aurait pu nous mettre la puce à l'oreille : * Reference to field TRegForm.ModalResult : TModalResult. Peu importe la signification de cela, pensons plutôt au shéma que le programme a pu adopter. Nous avons supposé jusque là que c'était lors du clic sur le bouton OK que la vérification s'effectuait ; mais nous avons omis le fait que la boîte d'enregistrement soit affichée en modal depuis une autre fenêtre (About), par l'intermédiaire du bouton Enter Registration Number. On peut donc tout à fait imaginer que la validation s'effectue ainsi :
fonction clic_sur_enregistrer
début
affiche le formulaire
vérifie les données après la fermeture de la fenêtre
fin
On s'empresse donc de trouver le nom de ce fameux label Enter reg..., et après quelques secondes de recherche, on constate qu'il s'agit de gsLabel1Click (dans l'unité About). Si l'on parcourt listing, on trouve comme prévu un appel à TRegForm.ShowModal(), et en dessous la vérification du serial avec des fonctions telles System.@LStrCat3, System.@LStrCmp, etc...
Cette étape est terminée, et comme vous avez pu le constater, la localisation n'a pas été des plus faciles. Rassurez-vous, la plus dur a été fait ;-).
Dernière chose : on note quelque part l'adresse 004D6D90 correspondant au début de la procédure afin d'analyser cela de plus près avec notre debugger.
On lance Dialog Builder avec un debugger (chez moi OllyDbg), et on pose un breakpoint en 004D6D90.
Lors d'un clic sur le bouton Enter Registration Number, le programme s'arrête immédiatement et nous nous retrouvons à ce niveau :
004D6D90 PUSH EBP
004D6D91 MOV EBP,ESP
004D6D93 XOR ECX,ECX
004D6D95 PUSH ECX
004D6D96 PUSH ECX
004D6D97 PUSH ECX
004D6D98 PUSH ECX
004D6D99 PUSH ECX
004D6D9A PUSH EBX
004D6D9B PUSH ESI
004D6D9C PUSH EDI
004D6D9D MOV EDI,EAX
004D6D9F XOR EAX,EAX
004D6DA1 PUSH EBP
004D6DA2 PUSH DlgBuild.004D6EF9
004D6DA7 PUSH DWORD PTR FS:[EAX]
004D6DAA MOV DWORD PTR FS:[EAX],ESP
004D6DAD MOV ECX,EDI
004D6DAF MOV DL,1
004D6DB1 MOV EAX,DWORD PTR DS:[4D5158]
004D6DB6 CALL DlgBuild.0046CE80 ; ?
004D6DBB MOV EBX,EAX
004D6DBD MOV EAX,EBX
004D6DBF MOV EDX,DWORD PTR DS:[EAX]
004D6DC1 CALL DWORD PTR DS:[EDX+E8] ; TRegForm.ShowModal() : affiche la boîte d'enregistrement
004D6DC7 LEA EDX,DWORD PTR SS:[EBP-4]
004D6DCA MOV EAX,DWORD PTR DS:[EBX+2F0]
004D6DD0 CALL DlgBuild.0044DE08 ; récupère la longueur du nom
004D6DD5 LEA EDX,DWORD PTR SS:[EBP-8]
...
Bref, il n'y a rien d'intéressant avant l'affichage du formulaire (004D6DC1), puisqu'il s'agit principalement d'opérations de sauvegarde des registres comme en en trouve souvent au début des fonctions.
Par contre, on remarque qu'une fois les informations entrées, le programme va calculer la longueur du nom (004D6DD0), et réitérer ceci pour le serial un peu plus loin. En fait, ces fonctions servent peut-être simplement à obtenir le nom et le serial à partir des boîtes d'édition, mais nous ne pouvons pas confirmer...
C'est donc à partir de 004D6DC7 que nous devons commencer à scruter le code plus en détail :
004D6DC7 LEA EDX,DWORD PTR SS:[EBP-4]
004D6DCA MOV EAX,DWORD PTR DS:[EBX+2F0]
004D6DD0 CALL DlgBuild.0044DE08 ; récupère la longueur du nom
004D6DD5 LEA EDX,DWORD PTR SS:[EBP-8]
004D6DD8 MOV EAX,DWORD PTR DS:[EBX+2F4]
004D6DDE CALL DlgBuild.0044DE08 ; récupère la longueur du serial
004D6DE3 LEA EAX,DWORD PTR SS:[EBP-14]
004D6DE6 MOV ECX,DlgBuild.004D6F10 ; ASCII "assa"
004D6DEB MOV EDX,DWORD PTR SS:[EBP-4]
004D6DEE CALL DlgBuild.004047B8 ; System.@LStrCat3 : [ebp - 14h] = nom + "assa"
004D6DF3 MOV EAX,DWORD PTR SS:[EBP-14]
Il y a une seule chose à retenir dans le listing ci-dessus, c'est l'ajout du préfixe assa au nom. Ce premier traitement nous laisse d'ailleurs supposer qu'il existe un serial valide sans qu'aucun nom ne soit rentré, puisque le suffixe sera toujours présent... Enfin, là aussi il ne s'agit que d'une hypothèse.
Nous pouvons d'ores et déjà commencer à rédiger une structure de base pour notre keygen. Bien évidemment, dans la réalité des faits, il est préférable de parcourir l'ensemble de la routine de validation, en prenant des notes, avant de se lancer dans quoi que ce soit. Mais dans le cadre de ce tutorial, notre but est de faire le rapprochement entre la source ASM "brute" et l'écriture de l'algorithme correspondant dans un langage de plus haut niveau. Cela peut paraître facile, voire évident pour certains, mais ne l'est pas forcément pour d'autres ; d'où l'interêt de cette démarche.
Voyons ce que donnerait un programme minimal en C à partir de ce que nous avons vu :
/* En-têtes */
#include <stdio.h>
#include <string.h>
/* Variables : nom et nom + suffixe */
char name[32], checkedName[sizeof(name) + 4];
/* Entrée du programme */
int main() {
/* Récupère le nom (30 caractères maximum) */
printf("Enter your name (without special characters): ");
fgets(name, sizeof(name), stdin);
/* Supprime le caractère de fin de ligne */
name[strlen(name) - 1] = 0;
/* Ajoute le suffixe "assa" et met le résultat dans checkedName */
strcat(strcpy(checkedName, name), "assa");
return 0;
}
À ce stade, notre programme ne fait pas grand chose, mais il a l'interêt de nous fournir une base de travail pour la suite. Si vous vous demandez pourquoi il est recommandé de ne pas mettre de caractères spéciaux dans le nom, c'est simplement à cause de la console MS-DOS qui n'emploie pas le même jeu de caractères que l'application Windows Dialog Builder.
Si l'on revient justement à Dialog Builder, on constate que la ligne d'après est un appel de fonction (CALL 004D5994). Dans la plupart des cas, ce genre de call sert à calculer un nombre à partir du nom passé en paramètre. Ici, on ne déroge pas à la règle, puisque l'on remarque qu'un pointeur sur le nom "suffixé" est assigné au registre eax juste avant l'appel, et qu'un nombre est ensuite retourné.
Je ne vais pas vous laisser patienter davantage, on entre donc sans plus tarder dans la procédure :
004D5994 PUSH ESI
004D5995 PUSH EBX
004D5996 XOR EDX,EDX
004D5998 OR EAX,EAX
004D599A JE SHORT DlgBuild.004D59C4
004D599C MOV EBX,EDX
004D599E MOV ECX,DWORD PTR DS:[EAX-4] ; ecx = la longueur du nom + 4
004D59A1 JECXZ SHORT DlgBuild.004D59C4 ; si ecx est nul, saute quitte la procédure
004D59A3 MOV ESI,EAX ; [esi] = le nom
004D59A5 CLD
004D59A6 /XOR EAX,EAX
004D59A8 |LODS BYTE PTR DS:[ESI] ; eax = le caractère suivant du nom
004D59A9 |SHL EDX,4 ; décale edx de 4 bits vers la gauche
004D59AC |ADD EDX,EAX ; ajoute eax à edx
004D59AE |MOV EBX,EDX ; ebx = edx
004D59B0 |AND EBX,0F000000 ; ebx = ebx and 0F000000h
004D59B6 |JE SHORT DlgBuild.004D59BD ; si ebx est nul, saute un peu plus loin
004D59B8 |SHR EBX,18 ; décale ebx de 18 bits vers la droite
004D59BB |XOR EDX,EBX ; edx = edx xor ebx
004D59BD |NOT EBX ; ebx = not ebx
004D59BF |AND EDX,EBX ; edx = edx and ebx
004D59C1 |DEC ECX ; décrémente ecx
004D59C2 \JNZ SHORT DlgBuild.004D59A6
004D59C4 MOV EAX,EDX
004D59C6 POP EBX
004D59C7 POP ESI
004D59C8 RETN ; retourne edx
C'est plutôt succint et c'est à notre avantage, car nous n'aurons aucun mal à retranscrire cela dans notre keygen.
Globalement, une boucle parcourt le nom et effectue des opérations pour chaque caractère. Ne cherchons pas à trouver un quelconque sens à cela, il faut retenir l'essentiel : 2 variables sont utilisées pour les calculs, respectivement représentées par les registres edx et ebx. edx est la valeur qui sera retournée à la fin, tandis que ebx est seulement une variable "temporaire" qui va servir pour les calculs (ces derniers étant d'ailleurs principalement des opérations binaires).
Cela ne pose pas de problème avec le C qui offre toute la panoplie nécessaire pour une implémentation de ce genre.
Voilà ce que donnerait la fonction recodée par nos soins :
int generateNumber() {
int i, number = 0, tempVal;
for(i = 0; i < strlen(checkedName); ++i) {
number <<= 4;
number += checkedName[i];
tempVal = number & 0x0F000000;
if(tempVal != 0) {
tempVal >>= 0x18;
number ^= tempVal;
}
number &= ~tempVal;
}
return number;
}
Je crois que c'est suffisament simple pour se passer de commentaires. Notons tout de même que la variable checkedName est celle déclarée précédemment dans l'espace global. Si malgré tout vous avez quelques soucis de compréhension, n'hésitez pas à réviser les opérateurs en C...
Nous pouvons ajouter cette définition de fonction dans notre source de keygen, en attendant que celle-ci serve à quelque chose.
La suite ne pose pas de problème majeur, en effet un nombre a été généré à partir du nom, et le programme va simplement le convertir en chaîne de caractère afin de le transformer en serial :
004D6DFB LEA ECX,DWORD PTR SS:[EBP-10]
004D6DFE MOV EDX,8
004D6E03 CALL DlgBuild.00408FD4 ; équivalent à printf avec le template "%.8X" et le serial en paramètre
004D6E08 MOV ECX,DWORD PTR SS:[EBP-10]
004D6E0B LEA EAX,DWORD PTR SS:[EBP-C]
004D6E0E MOV EDX,DlgBuild.004D6F20 ; ASCII "DBD - "
004D6E13 CALL DlgBuild.004047B8 ; System.@LStrCat3 : ajoute un préfixe au serial
004D6E18 MOV EDX,DWORD PTR SS:[EBP-C]
004D6E1B MOV EAX,DWORD PTR SS:[EBP-8]
004D6E1E CALL DlgBuild.004048B0 ; System.@LStrCmp : compare le faux serial au vrai
004D6E23 JNZ DlgBuild.004D6EDE ; s'ils ne sont pas égaux, saute vers bad boy
Le serial va être formaté sous la forme héxadécimale, puis le préfixe DBD - va lui être ajouté.
Dialog Builder va ensuite faire une bête comparaison pour tester la validité de notre serial. Si ce n'est pas le cas, il quitte, sinon il affiche une boîte de message de remerciement et inscrit les données dans le registre. Je vous donne d'ailleurs la clé, elle pourra vous resservir lorsque vous effectuerez vos tests :
HKEY_CURRENT_USER\Software\Gaivans\DlgBuilder\RegData.
Pour repasser le mode non enregistré, il suffit de la supprimer. Vous pouvez vous servir du fichier unregister.reg qui automatise cette tâche.
Il est désormais grand temps de terminer le codage de notre keygen.
Avant toute chose, je crois qu'un petit récapitulatif des opérations ne serait pas de trop :
Il n'y a plus qu'à "traduire" tout cela en C.
On peut arriver à quelque chose de ce genre en respectant simplement les étapes :
/* Dialog Builder 1.3.1.66 keygen
* =============================================================================
* Author : Dean/Canterwood <charloweb@hotmail.com>
* Version : 1.0 (07.25.2003)
* Greetings: The Analyst, Lise_Grim, RocketSpawn, +Christal...
* =============================================================================
* Copyright © 2003 ICIteam */
#include <stdio.h>
#include <stdlib.h> /* For the system function */
#include <string.h>
/* Buffers and related pointers */
char name[32], checkedName[sizeof(name) + 4];
/* int generateNumber(void)
* -----------------------------------------------------------------------------
* Description: this function returns a number computed with the value of
* checkedName. Next, you can format it to generate a serial.
* -----------------------------------------------------------------------------
*/
int generateNumber() {
int i, number = 0, tempVal;
for(i = 0; i < strlen(checkedName); ++i) {
number <<= 4;
number += checkedName[i];
tempVal = number & 0x0F000000;
if(tempVal != 0) {
tempVal >>= 0x18;
number ^= tempVal;
}
number &= ~tempVal;
}
return number;
}
int main() {
printf("Dialog Builder 1.3.1.66 keygen by Canterwood (%s)\n\n", __DATE__);
/* Get the name */
printf("Enter your name (without special characters): ");
fgets(name, sizeof(name), stdin);
/* Delete the '\n' (new line) character */
name[strlen(name) - 1] = 0;
/* Add the suffix and store the result in checkedName */
strcat(strcpy(checkedName, name), "assa");
/* Display the name (truncated if necessary),
and the serial, computed with the generateNumber function */
printf("\n----\n\nName : %s\nSerial: DBD - %.8X\n\n----\n\n", name,
generateNumber());
system("PAUSE");
return 0;
}
On retrouve le squelette établi un peu plus haut avec quelques instructions ajoutées. Je vous laisse découvrir cela par vous-même (la source est dans le fichier dlgbuilder.keygen.c situé dans le répertoire code).
Vous pouvez aussi vous amuser à le reproduire dans d'autres langages, et par-là même ajouter quelques fonctionnalités, notamment en ce qui concerne l'usage de caractères spéciaux dans le nom.
Ce tutorial d'initiation est enfin terminé ! J'espère qu'il vous servira par la suite pour faire vos premiers pas en keygenning !
Dialog Builder constitue malgré tout une cible relativement simple, donc ne vous inquiétéz pas si vous avez quelques difficultés avec d'autres programmes...
D'ailleurs, notre approche a été assez laborieuse pour le peu de chose qu'il y avait à faire. Un habitué des keygens aurait carrément rippé des portions de code et inclus le tout dans un template en ASM... Mais sachez aussi que ce n'est pas toujours la meilleure solution ! Parfois, vous avez tout interêt à comprendre les routines... c'est le cas pour la plupart des programmes Delphi qui ont un shéma assez particulier.
Il faut savoir utiliser les bonnes méthodes au bon moment.
Je vous souhaite bonne chance pour la suite !
Remerciements : The Analyst, Lise_Grim ainsi que la ShmeitCorp pour leurs tutoriaux.
Je salue par ailleurs les membres de la ICIteam, et tous les gens fréquentant le CrFF !