Tutorial crackme1 [HolyView] - par [HolyView] - UCT
Ce tutorial a été créé dans le but de mieux appréhender les méthodes d'analyse de code ASM généré par Delphi. La taille des fichiers étant souvent supérieure à 400ko il n'est pas facile de s'y retrouver ! Ce tutorial présente quelques approches possibles face à un soft programmé sous Delphi.
Outils utilisés :

- Editeur de ressource : eXescope 6.41
- Désassembleur/débuggeur : OllyDbg 1.10
- Editeur héxadécimal : Hex Workshop 4.0
- Désassembleur Delphi : DéDé 3.50.02
- PEiD 0.92 pour connaître le compilateur ou si l'exe est packé/crypté
- Un cerveau avec des vrais neurones qui vont bien dedans :)

Le crackme :

Voila la liste des tâches à faire :

- Supprimer les informations de démonstration (messages, titre ...) à l'aide d'un éditeur de ressources
- Supprimer les informations de démonstration (messages, titre ...) à l'aide de OllyDbg + éditeur héxa
- Faire de cette version une version enregistrable
- S'enregistrer en utilisant n'importe quel nom+sérial
- S'enregistrer en faisant un keygen

0) Première approche

Bon on commence à exécuter le crackme, on a un message de démonstration au démarrage et à la fermeture de l'application. Dans l'intitulé de la fenêtre on voit qu'il s'agit d'une version de démonstration. Si on parcourt le menu, le menu Enregistrer le logiciel ... est grisé. Une fois dégrisé, il doit être possible d'enregistrer le crackme via une boite de dialogue. C'est à peu près tout ...






Afin de voir avec quel compilateur a été utilisé on prends PEiD. Celui-ci nous indique : Borland Delphi 6.0 - 7.0. Il s'agit en effet de Delphi 7.0. Lorsque l'on a affaire à du Delphi le plus simple est souvent d'utiliser DéDé qui est spécialisé dans l'analyse de code Delphi.

1) Supprimer les informations de démonstration à l'aide d'un éditeur de ressources

L'éditeur utilisé ici est eXescope, il en existe beaucoup d'autres, plus ou moins bien. On ouvre le crackme avec eXescope. On va dans Resource / RCData / TFORM1, c'est là que se situent les informations concernant la fenêtre principale.

Nous allons modifier 3 choses :
- le titre de la fenêtre
- suppression du message au démarrage
- suppression du message à la fermeture

La propriété caption indique le titre de la fenêtre. On modifie donc la ligne : Caption = 'Crackme UCT - Delphi power - Version d'#233'mo' en Caption = 'Crackme UCT - Delphi power :)'

La propriété OnCreate correspond à la fonction exécutée au démarrage. Dans la ligne OnCreate = FormCreate la fonction exécutée s'appelle FormCreate(). On supprime donc cette ligne afin que le message n'apparaisse pas. Ici cela ne pose pas de problèmes car la fonction ne fait qu'afficher le message au démarrage ...

La fonction OnCloseQuery correspond à la fonction exécutée juste avant la fermeture d'un programme. On supprime également la ligne OnCloseQuery = FormCloseQuery

On sauve ce qu'on vient de modifier en cliquant sur Save Update. Fermez eXescope et lancer le crackme : Il n'a a plus de messages de démonstration et l'intitulé de la fenêtre a changé !. Et hop voila une bonne chose de faite ... ;-)

2) Supprimer les informations de démonstration à l'aide de OllyDbg + éditeur héxa

Je vais aborder dans ce passage plusieurs manières d'arriver à ses fins, plus ou moins efficaces .... :)

