Developpez.com - Delphi
X

Choisissez d'abord la catégorieensuite la rubrique :


Créer une Application Shareware avec Delphi

Date de publication : 06/12/2004 , Date de mise a jour : 07/06/2005

Par Bedji Badrou (ma page)
 

Ce tutoriel est une inspiration de l'article de Delphicool sur la protection des logiciels contre le piratage .
Le but de ce tutorial est de donner une petite idée aux programmeurs sur les différentes méthodes de protection de leurs logiciels en étudiant les différents points faibles et forts.En parcourant ce tutorial vous aller remarquer qu'On va étudier les stratégies
des crackeurs et prévisualiser leurs méthodes d'attaque pour mieux se protéger.


I. Pourquoi protéger son logiciel ?
II. Les différents types de protections
1. Démonstration
2. Protection par Limite de temps ou nombre d'utilisations (Trial)
2.1. Limite de temps
Exemple
2.2. nombre d'utilisations
Exemple
3. la protection par une clef d'activation
Exemple
4. les Packers
5. le Dongle
Exemple
III. le mot de la fin
Sources


I. Pourquoi protéger son logiciel ?

Afin de pouvoir distribuer votre logiciel et qu'il soit connu par le marché, chaque développeur doit le mettre à disposition des utilisateurs pour qu'ils puissent le tester, puis, au cas où il correspond à leur besoin, décider de l'acheter.


II. Les différents types de protections

Il existe différentes façons pour protéger son produit, que l'on va détailler ci-dessous.
Ces types-là ne sont pas les seuls mais les plus utilisés. D'autres méthodes font le mélange de ces types.

Les exemples fournis dans ce tutorial ne sont pas une règle genérale ; il existe des protections plus compliquées.

1. Démonstration

Cette méthode consiste à donner à l'utilisateur la liberté d'utiliser une certaine fonctionnalité de votre programme mais pas la totalité, en lui fournissant une copie non complète du logiciel. Au cas où l'utilisateur est intéressé par votre logiciel, il est invité à acheter la version complète disponible sur votre site ou sur CD.
Il n'est pas possible d'attaquer la version démonstrative car elle est livrée gratuitement.


2. Protection par Limite de temps ou nombre d'utilisations (Trial)

La protection par limite de temps s'appuie sur le principe de donner là une version complète de votre logiciel utilisable pour une certaine période de temps (un mois, un jour, une heure). A la fin de cette période, il est impossible pour l'utilisateur d'employer votre produit, sauf s'il achète la version non limitée.


2.1. Limite de temps

Du premier instant on sait que cette méthode utilise la gestion de temps en calculant la différence entre la date de l'installation ou la première utilisation du produit, et la date prévue de la fin. Pour cela il faudra enregistrer la première date de l'exécution (ou bien celle de la fin de validité) pour pouvoir y revenir et faire le test sur elle.


Exemple

Pour enregistrer la date on peut utiliser un fichier INI mais en réalité ce dernier est facile d'accès alors on choisit plutôt la base de registre, qui serait plus pratique.
Pour cela on crée une clé qui contienne une valeur nommée "ref" où on va stocker la date et tout cela dans la propriété OnCreate de la forme principale.

uses Registry; .......... procedure TForm1.FormCreate(Sender: TObject); Var Registre : TRegistry; begin Registre:=TRegistry.Create; Registre.RootKey:=HKEY_CURRENT_USER; Registre.OpenKey('\bedji',true); registre.WriteDate('ref',date); Registre.CloseKey; Registre.Free; end;
Afin que le programme ne crée pas cette valeur chaque fois que l'application s'exécute mais seulement à la première fois, on fait un test sur son existence avec la méthode ValueExists

begin Registre:=TRegistry.Create; Registre.RootKey:=HKEY_CURRENT_USER; Registre.OpenKey('\bedji',false); if not registre.ValueExists('ref') then begin {en cas ou la valeur ref n'existe pas ce qui veux dir que l'utulisateur execute le programme pour la premiere fois } Registre.OpenKey('\bedji',true); registre.WriteDate('ref',date); end; .............. ..............
Maintenant que la date est enregistrée, il suffit de calculer le nombre de jours écoulés entre la première date et la date actuelle, et la comparer avec le nombre permis (dans notre cas 30 jours).

uses DateUtils; ............... d1:=registre.ReadDate('ref'); d2:=date; co:=(DaysBetween(d1,d2));
Si cette différence est inférieure à la période autorisée, l'application s'exécute avec un message rappellant à l'utilisateur le nombre de jours qu'il lui reste.
Sinon la période permise est dépassée, l'application se ferme.

