• Partie 1 : RSA

     

    Crypter et décrypter

    Quoi de mieux pour introduire la cryptologie qu'un petit historique ?
    Il était une fois sur la planète bleue, dans un lointain passé :p , le désir de cacher et de coder ses messages (attention, cacher fait appel à la stéganographie et coder à la cryptographie !). Tout commença en 1900 avant J-C où un scribe égyptien employa des hiéroglyphes erronés volontairement. (Je vais griller quelques étapes ^^ ). Nous arrivons donc aux méthodes grecques et hébraïques. On notera parmi les méthodes grecques, celle de l'historien Polybe, puisque son invention inspirera le futur. La méthode est basée sur un principe de substitution avec l'utilisation d'un carré (voir ici). Toujours avec le principe de substitution, le bon vieux César y a mis sa patte. :D En effet vers 1 siècle avant J-C, César crypte ses messages en remplaçant une lettre par une autre n place(s) après. Accélérons le temps pour faire un tour en 1919, date de parution de la machine Enigma, machine à crypter, qui fut employée par les nazis durant la guerre. En 1977 apparait DES, qui est succédé par AES qui lui, sera utilisé pour le chiffrement de documents aux USA. Nous touchons le but. :p Effectivement, avril 1977, RSA montre le bout de son nez. :D

    Cryptage

    On trouve principalement deux grandes familles de cryptage : le cryptage symétrique (ou dit à clé secrète) et le cryptage asymétrique (dit aussi à clé publique).

    Le cryptage symétrique



    On parle de cryptage symétrique lorsqu'un texte, document, etc. est crypté et décrypté avec la même clé, la clé secrète, ce procédé est à la fois simple et sûr. On trouvera principalement parmi les algorithmes de cryptage asymétrique : AES, qui serait utilisé pour protéger des documents secrets aux États-Unis (ici). Principal inconvénient : étant donné que l'on n'a qu'une clé, si vous la donnez à X pour qu'il puisse vous envoyer des messages cryptés avec celle-ci, il pourra aussi bien décrypter tous les documents que vous avez crypté avec cette dernière. La clé est donc connue uniquement par le destinataire et l'émetteur et il est plus sûr de faire une clé pour un échange entre X et Y, pour éviter qu'avec une clé on puisse tout décrypter.

    Le cryptage asymétrique



    Contrairement au cryptage symétrique, ici avec l'asymétrique, on a 2 clés.
    Tout d'abord nous avons la clé publique. Celle-ci, tout le monde peut la posséder, il n'y a aucun risque, vous pouvez la transmettre à n'importe qui. Elle sert à crypter le message. Puis il y a la clé privée que seul le récepteur possède, en l'occurrence vous. Elle servira à décrypter le message crypté avec la clé publique.
    Pour clarifier mon charabia, une petite illustration :

    Principe du cryptage asymétrique


    Devinez quoi ? RSA fait partie des cryptages asymétriques ! :D

    Rivest, Shamir, Adleman



    Alors, vous avez remarqué ? R... S... A... ! Oui, RSA est un sigle provenant des noms de ses inventeurs, qui sont respectivement Ron Rivest, Adi Shamir et Léonard Adleman.
    Quelques informations pour situer ces personnages tirées de Wikipédia :
    Ron Rivest est né à New York en 1947, et devient cryptologue. Adi Shamir, d'origine Israëlienne, né à Tel Aviv en 1952, est cryptologue et professeur au département de mathématiques appliquées du Weizmann Institute of Science. Il est reconnu pour ses qualités de cryptanalyste. Pour finir, Leonard Adleman, chercheur en informatique théorique et professeur en informatique et en biologie moléculaire à l'Universitée de la Californie du Sud, né en 1945.
    Avec cette brochette de génies, on ne peut pas dire que l'algorithme RSA a été fait à la légère. ^^

    Un algoriquoi ?

    Un algorithme, c'est une suite d'instructions visant à résoudre un problème. Celui-ci a été créé en 1977 et breveté en 1983 par le MIT (Massachusetts Institute of Technology) qui expira le 21 septembre 2000.

    Fonctionnement



    Alice est retenue prisonnière dans une grotte sombre et répugnante par un ogre affamé, ses jours sont comptés, elle veut envoyer un message à Bob par pigeon voyageur pour qu'il prévienne la gendarmerie nationale.

    Euh comment dire... Je veux apprendre à crypter avec RSA et toi tu me parles d'une grotte et d'un ogre. :o

    Non, attendez, Alice va crypter son message à l'aide de RSA. :D
    Avant que notre Alice soit prisonnière, Bob a créé une clé publique et une clé privée. Il donne la clé publique à Alice et garde soigneusement la clé privée.
    Regardons de plus près le travail de Bob.
    Bob créé 2 nombres p et q, les autres nombres seront trouvés grâce à eux :

    Nombres Descriptions
    p, q 2 grands nombres premiers
    n p*q
    Image utilisateurn (p-1)*(q-1)
    e p,q < e < Image utilisateurn, pgcd(Image utilisateurn , e) = 1
    d p,q < d < Image utilisateurn, e*d mod Image utilisateurn = 1

    La clé publique est sous la forme (e, n), celle que Alice possède et que tout le monde peut avoir sans aucun problème, ce couple permettra le cryptage de chaque message et la clé privée qui servira au décryptage se présente sous la forme (d, n), celle que Bob garde précieusement.

    Crypter un message, en théorie



    Notre Alice veut envoyer un message à Bob. Elle a donc à la base un message clair (mc), elle doit tout d'abord transformer chaque caractère en numérique (cn). Exemple : position dans l'alphabet, ASCII, etc., puis chiffrer chaque nombre pour obtenir les caractères codés (cc).
    cne mod n = cc
    e et n sont connus grâce à la clé publique de Bob.

    Déterminer p, q, n, Image utilisateurn, e



    Bon c'est bien gentil la théorie, mais Alice commence à déprimer dans la grotte. Passons à l'action !
    Voyons les calculs de Bob pour obtenir les clés et pouvoir chiffrer et déchiffrer.
    Bob choisit p = 503 et q = 563, 503 et 563 sont premiers car ils ne sont divisibles que par 1 et eux-mêmes, c'est justement ce qu'il nous faut. Leur taille est plutôt petite, mais pour les explications elle suffira, pour un cryptage fiable avec RSA, il est recommandé d'utiliser des nombres de l'ordre de 100 chiffres voir plus.
    Une fois p et q généré, le reste n'est que calcul. Sans trop de difficulté, on trouve n = 283189 = 503*563 et on se calcule vite fait bien fait Image utilisateurn = 282124 = (503-1)*(563-1).

    On trouve e par conditions, celles vu plus haut, je vous les rappelle : p,q < e < Image utilisateurn, PGCD(Image utilisateurn, e) = 1.
    La première condition délimite une portion de nombre dans laquelle se trouve e. Avec la deuxième on teste chaque nombre compris dans cette portion. Ils sont testés avec le PGCD (plus grand commun diviseur). Il y a de fortes chances que plusieurs e soient possibles, prenez le premier trouvé. ^^ Alors que nous a choisi Bob ?
    Déjà on sait que e se trouve entre 563 et 282124. On teste chaque nombre et si PGCD(Image utilisateurn, e) = 1, on arrête, on dit alors que Image utilisateurn et e sont premiers entre eux. Vous êtes prêt à tester une centaine de milliers de nombres ? :D Je vous rassure, e ne se cache pas très loin :
    PGCD(282124, 564) = 4
    PGCD(282124, 565) = 1
    PGCD(282124, 566) = 2
    PGCD(282124, 567) = 1
    ...
    Donc comme je vous ai dit, prenez le 1er venu, soit e = 565.
    Bob a donc p, q, n, Image utilisateurn et e ; il va pouvoir créer la clé publique (e, n).
    Clé publique de Bob : (565, 283189).
    Il peut maintenant la donner à Alice et même la publier, il n'y a aucun risque.

    Alice est enfermée dans un coin obscur de la grotte, pendant que l'ogre, servi par des gobelins, mange ; elle griffonne sur un bout de papier les calculs pour crypter avec RSA et la clé publique de Bob. :D Elle veut chiffrer le texte suivant :
    "Help !".
    Message d'une importance capitale. :p

    Coder chaque caractère en ASCII



    Tout d'abord qu'est ce que l'ASCII ?
    Votre ordinateur ne sait lire et sauvegarder que des données numériques. Oh la grosse quiche ! :p Donc il doit remplacer chaque caractère par un nombre, grâce à l'ASCII (American Standard Code for Information Interchange). L'ASCII est en fait une table où chaque caractère a son équivalent numérique.
    Une fois votre message élaboré, la première des choses à faire est de transcrire toutes les lettres le composant en un nombre, simplement pour réussir à calculer, donc à crypter chacune des lettres. On peut coder les caractères en ASCII, ou alors, les remplacer par leur position dans l'alphabet, ou encore d'autres possibilités, mais le récepteur devra être au courant de la méthode utilisée.

    Caractère ASCII
    H 72
    e 101
    l 108
    p 112
    (espace) 32
    ! 33

    Le message, transcrit en numérique avec l'ASCII, est donc 72 101 108 112 32 33.


    Un problème d'envergure !



    Imaginez un instant que l'on ait un long texte. Si ce texte venait à être crypté, on aurait donc pour chaque caractère un nombre qui résulte du cryptage, mais dans ce genre de texte la même lettre peut réapparaître plusieurs fois, donc aussi le nombre résultant du cryptage de celle-ci.
    Exemple :
    coucou
    On retrouve le c, le o et le u 2 fois. Un cryptage potentiel pourrait donner :
    1053 2035 8456 1053 2035 8456
    Pour une même lettre, on retrouve le même code ! Pour un texte codé d'une page si pour une même lettre on a un même code, cela pourrait compromettre sérieusement la sécurité du document puisque si l'analyse de fréquence d'apparition des lettres est utilisé, le texte peut être facilement décodé. En quoi consiste cette analyse ? Tout simplement à dire que pour la langue française en moyenne telle lettre a une fréquence d'apparition de tant de pour cent. Si on retrouve donc sur un texte de 500 mots, 50 fois le code 5489, on peut admettre grâce à cette analyse que tous les 5489 correspondent à "a" par exemple. Même principe pour "b", "c", "d", etc.

    La solution :

    La solution est de regrouper les codes ASCII en blocs de même longueur.
    On leur donne d'abord à tous une longueur fixe, ici 3, en rajoutant des 0 devant si nécessaire. Cette longueur fixe sera toujours 2 ou 3 puisque le code ASCII ne contient aucun nombre avec plus de 3 chiffres.
    072 101 108 112 032 033
    Et on les regroupe en blocs de longueur 4 :
    0721 0110 8112 0320 0033
    Chaque bloc doit être inférieur à n soit ici 283189.

    Avec ceci on n'aura jamais un même code pour une même lettre. Par contre le destinataire devra connaître au préalable les longueurs, sinon une fois décrypté, il ne saura pas comment découper pour retrouver l'ASCII.
    On crypte. On décrypte. On retrouve :
    0721 0110 8112 0320 0033
    On découpe en blocs de 3.
    072 101 108 112 032 000 33
    Soit :
    72 101 108 112 32 33
    On a bien retrouvé notre ASCII.
    Bon faut avouer, la méthode est fastidieuse, pour la suite du tutoriel et pour le programme, on ne l'utilisera pas pour privilégier la simplicité. ^^

    Crypter le code ASCII



    Nous allons crypter le code ASCII de chaque caractère du message. Ce cryptage se fait grâce à un simple calcul, qui prend en compte le bloc, e et n qui sont présents dans la clé publique.
    Formule :
    Bloce % n = cc
    "%" signifie modulo (ou mod).

    Maintenant, il suffit juste de crypter chaque bloc comme le montre la formule.

    072565 % 283189 = 80488
    101565 % 283189 = 2826
    108565 % 283189 = 241808
    112565 % 283189 = 183218
    032565 % 283189 = 45154
    033565 % 283189 = 84918

    Chacun des résultats doit être inférieur à Image utilisateurn.

    Alice a donc crypté son message "Help !", avec la clé publique de Bob, et elle obtient : 80488 2826 241808 183218 45154 84918.
    Il ne lui reste plus qu'à transmettre le message crypté et elle aura fait ce qu'elle pouvait notre pauvre Alice. :p


    Décryptage

    À partir de maintenant, deux histoires peuvent s'écrire. Soit son message est intercepté et décrypté, soit Bob le reçoit et Alice est sauvée.

    Décrypter un message, en théorie



    Destinataire indésirable


    Ce destinataire, une fois en possession du message, doit créer la clé privée, par le biais de la factorisation de n, puis il décrypte chaque caractère codé (cc) et il obtient le code ASCII de chaque caractère (cn), puis il les transcrit en caractères clairs (ccl) pour obtenir le message clair.
    ccd % n = cn
    cn -> ccl -> mc
    n connu grâce à la clé publique et d doit être calculé après la factorisation de n.

    Véritable destinataire


    Prenons l'exemple de Bob. Il reçoit le message crypté, il décrypte chaque caractère codé (cc) et il obtient le code ASCII de chaque caractère (cn), puis il les transcrit en caractères clairs (ccl) pour obtenir le message clair.
    ccd % n = cn
    cn -> ccl -> mc
    d et n sont connus grâce à la clé privée de Bob.

    Comme vous pouvez le constater, ces 2 cas sont similaires, ce qui les différencie et d'ailleurs ce qui est à l'origine de la fiabilité de RSA, c'est la factorisation de n. On va donc envisager les 2 cas. Alors, on commence par lequel ? :p

    Bon, commençons par le cas où le message serait malheureusement intercepté. Admettons que ce soit un gros gobelin méchant, qui en chassant le pigeon a récupéré celui d'Alice. Comme il n'y comprend rien, il va le porter au gobelin cryptologue. :-° Celui-ci trouvera donc à l'intérieur du message les blocs cryptés. Puisque ce n'est pas lui le destinataire, l'approche pour le décryptage sera différente. Le véritable destinataire, le créateur des clés, aurait utilisé la clé privée, mais lui devra créer la clé privée à partir de la clé publique, pour réussir à décrypter le message !
    Clé privée : (d, n).
    Comme n est déjà présent dans la clé publique, il reste d à trouver.
    Nous avions évoqué plus haut comment l'avoir. Je vous redonne la ligne : p,q < d < n, e*d mod n = 1. Misère ! p et q, on ne les connaît pas ! Exact, mais n est le produit de 2 facteurs premiers qui sont p et q, donc la factorisation de n, nous donne p et q. Cette factorisation est longue, voire très très longue selon la taille de n et vous verrez que c'est le but recherché. On regardera ceci de plus près quelques lignes plus bas.
    En factorisant n, on retrouve donc p = 503, q = 563.
    Je ne vais pas détailler les calculs suivants car nous les avons déjà rencontrés plus haut. Image utilisateurn = 282124, e = 565.

    Déterminer d



    En analysant un peu les conditions pour trouver d, on remarque que le principe est le même que pour e. On délimite tout d'abord une portion de nombre où se trouve d, d plus grand que p et q et d inférieur à n. Puis on teste les nombres compris dans cette zone avec ce calcul e*d mod Image utilisateurn = 1.
    565 * 564 % 282124 = 566
    565 * 565 % 282124 = 37101
    ...
    565 * 140313 % 282124 = 1
    565 * 140314 % 282124 = 566
    Cette fois-ci, manuellement il aurait fallu passer un bon bout de temps avant de trouver d. Il vaut mieux appliquer cette méthode à un programme qui nous trouvera d. Mais il y a une autre solution bien plus rapide qui se résume à un seul calcul.

    d = 1/e % Image utilisateurn
    d = 1/565 % 282124
    d = 140313

    Cette dernière méthode est bien plus pratique.
    Avec ces 2 méthodes, on peut aisément trouver d, et donc la clé privée.
    Clé privée de Bob (trouvée par les méchants gobelins avec la factorisation de n) : (140313, 282124).

    Décrypter le message



    Citation : Alice
    80488 / 2826 / 241808 / 183218 / 45154 / 84918

    Le décryptage sera aussi simple que le cryptage, une simple formule, et nous retrouverons nos blocs de départ, puis on les retranscrit en lettres grâce à la table ASCII.

    Formule :
    ccd % n = cn

    80488140313 % 283189 = 72
    2826140313 % 283189 = 101
    241808140313 % 283189 = 108
    183218140313 % 283189 = 112
    45154140313 % 283189 = 32
    84918140313 % 283189 = 33

    ASCII Caractère
    72 H
    101 e
    108 l
    112 p
    32 (espace)
    33 !

    Et voilà, le message est décrypté, même si ce n'était pas le véritable destinataire !
    Pourquoi ?
    Tout simplement parce que la sureté du RSA repose sur la factorisation de n et notre n était bien trop petit, il a été factorisé rapidement avec un factorisateur banal.

    Je vais prendre un nombre semi-premier, c'est-à-dire le produit de 2 nombres premiers, soit n, du challenge RSA qui n'est plus en vigueur, mais il est encore possible d'accéder à ces nombres. C'est le RSA-576, composé de 174 chiffres :

    Citation : Challenge RSA
    188 198 812 920 607 963 838 697 239 461 650 439 807 163 563 379 417 382 700 763 356 422 988 859 715 234 665 485 319 060 606 504 743 045 317 388 011 303 396 716 199 692 321 205 734 031 879 550 656 996 221 305 168 759 307 650 257 059

    La factorisation en image : début et fin.
    Vous pouvez remarquer que fin, c'est vite dit ^^ . En fait la factorisation n'a pas abouti et je ne pouvais pas me permettre de faire tourner mon ordinateur pendant des lustres. L'algorithme de factorisation est peut-être basique mais même avec du bon matériel et un bon algorithme, cela prend quand même énormément de temps, c'est pourquoi RSA est sûr !

    Le décryptage du côté de Bob, qui lui est le vrai destinataire, soit le créateur des clés privée et publique, est similaire à celui d'un destinataire indésirable, à la différence que Bob ne doit pas passer par la factorisation de n pour trouver p et q puisqu'il les a déjà.
    Voilà, la force de RSA !
    Il calcule d, comme vu plus haut et il peut ensuite sans problème décrypter le message d'Alice.
    Bob crée donc sa clé privée : (140313, 282124).
    Rien de nouveau, il va décrypter de la même manière que notre gobelin cryptologue. Une fois décrypté, Bob pourra sauver Alice. ^^ Houraa ! :-°

    Pour un programme de décryptage, il y a donc toujours 2 cas à prendre en compte. Soit il faut factoriser n pour trouver la clé privée ou simplement demander la clé privée à l'utilisateur.


    Clé de chiffrement

    Dans cette partie annexe, nous allons approfondir le terme de clé et nous verrons les législations qui s'y rapportent.

    Qu'est ce qu'une clé de chiffrement ?



    Citation : Wikipédia
    Une clé est un paramètre utilisé en entrée d'une opération cryptographique (chiffrement, déchiffrement, scellement, signature numérique, vérification de signature).
    Une clé de chiffrement peut être symétrique (cryptographie symétrique) ou asymétrique (cryptographie asymétrique) : ...
    Une clé peut se présenter sous plusieurs formes : mots ou phrases, procédure pour préparer une machine de chiffrement (connexions, câblage, etc. Voir machine Enigma ), données codées sous une forme binaire (cryptologie moderne).


    Dans ce chapitre, nous avons travailler avec 2 clés pour un cryptage asymétrique. Ces 2 clés étaient sous forme de chiffres, mais tout ceci a été vu plus haut. Nous allons maintenant nous intéresser à la taille d'une clé, exprimée en bits.
    La taille d'une clé en bits est le nombre de bits qu'il faut pour l'écrire.
    Exemple :
    Ma clé est 10. Elle a donc une taille 4 bits. Puisque 10 égal en binaire 1010, il y a deux 1 et deux 0 soit 4 bits.

    En cryptage symétrique, on dira qu'il y a 24 choix pour trouver la clé, soit 16 possibilités.

    En asymétrique, c'est la taille de n qui est importante, puisque c'est n qui est factorisé et qui permet donc le décryptage.
    Et le nombre de possibilités de n n'est guère utile puisqu'il est donné dans la clé publique.
    Exemple :
    Reprenons notre n.
    n=283189 -> 1000101001000110101. Soit une taille de 19 bits, ce qui est beaucoup trop mince pour un chiffrement fiable.

    Les tailles des clés utilisées en asymétrique et en symétrique ne peuvent être comparées. On utilise des tailles plus importantes pour des algorithmes comme RSA que pour DES.
    Pourquoi ?
    Parce que les chiffrements asymétriques reposent sur des problèmes arithmétiques, comme la factorisation pour RSA. Il est alors nécessaire que n dépasse les 1024 bits soit environ 300 chiffres, sinon la factorisation est trop rapide et le document n'est plus sécurisé.
    Pour les chiffrements symétriques, étant donné qu'il n'y a qu'une clé, on utilise la méthode du brute force. Avec une clé beaucoup plus petit que n, on a quand même une sécurité fiable. Il faut un minimum de 128 bits soit 2128 possibilités, ce qui laisse déjà un petit moment au brute force pour se faire les dents. ^^

    Il est possible aussi pour remédier au problème de l'échange de la clé secrète, de la crypter avec un chiffrement asymétrique et de crypter le message avec un chiffrement symétrique. Étant donné que la clé secrète est cryptée, elle peut être envoyée sans problème au destinataire du message qui devra faire un double décryptage : celui de la clé et celui du message. :p

    La loi



    La loi du 21 juin 2004, libéralise l'utilisation des moyens de cryptologie. Cependant la fourniture de clés de déchiffrement est soumise à déclaration ou autorisation. Voir wikipédia.

    Avant de conclure, voici une petite liste de quelques outils qui pourront vous être d'une grande utilité avant d'avoir fait votre programme :

    Ce chapitre est maintenant clos. J'espère avoir accompli ma noble tâche que je me suis fixée :D : vous faire comprendre clairement l'utilisation de l'algorithme RSA. D'ailleurs si j'ai oublié des points importants, n'hésitez pas à m'envoyer des MP ou à laisser un commentaire. ^^
    Êtes-vous prêt à vous lancer dans l'élaboration de programmes pour RSA ? Je suis sûr que oui, alors rendez-vous à la partie suivante. :p

    Les programmes de cryptage et décryptage

    Passons dès maintenant à la programmation python ! Si vous pensez que vous allez devoir adopter un serpent, un conseil, rendez-vous ici. :p
    Mais si vous avez déjà quelques bases, commençons tout de suite.

    Le programme de cryptage

    Pour ce qui est de la programmation python, ne vous inquiétez pas, elle est très intuitive. Tout d'abord il vous faudra installer Python.
    Nous travaillerons avec l'IDLE, donc une fois ouvert comme sur l'image, faites : Files > New Window, et nous taperons le code dans cette nouvelle fenêtre.

    Déterminer p, q, n et Image utilisateurn :



    Entrons dans le vif du sujet ^^ , souvenez-vous pour crypter avec RSA nous avons tout d'abord choisi deux nombres premiers, nous allons donc demander à l'utilisateur d'en choisir deux par le biais de la commande input() :

    Il faut savoir qu'en python : on n'utilise pas de ";" et de "{...}" , les parenthèses sont remplacées si je puis dire par l'indentation et les dièses servent à signaler les lignes de commentaires

    Code : Python
    1
    2
    3
    4
    5
    # L'utilisateur entre p
    p = input('Entrez un grand nombre premier p : ')
    
    # L'utilisateur entre q
    q = input('Entrez un grand nombre premier q : ')
    


    Nous demandons donc à l'utilisateur d'entrer p et q, puis nous pouvons calculer n et Image utilisateurn.
    Pour ceux qui ont déjà fait du C, vous retrouverez "\n" le retour à la ligne.

    Code : Python
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # On calcule n 
    n = p*q
    
    print "\nn = ",n,
    
    # On calcul phi(n)
    phiden = (p-1)*(q-1)
    
    print "\nphi de n = ",phiden,
    

    Pensez à mettre cette ligne à la fin du code, pour que le programme ne se ferme sans avoir vu les résultats. ^^

    Code : Python
    1
    raw_input('\n\nFin\n\n')
    


    Enregistrer et exécuter :



    Déjà fatigué :D , alors faisons un petit break pour l'exécution de notre ébauche de programme ! Comment faire ? Bon, je suppose que vous avez tapé le code au fur et à mesure dans la fenêtre "Untitled", alors faites donc : Files > Save as > Nom du fichier : nom_de_votre_fichier.py > Enregistrer.
    Vous pouvez ensuite lancer votre .py ou faire F5 et le programme se lancera dans l'IDLE, c'est vous qui choisissez. ^^ Si vous avez des problèmes d'encodage (dus aux accents, etc.), je vous conseille de mettre cette ligne en début de code.
    Code : Python
    1
    # encoding: utf-8
    

    Déterminer la clé publique :



    Il nous faut trouver e pour créer la clé publique, remémorons-nous comment trouver e :
    • p,q < e < Image utilisateurn
    • e et Image utilisateurn premiers entre eux.

    Nous allons donc faire une boucle qui cherche p,q < e < Image utilisateurn et par-dessus une autre boucle qui continue jusqu'à ce que PGCD de e et Image utilisateurn = 1.

    Les variables :


    Code : Python
    1
    2
    3
    4
    5
    6
    # Variables de la boucle
    compteur = 0
    PGCD1 = 0
    
    # Notre e qui s'incrémentera
    e = 0
    


    La boucle et le if :


    Code : Python
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    # Tant que PGCD de e et phi(n) différents de 1
    while PGCD1 != 1 :
        # Tant que compteur=0
        while compteur == 0 :   
            # Si p inférieur à e et si q inférieur à e et si e inférieur à n
            if((p < e)and(q < e)and(e < phiden)) : 
                # La boucle se coupe (on peut aussi mettre le mot-clé : break
                compteur = 1     
            # Tant que rien n'est trouvé, e s'incrémente
            e = e + 1
        # On récupère le résultat du pgcd    
        PGCD1 = pgcd(e,phiden)
    


    Une fois que e répond à la première condition, on vérifie s'il est premier avec Image utilisateurn. Pour cela on crée une fonction PGCD qui retourne le PGCD de e et Image utilisateurn.
    En Python, une fonction est définie à partir du mot-clé "def". Nous écrirons notre fonction en début de code.

    Code : Python
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # La fonction PGCD avec ses 2 arguments a et b
    def pgcd(a,b):
        # L'algo PGCD
        while a != b:
            if a > b:
                a = a - b
            else:
                b = b - a
        return a;
    


    Maintenant que nous avons tout le nécessaire pour la clé publique, autant l'afficher. :p

    Code : Python
    1
    2
    # On affiche notre clé publique
    print "\nCle publique (",e,",",n,")"
    


    Crypter



    Comme tout à l'heure nous utiliserons une commande pour récupérer le texte que la personne voudra crypter, cette fois ce ne sera pas input() mais une variante, raw_input qui a une différence près : celle-ci prend les lettres.

    Code : Python
    1
    2
    # On demande d'entrer le texte à crypter
    mot = raw_input('\nEntrez le mot ou la phrase à crypter : ')
    

    Qui se souvient de ce qu'il faut faire après ? hein ? Alors ! personne... Rohh. :p
    On va devoir transcrire chaque lettre en son code ASCII, mais comment va-t-on s'y prendre ?
    Tout simplement de cette manière :
    • On cherche la taille de la chaîne de caractères à crypter.
    • On se fait une petite boucle qui tourne autant de fois que le nombre de lettres de cette dernière chaîne.
    • Dans cette boucle, on convertit chaque caractère en son code ASCII et on crypte le code ASCII grâce à la clé publique.
      Pour la simplicité et la bonne compréhension du programme, je crypterai chaque code ASCII un par un, je ne les regrouperai pas par deux, comme expliqué dans le premier chapitre.

    Bon jusque là pas besoin de travailler à la NSA. ^^
    Arrêtons les bavardages et codons.

    La taille de la chaîne :


    Code : Python
    1
    2
    # On récupère le nombre de caractères du texte.
    taille_du_mot = len(mot)
    


    Les variables :


    Code : Python
    1
    i = 0
    


    La boucle :


    Code : Python
    1
    2
    # Tant que i inférieur au nombre de caractères
    while i < taille_du_mot :
    

    À partir d'ici, tout le code devra être dans la boucle et n'oubliez pas d'indenter, à l'aide de la touche tab.


    ASCII :


    Avec la fonction ord, nous pouvons très facilement convertir chaque caractère en son code ASCII. Celle-ci prend en argument le caractère à convertir.

    Code : Python
    1
    2
    # Comme i s'incrémente jusqu'à égalité avec la taille du mot, à chaque passage dans la fonction chaque lettre sera convertie.
        ascii = ord(mot[i])
    


    Crypter le code ASCII de chaque lettre :


    Ahhh enfin on y est ! Aucun changement, ne vous inquiétez pas, le cryptage RSA se fait toujours sous la forme :
    bloc ^ e % n

    Code : Python
    1
    2
    # On crypte la lettre ou plutôt son code ASCII
        lettre_crypt = pow(ascii,e)%n
    


    Deux dernières petites conditions :


    Je ne vais pas vous faire attendre les voilà :
    • ASCII < n
    • bloc crypter < Image utilisateurn
    Code : Python
    1
    2
    3
    # Si le code ASCII est supérieur à n 
        if ascii > n :
            print "Les nombres p et q sont trop petits veuillez recommencer."
    

    Code : Python
    1
    2
    3
    # Si le bloc crypté est supérieur à phi(n)
        if lettre_crypt > phiden :
            print "Erreur de calcul"
    


    Finalisons :
    En affichant chaque lettre cryptée et sans oublier d'incrémenter i à la fin de la boucle.

    Code : Python
    1
    2
    3
    4
    5
    6
    7
    8
    # On affiche chaque bloc crypté 
        print "\n Block : ",lettre_crypt,
        
        # On incrémente i
        i = i + 1
    
    # On bloque le programme avant la fermeture
    raw_input('\n\nFin\n\n')
    


    Et gracieusement je vous donne l'ensemble du code et le programme en python.


    Le programme de décryptage

    Les quelques notions importantes de python ont été comprises ? Alors, passons tout de suite au décryptage.
    La sécurité comme le programme de décryptage repose sur la factorisation de n. Nous allons donc récupérer n puis le factoriser pour obtenir les deux grands nombres premiers p et q.

    La fonction de factorisation :


    Comme pour le PGCD, on lui envoie n et elle se débrouille pour nous retourner p et q ^^ .

    Code : Python
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    # La fonction factoriser avec en argument n
    def factoriser(n):
        b=2
        while b:
            while n%b!=0 :
                b=b+1
            if n/b==1 :
                print "p = ", b,
                # On créé une variable globale p pour la réutiliser hors de la fonction et p=b
                global p
                p = b
                break
            print "\nq = ", b,
            # On créé une variable globale q pour la réutiliser hors de la fonction et q=b
            global q
            q=b
            n=n/b;
    

    Vous placez donc la fonction en début de programme, puis nous allons utiliser input() pour demander n et appeler la fonction.

    Code : Python
    1
    2
    3
    4
    5
    # On récupère n.
    n = input("Entrez le nombre n : ")
    
    # On appelle la fonction pour le factoriser.
    factoriser(n)
    


    Déterminer Image utilisateurn, e et d:



    Pour Image utilisateurn rien de bien compliqué, comme d'habitude, un petit calcul pour le trouver.

    Code : Python
    1
    2
    # On calcule phi(n)
    phiden = (p-1)*(q-1)
    


    Nous savons déjà comment trouver e, on utilise le même principe que pour le cryptage.
    Comme e est présent dans la clé publique, le code suivant est facultatif, vous pouvez si vous le souhaitez ajouter à la place la commande input() pour récupérer e.

    • Une boucle ;
    • Des conditions ;
    • Et vérification que PGCD(e, phiden)=1.

    Il va d'abord falloir que vous réécriviez la fonction PGCD en début de code.

    Code : Python
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # La fonction PGCD avec ses 2 arguments a et b.
    def pgcd(a,b):
        # L'algo PGCD
        while a != b:
            if a > b:
                a = a - b
            else:
                b = b - a
        return a;
    


    Et maintenant on récrit la boucle et l'appel du PGCD. Trouver e est très important pour le calcul qui nous permet d'avoir d.

    Code : Python
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # Variable pour notre boucle while
    compteur = 0
    PGCD1 = 0
    
    # Notre e qui s'incrémentera
    e = 0
    
    # Tant que PGCD de e et phi(n) différent de 1
    while PGCD1 != 1 :
        # Tant que compteur=0
        while compteur == 0 :   
            # Si p inférieur à e et si q inférieur à e et si e inférieur à n
            if((p < e)and(q < e)and(e < phiden)) : 
                # La boucle se coupe (on peut aussi mettre le mot-clé : break
                compteur = 1     
            # Tant que rien n'est trouvé, e s'incrémente
            e = e + 1
        # On récupère le résultat du pgcd    
        PGCD1 = pgcd(e,phiden)
    


    Voilà nous y sommes, il faut calculer d et nous avons notre clé privée, si vous vous souvenez du premier chapitre :
    e*d mod n = 1 et p,q < d < Image utilisateurn.
    Comme pour e une boucle et des des conditions.

    Code : Python
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    # On calcule d
    d = 0
    compteur = 0
    while compteur == 0:
        # Les conditions vues ci-dessus :
        if((e * d % phiden == 1)and(p < d)and(q < d)and(d < phiden)):
            compteur = 1
        d = d + 1
    d = d - 1
    
    # On affiche la clé privée
    print "\nCle privee (",d,",",n,")"
    


    Il ne faut pas oublier le cas où ce serait le vrai destinataire qui utiliserait le programme. Tout le code entré jusqu'à présent doit être dans un if, et avant ce if, on demande à l'utilisateur s'il connaît p et q. Soit il ne les connaît pas et on entre dans ce premier if avec la factorisation de n ou soit il les connaît et on a un deuxième if où il rentra p et q non pas n.

    Modifiez le code en entrant après les fonctions ces lignes qui permettent de gagner du temps si l'utilisateur est le bon destinataire.

    Code : Python
    1
    2
    3
    4
    pqconnu = 0
    pqconnu = input("Si vous êtes en possession de p et q, entrez 1 sinon 0 : ")
    
    if pqconnu == 0 :
    


    Dans ce if on met donc le code du dessus.
    Et celui-ci, le cas où nous n'avons pas à faire à un hacker. ^^

    Code : Python
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    if pqconnu == 1 :
        p = input("Entrez le nombre p : ")
        q = input("Entrez le nombre q : ")
    
        # On calcule n
        n = p*q
    
        # On calcule phiden
        phiden = (p-1)*(q-1)
    
        #On demande d
        d = input("Entrez le nombre d : ")
    


    Dans les deux cas on aboutit à d. Seul le deuxième permet de passer la factorisation si l'on connaît p et q. Il n'est pas nécessaire de connaître e car e sert dans le cas où il faut trouver d, ici nous n'en avons pas besoin. Maintenant que d est connu, le décryptage peut réellement commencer.

    Décrypter


    Tout aussi simple le décryptage, regardons ensemble comment on va s'y prendre : :p
    • Par ce calcul savant : lettre cryptée ^ d % n = ASCII de la lettre, on trouve notre code ASCII.
    • Avec la fonction chr(ASCII), on retrouve la lettre.
    On fait mijoter tout ceci dans la casserole boucle qui va tourner le nombre de fois qu'il y a de lettres, donc ceci c'est nous qui lui disons.

    Le nombre de blocs et la boucle :


    Code : Python
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    compteur = 0
    # Tant que r inférieur au nombre de lettres
    while compteur < i :
        # L'utilisateur entre le premier bloc à décrypter
        lettre_crypt = input("\nEntrez le bloc a décrypter :")
        # On trouve le ASCII de chaque lettre par le calcul de décodage
        ascii = (pow(lettre_crypt,d)%n)
        # Avec la fonction chr(ASCII), on trouve le caractère correspondant.
        print "lettre :",chr(ascii),
        compteur = compteur + 1
    


    Pour exécuter le programme reportez-vous à la première partie, sinon voici le code et le .py. Je vous ai fait aussi une petite démo sur photo. ^^

    Eh bien voilà, on l'a fait ce programme ! Pas si dur hein ?
    Si l'envie vous prend, sachez que de multiples améliorations sont possibles pour les programmes, comme faire un même programme pour le cryptage et le décryptage, un 2 en 1 ^^ , ou améliorer l'algorithme de factorisation, en bref, libre cours à vos idées...
    Par exemple, les 2 programmes avec interface graphique : ici et .
    Sinon pour toutes hésitations mathématiques, la partie suivante est là pour vous :p (entièrement rédigée par L01c).
    Bonne continuation et à bientôt !

    Explication Mathématique

    Bonjour à toutes et à tous !

    Ce chapitre va aborder des points mathématiques importants pour bien comprendre le cryptage RSA.

    Tout d'abord nous allons voir la division euclidienne, ce qui va nous permettre de définir les congruences. Ensuite nous verrons les nombres premiers qui sont la base du cryptage RSA puisque ce sont deux nombres premiers que nous avions appelés p et q qui nous permettront de calculer n. Ensuite pour le décryptage, vous aurez besoin de factoriser n. Nous verrons donc la factorisation.

    Évidemment, je vais donc tout vous expliquer depuis le début (faut juste savoir un peu compter, multiplier, soustraire, additionner ; et encore ça sera souvent des programmes qui feront ça à notre place :D ), donc il n'y a pas vraiment de pré-requis spécifiques.

    Vous êtes fort, grand, beau, intelligent, prêt ? Let's go.

    La division euclidienne

    Tout d'abord, avant de nous lancer dans les congruences, nous allons définir et expliquer la division euclidienne, dont on a grandement besoin de comprendre le fonctionnement pour attaquer la suite.

    Un exemple pour une première approche



    Nous allons commencer tout doucement ce tuto avec les divisions comme on les faisait en... CM2, c'est-à-dire avec uniquement des nombres entiers positifs. Qui a dit que ce tuto était réservé à une élite de mathématiciens ayant au moins un Bac + 5 ? :-°

    Bien, nous allons donc prendre un exemple: diviser 1489 par 17, et sans calculatrice s'il vous plaît :

    Image utilisateur


    Mouais et c'est quoi ce truc moche ?

    C'est sûr, c'est pas avec ça que vous allez inventer la poudre (quoique... ;) ), mais c'est déjà un début, alors qu'est-ce que j'ai fait là ?
    Je suis parti de 1489, et j'ai vu qu'en mettant 80*17 je trouvais 1360, je soustrais donc 1360 à 1489, et il ne me reste plus que 129, or je remarque que 7*17=119, je prends donc le 129 auquel je soustrais 119, et je trouve 10.

    Vous remarquez qu'à aucun moment je n'ai fait une soustraction de telle sorte que je me retrouve avec un reste négatif : il faut toujours avoir des nombres positifs, c'est important. Si jamais vous dépassez (par exemple 100*17 est plus grand que 1489), diminuez le 100, essayez avec 90, "Ha mince c'est encore trop grand", jusqu'à ce que vous trouviez le 80. Ensuite, une fois que vous avez le bon chiffre avec les dizaines, vous passez aux unités, pour trouver de la même manière un 7.

    Bon, je me retrouve donc avec un 10 à la fin de ma division euclidienne.
    Et là je fais quoi ? Ben rien, je laisse tel quel, "pourquoi donc?" me direz-vous.
    Tout simplement parce que 10 est plus petit que 17 et que donc on ne peut plus continuer tel quel car, comme je vous l'ai dit plus haut, nous n'utilisons que des nombres entiers positifs.

    Nous avons donc fait ce que nous appellerons à partir de maintenant la division euclidienne de 1489 par 17, nous avons trouvé comme quotient 87 et comme reste 10.
    Nous remarquons que nous pouvons toujours retrouver 1489, en effet 17*87 +10 = 1489.

    Une définition de la division euclidienne



    Nous pouvons définir la division euclidienne de a par n telle que :
    a=nq+r

    Plusieurs remarques s'imposent :
    • a, n, q et r sont des entiers et nous n'emploierons dans le domaine de la cryptologie (et donc dans ce chapitre) que des entiers positifs (appelés encore entiers naturels).
    • Il faut absolument que r < n ; sinon, si r > n cela veut dire que nous n'avons pas fini la division. Un peu comme si j'avais dit que le reste dans l'exemple précédent était 129 au lieu de 10.
      Il faut aussi que r soit plus grand ou égal à 0. Si r est négatif, c'est que nous sommes allés trop loin.
    • Si vous effectuez la division euclidienne de a par n et que vous respectez les deux points ci-dessus, vous devez obtenir un (l'existence) et un seul (l'unicité) couple : q et r, qui sont respectivement le quotient et le reste de la division euclidienne de a par n.
      La démonstration de l'existence et l'unicité de la division euclidienne ne seront pas développées ici, car c'est un peu compliqué et c'est franchement pas très utile ; néanmoins si vous êtes fous curieux, allez voir la démonstration sur le site de wikipedia : il faut cliquer sur le premier "Dérouler" pour voir apparaître sous vos yeux ébahis la démonstration. ;)
    • Pour votre culture générale, on appelle a le dividende et n le diviseur ; si vous ne retenez pas cela, ce n'est pas très grave.
    • Lorsque le reste r est nul, alors a=nq : on dit que n divise a ou que a est un multiple de n.


    Le PGCD



    Avant de se lancer tête baissée dans l'abstrait, je vais vous donner un exemple pour essayer de vous faire comprendre la notion de PGCD :

    Pierre et Marie (Curie) :D travaillent tous les deux dans la même entreprise et sont amis. Pierre travaille 8 heures par jours et Marie 6 heures. Le matin, ils commencent à travailler en même temps à 8 heures.
    Sachant que Pierre et Marie ont le droit de faire des pauses pour aller boire un café toutes les X heures avec (X un nombre entier et qui doit être le même pour tous les deux fixés par tous les deux), sachant que leur première pause peut être faite après au moins une heure de travail, que leur dernière heure de travail doit aboutir à une dernière pause et qu'il n'y a pas de d'arrêt de travail pour le repas du midi (bouuhh le méchant patron qui laisse pas ses employés manger).
    Seulement il n'y pas longtemps, Pierre et Marie se sont brouillés. Depuis ils s'évitent.
    Combien de fois Pierre et Marie peuvent-ils au minimum se supporter se voir à la machine à café ? Et quelle sera alors la valeur de X ?


    Allons-y avec de la méthode, on sait que Pierre travaille 8 heures par jour et qu'il faut qu'il fasse des pauses toutes les X heures et que sa dernière heure de travail doit aboutir à une pause, donc 8=Xn, n étant le nombre de pauses que Pierre fera ; de même Marie travaille 6 heures donc 6=Xn' avec n' le nombre de pauses que fera Marie.
    Donc X divise 6 et 8 (en effet le reste est nul et n est un nombre entier).
    Les diviseurs de 6 sont : 1, 2, 3, 6
    Les diviseurs de 8 sont : 1, 2, 4, 8
    Il faut donc prendre ce qui est en commun aux deux listes de diviseurs, c'est-à-dire que X peut valoir 1 ou 2.
    C'est-à-dire : soit ils font une pause toutes les heures, et dans ce cas Pierre va faire 8 pauses, et Marie 6, auquel cas ils se verront 6 fois ; soit ils font une pause toutes les deux heures et dans ce cas Pierre va faire 4 pauses et Marie trois, dans ce cas ils se verront trois fois.
    Comme ils sont brouillés, ils vont prendre la deuxième option, puisque c'est là qu'ils se verront le moins.
    Mais la valeur de X sera 2, donc la plus grande des valeurs que pouvait prendre X.

    X divise 8 et 6. Quand X=2, X est le plus grand diviseur commun de 8 et 6 : on dit donc que X est le PGCD (8, 6) (PGCD = Plus Grand Commun Diviseur. L'inversion entre commun et diviseur est volontaire ; en effet, pour la petite histoire, sachez que les Anglais appellent ça le GCD (Greatest common divisor), comme ça ils comprennent notre "PGCD" :lol: ).

    Si vous prenez deux nombres entiers positifs quelconques, ils ont au moins un diviseur en commun qui est 1. On a dans tous les cas un diviseur commun, il y a donc un de ces diviseurs qui est plus grand que tous les autres, donc le PGCD existe quels que soient ces nombres.

    Déterminer le PGCD de deux nombres



    On sait que, pour tout couple de nombres entiers, il existe un PGCD. Nous allons maintenant déterminer le PGCD de deux nombres : 1352 et 124. Pour cela, nous allons faire... plusieurs divisions euclidiennes, que nous appelons algorithme d'Euclide:

    1352=124*10+112 //on pose la division euclidienne du plus grand nombre par le plus petit
    124=112*1+12 //on refait une division euclidienne mais le diviseur 124 devient dividende et le reste 112 devient diviseur
    112=12*9+4 //on continue en interchangeant les nombres de la même manière
    12=4*3+0

    Une fois qu'on a atteint le 0, c'est qu'on a fini, ouf !
    Mais il est où le PGCD dans tout ça ?

    Le PGCD est le dernier reste non nul. Autrement dit, dans la dernière division euclidienne, on avait le reste nul : il faut donc prendre le reste du dessus, c'est-à-dire 4, donc PGCD (1352, 124)=4.

    J'ai rien compris,. Comment tu sais que c'est lui le PGCD et pas un autre, ? Et puis pourquoi t'as fait des divisions euclidiennes ? T'aurais pu prendre les deux nombres les soustraire, mettre à la racine carrée en passant le tout au cosinus.


    Oui, mais cette méthode donne le PGCD. Je ne peux pas vous le démontrer car c'est assez difficile à comprendre, mais vous pouvez vérifier par vous-même sur ce lien sur Wikipedia.

    Retenez le principe uniquement si vous voulez vous-même coder un programme pour obtenir le PGCD. Sinon, vous trouverez, sur le lien précédent, le code source de l'algorithme d'Euclide dans beaucoup de langages (pratique pour vos programmes de cryptage ;) .

    Nombres premiers entre eux



    Deux nombres sont premiers entre eux lorsque leur PGCD vaut 1. Cela veut dire que, si on met ces nombres en fraction (l'un en dénominateur, l'autre en numérateur), cette fraction n'est pas simplifiable.
    Par exemple 8/6=4/3
    PGCD (8, 6) = 2 : on peut donc simplifier la fraction en divisant en haut et en bas par 2 ce qui nous amène au 4/3 ; en revanche, PGCD (4, 3)=1 : on ne peut donc plus simplifier cette fraction.
    On dit que cette fraction est irréductible gaulois qui résistent encore et toujours à l'envahisseur... Pardon je m'emporte là :p .

    Conséquences de la division euclidienne



    Si vous avez compris ce qu'était une division euclidienne, vous serez d'accord avec moi : le reste de la division euclidienne de la division de a par 2 est un nombre positif inférieur à 2, il n'y a donc que deux restes possibles : 1 et 0.
    Ainsi : a=2q+0
    Ou : a'=2q+1

    Dans le premier cas, nous avons un nombre qui vaut deux fois q (avec q un nombre entier), ce nombre est donc un nombre pair. Dans le deuxième cas, nous avons a' qui vaut un nombre pair (2q) ajouté à 1, nous avons donc a' qui est un nombre impair.

    Il faut que vous compreniez que tous les nombres entiers peuvent s'écrire 2q ou 2q+1 (ben ouais, facile : un nombre entier est soit pair, soit impair). Mais il faut surtout que vous compreniez que, de la même manière, tous les nombres entiers peuvent s'écrire 3q, 3q +1 ou 3q+2. On peut encore aller plus loin et faire avec 4 : 4q, 4q+1, 4q+2, 4q+3 etc...

    Il existe donc des nombres qui ont le même reste dans la division euclidienne par n. Par exemple, tous les nombres impairs ont pour reste 1, dans la division euclidienne par 2.
    Autre exemple : si on fait la division euclidienne de 23 par 5 et de 18 par 5.
    23 = 5 * 4 +3
    18 = 5 * 3 +3

    On a donc le même reste qui vaut 3.

    Deux nombres a et a' qui ont le même reste r dans la division euclidienne par n peuvent s'écrire :
    a=nq'+r
    a'=nq'+ r
    Si on soustrait les deux égalités, on obtient :
    a-a'=nq +r -(nq' + r) =nq -nq'
    On peut donc factoriser à droite par n et obtenir : a-a'=n(q-q')

    On voit donc que a-a' est un multiple de n.

    On peut alors établir que, lorsque deux nombres ont le même reste par la division euclidienne par n, la différence de ces deux nombres est un multiple de n.

    La réciproque est aussi vraie : lorsque a-a' est un multiple de n, alors a et a' ont le même reste dans la division euclidienne par n.


    Nous voilà donc aux congruences : a et a' sont congrus modulo n si et seulement si ils ont le même reste dans la division euclidienne par n.


    Les congruences

    Nous allons maintenant attaquer Image utilisateur un point important de ce chapitre : les congruences. Nous avons vu avant que deux nombres étaient congrus modulo à un nombre n lorsque la différence de ces deux nombres est divisible par n. Nous allons maintenant voir plus en profondeur les congruences.

    La notation



    Soient deux nombres a et b qui sont congrus modulo n. Nous allons noter la congruence de manière mathématique :
    a\equivb [n] qui se lit a et b congrus modulo n.

    Oui mais là tu dis que ces deux nombres sont égaux, ce qui n'est pas forcément le cas.


    Non le [n] veut dire "modulo n" et enlève donc toutes ambiguïtés et / ou absurdités, de plus le signe \equiv n'est pas un égal, puisqu'il y a trois barres.

    Plusieurs remarques



    • On sait que si a\equivb [n] cela veut dire que a-b=qn, q étant un entier relatif (c'est-à-dire un entier positif ou négatif). Donc, si b=0 alors qn=a-0=a donc n divise a.
    • Il existe plusieurs notations pour les modulo :
      a\equivb (mod. n)
      a\equivb (n)
      Pendant la suite du tuto, j'utiliserai plus volontiers la dernière notation qui est plus simple à utiliser. :-°
    • Si a\equivb (n), alors b\equiva (n).
    • Si n=1, alors a-b est toujours divisible par 1. En effet, on peut toujours écrire un nombre x tel que x=x*1). Donc a\equivb (1) quels que soient a et b. C'est pour ça que les congruences modulo 1 n'ont pas un grand intérêt. On utilisera donc n plus grand ou égal à 2.
    • Lorsque n=10, il faut que a-b=10q. Il faut donc que a et b aient la même unité. Par exemple : 37-17=20 donc 37\equiv17 (10).
    • La division euclidienne de a par n est définie par a=qn+r, donc a-r=qn. Donc, a-r est divisible par n, donc a\equivr (n). Cela est noté a%n = r.


    Un programme pour vérifier la congruence



    Vous vous souvenez dans le premier chapitre nous faisions des calculs avec les modulos, comme ici :
    Citation :
    072565 % 283189 = 80488

    Imaginez un moment que vous avez peur et que vous voulez vérifier vos calculs mais que vous n'avez pas envie de faire ça de tête. Pas de problème ! Il suffit de faire un petit programme où vous n'aurez qu'à rentrer vos trois nombres dans la console. Votre programme fait alors la différence des deux premiers nombres et voit si la division de la différence par le troisième nombre est une division dont le résultat est un entier (positif ou négatif car la soustraction de deux entiers naturels peut donner un nombre négatif ^^ ). Dans le cas contraire, les deux nombres ne sont pas congrus entre eux.

    En clair si \frac{072^{565}-80488}{283189} donne un nombre entier alors c'est bon :) .


    Les nombres premiers

    Une définition



    Prenons par exemple le nombre 24. Nous pouvons dire, grâce à nos souvenirs des tables de multiplication, que 24=12*2=6*4. Mais avec le nombre 17, si on cherche des nombres entiers a et b tels que a*b=17, on ne trouve pas beaucoup de possibilités : soit c'est a=1 et b=17, soit c'est l'inverse ; les diviseurs de 17 sont donc 17 et 1.

    La définition d'un nombre premier est donc : un nombre est premier si et seulement s'il n'a que deux diviseurs positifs, à savoir 1 et lui-même.

    Quelques remarques :
    • 0 n'est pas premier. En effet, on peut écrire que 0=a*0, quel que soit a. 0 ayant une infinité de diviseurs, il n'est pas un nombre premier.
    • 1 n'est pas non plus premier. En effet, pour être premier, un nombre doit avoir deux diviseurs ; or 1 n'a qu'un seul diviseur : lui-même.
    • 2 est divisible uniquement par 2 et par 1 : c'est donc un nombre premier. Tous les nombres pairs étant de la forme 2q, ils sont toujours divisibles par 2. Lorsque q est plus grand que 1, on a un nombre pair divisible par 2 nombres différents de 1 : 2 et q, donc 2 est le seul nombre pair premier.
    • Il existe une infinité de nombres premiers, le démontrer est assez difficile car cela nécessite des propriétés supplémentaires dont on n'a pas vraiment besoin. Cependant, voici un lien de Wikipédia si vous voulez comprendre.
      En tout cas, c'est plutôt une bonne nouvelle pour vos données car plus vous prendrez des nombres premiers élevés, plus n sera élevé et donc ça sera sécurisé.


    Comment vérifier qu'un nombre est premier ?



    Vous voulez crypter en utilisant le système RSA et vous avez choisi deux nombres dont vous n'êtes pas sûr qu'ils soient premiers. Il va donc falloir le vérifier, sinon votre cryptage ne marchera pas.
    Il va falloir créer un petit programme qui va vérifier si le nombre que vous mettez est bien premier. Celui ci sera écrit en C histoire d'alterner avec le python, mais si vous le comprenez dans un langage vous le comprendrez dans l'autre :p .
    Le voici dans sa totalité, les explications du programme sont juste en-dessous :

    Code : C
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h> 
    
    int main(int argc, char *argv[])
    {
        long nombre = 0;
        long premier = 0;
        long essai = 0;
    printf ("Bonjour, ce programme permet de tester la primalité d'un nombre!\n\n");
    printf ("Entrez un nombre entier positif :");
    scanf ("%ld", &nombre);
    if ( nombre % 2 == 0 )
            {
        premier = 3;
            }
    else
    {
    for (essai = 3 ; sqrt (nombre) >= essai  ; essai+=2)
            {
             if (nombre % essai == 0)
                    {
                    premier = essai;
                    }
            }   
    }
    
    if (nombre == 2)
    {
    premier = 0;
    }
    if (nombre == 1)
    {
    premier = 1;
    }
    if ( premier == 0)
    {
    printf ("ce nombre est premier\n\n\n");
    }
    else
    {
    printf ("Ce nombre n'est pas premier\n\n\n");
    }
    
    getchar();
    return 0;
    }
    


    Explication:
    Pour la première partie : le if
    Nous savons qu'aucun nombre pair, sauf 2, n'est premier.
    Code : C
    1
    2
    3
    4
    if ( nombre % 2 == 0 )
            {
        premier = 3;
            }
    


    Le code ci-dessus nous permet de savoir si le nombre est pair. S'il est pair (et différent de 2), il n'est pas premier. Il va donc falloir que la variable premier prenne une valeur différente de 0 (ici 3). En effet, à la fin, le code teste la variable premier et indique qu'un nombre est premier si la variable premier est nulle. Il faut cependant retenir que 2 est le seul nombre pair premier ; il faut donc ensuite faire un test pour voir si le nombre rentré par l'utilisateur est 2 ou pas ; si c'est le cas, il faut que la variable premier soit égale à 0, d'où le Code : C
    1
    2
    3
    4
    if (nombre == 2)
    {
    premier = 0;
    }
    


    Ce bout de code doit être inséré après le test qui permet de dire si le nombre est pair ou pas. Si ce n'est pas le cas, la variable premier prendra, dans le cas où le nombre est deux, d'abord la valeur 0, puis la valeur 3.

    Deuxième partie : le else
    Pour comprendre cette partie, il faut que vous sachiez une chose : un nombre non premier a des diviseurs autres que un et lui-même. Ça, vous le savez, mais ce que je ne vous ai pas dit, c'est que, parmi ces diviseurs, il y a au minimum un nombre premier : par exemple, dans le cas du nombre 36, ses diviseurs sont (en gras les diviseurs qui sont premiers) :
    1, 2, 3, 4, 6, 9, 12, 18, 36
    Donc, en fait, ça serait simple : il suffirait de prendre un nombre et de le diviser par tous les nombres premiers inférieurs à ce nombre, et voir si un de ces nombres premiers est bien un diviseur ?


    Oui, mais cela suppose de connaître tous les nombres premiers qui sont inférieurs à ce nombre, et ce n'est pas dit que ça soit le cas : vous pouvez me réciter tous les nombres premiers inférieurs à 4787 :-° ?
    Alors, pour le programme, on va devoir diviser par d'autres nombres que des nombres premiers. Essayer tous les nombres serait trop long mais on sait déjà que tous les nombres pairs supérieurs à 2 ne sont pas premiers : on n'a donc qu'à faire des tests pour des nombres impairs supérieurs à 3 et incrémenter la variable essai de 2 pour éviter d'avoir des nombres pairs, d'où ceci : Code : C
    1
    for (essai = 3 ; sqrt (nombre) >= essai  ; essai+=2)
    


    Oui mais pourquoi la racine carrée (représenté par sqrt) ?

    Excellente question :p , la question est aussi intéressante que la réponse sera compliquée. :o

    Vous êtes d'accord pour dire que, si on prend un nombre a positif, alors on a : a=\sqrt{a}*\sqrt{a}.
    Dans la pratique, a aura des diviseurs ; on peut donc dire que certains seront supérieurs ou égaux à \sqrt{a} et d'autres seront inférieurs ou égaux à \sqrt{a}.
    Supposons que b et c soient des diviseurs de a tels que a=bc et que \sqrt{a}> b.
    Si on prend cette égalité, que l'on multiplie tout par \sqrt{a}, on obtient :\sqrt{a}*\sqrt{a}>b*\sqrt{a}
    soit a > \sqrt{a}*b, mais on sait aussi que a=bc, on remplace donc a par bc.
    Donc b*c> b* \sqrt{a}en divisant par b : c>  \sqrt{a}
    Et là tout s'éclaire o_O , non ?
    Ça veut dire qu'un nombre quelconque a autant de diviseurs supérieurs (ou égaux) à racine de a que de diviseurs inférieurs à racine de a.
    D'ailleurs, on le voit avec les diviseurs de 36 : 1, 2, 3 et 4 sont inférieurs à 6 tandis que 9, 12, 18 et 36 sont supérieurs à 6.
    Donc, si on fait des tests avec des nombres inférieurs à racine de a et qu'on ne trouve pas de diviseur, c'est qu'il n'y en aura pas non plus au-dessus de racine de a. Le nombre est donc premier.
    A contrario, il suffit de trouver un nombre qui divise a pour que a ne soit plus premier. Et il faut donc changer la variable premier et lui mettre une autre valeur : j'ai pris la valeur essai dans mon programme, mais je peux très bien mettre 4 ou tout autre nombre différent de 0.

    Troisième partie : les tests

    Il suffit de tester avec des if si le nombre premier est nul ou pas. S'il est nul, le nombre est premier ; dans le cas contraire, il n'est pas premier. Il faut aussi faire attention au 1, qui n'est pas premier. Il faut donc modifier la variable premier en faisant un test.

    Il est évident que si ce test vous a permis de vérifier que 11 était effectivement premier en quelques dixièmes de secondes, il est beaucoup plus lent. Tester la primalité d'un nombre (la primalité étant le fait qu'un nombre soit premier) demande des ordinateurs beaucoup plus puissants et des algorithmes plus avancés.

    Générer des nombres premiers


    On arrive à trouver des expressions où, en faisant varier un entier positif n, on peut aboutir à la génération de certains nombres premiers ; mais il faut être prudent car cela ne marche pas à tous les coups. Je vais donc vous donner 3 suites, ce qui vous permet déjà de créer quelques nombres premiers :

    Une première suite



    Soit l'équation p=n²-n+41 avec n un nombre entier positif. Si nous faisons varier n, nous voyons que p prend des valeurs de nombre premier. Ci-dessous le tableau des premières valeurs de p :

    valeur de n valeur que prend p p premier ?
    0 41 Oui
    1 41 Oui
    2 43 Oui
    3 47 Oui
    4 53 Oui
    5 61 Oui
    de 5 à 39 ... Oui
    40 1601 Oui
    41 41²-41+41=41²=1681 Non


    Et là c'est le drame car pour n=41, p=41² : ce n'est pas un nombre premier car il a au moins 3 diviseurs : un, lui-même et 41.

    Cette suite ne marche pas : dommage ça aurait été un bon moyen de créer une suite de nombres premiers. Si vous avez un tableau de nombres premiers sous les yeux, vous pouvez vous apercevoir que, pour des valeurs de n qui vont de 0 à 40, ça marche mais pas au-delà :( .

    Vous pouvez aussi remarquer que cette suite ne donne pas tous les nombres premiers. En effet, il y avait de grands écarts au bout d'un moment entre deux valeurs de p consécutives alors qu'il y avait plein d'autres nombres premiers au milieu. Mais cette suite peut quand même vous donner des nombres premiers jusqu'à 1601.
    Le problème, c'est que si tout le monde utilisait les nombres premiers de cette suite, les pirates le sauraient et ils n'auraient plus qu'à prendre le nombre n et à le diviser par les nombres de la suite pour des valeurs de n inférieures à 41. Ils pourraient donc en un rien de temps factoriser n. Il va falloir donc trouver autre chose.

    Les nombres de Fermat



    Pierre de Fermat a conjecturé (sans le prouver) il y a quatre siècles que les nombres du genre p=22n+1 étaient premiers.

    Avec cette suite, ça a marché jusqu'à n=4 où on avait un nombre premier p = 65 537, mais Euler (si vous êtes en Première ou en Terminale S, vous avez sûrement entendu parler d'Euler et de la méthode d'Euler pour les approximations affines) démontra que, pour n=5, on avait p=4 294 967 297, ceci étant (comme chacun sait :D ) le produit de 6 700 417 par 641.

    Nous n'avons donc pas beaucoup de nombres premiers sûrs avec cette suite mais elle donne quelques valeurs assez grandes de nombres premiers. D'ailleurs, il existe d'autres nombres premiers qui sont supérieurs au contre-exemple d'Euler, mais on ne sait pas s'il y en a une infinité ou pas. Donc il faudra faire des vérifications.

    Une autre suite : les nombres de Mersenne



    Les nombres de Mersenne sont des nombres du genre : 2p-1 avec p un nombre premier. Cette suite donne des nombres qui ne sont pas forcément premiers mais qui ont une possibilité de l'être. Il faut donc faire un test de primalité grâce au programme que je vous ai montré ci-dessus.
    Cependant, les nombres de Mersenne peuvent quand même donner de grands nombres premiers. Par exemple, le nombre 124575026[&]053967871 est un nombre premier comprenant exactement 9 808 358 chiffres (si vous retenez que ça fait presque 10 millions de chiffres, vous comprendrez pourquoi je peux pas l'écrire en entier :-° ) .

    Il existe beaucoup de méthodes différentes de créer des nombres premiers ou des nombres ayant une forte probabilité de l'être, il est évident que les gens qui utilisent le système RSA pour des transactions de banques ne vont pas se servir que des nombres de Mersenne :p et autres...

    Conclusion



    Vous pouvez donc générer pas mal de nombres premiers en faisant un programme qui va choisir aléatoirement, entre ces trois suites (veuillez noter qu'il existe d'autres suites de ce genre), deux d'entre elles (si c'est la même suite c'est pas très grave) et générer deux nombres premiers q et p pour le cryptage RSA. Mais n'oubliez pas que, pour la première suite que je vous ai donnée, il faut que n soit inférieur à 41. Il faudra également faire des tests de primalité si vous utilisez les deux autres suites.
    Évidemment, je vous ai présenté des moyens simples de créer des nombres premiers, il est évident qu'il existe d'autres algorithmes plus performant mais aussi plus compliqué :) .

    Vous pouvez aussi intégrer dans votre programme les autres suites que je vous ai données en lien pour augmenter le nombre de nombres premiers que vous pouvez faire.


    La factorisation

    La factorisation est un point important dans le décryptage, en effet, en connaissant n, il faut trouver les nombres p et q.
    C'est ce qu'on appelle la factorisation.

    Première approche : qu'est-ce que la factorisation ?



    Vous avez peut-être appris en classe que k(a+b)=ka+kb (si vous ne l'avez encore jamais vu, maintenant au moins c'est fait :-° ), par exemple 12(5+4)=12*5+12*4.

    Lorsque l'on passe de k(a+b) à ka+k,b cela s'appelle développer.
    Et dans l'autre sens, c'est la factorisation : cela veut dire que quand vous avez une somme de plusieurs produits, vous prenez ce qui est commun à tous ces produits et vous le multipliez par le reste que vous mettez dans des parenthèses.

    Concrètement cela donne : 8*6+8*7+1568*8+67*8-8*3+8
    On remarque que dans chacun des différents produits il y a un 8. On met donc 8 en facteur et on obtient :
    8*6+8*7+1568*8+67*8-8*-3+8*1=8(6+7+1568+67-3+1).
    Ainsi, si vous voulez calculer le résultat, vous avez moins d'opérations à faire : il suffit de calculer le résultat de la parenthèse et de le multiplier ensuite par 8. Vous gagnez donc un temps précieux et ce même en faisant le calcul avec votre calculatrice car vous réduisez le risque d'erreur en rentrant vos nombres.

    Deux petites remarques :

    • Je n'ai pris la propriété qu'avec trois nombres k, a et b mais, comme vous le voyez dans l'exemple, on peut en mettre beaucoup plus : en fait vous en mettez autant que vous voulez. :D
    • Vous remarquez qu'il y a 1 à la fin : c'est tout simplement parce que 8=8*1. Si j'oublie ce 1, je modifie le résultat, ce qui n'est pas bon. ^^


    Maintenant voyons ce qu'est la décomposition en produit de facteurs premiers.

    La décomposition : ça va nous aider à trouver n



    On a vu au-dessus qu'on pouvait factoriser, c'est-à-dire mettre sous forme de différents facteurs. C'est ce que nous allons maintenant faire mais cette fois-ci nous allons partir d'un seul nombre n et le décomposer.

    Tout d'abord on peut dire que tous les nombres sont soit premiers soit un produit de nombres premiers, par exemple :

    7 est premier
    8=4*2=2*2*2=23 avec 2 qui est premier,
    42=7*6=7*3*2 avec 7, 3, 2 qui sont premiers,
    etc.

    Cette décomposition est unique, si on ne tient pas compte de l'ordre dans lequel on met les différents facteurs (voilà le lien de Wikipédia pour la démonstration).

    Il va donc falloir maintenant savoir comment factoriser n :

    vous prenez un nombre et vous voyez s'il est divisible par deux. Si c'est le cas, notez son résultat et notez que vous avez mis un 2. Ensuite vous divisez ce résultat par deux si vous le pouvez. Si vous ne pouvez pas vous passez au nombre premier suivant, c'est-à-dire 3, vous le divisez par 3 autant de fois que vous pouvez, et après vous passez à 5, etc.
    Prenons un exemple pour bien comprendre. On va décomposer 3960 :

    Image utilisateur

    On part de la valeur 3960 et on la divise par le premier nombre premier qui est 2 : on note qu'on a divisé une fois par 2 à droite. On voit qu'on obtient 1980, un nombre encore divisible par 2 : on rajoute donc un autre 2 à droite. Une fois cela fait, on passe à trois, puis à 5 et pour finir à 11.
    Au final on trouve 1. Cela veut dire qu'on a fini la factorisation. En effet : 2 *2 *2 *3 *3 * 5 *11=2³*3²*5*11=3960. On a donc bien décomposé 3960 en facteurs de nombres premiers. Dans la pratique, la décomposition est plus simple dans la mesure où n est un produit de deux nombres premiers : il n'y a donc que deux nombres à trouver, p et q, mais cela nécessite beaucoup plus de temps. En effet, le nombre n ne sera sûrement pas divisible par 2, par 3 ou par 5. Souvenez-vous que nous préférons mettre de grands nombres premiers.

    Si vous regardez le code permettant de trouver p et q qui vous a été donné pour faire le programme de décryptage, vous verrez que cela correspond à ce que nous avons fait au dessus, seulement là c'est l'ordinateur qui fait tout pour vous, chanceux ! :D

    Voilà, j'espère que vous avez beaucoup appris et que vous comprenez mieux ces notions mathématiques essentielles pour bien maîtriser le système RSA sur le bout des doigts.
    J'ai essayé de faire en sorte que ce chapitre ne soit pas un cours de maths classique souvent bien ennuyeux, car l'objectif est surtout de vous faire comprendre des points importants du cryptage RSA. C'est pour ça que j'ai décidé de ne pas vous mettre de QCM Image utilisateur.

    Pour tout remerciement, critique, suggestion, insulte, admiration, signalement d'erreur, manque de clarté, vous pouvez poster un message dans les commentaires.

    Rédigé par L01c.
     

    Et voilà, c'est fini, vous avez maintenant toutes les cartes en main pour pouvoir crypter / décrypter ce que vous voulez (tant qu'il ne s'agit pas de décrypter le code secret de mon compte en banque

    :-°

    ) . Vous pouvez, si vous le voulez, refaire vous-même un gros programme : pourquoi pas un programme avec de jolies fenêtres qui vous permet de crypter / décrypter, en faisant le gros du travail, c'est-à-dire en créant des nombres premiers, en générant les clefs publiques et secrètes etc. ? Vous voyez, même après avoir lu ce tuto, vous avez encore du travail.

    o_O

    votre commentaire
  • Softice 4.05
    Windows NT/2000/XP

    Débugger.Éxécute un programme pas à pas.Pour les utilisateurs de Windows XP j'y ai ajouté 2 patchs : un pour assurer la compatibilité XP et un pour avoir le clavier fr.

    Serial

    5.95 Mo

    Softice 4.05
    Windows 95/98

    Débugger.Éxécute un programme pas à pas.
    Les utlisateurs de Windows Me devront en plus utiliser ce
    loader pour charger softice au démarrage.

    Serial

    5,45 Mo

    W32dasm 8.93

    Désassembleur.Permet de voir le code du programme en assembleur et d'y trouver ses string data reference.

    libre

    592 Ko

    OllyDbg 1.10

    Désassembleur et débuggeur.Fourni avec les plugins

     Bookmark.dll : enregistrer des raccourcis vers des adresses.
     
    CleanupEx.dll : effacer les fichiers *.udd et *.bak.
     
    CmdBar.dll    : commandes de Softice dans une barre ollydbg.
     
    Cmdline.dll     : lignes de commande dans une petite fenêtre.
     
    HideDebugger.dll : cacher ollydbg à l'API isDebuggerPresent.
     
    OllyScript.dll  : créer et exécuter des tâches automatiquement.
     
    OllyDump.dll  : vider un processus actif dans un fichier.
     
    PeDumper.dll : dump de processus et liste des sections.

     

    libre

    1.25 Mo

    WinHex 10.2

    Éditeur héxadécimal.Permet de modier directement une fichier .exe en écrivant dans son code.

    craqué

    716 Ko

    Resource Hacker 3.4

    Editeur de resources pour les fichiers exe,dll...
    Permet de récupérer les icônes du programme

     libre

    547 Ko

    Regmon 6.06

    Surveiller les échanges entre un
    programme et le registre de windows.

     libre

    188 Ko

    UPX 0.72

    Compresseur de fichiers exécutables
    Il divise par 3 ou 4 la taille de l'exe suivant son contenu.

     libre

    103 Ko

    Stripper v2.11 rc2

     Unpacker un programme compressé avec le packer AsPack ou Asprotect et obtenir un listing de désassemblage avec des string data référence non vides

    libre

    137 Ko

    ProcDump32 1.6.2

    Editeur de sections et unpacker d'exécutables.

    libre

    160 Ko

    PEID 0.93

     Détecteur de 476 types de packers. Fourni avec des plugins qui permettent de trouver le packer qui a été utilisé pour compresser l'application, d'obtenir l'OEP du programme et d'unpacker le programme.

    libre

     314 Ko

    eXescope 6.30

    Permet de modifier les menus et sous-menu d'une programme sans rien y connaître en programmation.

    serial

    526 Ko

    Le franglophile 2.1.0

    Dictionnaire bilingue Français/Anglais.

    keygen

    3,47 Mo

    AUTRE LOGICIEL :

    reflector_5.1.4.0 + Adin Deblector Décompilateur VB NET (Dernère version)  
    Reflector 4210 Décompilateur VB NET
    Smartcheck 6.03  + Serial Décompilateur VB
    32bitcalc_xtx Calculatrice 32 bit
    BASECALC Calculatrice 32 bit
    CrackersTool_xtx Kit avec divers tools pour cracker
    DAMN.Hash.Calculator.1.5.1 Calcul du hash
    DAMN_HashCalc Calcul du hash
    DelphiFilesSetup installation de dll pour delphi
    KeyMakerGen KeyMakerGen à tester !!
    BlocNote Pour prendre des notes
    RSATool2v17 Calcul des clés RSA
    ResHacker_3.4.0_xtx Editeur de fenêtre
    burnerxm Lecteur de sons XM pratique pour lire les XM
    fileinsPEctorXLfr_xtx Editeur le fichier PE
    hcalc Calcul du hash
    imprec16f_xtx Reconstruction d'IAT

    votre commentaire
  • Nouvelle rubrique sur le cracking

     

    Je vous y présente pein de cours pour faire vos serial ect en fonction de vos programmes

    Donc plus besoin de demander à un copin le numéro de série de son logiciel !

    Lien de la nouvelle rubrique :

    http://scans-world.kazeo.com/Cours-appronfondie-n-1,a461958.html

     


    votre commentaire
  • 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

     


    votre commentaire
  • // Code source : crack en C++ \\

    //---------------------------------------- Licence ------------------------------------------
    //
    //Auteur : Pifoman
    //Email : pifoman@yahoo.com
    //Adresse site : http://crackinfrance.free.fr
    //Url redirection : www.geocities.com/mitonnes
    //Première source : version 1.0 du 13/10/2004
    //Dernière source : version 1.1 du 06/01/2004
    //Langage : C++
    //Compilateur : dev C++ 4 build 10/09/2000 (gratuit)
    //Adresse téléchargement : http://www.telecharger.com ou http://www.bloodshed.net/devcpp.html
    //Compilation : Windows XP édition familiale
    //Tests : Windows XP édition familiale / Windows 98
    //Type licence : libre. Vous pouvez copier et distribuer cette source
    // si vous laissez les lignes concernant la licence.
    // //------------------------------------------------------------------------------------------- //------------------------------ Instructions de compilation -------------------------------- // // Avant compilation du code // // 1/ Ouvrez STARTCLN.EXE dans Resource Hacker (version 3.4) // Faites action->Enregistrer toutes les ressources pour récupérer l'icône du programme Start clean // // 2/ Définissez un nouveau projet vide dans dev c++ // File->New Project->Empty Project // // 3/ Définissez un répertoire de travail au lancement de DevCpp.exe (dev c++) // Par exemple C:/Documents and Settings/inconnu/Bureau/Start Clean/ // // 4/ Copiez cette page (CTRL A CTRL C) dans Untitled1 (le fichier ouvert qui est vide) // // 5/ Définissez le fichier de resources // Vous devez définir un fichier de ressources dans lequel vous spécifiez l'icône à utiliser et le chemin du fichier info.htm // Ce fichier s'appelle par défaut Rsrc.rc et vous y accéder dans le menu Project->Edit Resource File de dev c++. // Copiez le contenu suivant en adaptant le chemin (supprimez les // devant 500 et texte pas devant BINAIRE1) // // 500 ICON "C:/Documents and Settings/inconnu/Bureau/Start Clean/icon.ico" // TEXTE BINARY "C:/Documents and Settings/inconnu/Bureau/Start Clean/infos_original.htm" // //BINAIRE1 BINARY "C:/Documents and Settings/inconnu/Bureau/Start Clean/ressource/ressource.exe" // // Faites ensuite une compilation de la ressource en cliquant sur le bouton "Build resource" accessible depuis le menu Projetc->Edit resource file // // 6/ Cocher l'option "Do not create a console" dans Project->Project option // // 7/ Compilez et lancez par CTRL F10 // //------------------------------------------------------------------------------------------- //---------------------------- Compression de l'exécutable compilé -------------------------- // // 1/ Après compilation avec dev c++ vous obtenez Project 1.exe // 2/ Téléchargez UPX (j'utilise la version 0.72) sur http://www.exetools.com/compressors.htm // 3/ Mettez upx.exe dans votre répertoire de travail // 4/ Créez un fichier compress.bat dans votre répertoire de travail (enlevez les //) // // @echo off // del Crack.exe // upx -9 -oCrack.exe "Project 1.exe" // echo Crack pour Start Clean v1.2>file_id.diz // pause // // 5/ Double-cliquez sur compress.bat // 6/ Votre crack est maintenant de taille réduite (vous divisez la taille du crack par 4) // 7/ Vous pouvez le zipper et le diffuser sur internet :) // //------------------------------------------------------------------------------------------- //------------------------------------- Rendu graphique -------------------------------------

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

    //-------------------------------------------------------------------------------------------- //--------------------------------- Commentaires sur le code ---------------------------------- // // C'est le code de ce crack que j'utilise pour mes cracks depuis cloneCD 5.0.2.2 fr. // Il n'y a que la disposition graphique des composants qui change sinon tout le reste est identique // Le code de ce crack est efficace en terme de résultat et de rapidité mais fût assez long à programmer.Ses particularités // // ->Il possède une fonction d'installation de fichiers à partir de ressources (fichiers de la version complète du programme que l'on stocke au préalable dans le crack) // Cette fonction d'installation effectue une opération de création/copie de fichiers et porte sur l'intégralité du fichier. // Cette fonction peut se combiner avec la procédure classique de craquage qui ne porte elle que sur quelques octets // // ->Il possède un contrôle d'erreur assez poussé qui rétabli l'ensemble des fichiers originaux dans le répertoire // en cas d'erreur rencontré pendant le processus de craquage. // // ->L'interface du crack comporte plus de commandes que l'ancienne version (un lien vers le téléchargement du programme // et un bouton caché pour lancer le programme inntallé sur le PC ont été ajoutés). // // // Fonctionnement de la procédure de craquage. // // Quand on appuie sur le bouton crack la fonction ecrire_en_boucle() est invoquée et on va // // -> Effectuer une copie temporaire sous forme de fichier .exe.temp et .bak.temp des fichiers .exe et .bak existant dans le répertoire. C'est la fonction sauver_fichiers() // // -> Appeler la fonction booléenne ecrire(int numero_fichier_a_craquer,int numero_boucle) sur chaque fichier à modifier et identifié par son numéro dans le tableau liste_identifiants_fichiers. // x Cette fonction crée d'abord les chemins d'accès (au fichier à craquer ainsi qu'à sa sauvegarde éventuelle) // x Cette fontion examine ensuite si le mode restauration est activé (checkbox3 à true) // Si C'est le cas la fonction écrase le fichier .exe par le fichier correspondant d'extension .bak // x Sinon on passe en mode craquage // On vérifie si le fichier existe => si le fichier n'existe pas => erreur déclenchée et fin de la focntion ecrire() qui renvoie un message et faux // On vérifie la taille du fichier à partir du tableau taille_somme => si pas bonne => erreur déclenchée et fin de la fonction ecrire() qui renvoie un message et faux // On vérifie l'intégrité du fichier à craquer à partir du tableau taille_somme si la case checkbox2 est cochée => si somme pas bonne => erreur déclenchée et fin de la fonction ecrire() qui renvoie un message et faux // On fait la copie de sauvegarde si la case checkbox1 activée => si erreur de copie => erreur déclenchée et fin de la fonction ecrire() qui renvoie un message et faux // On regarde si le fichier à modifier est une ressource à copier ou bien un craquage classique (test sur le 4ième paramètre du tableau d'information du fichier dans liste_fichiers) // On effectue la copie ou le craquage => si erreur => erreur déclenchée et fin de la fonction ecrire() qui renvoie un message et faux // On renvoie true si aucune erreur apparue sinon false à la fonction appelante ecrire_en_boucle() // // -> Si resultat des différentes écritures pendant le craquage a donné true tout le temps (valeur de la variable etat_ecriture) // x On coche la case nommée restaure // x On remplace le texte du bouton "Crack" par "Restaurer" // x On efface tous les fichiers temporaires crées (fichiers d'extension .temp) // x On affiche dans une boîte à message le message de réussite // // -> Si resultat des différentes écritures pendant le craquage a donné au moins un résultat faux (valeur de la variable etat_ecriture) // x On renomme les fichiers d'extension .exe.temp en .exe et les .bak.temp en .bak // x on efface les fichiers d'extension *.bak crées (il n'ont pas de .bak.temp puisque c'est nous qui l'avons crée sur demande) // x On affiche dans une boîte à message le message d'erreur // // //-------------------------------------------------------------------------------------------- //----------------------------------- Mise à jour du code ------------------------------------ // // // 06/01/2005 Version 1.1 : correction d'un bug dans la fonction calculer_taille_sous_matrice // Le processus de craquage sur les octets définis dans le tableau nommé matrice // s'arrêtait prématurément si un octet à patcher recevait la valeur hexadécimale 00. // // //-------------------------------------------------------------------------------------------- //--------------------------------------- Code source ---------------------------------------- //Inclusion des bibliothèques #include <windows.h> // Accès aux API windows #include <stdio.h> // Pour avoir sprintf : la fonction de traitement sur les chaînes // Déclaration des fonctions (autres que winmain) // Ici c'est inutile car winmain est à la fin du fichier // Quand on entre dedans il connaît les fonctions définies avant puisque le fichier est lu de haut en bas // Variables globales char * nom_application = "Start Clean v 1.2"; char * titre_application = "Start Clean v 1.2"; char cle_registre[] = "Start Clean"; char * date_crack = "12/10/2004"; char adresse_site_editeur[] = "http://www.adresse_editeur_fictive.com"; char adresse_site_telechargement[] = "http://membres.lycos.fr/pifoman/cours_cracking/cours_telechargements.htm"; char adresse_site_cracker[] = "http://www.geocities.com/mitonnes/"; char adresse_mail[] = "pifoman@yahoo.com"; char titre_mail[] = "Remarques sur votre crack "; char corps_mail[] = "Bonjour"; char * fichier_infos = "infos.htm"; char * titre_boite_dialogue = "Sélectionnez le fichier"; char message_reussite[] = "Le programme est maintenant cracké.\nVous pouvez saisir n'importe quel code d'enregistrement."; // Dimensions long largeur_fenetre = 350; long hauteur_fenetre = 245; long ordonnee_checkbox = 120; long ordonnee_edit = 156; long ordonnee_bouton = 180; long hauteur_bouton = 23 ; long largeur_bouton = 100; long abscisse_text = 16 ; long abscisse_static = 85 ; long ordonnee_static1 = 15 ; long ordonnee_static2 = 37 ; long ordonnee_static3 = 59 ; long ordonnee_static4 = 81 ; long hauteur_groupe = 210; long hauteur_static = 17 ; long largeur_static = 245; long hauteur_static4 = 73 ; long largeur_icone = 32 ; long hauteur_icone = 32 ; // Définition de zone rectangulaires RECT cracker = {largeur_fenetre * 4 / 100 ,38,largeur_fenetre * 4 / 100 + 38,50}; //left,top,right,bottom de la zone rectangulaire RECT auteur = {largeur_fenetre * 52 / 100 ,38,largeur_fenetre * 52 / 100 + 38,50}; //left,top,right,bottom de la zone rectangulaire RECT message_date = {largeur_fenetre * 48 / 100 ,62,largeur_fenetre * 48 / 100 + 60,70}; //left,top,right,bottom de la zone rectangulaire RECT icone = {largeur_fenetre * 4 / 100 ,hauteur_fenetre * 28 / 100,largeur_fenetre * 4 / 100+largeur_icone ,hauteur_fenetre * 28 / 100+hauteur_icone}; RECT lancement = {largeur_fenetre * 95 /100 ,ordonnee_edit, largeur_fenetre * 99 /100,ordonnee_edit+18}; //left,top,right,bottom de la zone rectangulaire // Position de la fenêtre affichant le crack long point_gauche_fenetre = (GetSystemMetrics(SM_CXFULLSCREEN)-largeur_fenetre)/2; long point_haut_fenetre = (GetSystemMetrics(SM_CYFULLSCREEN)-hauteur_fenetre)/2; // Etat initial des boutons checkbox bool checkbox1 = true; // Etat coché du bouton checkbox1 (sauve) bool checkbox2 = true; // Etat coché du bouton checkbox2 (Vérifie) bool checkbox3 = false; // Etat décoché du bouton checkbox3 (restaure) //Paramétrages de l'application // Tableau représentatif des fichiers à craquer // et des ressources à copier stockées dans le crack. // Mettre les fichier à craquer avant les ressources à cause de la matrice // 1 ière variable = nom affiché dans le crack // 2 ième variable = nom de l'exécutable à craquer correspondant au nom donné dans la 1 ière variable // 3 ième variable = chemin d'accès relatif du fichier à craquer sans / initial mais un / final depuis le chemin calculé dans la zone edit ; "" => chemin calculé dans la zone edit = chemin de l'exe donnée en 2 ième variable // 4 ième variable = type du fichier : "" = pas une ressource; "r"=ressource // 5 ième variable = filtre utilisé dans la boîte de sélection de fichiers char * liste_fichiers[][5] = { {"Start Clean v1.2","StartCln.exe","","","Start Clean v1.2 (StartCln.exe)\0STARTCLN.EXE\0\0"}, //{"BINAIRE1","Ressource.exe","","r",""}, }; //Liste des modifications offset / valeur pour chaque fichier qui n'est pas une ressource //Chaque sous-liste délimitée par des {} de la liste principale correspond aux modifications à apporter à un seul fichier //Le fichier à modifier est celui indiqué dans liste_fichiers dont le numéro d'ordre correspond à celui de la sous-liste //Attention : si 5 offsets et 5 octets soient 10 valeurs il faudra mettre au moins 11 pour la dimension long matrice[][11] = { { 0x5DE,0x50, 0x5DF,0x90, 0x5E0,0x90, 0x5E1,0x90, 0x5E2,0x90, }, }; //Tableau des tailles et des sommes des fichiers et des resssources //1 ier élément de chaque sous-liste : taille attendue du fichier à craquer ou de la ressource nommé dans liste_fichiers //2 ième élément de chaque sous-liste : somme des octets du fichier à craquer ou taille de la ressource à écrire //3 ième élément de chaque sous-liste : taille de la ressource si le fichier est une ressource long taille_somme[][3] = { {31744,2151373,0}, //{0,0,6144}, }; //Numéro du fichier à craquer int numero_fichier_a_craquer = 0; //Liste des identifiants de fichiers //Taille du tableau = nb identifiants +1 long liste_identifiants_fichiers[2] = {0}; //Liste des états des fichiers bool etat_craquage[]={false}; //Pour le debug bool afficher_somme = 0; // Fin des paramétrages de l'application // Déclarations nécessaires pour le choix de la police HDC hdc; LOGFONT lf; HFONT hfont; HFONT hfontOld; // Type de police choisie char f[32]={"ARIAL"}; // Déclarations des composants pour pouvoir les utiliser dans tout le programme HWND hwnd; /* Pointeur vers notre fenêtre */ HWND hwndEdit1; /* La zone de texte */ HWND hwndButton1; /* 1 ier bouton */ HWND hwndButton2; /* 2 ieme bouton */ HWND hwndButton3; /* 3 ieme bouton */ HWND hwndCheckBox1; /* 1 iere boite à cocher */ HWND hwndCheckBox2; /* 2 ieme boite à cocher */ HWND hwndCheckBox3; /* 3 ieme boite à cocher */ char chemin_registre[100]; char chemin_registre_edit[100]; char chaine[100]; char res[100],res2[100],res3[100]; char premier_fichier[100]; char chemin_nom_fichier [100] =""; char chemin_nom_fichier2[100] =""; char chemin_nom_fichier3[100] =""; char chemin_nom_fichier4[100] =""; char derniere_erreur[200]; // chaine contenant la dernière erreur rencontrée pour l'écriture char * fichier_courant; static HINSTANCE hGlobInstance = NULL; // Pour le traitement des ressources static HGLOBAL hresource; // Pour le traitement des ressources HINSTANCE hThisInstance; /***********************************************************
    * *
    * Les fonctions *
    * *
    ***********************************************************/
    // Fonction qui ouvre le site web donné dans la variable adresse // Retour : aucun void site(char adresse[], bool mon_site) { // Si l'adresse à charger dans le navigateur web est celle de mon site // j'ajoute un paramètre supplémentaire pour signifier à mon site qu'on y vient // suite à un clic sur une zone de mon crack. if (mon_site) {sprintf(adresse,"%s?Crack pour %s",adresse,nom_application);} // J'ouvre l'adresse du site web demandée dans le navigateur web ShellExecute(NULL,"open",adresse,NULL,NULL,SW_SHOWNORMAL); } // Fonction qui envoie un mail au destinataire désigné par adresse_mail // Retour : aucun void mail() { // On ré-initialise la variable chaine en mettant un caractère de fin de chaine // en première position chaine[0]='\0'; // On crée la chaine de connexion pour le logiciel de messagerie // sprintf permet de formater des expressions suivant des commutateurs %s,%ld ... sprintf(chaine, "mailto:%s?subject=%s%s&body=%s",adresse_mail,titre_mail,nom_application,corps_mail); // On lance le logiciel de messagerie ShellExecute(NULL,"open",chaine,NULL,NULL,SW_SHOWNORMAL); } // Fonction qui lance le programme de la zone EDIT // Retour : aucun void lancer_programme() { GetWindowText(hwndEdit1,chaine,100); ShellExecute(NULL,"open",chaine,NULL,NULL,SW_SHOWNORMAL); } // Fonction qui affiche les informations // Retour : aucun void infos() { // On crée et on ouvre le fichier nommé fichier_infos en mode write (écriture) FILE * pFile = fopen(fichier_infos,"w"); if (pFile!=NULL) { // On copie la ressource correspondant au fichier d'informations // dans un fichier d'infos static char *ptexte; // On charge la ressource nommé TEXTE de type BINARY dans la variable pTexte hresource=LoadResource(hGlobInstance,FindResource(hGlobInstance,"TEXTE","BINARY")); ptexte=(char*) LockResource(hresource); // On écrit le contenu de la variable ptexte dans le fichier // représenté par le pointeur pFile fputs(ptexte,pFile); // On ferme le fichier fclose(pFile); // On lance le fichier d'infos dans le navigateur web ShellExecute(NULL,"open",fichier_infos,NULL,NULL,SW_SHOWNORMAL); } else { // Si on n'a pas réussi à créer et ouvrir le fichier d'infos // On affiche le message d'erreur suivant sprintf(chaine, "Impossible de créer le fichier %s dans le répertoire courant.",fichier_infos); MessageBox(hwnd, chaine, nom_application, MB_OK|MB_ICONERROR); } } // Fonction d'extraction du chemin de désinstallation de l'éxécutable à partir de la clé trouvée dans le registre // Retour : chemin de l'éxécutable formaté char * formater_chemin_registre(char * ch) { int k=0; int debut=0,fin=0,guillemet_suivant=1; for (int i=0 ;ch[i] ;i++) { //if (ch[i]==' ') {debut=i+1;} //if ((ch[i]=='"' || ch[i]=='\'') && guillemet_suivant) {debut=i+1;guillemet_suivant=0;} //if (ch[i]=='\\') {fin=i;} //if (ch[i]=='\\' || ch[i]=='/') {fin=i;} fin=i; } for (int i=debut ;i<=fin ;i++) { res[k]=ch[i]; k++; } // On ajoute ce caractère terminal pour pouvoir // ensuite ajouté derrière le nom de l'executable res[k]='\\'; return res; } // Fonction d'extraction du chemin d'accès dans une chaîne // Retour : chemin de l'éxécutable char * extraire(char* ch2) { int i=0,k=0,fin=0; // Boucle qui lit la chaine ch2 pour trouver le caractère \ ou / du chemin d'accès for(i;ch2[i]!='\0' ;i++) { if (ch2[i]=='\\' || ch2[i]=='/') {fin=i;} } // Boucle qui construit le chemin extrait for(i=0;i<=fin ;i++) { res2[k]=ch2[i]; k++; } // On termine la chaine res2 par le caractère de fin de chaine // Cela evite une erreur dans l'extraction du chemin dans la fonction ecrire // si on extrait successivement 2 chemins de longueur différents (le premier plus // long que le deuxième). res2[k]='\0'; return res2; } // Fonction d'extraction du nom de fichier dans une chaîne // Retour : chemin de l'éxécutable char * extraire_nom_fichier(char* ch3) { int i=0,k=0; for(i=strlen(ch3)-2;ch3[i]!='\\' && ch3[i]!='/' ;i--) {;} for(i=i+1;i<=strlen(ch3)-1;i++) { res3[k]=ch3[i]; k++; } // On termine la chaine res2 par le caractère de fin de chaine // Cela evite une erreur dans l'extraction du chemin dans la fonction ecrire // si on extrait successivement 2 chemins de longueur différents (le premier plus // long que le deuxième). res3[k]='\0'; return res3; } // Fonction qui lit le chemin d'accès de l'application exécutable depuis la clé uninstall du registre // Retour : valeur du chemin char * lire_registre() { HKEY hKey; DWORD dwLen = 255; char dwKeyEn[255]={'\0'}; sprintf(chaine,"SOFTWARE\\Microsoft\\windows\\CurrentVersion\\Uninstall\\%s",cle_registre); // On ouvre la clé uninstall du registre RegOpenKey(HKEY_LOCAL_MACHINE,chaine,&hKey); // On récupère dans dwKeyEn la valeur associée au nom InstallLocation RegQueryValueEx(hKey,"InstallLocation", NULL, NULL, (LPBYTE)&dwKeyEn, &dwLen); RegCloseKey(hKey); // On met le chemin trouvé dans chemin_registre sprintf(chemin_registre,"%s", formater_chemin_registre(dwKeyEn)); return chemin_registre; } // Fonction qui dessine des objets sur la fenêtre principale // Retour : aucun void dessiner() { PAINTSTRUCT ps; // Début du dessin hdc = BeginPaint (hwnd, &ps); // Initialisation de la structure LOGFONT ZeroMemory(&lf, sizeof(LOGFONT)); // On met le nom de la police dans la propriété lfFaceName de lf strcpy(lf.lfFaceName,f); lf.lfHeight = 14; // hauteur des caractères lf.lfWeight = 0; // épaisseur des caractères lf.lfItalic = 0; // caractères en italique ou non lf.lfUnderline = 0; // caractères soulignés ou non // Création de la police hfont = CreateFontIndirect(&lf); hfontOld = (HFONT)SelectObject(hdc, hfont); // Arrière plan du dessin où l'on va écrire le texte // de la même couleur que le fond de la fenêtre SetBkMode(hdc,TRANSPARENT); // On écrit en bleu navy SetTextColor(hdc, RGB(0,0,128)); //DrawText(hdc, date_crack, -1, &message_date, DT_SINGLELINE); // On écrit le texte à partir des coordonnées d'un point // strlen calcule la taille d'une chaine TextOut (hdc, abscisse_text,15,"Programme",strlen("Programme")); TextOut (hdc, abscisse_text,38,"Cracker" ,strlen("Cracker")); TextOut (hdc, largeur_fenetre * 10 / 100,ordonnee_checkbox ,"Sauve",strlen("Sauve")); TextOut (hdc, largeur_fenetre * 10 / 100,ordonnee_checkbox+20,"Vérifie",strlen("Vérifie")); TextOut (hdc, largeur_fenetre * 10 / 100,ordonnee_checkbox+40,"Restaure",strlen("Restaure")); // Charger une icône située dans les ressource sous le numéro 500 // Remplacer le mot ICON par BITMAP pour charger une image de type bitmap HICON hBmp = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(500), IMAGE_ICON, largeur_icone, hauteur_icone, 0); DrawState(hdc,NULL,NULL,(LPARAM)hBmp,(WPARAM)NULL,icone.left,icone.top,0,0,DST_ICON); // Fin du dessin EndPaint (hwnd, &ps); } // Fonction qui calcule la somme tous les octets du fichiers // Retour : booléen qui dit si la somme est égale à celle attendue bool verification_integrite_fichier(char * f,int numero_fichier_a_craquer) { // On ouvre le fichier f en mode read binary (lecture binaire) FILE * pFile=fopen(f,"rb"); // On initilaise les variables long i=0; long somme=0; bool resultat=false; long taille = taille_somme[numero_fichier_a_craquer][0]; //On fait la somme des octets du fichier f while(i<taille) { somme+=fgetc(pFile); i++; } fclose(pFile); // Si on a demandé l'affichage de la somme. // On ne l'utilise une seule fois que pour le debugging if(afficher_somme) { chaine[0]='\0'; sprintf(chaine, "Somme octets de %s = %ld.", liste_fichiers[numero_fichier_a_craquer][1] , somme); MessageBox(hwnd, chaine , nom_application ,MB_OK|MB_ICONINFORMATION); } return (somme==taille_somme[numero_fichier_a_craquer][1]); } // Fonction qui calcule la taille d'une sous-matrice du tableau matrice // Retour : nombre d'octets à craquer pour le fichier considéré long calculer_taille_sous_matrice(long sous_matrice[]) { long i=0; //On evite avec i%2==1 l'arrêt prématuré sur un offset recevant la valeur 0x00 //La boucle while parcourt en effet toutes les valeurs du tableau offset/valeur //nommé sous_matrice mais elle peut s'arrêter avant le fin du tableau si un 0 est //trouvé dans les valeurs du tableau. while(sous_matrice[i] || i%2==1) i++; return i; } // Fonction qui écrit dans les fichiers listés dans liste_fichiers. // Retour : booléen. Vrai si aucune erreur rencontrée faux sinon. bool ecrire(int numero_fichier_a_craquer,int numero_boucle) { FILE * pFile; int nb_erreurs=0; GetWindowText(hwndEdit1,chemin_nom_fichier,100); // Si c'est la première boucle on vérifie le nom du fichier donné par la zone EDIT // Sinon on prend sans vérifier le nom du fichier à craquer dans la liste_fichiers if(numero_boucle==1) { fichier_courant = extraire_nom_fichier(chemin_nom_fichier); // On concatène le chemin de la zone edit et le nom de l'exécutable à craquer sprintf(chemin_nom_fichier, "%s%s", extraire(chemin_nom_fichier),fichier_courant); // On sauvegarde le nom du premier fichier ouvert pour le nom à afficher en // cas de restauration sprintf(premier_fichier,"%s",fichier_courant); } else { fichier_courant = liste_fichiers[numero_fichier_a_craquer][1]; // On concatène le chemin de la zone edit,le chemin relatif du fichier et // le nom de l'exécutable à craquer sprintf(chemin_nom_fichier, "%s%s%s", extraire(chemin_nom_fichier),liste_fichiers[numero_fichier_a_craquer][2],fichier_courant); } // Le nom donné au fichier de sauvegarde est d'extension .bak sprintf(chemin_nom_fichier2, "%s.bak", chemin_nom_fichier); // Le nom donné au fichier temporaire .exe est d'extension .temp sprintf(chemin_nom_fichier3, "%s.temp", chemin_nom_fichier); // Si le fichier existe et est en lecture seule // on enlève la protection en écriture if (GetFileAttributes(chemin_nom_fichier) | FILE_ATTRIBUTE_READONLY) { SetFileAttributes(chemin_nom_fichier, FILE_ATTRIBUTE_NORMAL); } // Si la case checkbox 3 est cochée on restaure if (checkbox3) { // On ouvre les fichiers en lecture pour voir s'ils existent FILE * pFile2=fopen(chemin_nom_fichier2,"r"); //Si le fichier .bak existe on effectue la restauration if(pFile2!=NULL) { // On remplace le fichier modifié par sa copie if(!CopyFile(chemin_nom_fichier2,chemin_nom_fichier,0)) { // Si une erreur est constatée sprintf(derniere_erreur,"Impossible de restaurer le fichier %s depuis %s.bak",fichier_courant); nb_erreurs++; } // On ferme le fichier ouvert avec succès fclose(pFile2); } else { // On lève une exception nb_erreurs++; // On empile l'erreur rencontrée sprintf(derniere_erreur, "Le fichier %s.bak à restaurer est introuvable !",fichier_courant); } } else { // Sinon on craque le fichier ou on copie la ressource pFile = fopen(chemin_nom_fichier,"rb+"); // Si l'ouverture en mode lecture binaire a réussi if (pFile!=NULL) { // Obtenir la taille du fichier. fseek (pFile , 0 , SEEK_END); long Taille_reelle_fichier = ftell (pFile); rewind (pFile); if (Taille_reelle_fichier==taille_somme[numero_fichier_a_craquer][0]) { bool erreur_somme=false; // Si la case est cochée on vérifie l'intégrité des fichiers if(checkbox2) { if (!verification_integrite_fichier(chemin_nom_fichier,numero_fichier_a_craquer)) { nb_erreurs++; // Si le fichier a déja été modifié ou cracké on arrête le craquage. sprintf(derniere_erreur, "Le fichier %s est déja cracké ou a été modifié !",fichier_courant); erreur_somme=true; } } // On écrit dans le fichier // -> si on ne demande pas de vérification d'intégité du fichier // -> s'il n'y a pas d'erreurs une fois la vérification de la somme du fichier effectuée // -> si c'est une ressource du crack if(!checkbox2 || (checkbox2 && erreur_somme==0) || liste_fichiers[numero_fichier_a_craquer][3]=="r") { // On fait une copie de sauvegarde du fichier // si le bouton checkbox1 est coché if(checkbox1) { if(!CopyFile(chemin_nom_fichier,chemin_nom_fichier2,0)) { sprintf(derniere_erreur, "Impossible de créer une copie de sauvegarde du fichier %s.",fichier_courant); nb_erreurs++; } } if(nb_erreurs==0) { //si c'est un fichier à craquer if(liste_fichiers[numero_fichier_a_craquer][3]=="") { for(int j=0;j<calculer_taille_sous_matrice(matrice[numero_fichier_a_craquer]);j=j+2) { //on se positionne sur l'offset à modifier du fichier à craquer fseek(pFile,matrice[numero_fichier_a_craquer][j],SEEK_SET); // On écrit le nouvel octet à l'offset fputc(matrice[numero_fichier_a_craquer][j+1],pFile); } // On ferme le fichier ouvert avec succès en mode écriture fclose(pFile); // Si aucune erreur d'écriture if(nb_erreurs==0) { etat_craquage[numero_fichier_a_craquer]=true; } } else { // Alors c'est une ressource à copier // On ferme le fichier ouvert avec succès en mode lecture fclose(pFile); if(nb_erreurs==0) { // On ouvre en mode écriture un nouveau fichier // dans lequel on va mettre tout le contenu de la ressource pFile = fopen(chemin_nom_fichier,"wb"); // Si l'ouverture en mode lecture binaire a réussi if (pFile!=NULL) { // On charge la ressource type binaire à partir de la liste des fichiers hresource=LoadResource(hGlobInstance,FindResource(hGlobInstance,liste_fichiers[numero_fichier_a_craquer][0],"BINARY")); char * contenu_ressource =(char*) LockResource(hresource); // On copie le contenu de la ressource dans le fichier crée fwrite(contenu_ressource,1,taille_somme[numero_fichier_a_craquer][2],pFile); // On ferme le fichier ouvert avec succès en mode écriture fclose(pFile); } else { // Si l'ouverture en écriture a échoué on lève une exception nb_erreurs++; // On empile l'erreur dans la pile nommée derniere_erreur sprintf(derniere_erreur, "Impossible d'écrire dans la ressource %s.",fichier_courant); } } //Fin du nb_erreurs=0 } //Fin des ressources à copier } //Fin du nb_erreurs=0 } //Fin du test sur checkbox2 //On ferme le fichier ouvert avec succès car il n'est pas fermé fclose(pFile); } // Fin du test sur les tailles else { //Si la taille n'est pas bonne nb_erreurs++; // On empile l'erreur dans la pile nommée derniere_erreur sprintf(derniere_erreur, "La taille du fichier est incorrecte.\n%s aurait dû faire %ld octets.\nCe n'est probablement pas la bonne version.",fichier_courant,taille_somme[numero_fichier_a_craquer][0]); //On ferme le fichier ouvert avec succès car il n'est pas fermé fclose(pFile); } } //Fin du test sur l'ouverture de fichier else { // Si une erreur apparaît à l'ouverture du fichier nb_erreurs++; if(GetFileAttributes(extraire(chemin_nom_fichier)) != FILE_ATTRIBUTE_DIRECTORY) { // Si le chemin d'accès du fichier est introuvable on renvoie une erreur sprintf(derniere_erreur, "Le chemin d'accès spécifié est introuvable.");} else { FILE * pFile = fopen(chemin_nom_fichier,"r"); // On teste l'existence du fichier chemin_nom_fichier if(pFile==NULL) { // Le chemin existe mais pas le répertoire sprintf(derniere_erreur, "Le fichier %s n'existe pas dans le répertoire spécifié.",fichier_courant); } else { // Sinon le fichier est déja utilisé auquel cas il est inaccessible. On renvoie à ce moment une erreur sprintf(derniere_erreur, "Le fichier %s est inaccessible\nIl est déja ouvert par un autre processus.",fichier_courant); // On ferme le fichier ouvert avec succes en mode lecture fclose(pFile); } } } } // Fin du test sur la restauration // On retourne l'état de l'écriture if(nb_erreurs==0) {return true;} else {return false;} } void effacer(long identifiants[],long nombre_id,bool erreurs) { GetWindowText(hwndEdit1,chemin_nom_fichier,100); for(int i=0;i<nombre_id;i++) { fichier_courant = liste_fichiers[identifiants[i]][1]; // On concatène le chemin de la zone edit,le chemin relatif du fichier et // le nom de l'exécutable à craquer sprintf(chemin_nom_fichier, "%s%s%s", extraire(chemin_nom_fichier),liste_fichiers[liste_identifiants_fichiers[i]][2],fichier_courant); // Le nom donné au fichier de sauvegarde est d'extension .bak sprintf(chemin_nom_fichier2, "%s.bak", chemin_nom_fichier); // Le nom donné au fichier temporaire est d'extension .temp sprintf(chemin_nom_fichier3, "%s.temp", chemin_nom_fichier); // Le nom donné au fichier temporaire .bak est d'extension .temp sprintf(chemin_nom_fichier4, "%s.temp", chemin_nom_fichier2); if(erreurs) { // On restitue le fichiers .exe original CopyFile(chemin_nom_fichier3,chemin_nom_fichier,0); // On restitue le fichiers .bak original CopyFile(chemin_nom_fichier4,chemin_nom_fichier2,0); //Si un fichier .bak existait avant la sauvegarde un .bak.temp a été crée //Donc on n'efface pas la sauvegarde qui existait avant FILE * pFile = fopen(chemin_nom_fichier4,"r"); if(pFile!=NULL) {fclose(pFile);} else {DeleteFile(chemin_nom_fichier2);} } //Si restauration activée // ->pas d'erreur on efface le .bak // ->erreur on ne touche pas au .bak if(checkbox3 && !erreurs) DeleteFile(chemin_nom_fichier2); DeleteFile(chemin_nom_fichier3); DeleteFile(chemin_nom_fichier4); } } // Effectue une copie préventive des fichiers .exe et .bak éventuels sous forme de fichiers // exe.temp et .bak.temp indiqués dans la liste des identifiants de fichiers liste_identifiants_fichiers. // Cela permet de rétablir facilement tous les fichiers originaux en cas d'erreur. // Retour rien (void = rien) void sauver_fichiers() { int i=0; GetWindowText(hwndEdit1,chemin_nom_fichier,100); // On sauve les fichiers identifiés dans liste_identifiants_fichiers // avec leur .bak éventuel. // En C tout ce qui différent de 0 est true => pb si indentifiant i vaut 0 => test du while lourd à écrire while(liste_identifiants_fichiers[i] || (liste_identifiants_fichiers[i]==0 && i==0)) { fichier_courant = liste_fichiers[liste_identifiants_fichiers[i]][1]; // On concatène le chemin de la zone edit,le chemin relatif du fichier et // le nom de l'exécutable à craquer sprintf(chemin_nom_fichier, "%s%s%s", extraire(chemin_nom_fichier),liste_fichiers[liste_identifiants_fichiers[i]][2],fichier_courant); // On enlève l'attribut en lecture seule sur le fichiers // car au moment de l'effacement le fichier .temp correspondant // qui aura été crée ne pourra être effacé if (GetFileAttributes(chemin_nom_fichier) | FILE_ATTRIBUTE_READONLY) { SetFileAttributes(chemin_nom_fichier, FILE_ATTRIBUTE_NORMAL); } // Le nom donné au fichier de sauvegarde est d'extension .bak sprintf(chemin_nom_fichier2, "%s.bak", chemin_nom_fichier); // Le nom donné au fichier temporaire est d'extension .temp sprintf(chemin_nom_fichier3, "%s.temp", chemin_nom_fichier); // Le nom donné au fichier temporaire .bak déja existant sera d'extension .temp sprintf(chemin_nom_fichier4, "%s.temp", chemin_nom_fichier2); // On fait une copie temporaire du fichiers .exe. // 0 dans CopyFile pour l'overwrite si le fichier .temp existe déja (normalement il n'y en a pas => mais prudence oblige) CopyFile(chemin_nom_fichier,chemin_nom_fichier3,0); // On fait une copie temporaire du fichiers .bak CopyFile(chemin_nom_fichier2,chemin_nom_fichier4,0); i++; } } // On écrit tous les fichiers et les ressources inscrits sous forme de numéro dans // liste_identifiants_fichiers en appelant la fonction ecrire pour chaque fichier à traiter // Retour : rien void ecrire_en_boucle() { bool etat_ecriture=true; int numero_boucle=1; int k=0; bool etat_checkbox3=checkbox3; //On sauve tous les fichiers avant d'effectuer les opérations d'écriture ou d'effacement sauver_fichiers(); //On balaie la liste des identifiants de fichiers //Attention : si un identifiant vaut 0 on sort de la boucle => il faut éviter ce pb par ce test dans le while. while(liste_identifiants_fichiers[k] || (liste_identifiants_fichiers[k]==0 && k==0)) { etat_ecriture = etat_ecriture & ecrire(liste_identifiants_fichiers[k],numero_boucle); numero_boucle++; k++; } // Si aucune erreur constatée sur tous les fichiers écrits if(etat_ecriture) { if(!checkbox3) { // Si on n'est pas en mode restauration // on affiche le message correspondant sprintf(chaine,message_reussite); //Si une copie de sauvegarde a été demandée on active la case checkbox3 //pour la restauration if(checkbox1) { SetWindowText(hwndButton1,"Restaurer"); } } else { // Si on était dans l'état restauration on affiche le message correspondant // et on repasse dans l'état initial if(k>1) { sprintf(chaine, "Tous les fichiers ont été restaurés. "); } else { sprintf(chaine, "Le fichier %s a été restauré. ",premier_fichier); } // On change le nom du bouton et on coche ou non la case checkbox3 SetWindowText(hwndButton1,"Crack"); } //On efface tous les fichiers temporaires crées effacer(liste_identifiants_fichiers,numero_boucle-1,false); //On modifie l'état coché ou non de checkbox3 si la sauvegarde a été demandée //Ne pas permuter avec ligne du dessus sinon pb sur état checkbox3 dans effacer if(checkbox1) { if(!checkbox3) {checkbox3=true;} else {checkbox3=false;} SendMessage(hwndCheckBox3, BM_SETCHECK, checkbox3, 0); } MessageBox(hwnd,chaine,nom_application,MB_OK|MB_ICONINFORMATION); } else { //On renomme tous les fichiers .temp crées en .exe //On efface tous les fichiers .bak crées effacer(liste_identifiants_fichiers,numero_boucle-1,true); //On renvoie la dernière erreur MessageBox(hwnd,derniere_erreur,nom_application,MB_OK|MB_ICONERROR); } } // Fonction qui ouvre la boite de dialogue de sélection de fichiers // Retour : rien char * selection_fichier(long numero_fichier) { OPENFILENAME ofn; char szFileName[MAX_PATH]; ZeroMemory(&ofn, sizeof(ofn)); szFileName[0] = '\0'; ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = hwnd; ofn.lpstrFilter = liste_fichiers[numero_fichier][4]; ofn.lpstrTitle = titre_boite_dialogue; // szFileName = le fichier avec son chemin qui a été sélectionné // dans la boite de dialogue ofn.lpstrFile = szFileName; // Si on n'a pas trouvé le chemin d'insatllation dans le registre // On positionne la boîte de sélection de fichiers sur C:\Program Files if(chemin_registre[0]=='\0') {ofn.lpstrInitialDir = "C:\\Program Files";} else {ofn.lpstrInitialDir = chemin_registre;} ofn.nMaxFile = MAX_PATH; ofn.lpstrDefExt = "exe"; ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; //Ouverture de la boîte de dialogue avec les paramètres précédents GetOpenFileName(&ofn); //Chemin + nom du fichier sélectionné sprintf(chaine, "%s", szFileName); // Si aucun fichier n'a été sélectionné on restitue la chaîne qui est dans le edit if (chaine[0]=='\0') GetWindowText(hwndEdit1,chaine,200); return chaine; } // Fonction qui désactive hwndButton1 quand la zone de texte hwndEdit1 est vide // ou qui le réactive sinon // Retour : aucun void controler_contenu_edit() { //Si le contenu de la zone edit est vide on désactive hwndButton3 GetWindowText(hwndEdit1,chaine,100); bool bouton_actif; if (chaine[0]=='\0') {bouton_actif=false;} else {bouton_actif=true;} EnableWindow(hwndButton1,bouton_actif); } // Fonction qui retourne le contenu de la zone edit // Retour : contenu de la zone edit char * contenu_edit() { GetWindowText(hwndEdit1,chaine,100); return chaine; } // Fonction qui change le chemin et de l'exe dans la zone edit quand on clique sur un bouton radio // Retour : aucun void changer_edit(char * chaine_chemin,char * nom_de_l_exe) { //On met le chemin suivi du nom de l'exe dans la zone edit sprintf(chaine,"%s\%s", chaine_chemin,nom_de_l_exe); SetWindowText(hwndEdit1,chaine); } /***********************************************************
    * *
    * Composants minimum de l'application graphique *
    * *
    ***********************************************************/
    // Cette fonction est appelée par la fonction windows DispatchMessage( ) LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdcStatic = (HDC)wParam; HDC hdc = (HDC)wParam; HBRUSH g_hbrBackground; switch (message) /* pointeur des messages */ { // Evénement déclenché lors du coloriage des composants statics du groupe case WM_CTLCOLORSTATIC: { // On fixe la couleur de fond des composants de type static g_hbrBackground = CreateSolidBrush(RGB(0xEC,0xE9,0xD8)); // On fixe la couleur du texte du premier composant static if (GetWindowLong((HWND)lParam, GWL_ID) == 11) { // On écrit en marron SetTextColor(hdcStatic, RGB(128,00,00)); } else { // On fixe la couleur du texte du deuxième composant static
    if (GetWindowLong((HWND)lParam, GWL_ID) == 12) { // On met la police en gras lf.lfWeight = FW_BOLD; // Création de la police hfont = CreateFontIndirect(&lf); hfontOld = (HFONT)SelectObject(hdc, hfont); // On écrit en vert SetTextColor(hdcStatic, RGB(0,128,128)); } else { // Sinon les autres composants static seront de couleur navy
    SetTextColor(hdcStatic, RGB(0,0,128)); } } SetBkMode(hdcStatic, TRANSPARENT); return (LONG)g_hbrBackground; } break; // Evénement déclenché lors du coloriage de la zone edit case WM_CTLCOLOREDIT: // On fixe la couleur de fond du composant EDIT g_hbrBackground = CreateSolidBrush(RGB(0xEE,0xEE,0xEE)); // On écrit en violet
    SetTextColor(hdc, RGB(128,0,128)); // L'arrière plan du rectangle dans lequel on écrit sera de la même // couleur que le fond SetBkMode(hdc, TRANSPARENT); return (LONG)g_hbrBackground; break; // Evénement déclenché quand la fenêtre doit être redessinée case WM_PAINT: dessiner(); break; // Evénement déclenché lorsque la souris bouge sur le fenêtre case WM_MOUSEMOVE: // Déclaration d'un point static POINT MousePos; // Récupération de la position du pointeur de la souris GetCursorPos(&MousePos); // Transformation des coordonnées absolues de la souris en coordonnées relatives à la fenêtre ScreenToClient(hwnd,&MousePos); // Si l'utilisateur passe au dessus d'une des zones "icone","cracker","auteur","message_date" on change le curseur par une main if( MousePos.x>=icone.left && MousePos.x<=icone.right && MousePos.y<=icone.bottom && MousePos.y>=icone.top || MousePos.x>=cracker.left && MousePos.x<=cracker.right && MousePos.y<=cracker.bottom && MousePos.y>=cracker.top || MousePos.x>=auteur.left && MousePos.x<=auteur.right && MousePos.y<=auteur.bottom && MousePos.y>=auteur.top || MousePos.x>=message_date.left && MousePos.x<=message_date.right && MousePos.y<=message_date.bottom && MousePos.y>=message_date.top || MousePos.x>=lancement.left && MousePos.x<=lancement.right && MousePos.y<=lancement.bottom && MousePos.y>=lancement.top ) { // Le curseur en forme de main est identifié par windows comme ayant le numéro 32649 const long HandCursor = 32649; // On charge le curseur HCURSOR hCursor = SetCursor(LoadCursor(hThisInstance,MAKEINTRESOURCE(HandCursor))); // On affiche le curseur en forme de main ShowCursor(TRUE); } break; // Evénement déclenché lorsque le bouton gauche de la souris est pressé case WM_LBUTTONDOWN: // Si l'utilisateur clique sur l'icône if(MousePos.x>=icone.left && MousePos.x<=icone.right && MousePos.y<=icone.bottom && MousePos.y>=icone.top) {site(adresse_site_editeur,false);} // Si l'utilisateur clique sur le nom du cracker qui correspond au rectangle "auteur" if(MousePos.x>=auteur.left && MousePos.x<=auteur.right && MousePos.y<=auteur.bottom && MousePos.y>=auteur.top) {site(adresse_site_cracker,true);} // Si l'utilisateur clique sur l'auteur du crack qui correspond à la zone nommée cracker if(MousePos.x>=cracker.left && MousePos.x<=cracker.right && MousePos.y<=cracker.bottom && MousePos.y>=cracker.top) {mail();} // Si l'utilisateur clique sur la zone message_date if(MousePos.x>=message_date.left && MousePos.x<=message_date.right && MousePos.y<=message_date.bottom && MousePos.y>=message_date.top) {site(adresse_site_telechargement,false);} // Si l'utilisateur clique sur la zone de lancement if(MousePos.x>=lancement.left && MousePos.x<=lancement.right && MousePos.y<=lancement.bottom && MousePos.y>=lancement.top) {lancer_programme();} break; // Evénement déclenché lorsque l'on ferme la fenêtre case WM_DESTROY: // Fermeture de la fenêtre DeleteFile(fichier_infos); // On efface le fichier d'infos PostQuitMessage(0); // On envoie un message WM_QUIT à la pile break; // Evénement déclenché lorsqu'une commande est envoyée case WM_COMMAND: // si l'utilisateur fait un clic sur le bouton checkbox1 on transforme en coché on non l'état du bouton. if ((LOWORD(wParam) == 31)) { if(SendMessage(hwndCheckBox1,BM_GETCHECK,checkbox1,0)==BST_CHECKED) {checkbox1=true;} else {checkbox1=false;} } // si l'utilisateur fait un clic sur le bouton checkbox2 on transforme en coché on non l'état du bouton. if ((LOWORD(wParam) == 32)) { if(SendMessage(hwndCheckBox2,BM_GETCHECK,checkbox2,0)==BST_CHECKED) {checkbox2=true;} else {checkbox2=false;} } // si l'utilisateur fait un clic sur le bouton checkbox3 on transforme en coché on non l'état du bouton. if ((LOWORD(wParam) == 33)) { if(SendMessage(hwndCheckBox3,BM_GETCHECK,checkbox3,0)==BST_CHECKED) {checkbox3=true;} else {checkbox3=false;} } // Clic sur le edit if (LOWORD(wParam) == 15) {controler_contenu_edit();} //Clic sur le bouton crack if ((LOWORD(wParam) == 21) && (HIWORD(wParam) == BN_CLICKED)) {ecrire_en_boucle();} //Clic sur le bouton infos if ((LOWORD(wParam) == 22) && (HIWORD(wParam) == BN_CLICKED)) {infos();} //Clic sur le bouton chercher if ((LOWORD(wParam) == 23) && (HIWORD(wParam) == BN_CLICKED)) {SetWindowText(hwndEdit1,selection_fichier(numero_fichier_a_craquer));controler_contenu_edit();} //Clic sur le bouton checkbox3 if ((LOWORD(wParam) == 33) && (HIWORD(wParam) == BN_CLICKED)&& checkbox3) {SetWindowText(hwndButton1,"Restaurer");} if ((LOWORD(wParam) == 33) && (HIWORD(wParam) == BN_CLICKED)&& !checkbox3) {SetWindowText(hwndButton1,"Crack");} break; // Evénement déclenché lorsqu'une touche du clavier est pressée case WM_KEYDOWN: switch (wParam) { case VK_ESCAPE: // Si la touche échappe est pressée DeleteFile(fichier_infos); // On efface le fichier d'infos PostQuitMessage(0); // On ferme le programme } break; // Pour les messages qu'on ne traite pas default: return DefWindowProc(hwnd, message, wParam, lParam); } return 0; } //Point d'entrée du programme (équivaut à la procédure main en C) int WINAPI WinMain(HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nFunsterStil) { MSG messages; /* Variable contenant les messages de l'application */ WNDCLASSEX wincl; /* Structure de données pour windowclass */ /* Structure de la fenêtre */ wincl.hInstance = hThisInstance; wincl.lpszClassName = "WindowsApp"; wincl.lpfnWndProc = WindowProcedure; /* Cette fonction est appelée par windows */ wincl.style = CS_HREDRAW|CS_VREDRAW; /* Réagir au demande de redessinage */ wincl.cbSize = sizeof(WNDCLASSEX); /* Utilisons l'icône par défaut et la flèche de la souris */ wincl.hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(500)); wincl.hIconSm = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(500), IMAGE_ICON, 16, 16, 0); wincl.hCursor = LoadCursor(NULL, IDC_ARROW); wincl.lpszMenuName = NULL; /* Pas de menu */ wincl.cbClsExtra = 0; /* Pas d'octets supplémentaires après la classe window */ wincl.cbWndExtra = 0; /* structure or the window instance */ // Fixons la couleur de fond de la fenêtre wincl.hbrBackground = CreateSolidBrush(RGB(0xCC, 0xCC, 0xCC)); // 0xEC,0xE9,0xD8 par défaut // Enregistrons la classe window, si échec quitons le programme if(!RegisterClassEx(&wincl)) return 0; // Initialisation de la structure LOGFONT ZeroMemory(&lf, sizeof(LOGFONT)); strcpy(lf.lfFaceName,f); lf.lfHeight = 14; // Taille des caractères dans la police choisie lf.lfWeight = 0; // Epaisseur de la police (Gras ou non) lf.lfItalic = 0; // Police en italic lf.lfEscapement = 0; // Espacement des caractères dans la police
    lf.lfQuality = PROOF_QUALITY; // Qualité de la police // Création de la fonte hfont = CreateFontIndirect(&lf); hfontOld = (HFONT)SelectObject(hdc, hfont); // La classe est enregistrée , créons le programme // Créons la fenêtre principale hwnd = CreateWindowEx( 0, /* Extended possibilites for variation */ "WindowsApp", /* Nom de la classe */ nom_application, /* Titre de la fenêtre */ WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, /* Style de la fenêtre */ point_gauche_fenetre, // coordonnée gauche du coin supérieur gauche de la fenêtre point_haut_fenetre, // coordonnée du haut du coin supérieur gauche de la fenêtre //CW_USEDEFAULT, /* Ou bien Windows decide automatiquement du coin supérieur gauche de la fenêtre */ //CW_USEDEFAULT, /* Ou bien Windows decide automatiquement du coin supérieur haut de la fenêtre */ largeur_fenetre, /* Largeur de la fenêtre en pixels */ hauteur_fenetre, /* hautre de la fenêtre en pixels */ HWND_DESKTOP, /* La fenêtre est fille du bureau */ NULL, /* Pas de menu */ hThisInstance, /* Pointeur de l'instance du programme */ NULL /* Pas de données pour la création de la fenêtre */ ); // Définition du cadre qui enveloppe toutes les zone de la fenêtre // C'est en fait un bouton transparent dont on ne voit que le contour (propriété BS_GROUPBOX) HWND hwndCadre1 = CreateWindow( "BUTTON", //Type BUTTON du composant à créer "", //Texte écrit sur le bouton (dans le cas des GROUPBOX c'est juste au dessus du cadre à gauche sinon c'est sur le bouton directement) WS_CHILD|WS_VISIBLE|BS_GROUPBOX, //Styles associés au bouton largeur_fenetre* 2 / 100 , 0,largeur_fenetre * 95 / 100,hauteur_groupe, //Coordonnées du point supérieur gauche et du point inférieur droit qui délimitent le bouton (abscisse,ordonnée,abscisse,ordonnée) hwnd, //Le bouton fait partie de la fenêtre repérée par le pointeur hwnd (WiNDow Handle) (HMENU)10, //10 est l'identifiant que nous allons donner à ce composant hThisInstance, /* Pointeur de l'instance du programme */ NULL); /* Pas de données pour la création de la fenêtre */ // On envoie un message au dispatcher : la fonction DispatchMessage() pour spécifier la police d'écriture qu'on va utiliser dans le composant
    SendMessage(hwndCadre1,WM_SETFONT,(WPARAM)CreateFontIndirect (&lf),MAKELPARAM (TRUE,0)); // On crée une zone static d'identifiant 11 HWND hwndStatic1 = CreateWindow ( "STATIC", titre_application, WS_CHILD | WS_VISIBLE | SS_CENTER | SS_SUNKEN, // SS_SUNKEN pour avoir un static de forme enfoncée dans la fenêtre abscisse_static , ordonnee_static1 , largeur_static, hauteur_static, hwnd, (HMENU) 11, hThisInstance, NULL ); // On envoie un message au dispatcher : la fonction DispatchMessage() pour spécifier la police d'écriture qu'on va utiliser dans le composant SendMessage(hwndStatic1,WM_SETFONT,(WPARAM)CreateFontIndirect (&lf),MAKELPARAM (TRUE,0)); // On crée une zone static d'identifiant 12 HWND hwndStatic2 = CreateWindow ( "STATIC", "Pifoman", WS_CHILD | WS_VISIBLE | SS_CENTER | SS_SUNKEN, abscisse_static , ordonnee_static2 , largeur_static, hauteur_static, hwnd, (HMENU) 12, hThisInstance, NULL ); // On envoie un message au dispatcher : la fonction DispatchMessage() pour spécifier la police d'écriture qu'on va utiliser dans le composant SendMessage(hwndStatic2,WM_SETFONT,(WPARAM)CreateFontIndirect (&lf),MAKELPARAM (TRUE,0)); // On crée une zone static d'identifiant 13 qui contient la date du crack HWND hwndStatic3 = CreateWindow ( "STATIC", date_crack, WS_CHILD | WS_VISIBLE | SS_CENTER | SS_SUNKEN, abscisse_static , ordonnee_static3 , largeur_static, hauteur_static, hwnd, (HMENU) 13, hThisInstance, NULL ); // On envoie un message au dispatcher : la fonction DispatchMessage() pour spécifier la police d'écriture qu'on va utiliser dans le composant SendMessage(hwndStatic3,WM_SETFONT,(WPARAM)CreateFontIndirect (&lf),MAKELPARAM (TRUE,0)); // On crée une zone static d'identifiant 14 qui contiendra les commentaires HWND hwndStatic4 = CreateWindow ( "STATIC", /* Type de composant à créer */ "\r\nCommentaires", WS_CHILD | WS_VISIBLE | SS_CENTER | SS_SUNKEN, abscisse_static , ordonnee_static4, largeur_static, hauteur_static4, /* Dimensions du composant */ hwnd, /* Le parent de ce composant est cette fenêtre */ (HMENU) 14, /* Identifiant de contrôle = 14 */ hThisInstance, NULL ); // On envoie un message au dispatcher : la fonction DispatchMessage() pour spécifier la police d'écriture qu'on va utiliser dans le composant SendMessage(hwndStatic4,WM_SETFONT,(WPARAM)CreateFontIndirect (&lf),MAKELPARAM (TRUE,0)); // On cherche le chemin d'accès par défaut de l'application dans le registre avec lire_registre(). // Et on l'écrit dans la zone d'édition le rséultat de cette interrogation. // On lit la valeur de la clé uninstall du registre sprintf(chemin_registre,"%s",lire_registre()); // Si on a trouvé le chemin dans le registre (ie chaine non nulle) on ajoute le nom de l'exe au chemin trouvé dans le registre avec la fonction sprintf if(chemin_registre[0]!='\0') sprintf(chemin_registre_edit,"%s%s",chemin_registre,liste_fichiers[numero_fichier_a_craquer][1]); // On crée une zone d'édition de texte d'identifiant 15 qui contiendra le chemin d'accès de l'exécutable à craquer hwndEdit1 = CreateWindow ( "EDIT", chemin_registre_edit, WS_CHILD | WS_VISIBLE | ES_LEFT | ES_AUTOHSCROLL, abscisse_static+1 , ordonnee_edit, largeur_static-1, hauteur_static, hwnd, (HMENU) 15, hThisInstance, NULL ); // On envoie un message au dispatcher : la fonction DispatchMessage() pour spécifier la police d'écriture qu'on va utiliser dans le composant SendMessage(hwndEdit1,WM_SETFONT,(WPARAM)CreateFontIndirect (&lf),MAKELPARAM (TRUE,0)); // Limiter le texte tapé à 100 caractères dans la zone edit SendMessage(hwndEdit1, EM_LIMITTEXT,100, true) ; // On crée le bouton Crack avec l'identifiant 21 hwndButton1 = CreateWindow ( "button", "Crack", WS_CHILD | WS_VISIBLE | BS_FLAT, largeur_fenetre* 5 / 100 , ordonnee_bouton , largeur_bouton , hauteur_bouton, hwnd, (HMENU) 21, hThisInstance, NULL ); // On envoie un message au dispatcher : la fonction DispatchMessage() pour spécifier la police d'écriture qu'on va utiliser dans le composant SendMessage(hwndButton1,WM_SETFONT,(WPARAM)CreateFontIndirect (&lf),MAKELPARAM (TRUE,0)); // On crée le bouton infos avec l'identifiant 22 hwndButton2 = CreateWindow ( "button", "Infos", WS_CHILD | WS_VISIBLE | BS_FLAT, 124, ordonnee_bouton, largeur_bouton, hauteur_bouton, hwnd, (HMENU) 22, hThisInstance, NULL ); // On envoie un message au dispatcher : la fonction DispatchMessage() pour spécifier la police d'écriture qu'on va utiliser dans le composant SendMessage(hwndButton2,WM_SETFONT,(WPARAM)CreateFontIndirect (&lf),MAKELPARAM (TRUE,0)); // On crée le bouton chercher avec l'identifiant 23 hwndButton3 = CreateWindow ( "Button", "Chercher", WS_CHILD | WS_VISIBLE | BS_FLAT, largeur_fenetre* 66 / 100 , ordonnee_bouton, largeur_bouton, hauteur_bouton, hwnd, (HMENU) 23, hThisInstance, NULL ); // On envoie un message au dispatcher : la fonction DispatchMessage() pour spécifier la police d'écriture qu'on va utiliser dans le composant SendMessage(hwndButton3,WM_SETFONT,(WPARAM)CreateFontIndirect (&lf),MAKELPARAM (TRUE,0)); // On crée une case à cocher avec l'identifiant 31 hwndCheckBox1 = CreateWindow ( "button", "", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, largeur_fenetre* 5 / 100 , 120, 13, 13, hwnd, (HMENU) 31, hThisInstance, NULL ); // On envoie un message pour spécifier l'état coché ou non du bouton
    SendMessage(hwndCheckBox1, BM_SETCHECK, checkbox1, 0); // On crée une case à cocher avec l'identifiant 32 hwndCheckBox2 = CreateWindow ( "button", "", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, largeur_fenetre* 5 / 100 , 140, 13, 13, hwnd, (HMENU) 32, hThisInstance, NULL ); // On envoie un message pour spécifier l'état coché ou non du bouton SendMessage(hwndCheckBox2, BM_SETCHECK, checkbox2, 0); // On crée une case à cocher avec l'identifiant 33 hwndCheckBox3 = CreateWindow ( "button", "", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, largeur_fenetre* 5 / 100 , 160, 13, 13, hwnd, (HMENU) 33, hThisInstance, NULL ); // On envoie un message pour spécifier l'état coché ou non du bouton SendMessage(hwndCheckBox3, BM_SETCHECK, checkbox3, 0); //Si la zone edit est vide on désactive le bouton crack controler_contenu_edit(); // Rendre la fenêtre visible à l'écran ShowWindow(hwnd, nFunsterStil); // Démarrer la boucle de messages // Elle fonctionnera jusqu'à ce que GetMessage( ) retourne 0 while(GetMessage(&messages, NULL, 0, 0)) { // Traduire les messages clé virtuel en messages avec caractères TranslateMessage(&messages); // Envoyer les messages à la fonction WindowProcedure DispatchMessage(&messages); } // Le programme retourne la valeur 0 // C'est la valeur donnée par la fonction PostQuitMessage( ) return messages.wParam; } //--------------------------------------------------------------------------------------------


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


    votre commentaire