Première approche : SDR - 1
On ouvre le crackme avec OllyDbg puis on va chercher toutes les chaînes de caractères présentes dans le programme. Pour cela il suffit de faire un clique droit dans la fenêtre de désassemblage puis Search for / All referenced text strings. On va rechercher dans la liste les chaînes qui nous intéressent : clique droit, Search for text. Au démarrage la boite de dialogue contient le message suivant : Crackme UCT - [HolyView] , Version non enregistrable ... , Merci d'acquérir la version enregistrable :-)On cherche donc la chaîne acquérir. Bizarrement, celle ci ne semble pas exister. Dans notre cas, il s'agit d'un petit problème d'analyse d'OllyDbg. On va donc chercher directement dans le code désassemblé. Clique droit, Search for / Binary string (Ctrl B). On tape acquérir en prenant soin de décocher Case sensitive et en cochant Entire scope. On a un seul résulat en 004555AB.
0045556C 43 DB 43 ; CHAR 'C' 0045556D 72 DB 72 ; CHAR 'r' 0045556E 61 DB 61 ; CHAR 'a' 0045556F 63 DB 63 ; CHAR 'c' 00455570 6B DB 6B ; CHAR 'k' 00455571 6D DB 6D ; CHAR 'm' 00455572 65 DB 65 ; CHAR 'e' 00455573 . 20 DB 20 ; CHAR ' ' 00455574 . 55 43 54 20 2D 20 >ASCII "UCT - [HolyView]" 00455584 . 0D 56 65 72 73 69 >ASCII ".Version non enr" 00455594 65 DB 65 ; CHAR 'e' 00455595 67 DB 67 ; CHAR 'g' 00455596 69 DB 69 ; CHAR 'i' 00455597 73 DB 73 ; CHAR 's' 00455598 74 DB 74 ; CHAR 't' 00455599 72 DB 72 ; CHAR 'r' 0045559A 61 DB 61 ; CHAR 'a' 0045559B 62 DB 62 ; CHAR 'b' 0045559C 6C DB 6C ; CHAR 'l' 0045559D 65 DB 65 ; CHAR 'e' 0045559E 20 DB 20 ; CHAR ' ' 0045559F 2E DB 2E ; CHAR '.' 004555A0 2E DB 2E ; CHAR '.' 004555A1 2E DB 2E ; CHAR '.' 004555A2 0D DB 0D 004555A3 4D DB 4D ; CHAR 'M' 004555A4 65 DB 65 ; CHAR 'e' 004555A5 72 DB 72 ; CHAR 'r' 004555A6 63 DB 63 ; CHAR 'c' 004555A7 69 DB 69 ; CHAR 'i' 004555A8 20 DB 20 ; CHAR ' ' 004555A9 64 DB 64 ; CHAR 'd' 004555AA 27 DB 27 ; CHAR ''' 004555AB 61 DB 61 ; CHAR 'a' 004555AC 63 DB 63 ; CHAR 'c' 004555AD 71 DB 71 ; CHAR 'q' 004555AE 75 DB 75 ; CHAR 'u' 004555AF E9 DB E9 004555B0 . 72 69 72 20 6C 61 >ASCII "rir la version e" 004555C0 . 6E 72 65 67 69 73 >ASCII "nregistrable :-)" 004555D0 . 00 ASCII 0
Le message commence en 0045556C. Pour l'affichage de ce message, il faut bien que le programme pointe vers son adresse. On va donc rechercher la constante 0045556C dans le listing. Clique droit, Search for / Constant . On a une occurence juste au dessus en 00455558. (On peut aussi cliquer sur la ligne à l'@ 0045556C. Clique droit, Find references to / Selected address (Ctrl-R).
00455558 . B8 6C554500 MOV EAX,modif2.0045556C ; @ du message 0045555D . E8 2221FDFF CALL modif2.00427684 ; Affichage du message (fonction ShowMessage) 00455562 . C3 RETN
Il ne reste plus qu'à "nopper" tout ca :). Double clique sur l'instruction dans le listing, puis on tape nop afin que les 2 instructions soient remplacées par des nop.
00455558 90 NOP ; @ du message 00455559 90 NOP 0045555A 90 NOP 0045555B 90 NOP 0045555C 90 NOP 0045555D 90 NOP ; Affichage du message (ShowMessage) 0045555E 90 NOP 0045555F 90 NOP 00455560 90 NOP 00455561 90 NOP 00455562 . C3 RETN
Il est enfin l'heure de voir ce que tout cela donne. On exécute le programme (F9). Cool ! plus de vilain message :). On réinitialise le programme (Ctrl F2) puis on "noppe" comme au dessus.
Il ne reste qu'à enregistrer les modifications. Clique droit, Copy to executable / All modifications / Copy all ( / Oui). Clique droit dans la nouvelle fenêtre, Save file / Enregistrer / Oui.
Le fichier a été modifié, il est désormais possible de le lancer depuis le disque.

Il ne reste qu'à faire de même pour le message lorsque l'on ferme le programme :
004554B8 . B8 CC544500 MOV EAX,_origina.004554CC ; @ du message - nopper 004554BD . E8 C221FDFF CALL _origina.00427684 ; Affichage msg (ShowMessage) - nopper 004554C2 . C3 RETN 004554C3 00 DB 00 004554C4 FF DB FF 004554C5 FF DB FF 004554C6 FF DB FF 004554C7 FF DB FF 004554C8 37 DB 37 ; CHAR '7' 004554C9 00 DB 00 004554CA 00 DB 00 004554CB 00 DB 00 004554CC 56 DB 56 ; CHAR 'V' 004554CD 65 DB 65 ; CHAR 'e' 004554CE 72 DB 72 ; CHAR 'r' 004554CF 73 DB 73 ; CHAR 's' 004554D0 69 DB 69 ; CHAR 'i' 004554D1 6F DB 6F ; CHAR 'o' 004554D2 6E DB 6E ; CHAR 'n' 004554D3 20 DB 20 ; CHAR ' ' 004554D4 64 DB 64 ; CHAR 'd' 004554D5 65 DB 65 ; CHAR 'e' 004554D6 20 DB 20 ; CHAR ' ' 004554D7 64 DB 64 ; CHAR 'd' 004554D8 E9 DB E9 004554D9 . 6D 6F 6E 73 74 72 6>ASCII "monstration, Mer" 004554E9 . 63 69 20 64 65 20 7>ASCII "ci de vous enreg" 004554F9 . 69 73 74 72 65 72 2>ASCII "istrer ...",0

Seconde approche : SDR - 2
Dans cette seconde étape nous allons rechercher la procédure lancée au démarrage de l'application et celle lancée à sa fermeture. Nous avons vu dans l'éditeur de ressource les lignes suivantes : OnCreate = FormCreate et OnCloseQuery = FormCloseQuery. Il nous faut maintenant localiser ces 2 fonctions dans le listing ASM.
Il suffit de faire une simple recherche dans les SDR avec FormCreate et FormCloseQuery . On trouve les occurences suivantes :
00455443 00 DB 00 00455444 . 58554500 DD modif2.00455558 ; l'@ de la procedure -> $455558 00455448 . 0A DB 0A 00455449 . 46 6F 72 6D 43 72 65>ASCII "FormCreate" ; Procedure lancee au demarrage 00455453 11 DB 11
004553F8 00 DB 00 004553F9 . B8544500 DD modif2_2.004554B8 ; l'@ de la procedure -> $4554B8 004553FD . 0E DB 0E 004553FE . 46 6F 72 6D 43 6C 6F>ASCII "FormCloseQuery" ; Procedure lancee quand on quitte 0045540C 14 DB 14
L'@ de chaque procédure est en fait "en dur" dans l'exécutable, placée juste avant le nom de la procédure. Nous avons ici les @ 455558 et 4554B8 . Un coup d'oeil à ces @ nous ramène à ce que nous avons vu précedemment :
00455558 . B8 6C554500 MOV EAX,modif2.0045556C ; @ du message - nopper 0045555D . E8 2221FDFF CALL modif2.00427684 ; Affichage msg (fonction ShowMessage) - nopper 00455562 . C3 RETN
004554B8 . B8 CC544500 MOV EAX,_origina.004554CC ; @ du message - nopper 004554BD . E8 C221FDFF CALL _origina.00427684 ; Affichage msg (ShowMessage) - nopper 004554C2 . C3 RETN

Troisième approche : APIs Delphi
Le but est ici de poser un breakpoint sur la fonction de Delphi qui permet l'affichage d'un message. En effet Delphi utilise ses propres APIs afin de simplifier la programmation pour le codeur. La principale fonction permettant l'affichage d'un message est ShowMessage() . Etudions cette fonction :
Voici l'affichage d'un simple message lors du clic sur un bouton en Delphi :
. procedure TForm1.Button1Click(Sender: TObject); . begin . ShowMessage('Bonjour !'); . end;
Et le code ASM de ShowMessage ... (Breapoint puis Fenêtre CPU de Delphi -> Ctrl-Alt-C)
ShowMessage: 004274EC 83C9FF or ecx,-$01 004274EF 83CAFF or edx,-$01 004274F2 E801000000 call ShowMessagePos 004274F7 C3 ret
Sachant que les APIs de Delphi sont forcément inclues dans l'exécutable au moment de la compilation, il devient assez facile de retrouver le code ASM de la fonction voulue, et donc de poser un point d'arrêt.
Revenons à notre crackme :)
Nous allons rechercher dans le listing le code de l'API ShowMessage qui ne changera jamais : ici, les deux or seront toujours présents.
On fait donc une recherche dans le listing ASM : clique droit, Search for / Sequence of commands (Ctrl-S). On inscrit ceci dans la fenêtre (on coche Entire block) :
or ecx,-01 or edx,-01
Une seule occurence est trouvée, il s'agit du code de la fonction ShowMessage().
00427684 /$ 83C9 FF OR ECX,FFFFFFFF 00427687 |. 83CA FF OR EDX,FFFFFFFF 0042768A |. E8 01000000 CALL modif2.00427690 0042768F \. C3 RETN
Le $ devant le code indique le début de la fonction. On clique sur cette ligne. Dans la fenêtre juste en dessous du listing on peut lire :
Local calls from 004550E1, 004550ED, 004550F9, 004554BD, 0045555D
Ce sont les @ qui appellent cette fonction. Si l'on regarde à chacune de ces @, 2 nous intéresse pour l'instant : 004554BD et 0045555D.
On retombe sur ce qu'on a vu précédemment ... ;-)

On vient de voir rapidement l'utilisation des APIs Delphi. Des programmes tels DéDé ou IDA permettent d'afficher les APIs Delphi dans leur code désassemblé. Le principe est similaire, c'est à dire qu'ils utilisent des signatures qui existent pour chaque fonction. La technologie utilisée s'appelle d'ailleurs FLIRT. DéDé 3.50.02 permet d'afficher les signatures de chaque fonction (Options / Symboles / DSF / Load / VCL7.dsf / View functions [ / Filter / Showmessage] )
Je recommande très vivement d'aller lire les essais de DaFixer (DéDé) qui sont très riches d'enseignement.
PS : il me semble qu'il existe un plug-in pour ajouter ces signatures à OllyDbg mais je n'ai jamais essayé ... 8)

On va maintenant modifier rapidement l'intitulé du crackme afin que ce soit tout beau :). Pour cela on utilise notre éditeur héxa préféré, pour moi ce sera : Hex Workshop. On charge le crackme et on cherche la chaîne de texte Version démo. Celle-ci ne devrait pas être trouvée car les caractères spéciaux (é) sont ... spéciaux justement :)
On recherche alors la chaîne de texte Version d. On obtiens plusieurs occurences mais une seule nous intéresse, celle en $00065F0E. On va modifier le texte tout en gardant le même nombre de caractères ! Par exemple on passe de ça :
00065EF1 4372 6163 6B6D 6520 5543 5420 2D20 4465 6C70 6869 2070 6F77 6572 202D Crackme UCT - Delphi power - 00065F0D 2056 6572 7369 6F6E 2064 C3A9 6D6F 0C43 6C69 656E 7448 6569 6768 7403 Version d..mo.ClientHeight.
A ça :
00065EF1 4372 6163 6B6D 6520 5543 5420 2D20 4465 6C70 6869 2070 6F77 6572 202D Crackme UCT - Delphi power - 00065F0D 2048 656C 6C6F 2021 2100 0000 0000 0C43 6C69 656E 7448 6569 6768 7403 Hello !!......ClientHeight.
Il ne reste plus qu'à enregistrer et c'est bon !
On a fini cette deuxième partie ... chouette ! :)

3) Faire de cette version une version enregistrable

Il va maintenant falloir réactiver le menu Enregistrer le logiciel ... qui est grisé. Pour cela nous n'allons n'utiliser qu'OllyDbg, bien qu'ici il soit possible d'utiliser un éditeur de ressource ou même DéDé :).

Chargeons donc notre programme avec OllyDbg. Une des fonctions API permettant d'activer / désactiver un menu est EnableMenuItem. Une petite recherche dans les noms de fonctions (Ctrl N) nous donne ceci :

00459620 .idata Import user32.EnableMenuItem
Clique droit / Set breapoint on every reference
Si l'on exécute (F9), le programme breake plusieurs fois mais le code n'est pas très intéressant ... Enfin si l'on clique sur le menu Aide, rien ne se passe :(
Pourquoi que ca marche pas heinn ????

Regardons les points d'arrêts que nous avons mis :

Breakpoints Address Module Active Disassembly Comment 004064D0 modif3 Always JMP DWORD PTR DS:[<&user32.EnableMenuItem>] Inutile 00445B45 modif3 Always CALL <JMP.&user32.EnableMenuItem> Interessant :) 0044F135 modif3 Always CALL <JMP.&user32.EnableMenuItem> Pas terrible 0044F151 modif3 Always CALL <JMP.&user32.EnableMenuItem> Pas terrible
Allons à l'@ $00445B45. Vu l'aspect du code il semblerait que l'API EnableMenuItem soit englobée dans une fonction de la VCL de Delphi (comme souvent). Si l'on remonte au début de la fonction, à l'@ 445AE0, on peut voir qu'elle est appelée à 4 adresses. Pour ne pas s'embéter à aller voir à toutes ces @, on place un point d'arrêt (F2) à l'@ 445AE0. On clique sur le menu Aide dans le crackme, et là OllyDbg breake :). On trace (F8) jusqu'à atteindre l'instruction RETN. On remarque au passage que ca jumpe rapidement vers la fin de la sous-fonction. On arrive ici :
004555D4 33D2 XOR EDX,EDX ; Si edx=0 alors menu desactive 004555D6 . 8B80 10030000 MOV EAX,DWORD PTR DS:[EAX+310] ; @ du menu dans eax 004555DC . E8 FF04FFFF CALL modif3.00445AE0 ; Fonction TMenuItem.SetEnabled() de la VCL 004555E1 . C3 RETN
Si l'on regarde un peu le code au-dessus, celui en-dessous, et que l'on remarque qu'on est en bas du listing, alors on peut facilement en déduire que l'on est dans une des fonctions de clic sur un bouton, démarrage de l'application .... Ici, on est dans la fonction qui s'exécute lorsque l'on clique sur le menu Aide
PS : pour ceux qui ne comprennent rien :) , regardez avec DéDé dans l'onglet Procedures ...
Le code démarre donc en $004555D4. On en déduit que le clique sur le menu Aide ne fait que désactiver un des éléments (en l'occurrence le premier).

Revenons un instant sur le fait que l'API EnableMenuItem n'était pas exécuté alors que la fonction a pour but de le faire.
Delphi vérifie l'état d'un menu avant de le cocher, de l'activer, de le désactiver ... Si cet état n'a pas changé alors il est inutile de perdre du temps à faire l'opération, vu que le résultat sera le même. Voila ! Cela peut juste poser quelques problème lors du debuggage quand on ne connait pas trop bien la facon de procéder de Delphi. ;)

La question du jour : Comment allons-nous procéder pour inverser l'état, c'est à dire passer de inactif à actif ....
En effet il faut modifier l'instruction en $004555D4 de façon à avoir edx à 1. La seule solution qui permet de ne pas supprimer d'informations est de faire un mov dl, 01 qui tient sur 2 octets. Cette solution n'est pas très propre mais bon ...

004555D4 B2 01 MOV DL,1 ; Si edx=1 alors menu active 004555D6 . 8B80 10030000 MOV EAX,DWORD PTR DS:[EAX+310] ; @ du menu dans eax 004555DC . E8 FF04FFFF CALL modif3.00445AE0 ; Fonction TMenuItem.SetEnabled() de la VCL 004555E1 . C3 RETN

On lance le prog. Cool le menu est activé !
Il ne reste qu'à enregistrer les modifications (après avoir fait un Ctrl F2) comme précisé plus haut et on passe à la suite :)

4) S'enregistrer en utilisant n'importe quel nom+sérial

Si l'on teste l'enregistrement du programme avec nom + pass bidons, on a le droit à un joli message : C'est faux !. On va essayer de trouver à quel endroit s'affiche ce message. 2 possibilités pour trouver : SDR et APIs Delphi (voir précédemment). Dans tous les cas on arrive en 00455010. Il s'agit de la procédure de vérification du sérial.

00455010 /. 55 PUSH EBP 00455011 |. 8BEC MOV EBP,ESP 00455013 |. 6A 00 PUSH 0 00455015 |. 6A 00 PUSH 0 00455017 |. 6A 00 PUSH 0 00455019 |. 53 PUSH EBX 0045501A |. 56 PUSH ESI 0045501B |. 8BD8 MOV EBX,EAX 0045501D |. 33C0 XOR EAX,EAX 0045501F |. 55 PUSH EBP 00455020 |. 68 19514500 PUSH _origina.00455119 00455025 |. 64:FF30 PUSH DWORD PTR FS:[EAX] 00455028 |. 64:8920 MOV DWORD PTR FS:[EAX],ESP 0045502B |. 8D55 FC LEA EDX,DWORD PTR SS:[EBP-4] 0045502E |. 8B83 FC020000 MOV EAX,DWORD PTR DS:[EBX+2FC] 00455034 |. E8 B7F0FDFF CALL _origina.004340F0 00455039 |. 8D55 F8 LEA EDX,DWORD PTR SS:[EBP-8] 0045503C |. 8B83 00030000 MOV EAX,DWORD PTR DS:[EBX+300] 00455042 |. E8 A9F0FDFF CALL _origina.004340F0 00455047 |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4] 0045504A |. E8 11F2FAFF CALL _origina.00404260 0045504F |. 83F8 04 CMP EAX,4 00455052 |. 0F8C 9C000000 JL _origina.004550F4 ; vers bad boy 00455058 |. 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8] 0045505B |. E8 00F2FAFF CALL _origina.00404260 00455060 |. 83F8 04 CMP EAX,4 00455063 |. 0F8C 8B000000 JL _origina.004550F4 00455069 |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4] 0045506C |. 0FB600 MOVZX EAX,BYTE PTR DS:[EAX] 0045506F |. 8B55 F8 MOV EDX,DWORD PTR SS:[EBP-8] 00455072 |. 0FB652 03 MOVZX EDX,BYTE PTR DS:[EDX+3] 00455076 |. 03C2 ADD EAX,EDX 00455078 |. 8B55 FC MOV EDX,DWORD PTR SS:[EBP-4] 0045507B |. 0FB652 02 MOVZX EDX,BYTE PTR DS:[EDX+2] 0045507F |. 8B4D F8 MOV ECX,DWORD PTR SS:[EBP-8] 00455082 |. 0FB649 01 MOVZX ECX,BYTE PTR DS:[ECX+1] 00455086 |. 0FAFD1 IMUL EDX,ECX 00455089 |. 03C2 ADD EAX,EDX 0045508B |. 8945 F4 MOV DWORD PTR SS:[EBP-C],EAX 0045508E |. 33DB XOR EBX,EBX 00455090 |. 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8] 00455093 |. E8 C8F1FAFF CALL _origina.00404260 00455098 |. 85C0 TEST EAX,EAX 0045509A |. 7E 13 JLE SHORT _origina.004550AF 0045509C |. BA 01000000 MOV EDX,1 004550A1 |> 8B4D F8 /MOV ECX,DWORD PTR SS:[EBP-8] 004550A4 |. 0FB64C11 FF |MOVZX ECX,BYTE PTR DS:[ECX+EDX-1] 004550A9 |. 03D9 |ADD EBX,ECX 004550AB |. 42 |INC EDX 004550AC |. 48 |DEC EAX 004550AD |.^75 F2 \JNZ SHORT _origina.004550A1 004550AF |> BE A9070000 MOV ESI,7A9 004550B4 |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4] 004550B7 |. E8 A4F1FAFF CALL _origina.00404260 004550BC |. 83E8 03 SUB EAX,3 004550BF |. 7C 14 JL SHORT _origina.004550D5 004550C1 |. 40 INC EAX 004550C2 |. BA 03000000 MOV EDX,3 004550C7 |> 8B4D FC /MOV ECX,DWORD PTR SS:[EBP-4] 004550CA |. 0FB64C11 FF |MOVZX ECX,BYTE PTR DS:[ECX+EDX-1] 004550CF |. 03F1 |ADD ESI,ECX 004550D1 |. 42 |INC EDX 004550D2 |. 48 |DEC EAX 004550D3 |.^75 F2 \JNZ SHORT _origina.004550C7 004550D5 |> 335D F4 XOR EBX,DWORD PTR SS:[EBP-C] 004550D8 |. 3BF3 CMP ESI,EBX 004550DA |. 75 0C JNZ SHORT _origina.004550E8 ; vers bad boy ... 004550DC |. B8 30514500 MOV EAX,_origina.00455130 ; Good Boy :) 004550E1 |. E8 9E25FDFF CALL _origina.00427684 004550E6 |. EB 16 JMP SHORT _origina.004550FE 004550E8 |> B8 64514500 MOV EAX,_origina.00455164 ; ASCII "C'est faux !" 004550ED |. E8 9225FDFF CALL _origina.00427684 004550F2 |. EB 0A JMP SHORT _origina.004550FE 004550F4 |> B8 64514500 MOV EAX,_origina.00455164 ; ASCII "C'est faux !" 004550F9 |. E8 8625FDFF CALL _origina.00427684 004550FE |> 33C0 XOR EAX,EAX 00455100 |. 5A POP EDX 00455101 |. 59 POP ECX 00455102 |. 59 POP ECX 00455103 |. 64:8910 MOV DWORD PTR FS:[EAX],EDX 00455106 |. 68 20514500 PUSH _origina.00455120 0045510B |> 8D45 F8 LEA EAX,DWORD PTR SS:[EBP-8] 0045510E |. BA 02000000 MOV EDX,2 00455113 |. E8 ACEEFAFF CALL _origina.00403FC4 00455118 \. C3 RETN
Il nous faut donc aller en 004550DC. Pour cela la facon la plus simple est de modifier le jl en 00455052 par un jmp vers la bonne adresse.
0045504F |. 83F8 04 CMP EAX,4 00455052 90 NOP 00455053 E9 84000000 JMP modif4.004550DC ; vers good boy :) 00455058 |. 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8]
Et voila on a le droit au message de réussite à chaque fois ... :)
On enregistre et on passe à la partie sérieuse ;)

5) S'enregistrer en faisant un keygen

Bon là c'est un peu plus marrant :)
On va poser un breakpoint (F2) en 00455047 afin de voir en détail ce que fait ce petit crackme ... On rentre par exemple comme nom : uctteam et comme pass : 1337 :). Voila le résultat de l'analyse :

00455047 |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4] ; nom 0045504A |. E8 11F2FAFF CALL modif3.00404260 0045504F |. 83F8 04 CMP EAX,4 ; nb carac du nom = 4 ? 00455052 |. 0F8C 9C000000 JL modif3.004550F4 ; -> bad boy si <4 00455058 |. 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8] ; serial 0045505B |. E8 00F2FAFF CALL modif3.00404260 00455060 |. 83F8 04 CMP EAX,4 ; nb carac du serial = 4 ? 00455063 |. 0F8C 8B000000 JL modif3.004550F4 ; -> bad boy si <4 00455069 |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4] 0045506C |. 0FB600 MOVZX EAX,BYTE PTR DS:[EAX] ; nom[1] 0045506F |. 8B55 F8 MOV EDX,DWORD PTR SS:[EBP-8] 00455072 |. 0FB652 03 MOVZX EDX,BYTE PTR DS:[EDX+3] ; serial[4] 00455076 |. 03C2 ADD EAX,EDX ; eax = nom[1] + serial[4] 00455078 |. 8B55 FC MOV EDX,DWORD PTR SS:[EBP-4] 0045507B |. 0FB652 02 MOVZX EDX,BYTE PTR DS:[EDX+2] ; nom[3] 0045507F |. 8B4D F8 MOV ECX,DWORD PTR SS:[EBP-8] 00455082 |. 0FB649 01 MOVZX ECX,BYTE PTR DS:[ECX+1] ; serial[2] 00455086 |. 0FAFD1 IMUL EDX,ECX ; edx = nom[3] * serial[2] 00455089 |. 03C2 ADD EAX,EDX ; eax = eax + edx 0045508B |. 8945 F4 MOV DWORD PTR SS:[EBP-C],EAX 0045508E |. 33DB XOR EBX,EBX 00455090 |. 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8] 00455093 |. E8 C8F1FAFF CALL modif3.00404260 00455098 |. 85C0 TEST EAX,EAX 0045509A |. 7E 13 JLE SHORT modif3.004550AF 0045509C |. BA 01000000 MOV EDX,1 ; i=1 004550A1 |> 8B4D F8 /MOV ECX,DWORD PTR SS:[EBP-8] 004550A4 |. 0FB64C11 FF |MOVZX ECX,BYTE PTR DS:[ECX+EDX-1] ; serial[i] 004550A9 |. 03D9 |ADD EBX,ECX ; ebx = ebx + serial[i] 004550AB |. 42 |INC EDX 004550AC |. 48 |DEC EAX 004550AD |.^75 F2 \JNZ SHORT modif3.004550A1 004550AF |> BE A9070000 MOV ESI,7A9 ; esi = $7A9 004550B4 |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4] 004550B7 |. E8 A4F1FAFF CALL modif3.00404260 004550BC |. 83E8 03 SUB EAX,3 004550BF |. 7C 14 JL SHORT modif3.004550D5 004550C1 |. 40 INC EAX 004550C2 |. BA 03000000 MOV EDX,3 ; i=3 004550C7 |> 8B4D FC /MOV ECX,DWORD PTR SS:[EBP-4] 004550CA |. 0FB64C11 FF |MOVZX ECX,BYTE PTR DS:[ECX+EDX-1] ; nom[i] 004550CF |. 03F1 |ADD ESI,ECX ; esi = esi + nom[i] 004550D1 |. 42 |INC EDX 004550D2 |. 48 |DEC EAX 004550D3 |.^75 F2 \JNZ SHORT modif3.004550C7 004550D5 |> 335D F4 XOR EBX,DWORD PTR SS:[EBP-C] ; ebx = ebx XOR $17C8 ($17C8 = eax) 004550D8 |. 3BF3 CMP ESI,EBX ; esi = ebx ? 004550DA |. 75 0C JNZ SHORT modif3.004550E8 ; -> si esi != ebx alors bad boy .... 004550DC |. B8 30514500 MOV EAX,modif3.00455130 ; Good boy :) 004550E1 |. E8 9E25FDFF CALL modif3.00427684 004550E6 |. EB 16 JMP SHORT modif3.004550FE 004550E8 |> B8 64514500 MOV EAX,modif3.00455164 ; ASCII "C'est faux !" 004550ED |. E8 9225FDFF CALL modif3.00427684 004550F2 |. EB 0A JMP SHORT modif3.004550FE 004550F4 |> B8 64514500 MOV EAX,modif3.00455164 ; ASCII "C'est faux !" 004550F9 |. E8 8625FDFF CALL modif3.00427684 004550FE |> 33C0 XOR EAX,EAX
Résumons l'algorithme :
. Si (Nom>=4 caracteres) ET (Serial>=4 caracteres) . tmp1 = nom[1] + serial[4] . tmp2 = nom[3] * serial[2] . tmp3 = tmp1 + tmp2 . . tmp4 = 0 . Pour (i variant de 1 a longueur(serial)) . tmp4 = tmp4 + serial[i] . Fin pour . . tmp5 = $7A9 . Pour (i variant de 3 a longueur(nom)) . tmp5 = tmp5 + nom[i] . Fin pour . . tmp4 = tmp4 XOR tmp3 . . tmp5 = tmp4 ? . Fin si


Voila ce que nous allons faire afin de calculer un sérial à partir d'un nom :
- Calcul de tmp5 d'après le nom
- On impose une valeur à tmp3 afin que ca marche à chaque fois (plusieurs tests)
- On calcule serial[2] et serial[4] d'après tmp3
- On calcule tmp4 (tmp4 = tmp5 xor tmp3)
- On en déduit le sérial ...


Détail du calcul :

tmp5 = $7A9 + nom[3] + nom[4] + ...

Soit tmp3 = 4000 (calculé pour fonctionner au mieux)
tmp3 = tmp1 + tmp2
tmp2 = 4000 - tmp1
tmp2 = 4000 - nom[1] - serial[4]
tmp2 + serial[4] = ( 4000 - nom[1] )
( nom[3] * serial[2] ) + serial[4] = ( 4000 - nom[1] )

serial[2] et serial[4] sont les inconnues
Pour résoudre cette équation on fait comme cela :

serial[2] = ( 4000 - nom[1] ) / nom[3]
serial[4] = ( 4000 - nom[1] ) modulo nom[3]

Rappel : le modulo est le reste de la division ...
L'algo devrait fonctionner pour à peu près tous les noms
La valeur ASCII de serial[4] pourra être comprise entre 0 et ASCII(nom[3])-1

tmp4 = tmp5 xor tmp3 = tmp5 xor 4000

On trouve un sérial valide selon la valeur de tmp4 :)


Essai avec un nom :

Prenons comme nom : uctteam

Valeurs ASCII :
- u : 117
- c : 99
- t : 116
- e : 101
- a : 97
- m : 109

tmp5 = $7A9 + 116 + 116 + 101 + 97 + 109
tmp5 = 2500

tmp3 = 4000
tmp2 = 4000 - 117
tmp2 = 3883

33 * 116 = 3828
33 * 116 + 55 = 3883

=> serial[2] = 33 = "!"
=> serial[4] = 55 = "7"

tmp4 = 2500 xor 4000 = 1636

On cherche les caractères du sérial à l'exception de serial[2] et serial[4]

1636 - ( 33 + 55 ) = 1548

1548 correspond à l'addition de la valeur ASCII de 12 caractères "z" + 1 caractère "T"
Le sérial pourra donc être : z!z7zzzzzzzzzzT


Le keygen :

Le keygen que j'ai programmé a été fait sous Delphi ...
Download ...

Outroduction ...

J'espère que ce petit crackme vous aura appris des choses, notamment sur le débuggage d'applications Delphi.
Je me suis bien amusé à faire le keygen, étant donné que j'avais fait l'algo sans savoir s'il serait possible de le reverser :). Enfin je m'en suis sortis sans brute-force pour une fois !!!

Je voudrais remercier certaines personnes pour ce qu'elles m'ont apportés :
Deamon, BeatriX, +Analyst, Lautheking, Kef, elooo, Crisanar, Mars, Clandestino, SeVen, mimas, Candirù, Randiox, etranger, Neitsa, BigBang, KasBX, DaFixer, Kaine, DelphiCool ... et les autres :)


[HolyView] - UCT - 2004/2005