case co of 0..30:showmessage('il vous reste'+inttostr(30-co)+ 'jour(s) avant lafin de votre période d''essait'); // on affiche un nagscreen pour prevnir l'utilisateur de nombre des jours qu'il lui reste 31..1000: begin //la periode est terminer l'application doit etre fermer showmessage('votre periode d''essait est terminer !'); application.Terminate; end;
Important : éviter le plus possible d'utiliser des IF Dans le cas où il est obligatoire de s'en servir, imbriquez-en plusieurs, car c'est l'instruction la plus importante du programme, qui intéresse le crackeur et que ce dernier va chercher pour déjouer votre protection.
{$R *.dfm} procedure TForm1.FormCreate(Sender: TObject); Var Registre : TRegistry; d1,d2:TDateTime; begin Registre:=TRegistry.Create; Registre.RootKey:=HKEY_CURRENT_USER; Registre.OpenKey('\bedji',false); if not registre.ValueExists('ref') then begin {en cas ou la valeur ref n'existe pas ce qui veux dir que l'utulisateur execute le programme pour la premiere fois } Registre.OpenKey('\bedji',true); registre.WriteDate('ref',date); end else {en ca ou la valeur ref existe alors on calcule le nombre des joures restant} begin Registre.OpenKey('\bedji',true); d1:=registre.ReadDate('ref'); d2:=date; co:=(DaysBetween(d1,d2)); end; case co of 0..30:showmessage('il vous reste'+inttostr(30-co)+ 'jour(s) avant la fin de votre période d''essait'); // on affiche un nagscreen pour prevnir l'utilisateur de nombre des jours qu'il lui reste 31..1000: begin //la periode est terminer l'application doit etre fermer showmessage('votre periode d''essait est terminer !'); application.Terminate; end; end; Registre.CloseKey; Registre.Free; end;
il se peut que l'utilisateur pense a changer la date du système a une date précédente a s'elle de l'expédition de la période autorisé ce qui veut dire moins de 30 jours et ainsi il pourra utiliser l'application encore pour invité cela enregistré la dernière date d'utilisation dans la base de registre et faite la vérification de la date de système avec cette dernière
la methode des nombres d'utilisations est aussi une autre solution


2.2. nombre d'utilisations

Avec cette méthode vous limitez l'usage de votre application à un nombre défini d'utilisations, en fonction du nombre de fois que votre application est exécutée.


Exemple

unit Unit1; {$R *.dfm} procedure TForm1.FormCreate(Sender: TObject); var Registre: TRegistry; begin Registre:=TRegistry.Create; Registre.RootKey:=HKEY_CURRENT_USER; Registre.OpenKey('\bedji',False); if not registre.ValueExists('compt') then begin Registre.OpenKey('\bedji',true); comp:=1; registre.WriteInteger('compt',comp); end else begin comp:=registre.Readinteger('compt'); if comp < 30 then begin inc(comp); registre.Writeinteger('compt',comp); end else begin messagedlg('votre nombre d''essait est terminer',mtinformation,[mbok],0); application.Terminate; end; end; registre.CloseKey; registre.Free; end; end.

3. la protection par une clef d'activation

Cette méthode fréquemment utilisée pour la plupart des logiciels est aussi la plus attaquée par la communauté des crackeurs. Il suffit au crackeur de trouver l'algorithme que le développeur a utilisé pour composer sa clef pour créer un logiciel appelé keygen. Cependant il n'est pas toujours facile de trouver cet algorithme.

la clé (ou Mot de passe) est une suite de caractères alphanumériques (servant de code)
dont l'introduction dans un système informatique réserve aux seules personnes autorisées l'accès à des procédures ou à des informations.

Pour générer cette clé chaque utilisateur doit disposer d'une clé qui lui est spécialement réservée, afin que même si l'utilisateur essaye d'emprunter la clé d'un utilisateur déjà enregistré, cela ne pourra pas lui servir.
Dans ce but il faudra utiliser quelques informations comme référence telles que le nom d'utilisateur, l'adresse IP de sa machine ou d'autres informations.
Il existe des applications qui utilisent deux ou trois clés d'enregistrement comme 3DSMAX.
Un simple algorithme peut être efficace si on sait bien l'améliorer.
Voyons l'exemple ci-dessous pour mieux comprendre :


Exemple

On va créer une fiche sur laquelle on va mettre un menu qui contiendra deux TmenuIthem, "Fichier" et "Aide".
Dans ce dernier un bouton pour l'enregistrement.



