Cours n°5 - Olly fait des siennes

 

        Ce dont vous avez besoin : Olly, PEID, LordPE et le crackme  : KeygenMe1

       

Analyse du logiciel

    PEID trouve enfin que le programme a été écrit avec MASM.

 

Observation

    Deux zones de textes, une pour notre nom, une pour notre serial. Si on entre rien => rien! Si on entre un nom et un serial bidon => "Sérial invalide".

 

Analyse du code

    En regardant un peu le code, on voit rapidement une API intéressante : GetDlgITemText qui renvient deux fois (tiens, on a justement 2 zones de texte).

   Juste en dessous, il y a "Serial invalide", "Vous avez réussi" et "Bravo" suivis d'une MessageBox. Il y a de grandes chances que notre routine de serial soit là-bas!

    On va donc poser un BP en 4010E3 (CMP eax,3). On lance le programme (F9), on entre notre nom et un serial> "Vérifier !"> ça breake.

   En passant les APIs GetDlgItemText avec F8, nous voyons que l'on récupère d'abord notre nom puis le serial.

    Regardez un peu le code et réfléchissez :

   Commençons du bas, avec la message box en 40115E.

   Les adresses de Text et Title sont respectivement enregistrées en 403147 et 403143. Juste au dessus, en fonction de eax (en vert), on va mettre dans ces adresses soit un pointeur vers le bon message soit vers le mauvais. Le CALL juste au dessus du OR EAX,EAX (en 40111A) est très certainement un test de validité du serial entré!

  Pour plus de lisibilité, prenez l'habitude d'ajouter des labels sur les CALLs. Cliquez sur la ligne du CALL pour la sélectionner puis faites ENTER. Vous vous retrouvez sur la première ligne de la fonction. Là, appuyez sur ' : ' pour entrer un label (c'est une description, ça ne change rien au code). Tapez un nom du style "TestSiBonSerial".

  On regardant le paramètre Buffer de la 2eme GetDlgItemText, en rouge, nous voyons que le serial entré est enregistré en 403044 (passez avec F8 au dessus). C'est justement un des 2 paramètres de la fonction présumée de test du serial. Le 2eme paramètre(403028, en bleu) est celui qui est utilisé par la fonction en 4010F2 et dont l'autre paramètre est le nom entré.

 Entrez également un label pour cette fonction (401021 qui est appelée en 4010F2). Par exemple "GenSerial" (vous verrez ci-dessous pourquoi)

   Résumons : Nous avons une fonction juste après la récupération du nom, elle prend deux paramètres : l'adresse où est enregistré notre nom et une autre (en bleu). Ce deuxième paramètre(en bleu) est utilisé juste après la récupération du serial entré par une fonction qui retourne une valeur dans EAX qui permet de savoir si le serial est valide ou pas.

  Il se pourrait bien que ce 2eme paramètre soit le serial qui correspond à notre nom. 

  Pour vérifier celà, on va avancer en pas à pas(F8) jusqu'à la ligne 401110, lorsqu'on y est, on recopie ce qui se trouve en 403028 (arrêtez vous au 0 de fin de chaîne) (Pour recopier, regardez dans la fenêtre DUMP en bas à gauche, faites un clic droit>Goto>Expression>403028). Pour "Crisanar", le serial que je trouve est : c1x+-c"--6-l"c1

  On fait un petit essai : on vire les BP, F9, on entre le même nom et le serial qu'on vient de trouver et ... ça marche! La message box affiche le bon message!

  Bizarre quand même, c'était plus facile qu'aux cours précédents!

  Ben oui, vous vous en doutez, ce n'est pas fini! Lancez le programme normalement (pas dans Olly quoi) rentrez votre nom et votre serial => "Serial invalide". Zut! Mais si vous le relancez dans Olly, le serial est toujours valide! Ça ne vous rappelle rien?

  Regardez un peu les APIs utilisées (les JUMPs DWORD en fin de programme), on y retrouve une à qui on a déjà eu à faire "IsDebuggerPresent".

  Mais on a beau fouiller dans le code, on ne la voit nulle part! 

  L'appel à cet API (le JUMP DWORD tout à la fin du programme en fait) se fait en 40116C. En fait j'ai utilisé une petite astuce pour cacher l'appel de cette fonction : je n'ai pas fait un CALL IsDebuggerPresent directement mais un CALL [addresse_de_la_fonction]. Olly ne reconnaît plus la fonction de cette façon. Pour trouver où l'appel se fait, soit on  cherche ce nombre dans le code (un truc du style [0040116C] ), soit on sélectionne la ligne du JUMP DWORD de la fonction, on ajoute un label (' : ') et on ecrit le nom de la fonction comme label. Quelque soit la méthode que vous choisirez, vous devriez trouver IsDebuggerPresent en 40106A.

  Vous voyez qu'il n'y a pas de CALL. En fait c'est un CALL détourné. Pour comprendre ce qui se passe ici, il faut savoir ce que fait un CALL: Il place dans la pile l'adresse où il doit retourner après l'exécution du CALL et ensuite il saute à l'adresse indiquée par le CALL.

  Un RET prend l'adresse qui se trouve au sommet de la pile et saute à cette adresse.

  Voici le bout de code commenté :

  On pousse l'adresse de retour qui est la ligne en dessous du RET puis on pousse l'adresse qui se trouve en 403039 (l'adresse de IsDebuggerPresent que l'on voit grâce au label que l'on a ajouté) et le RET appelle IsDebuggerPresent.

  Ces 3 lignes équivalent à un CALL IsDebuggerPresent mais cette façon d'écrire le CALL dupe Olly et "cache" à l'utilisateur l'appel à la fonction.

  Si vous vous rappelez, cette fonction retourne 1 dans EAX si le programme tourne sous un environnement de débuggeur et 0 dans le cas contraire.

  Or il y a un XOR AL,1.

A B XOR
0 1 1
1 1 0

  Ce XOR inverse donc la valeur de AL. Si AL vaut 1, il devient 0 et inversement.

  On soustrait ensuite à la variable en 40103D la valeur de AL.

  Rien ne vous étonne dans cette valeur? Et oui, c'est du code et non une variable (les variables commencent ici en 403000). Allons regarder ce qui se trouve en 40103D : CMP ECX,4. On va donc retirer ou non 1 à 4. Et ce CMP, où se trouve-t-il? Dans une fonction qui commence en 401021. Justement dans la fonction que nous avions vu tantôt et à laquelle nous avons ajouté un label : "GenSerial"!

  Voici donc la solution à notre problème : lorsque nous sommes dans un environnement de débuggeur, la fonction qui génère le serial est différente.

 

        Création du keygen

  Je considère que vous êtes assez grand pour analyser la routine de création du Serial tout seul et je vais faire le paresseux, c'est à dire que l'on va créer un keygen à partir du programme lui-même.

  Une méthode assez courante est d'afficher une MessageBox avec le bon serial. Cette méthode est assez simple du fait que l'API MessageBox est souvent incluse dans la plupart des programmes mais présente le gros inconvénient de ne pas pouvoir recopier le serial affiché directement.

  Il faudrait donc pouvoir faire comme dans le cours 4, c'est-à-dire afficher le bon serial directement dans la zone de texte, mais pour cela il nous faut une API (SetDlgItemText) que nous n'avons pas ici (SendMessage,...,WM_SETTEXT devrait fonctionner aussi mais le problème reste le même vu que nous n'avons pas de SendMessage non plus).

  Pour ajouter une API, quitter KeygenMe1(même dans Olly) lancez LordPE, cliquez sur PE Editor>Choisissez le fichier>Directories>Le bouton "..." de la ligne ImportTable. Dans la fenetre qui s'est ouverte, faites un clic droit sur une des DLL affichée>add import...

  En regardant dans le WIN32SDK à SetDlgItemText sur Quick info, nous voyons que cette fonction se trouve dans user32.dll.

  Tapez donc user32.dll dans la zone pour la dll et et SetDlgItemTextA dans la zone pour le nom de l'API (attention, sans le 'A' à la fin, vous risquez d'avoir des problèmes). Appuyez ensuite sur le bouton ' + ' > OK. Cliquez sur la dll que vous venez d'ajouter et dans la ligne de la nouvelle API, retenez le nombre sous ThunkRVA(moi j'ai 501D). Fermez la fenêtre > Save > OK > Save > OK et fermez LordPE.

  L'API maintenant rajoutée il nous suffit de l'utiliser. Relancez Olly et ré-ouvrez KeygenMe1.

  Sachez que vous ne pouvez pas utiliser directement l'API que vous venez d'ajouter en l'appelant par son nom, Olly ne posera pas de problème pour le faire mais votre exécutable ne sera plus portable. Si vous l'exécutez sur une autre machine, il plantera.

  Pour pouvoir appeler correctement votre API, voici comment faire : vous ajoutez l'ImageBase(habituellement 40000) à la valeur que je vous ai dit de retenir ce qui donne 40501D. Attention ceci ne fonctionne que si vous avez rajouté une API. Pour les APIs déjà existantes il faut faire ImageBase + FirstThunk + (ThunkRVA - FirstThunk). Pour une API ajoutée avec LordPE, FirstThunk = ThunkRVA donc c'est moins compliqué.

  Vu qu'ici nous ne comptons pas utiliser l'API plus d'une fois il nous suffira de faire un CALL [40501D] pour appeler notre API, sinon il est plus "propre" d'ajouter un JMP DWORD sous les autres JMP DWORD des APIs(à la fin du programme) et d'appeler ce JMP plutôt que l'API directement. Si on avait fait comme cela, pour plus de facilité placez un label sur le JMP DWORD de la nouvelle API et lorsque vous voudrez l'utiliser, faite un CALL Votre_Label. Ici nous choisirons la simplicité et nous nous contenterons d'un simple CALL.

  Avant d'écrire notre nouvelle fonction, regardons tout d'abord quand nous voulons exécuter notre fonction. Pour moi après l'affichage de la message box disant que le serial est invalide est un bon endroit.

  Il faudra écrire notre fonction quelque part. Il y a de la place en 4011CC, nous pouvons le faire là-bas. Pour arriver à cet endroit, il va falloir écrire un JMP 4011CC. Si nous l'écrivons en 401123, nous allons écraser les données qui s'y trouvent. Il faut donc les recopier quelque part. Une fois que cela est fait, écrivez donc JMP 4011CC en 401123.

 En 4011CC, recopiez donc le MOV DWORD PTR [403147],403000 que vous venez d'écraser.

 Ensuite il faut utiliser l'API SetDlgItemText que l'on vient d'ajouter.

  Les paramètres à passer à cette fonction sont :

  1) Le hWnd de la fenêtre : il se trouve comme d'habitude en [EBP+8], si vous n'êtes pas sûr, vérifiez lors de l'appel du fonction quelconque dans le programme.

  2) L'ID de la zone de texte : on le connaît grâce à la 2eme GetDlgItemText, c'est 68h (Vous pouvez vérifier avec ResHack)

  3) L'adresse de la chaîne de texte à écrire : lors de notre analyse tantôt, nous avions remarqué que le bon serial était enregistré en 403028.

  Plaçons nous à la fin du programme, là où il y a de la place (en 4011CC par exemple) et écrivons tout cela.

  Rappelez-vous qu'il faut pousser les paramètres dans l'ordre inverse :

  PUSH 403028  ; L'adresse de la chaîne de caractère

  PUSH 68h         ; ID de la zone de texte

  PUSH DWORD PTR [EBP+8]  ; hWnd de la fenêtre

  CALL [40501D]    ; SetDlgItemTextA

  Après avoir défini le bon texte, nous allons retourner comme si rien ne s'était passé en 40112D, au 2eme MOV => JMP 40112D.

  En faisant F9, on peut constater que ça fonctionne. On entre notre nom, ça affiche serial invalide puis ça entre le bon serial. Le problème c'est que ce serial est masqué par des *.

  Sauver les modifications et fermez le programme.

  Pour enlever les étoiles, prenez ResHack et ouvrez KeygenMe1 (la version que vous venez d'enregistrer).

  Dialog>100>1033, une fenêtre s'ouvre cliquez sur la zone de texte où l'on entre le serial. Une petite * s'affiche sur la ligne de code correspondant à la zone de texte.

  Vous remarquerez le "| ES_PASSWORD" qui défini que la zone de texte doit s'afficher avec des étoiles. Supprimer donc cela>Compile Script , File>Save.

  Voilà, vous avez un beau keygen ;)

  Pour vous exercer, essayez de rajouter un label avec votre pseudo, de changer le texte du bouton en "Générer !", le titre de la fenêtre en "Keygen" et de ne plus afficher de message box mais directement le bon serial.

  Voici un exemple :

 

 

Bon crack,                  

Crisanar         

[ << Cours n°4 ] [ Cours n°6 >> ]

 

Dernière mise à jour le 21/10/2004 20:47