On Crée un deuxième fiche ou on pose deux Tedit avec leur label et deux boutons Comme dans la figure !



dans l'événement Onclick du bouton "Valider", insérez le code suivant :
on déclare les variables suivantes

t:char; Registre : TRegistry; us,temp:string; (*US = user pour stocker le nom de l'utilisateur*) key:string[18]; (*key la clef de cryptage ) part1,part2,part3,part4:String[4];(* les 4 parties de notre serial*) m,i,j:integer;
-Découpez votre serial, crypter les différentes parties
Notre serial va être découper en 4 parties (qu'on va traiter chacune séparément), au format suivant :

La decomposition du serial
part1 part2 part3 part4
XXXX XXXX XXXX XXXX
On vérifie que l'utilisateur a bien saisi un nom dans le champ d'édition edit1 et on récupère sa longueur dans M et son contenu dans us.

if not (edit1.Text = '') then begin key := 'WwW0developpez0CoM'; temp:=''; part1:=''; part2:=''; part3:=''; part4:=''; us := edit1.Text; m:=length(us);
On va commencer par la première partie :
tout simplement on stocke dans une chaîne temporaire le code ASCII du nom de l'utilisateur et puisque la taille de part1 est de 4 caractères on ne prend que les 4 premiers nombres.

première partie
//1ere partie for i:= 1 to m do temp:= temp+inttostr(ord(us[i])); part1:=copy(temp,0,4);
dans la deuxième partie on utilise toujours cette chaine temporaire et on la compose avec les 4 caractères de la deuxième partie :

deuxième partie
//2eme partie for i:=0 to 4 do part2[i]:=chr(32+m+strtoint(copy(temp,i*2,2)));
La troisième partie reçoit la conversion du code ASCII en hexadécimal :

troisième partie
//3eme partie for i:=1 to 4 do part3:=part3+IntToHex(strtoint(copy(temp,0,6))+(strtoint(temp[length(temp)-i])),i);
Enfin la quatrième partie va contenir l'inversion du nom d'utilisateur à laquelle on ajoute la longueur de son nom :

quatrième partie
//4 eme partie for i:= 1 to 3 do temp[i]:=chr(ord(us[m-i+1])); // poar exemple si on choisi le nom "badrou" part4="uor6" part4:= copy(temp,0,3)+inttostr(m);
c'est fini maintenant il suffi de rassembler les 4 parties

Assemblage des 4 parties
temp:=part1+part2+part3+part4;
bon une dernier inversion fera bien l'affaire

for i:= 1 to length(temp)div 2 do begin j:=i+1; t:=temp[j]; temp[j]:=temp[length(temp)-j]; temp[length(temp)-j]:=t; end;
ainsi si vous choisissez un nom d'utilisateur comme " Badrou "( en respectant les majuscules et les minuscules), le serial qui correspond sera :
"6ou0-83&1-Amk7-96r6"
Il ne reste qu'à vérifier le vrai serial avec celui saisi par l'utilisateur dans edit2 :

if not (edit2.Text = copy(temp,0,4)+'-'+copy(temp,5,4)+'-'+copy(temp,9,4)+'-'+copy(temp,13,4))then begin edit1.Clear; edit2.Clear; end;
Attenstion : évitez de mettre des messages du style : "Mauvais code d'enregistrement"
c'est tout ce qu'il faut pour le crackeur

Si le serial est bon alors on doit l'enregistrer avec le nom de l'utilisateur
pour évité de lui demander de s'enregistré la prochaine fois qu'il lance le programme
et pour pouvoir récupérer les informations en cas d'offre pour les utilisateurs enregistrés comme une mise a jour.
Pour cela on utilise toujours la base de registre en créant deux valeurs "SU" ( le nom de l'utilisateur) et "CR" (le serial).

Une fois l'utilisateur enregistré, évitez de mettre le serial trop visible.
Le crackeur va chercher dans la base de registre les clés écrites par votre application et pourra facilement trouver une astuce
et programmer un loader...
Evitez l'exemple ci-dessous :
il est très conseillé d'utiliser la fonction logique XOR

Exemple de l'utilisation de XOR
function XorString(value,key:string ):string; var i:integer ; begin Result:= Value; for i:= 1 to length(Result) do begin Result[i]:=chr(ord(Result[i]) Xor ord (key[i mod Length(key)+1])); end; end;
begin Registre:=TRegistry.Create; Registre.RootKey:=HKEY_CURRENT_USER; Registre.OpenKey('\bedji',true); registre.Writestring('SU',xorstring(edit1.text,key)); registre.Writestring('CR',xorstring(edit2.text,key)); registre.CloseKey; registre.Free; edit1.Clear; edit2.Clear;
et pour que la version soit bien enregistrée,
il faut mettre un test sur la présence des deux clés SU et CR au lancement du logiciel.
Allez dans la forme principale et ajoutez-y le code suivant dans l'événement OnCreate de la forme principale :

verification de l'enregistrement
procedure TForm1.FormCreate(Sender: TObject); var Registre: TRegistry; begin Registre:=TRegistry.Create; Registre.RootKey:=HKEY_CURRENT_USER; Registre.OpenKey('\bedji',False); if not registre.ValueExists('SU') then begin application.Title:= application.Title+' - Non enregistré'; form1.Caption:=form1.Caption+' - Non enregistré'; form1.Enregistrer1.Enabled:= false;//désactiver quelque fonction de votre logiciel end else begin form1.Active.Visible:= false; end; registre.CloseKey; registre.Free; end;

4. les Packers

je vais tenter de vous expliquer brièvement ce que sont les packers.
Ce sont des logiciels qui permettent de modifier un exécutable donné afin de le compresser et/ou le protéger contre le débugguage, le dump,... Ce n'est en fait qu'une protection rajoutée sur le programme originel. En gros, le principe est une sorte d'encapsulation: le programme en mémoire est exactement pareil


5. le Dongle

Clé se présentant sous la forme d'un dispositif électronique qui se branche sur le port parallèle (ou USB) du micro et sans lequel l'utilisation du logiciel est impossible. A chaque lancement, le logiciel va rechercher dans cette clé un code précis -, s'il ne le trouve pas, le chargement de l'application s'interrompt.

Son inconvénient majeur, c'est son prix. ces clés posent également de nombreux problèmes de communication du PC aux périphériques connectés sur le port USB tels qu'imprimantes ou lecteurs ZIP. Pas de solution, si ce n'est d'opter pour des boîtiers partageurs souvent coûteux, ou de jongler avec les périphériques se connectant sur le port USB le plus souvent.

Exemple

type PDEV_BROADCAST_HDR = ^TDEV_BROADCAST_HDR; TDEV_BROADCAST_HDR = packed record dbch_size : DWORD; dbch_devicetype : DWORD; dbch_reserved : DWORD; end; PDEV_BROADCAST_VOLUME = ^TDEV_BROADCAST_VOLUME; TDEV_BROADCAST_VOLUME = packed record dbcv_size : DWORD; dbcv_devicetype : DWORD; dbcv_reserved : DWORD; dbcv_unitmask : DWORD; dbcv_flags : WORD; end; TForm1 = class(TForm) Memo1: TMemo; Balle: TShape; Timer1: TTimer; Bevel1: TBevel; LScore: TLabel; procedure Timer1Timer(Sender: TObject); procedure BalleMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure FormCreate(Sender: TObject); private { Déclarations privées } procedure WMDeviceChange(var Msg: TMessage); message WM_DEVICECHANGE; public { Déclarations publiques } end; var Form1: TForm1; NumSerie:dword; findusb:boolean; DongleOK: string; GetLettre: string; VolName, FileSysName : Array[0..Max_Path]Of Char; VolSerial, FileMaxLen, FileFlags : DWord; implementation {$R *.dfm} /////////////////////////////////////////////// // Protegez vos codes avec une clé USB flash // // Badrou 22:30 19/07/2005 // /////////////////////////////////////////////// //Tester si la clé est presente procedure TForm1.FormCreate(Sender: TObject); var i: integer; begin NumSerie:=000000; //C'est ici !! que vous devez placer le n°serie // Pour connaitre le N°serie de votre clé ,lancer l'invité de commande "cmd" puis allez a // votre lecteur USB puis tapper "Vol" vous trouvrait le numero de serie en hex XXXX-XXXX for i:= 66 to 90 do begin //ont va parcourir les lettres des lecteurs de B a Z If GetVolumeInformation(pchar(char(i)+':\'),VolName,Max_Path,@VolSerial, FileMaxLen, FileFlags,FileSysName,Max_Path) then //*************************************************************************** if VolSerial= NumSerie then begin //Si le n°serie est identique DongleOK := inttostr(i); //Mémmorise la Lettre du dongle findusb:=True; end else begin findusb:=False; end; //*************************************************************************** end; if not findUsb then begin end else panel1.Visible:=False; end; //\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ // Capture Evenement windows "WMDeviceChange" procedure TForm1.WMDeviceChange(var Msg: TMessage); begin //\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ // TESTER si DBT_DEVICEARRIVAL = $8000 (USB se connecte) // si oui, ne traiter que les périphériques de stockage de type USB //\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ if Msg.wParam =$8000 then begin if PDEV_BROADCAST_HDR( Msg.LParam )^.dbch_devicetype <>2 then exit; // *USB if PDEV_BROADCAST_VOLUME( Msg.LParam )^.dbcv_flags <>0 then exit; // *USB // Sa lettre "DeviceID" str(ln(PDEV_BROADCAST_VOLUME( Msg.LParam )^.dbcv_unitmask)/ln(2)+Ord('A'):2:0,GetLettre); // NomVolume + SySfichier + N° serie du periphérique ... If GetVolumeInformation(pchar(char(strtoint(GetLettre))+':\'),VolName,Max_Path,@VolSerial, FileMaxLen, FileFlags,FileSysName,Max_Path) then //*************************************************************************** //Pour connaitre le N°serie de votre clé ,mettez un point d'arret et pointez sur "VolSerial" //*************************************************************************** if VolSerial= NumSerie then begin //Si le n°serie est identique DongleOK := GetLettre; //Mémmorise la Lettre du dongle findusb:=True; end else begin findusb:=False; end; //*************************************************************************** if not findUsb then else begin panel1.Visible:=False; end; end; //\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ // TESTER si DBT_DEVICEREMOVECOMPLETE = $8004 (USB se deconnecte) // si oui, ne traiter que les périphériques de stockage de type USB //\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ if Msg.wParam =$8004 then begin if PDEV_BROADCAST_HDR( Msg.LParam )^.dbch_devicetype <>2 then exit; // *USB if PDEV_BROADCAST_VOLUME( Msg.LParam )^.dbcv_flags <>0 then exit; // *USB // Sa lettre "DeviceID" str(ln(PDEV_BROADCAST_VOLUME( Msg.LParam )^.dbcv_unitmask)/ln(2)+Ord('A'):2:0,GetLettre); if DongleOK=GetLettre then close; // Si le dongle est déconnecté fin du prog end; end;
test


III. le mot de la fin

quelques conseils

    Astuces
    voici quelques astucs pour et conseiles pour amiliorer vos protection

  • Si vous penser que de mettre plusieurs mode d'activation a votre logiciel va le rendre incassable alors vous faite une erreur
    car vous donner au crackeure la possibilité de choisire la méthode la plus facile a cassée et comme ça vous baissé la protection de votre
    application a la place de l'augmenter, mais s'il en faut utiliser par exemple deux modes d'activation,
    l 'activation sera par les deux et non par la 1er ou la 2eme
  • deuxsieme
  • troieme
  • quatriem
  • cinque
  • six
  • sept
  • huite
  • neuf
Code Exemple
function CreateSerial(Nom:String): String; var i, Ordinal : integer; tmp, tmp2, tmp3, tmp4, Final : String; begin tmp := ''; for i:=1 to length(Nom) do begin Ordinal := ord(Nom[i]); tmp := tmp + Char[Ordinal mod 36]; tmp2 := tmp2 + Char[(Ordinal-10) mod 36]; tmp3 := tmp3 + Char[(Ordinal-15) mod 36]; tmp4 := tmp4 + Char[(Ordinal-20) mod 36]; end; Final := Copy(tmp,1,4)+'-'+Copy(tmp2,1,4)+'-'+Copy(tmp3,1,4)+'-' +Copy(tmp4,1,4); Result := Final; end; (* un peut loin d'ici *) Nom := EditNom.Text; Serial := CreateSerial(Nom); // generation de la vraie serial (*//////////////////////verification////////////////////*) (**) if EditSerial.Text = Serial then // (**) begin // (**) ShowMessage('Bravo tu est un boss ;)'); // (**) end else ShowMessage(Serial); // (**)end; // (*//////////////////////////////////////////////////////*)
shema
for i:= 1 to 10 do gen(s,i); //1l1c
shema2

Sources

Telecharger la source de l'exemple de la limite de temps trial.zip
Telecharger la source de l'exemple de nombre d'utilisation nexe.zip
Telecharger la source de l'exemple de la version shareware Shar.zip
Telecharger la source de l'exemple du dongle Dongle.zip

Developpez

Liste de mes articles :
Créer une Application Shareware avec delphi
créer un assistant d installation avec Iexpress


Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2004 badrou. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsables bénévoles de la rubrique Delphi : Gilles Vasseur - Alcatîz -