Crackme avec KeyFile
Nous allons dans ce cours étudier un type d'enregistrement différent de ceux que nous avons rencontré au cours de mes anciens tutoriaux. Ici pas de serial, ni de nag-screen ou autres mais c'est de KeyFile qu'il est question. Pour ceux qui ne connaissent pas, le fonctionnement est le suivant : pour vérifier si le programme est enregistré celui-ci va regarder dans un fichier (le plus souvent c'est un .ini, .dat, ...) qui contient une clé. Si la clé est valide le programme est enregistré.
1° Cracker ce crackme (pléonasme devenu habituel dans mes cours !)
Nous allons donc lancer le crackme pour voir ce qu'il nous demande. Il nous affiche une message box affichant : "Your time-trial has ended... Please register and copy the keyfile sent to you to this directory". Pour les anglophobes voici la traduction : "La période d'essai est terminé... Veuillez vous enregistrer et copier le fichier clé, que vous avez reçu, dans ce dossier". Même pas eu le temps d'utiliser ce crackme et la période d'essai est déjà terminée ! Quelle arnaque ! :-P
00401078 . 83F8 FF CMP EAX,-1 ; |
0040107B . 75 1D JNZ SHORT due-cm2.0040109A ; |
0040107D . 6A 00 PUSH 0 ; |/Style = MB_OK|MB_APPLMODAL
0040107F . 68 01204000 PUSH due-cm2.00402001 ; ||Title = "Duelist's
Crackme #2"
00401084 . 68 17204000 PUSH due-cm2.00402017 ; ||Text = "Your
time-trial has ended... Please register and copy the keyfile sent to you to this
directory!"
00401089 . 6A 00 PUSH 0 ; ||hOwner = NULL
0040108B . E8 D7020000 CALL <JMP.&USER32.MessageBoxA> ; |\MessageBoxA
00401090 . E8 24020000 CALL <JMP.&KERNEL32.ExitProcess> ; \ExitProcess
00401095 . E9 28010000 JMP due-cm2.004011C2
0040109A > 6A 00 PUSH 0 ; /pOverlapped = NULL
Il y a donc un test pour savoir si EAX = -1 et si ce n'est pas le cas il saute
après le mauvais message. Nous verrons par la suite à quoi cela correspond. On
va donc remplacer le JNZ par un JMP (à noter que JZ=JE et JNZ=JNE, ce sont les mêmes sauts,
sous OllyDbg ils sont plutôt notés JZ/JNZ et sous WinDasm JE/JNE). Pour
tester dans OllyDbg on double-clique sur l'instruction JNZ et on remplace
JNZ SHORT 0040109A par JMP
SHORT 0040109A. On valide et on regarde si ça marche. On lance donc le
crackme dans OllyDbg avec F9 (les raccourcis claviers des fonctions principales
sont identiques à celle du débuggeur de windasm : F2 pour poser un BreakPoint,
F9 pour Run, F7 : Step Into, F8 : Step Out...).
0040107B . 75 1D JNZ SHORT due-cm2.0040109A
---> JMP
004010B0 . 75 02 JNZ SHORT due-cm2.004010B4
---> JMP
004010BF . 7C 36 JL SHORT due-cm2.004010F7
---> JNL ou NOP
004010D6 . 7C 1F JL SHORT due-cm2.004010F7
---> JNL ou NOP
004010F5 . 74 1D JE SHORT due-cm2.00401114
---> JMP
00401155 . 75 A0 JNZ SHORT due-cm2.004010F7
---> NOP ou JZ
Bon on recommence tout depuis le début. On ouvre avec OllyDbg le crackme (et pas celui déjà modifié ;-) ). On va vers notre premier message d'erreur ("Your time-trial..."). Et on remarque que juste au dessus l'API CreateFileA, donc l'accès à un fichier. Et dans les paramètres de l'API que voit-on ?
0040106E . 68 79204000 PUSH due-cm2.00402079
; ||FileName = "due-cm2.dat"
00401073 . E8 0B020000 CALL <JMP.&KERNEL32.CreateFileA>
; |\CreateFileA
FileName="due-cm2.dat" !! Ça ne serait pas notre keyfile par hasard ? Pour le savoir on va créer un fichier nommé "due-cm2.dat" dans le dossier du crackme. Mais on ne va pas le laisser vide on va (avec le bloc-notes par exemple) mettre "123456" dedans (sans les guillemets bien sûr !). Vous enregistrez et vous lancez le crackme (en dehors d'OllyDbg). Et là le deuxième message d'erreur apparaît : "Your current keyfile is invalid... Please obtain a valid one from the software author!". Bon on va voir pourquoi le contenu du fichier n'est pas bon. Pour cela direction OllyDbg.
Donc là on comprend mieux à quoi sert le saut situé entre le CreateFileA et le 1er message :
00401078 . 83F8 FF CMP EAX,-1
0040107B 75 1D JNZ SHORT due-cm2.0040109A
--> si le fichier n'existe
pas, alors ne saute pas et passe par le 1er message.
Ce saut nous emmène sur l'API ReadFile (tout le monde aura deviné à quoi cette
API sert :-) ) :
0040109A > 6A 00 PUSH 0 ;
/pOverlapped = NULL
0040109C . 68 73214000 PUSH due-cm2.00402173 ;
|pBytesRead = due-cm2.00402173
004010A1 . 6A 46 PUSH 46 ;
|BytesToRead = 46 (70.)
004010A3 . 68 1A214000 PUSH due-cm2.0040211A ;
|Buffer = due-cm2.0040211A
004010A8 . 50 PUSH EAX ;
|hFile
004010A9 . E8 2F020000 CALL JMP.&KERNEL32.ReadFile
; \ReadFile
004010AE . 85C0 TEST EAX,EAX
On va donc breaker juste après l'API sur le TEST EAX, EAX. On sélectionne
la ligne est on fait F2 (le bout de la ligne devient rouge). On Run (F9) et ensuite on
réfléchit. On peut voir (dans la fenêtre numérotée 4 ou 6 sur le schéma
au-dessus) que EAX est à 1. On va avancer en pas à pas avec F8 (ou F7 mais ce
dernier va vous faire rentrer dans les CALL et là on n'en aura sans doute pas
besoin). Pour mieux vous repérer vous pouvez mettre des commentaires sur les
lignes en faisant clic-droit et "Comment" (;) sur la ligne que vous souhaitez.
Le saut JNZ de la ligne suivante saute au-dessus du JMP qui nous mène au bad message. Ce qui est bien donc. Ensuite on trouve deux XOR sur EBX lui-même et ESI également ce qui a pour effet de mettre ces registres à 0. Les XOR sont souvent utilisés pour réinitialiser les registres. On arrive après les XOR ici :
004010B8 . 833D 73214000 CMP DWORD
PTR DS:[402173],12
--> compare
DS:[402173] à 12h
004010BF . 7C 36
JL SHORT due-cm2.004010F7
--> saute vers le mauvais message si
DS:[402173] est inférieur à 12h
A ce moment DS:[402173] vaut 6, si on se souvient bien on à mis le nombre 123456
dans le keyfile. 6 correspond donc à son nombre de caractères. Il faut donc que
le serial du keyfile fasse 12h lettres minimum (12 en hexa correspond à 18 en
décimal, donc 18 caractères minimum). On note donc cette condition et on
continue en changeant le flag S pour éviter le saut du JL. Et là on arrive sur
une petite routine :
004010C1
MOV AL,BYTE PTR DS:[EBX+40211A] --> met le caractère dans AL
004010C7 CMP AL,0
--> compare le caractère à 00h
004010C9 JE SHORT
due-cm2.004010D3 --> sort de la routine si le caractère = 00h
004010CB CMP AL,1
--> compare le caractère à 01h
004010CD JNZ SHORT
due-cm2.004010D0 --> saute au dessus de INC ESI si le caractère est
différent de 01h
004010CF INC ESI
--> incrémente ESI
004010D0 INC EBX
--> incrémente EBX = caractère suivant
004010D1 JMP SHORT
due-cm2.004010C1 --> boucle au début de la routine
004010D3 CMP ESI,2
--> compare ESI à 2
004010D6 JL SHORT
due-cm2.004010F7 --> Saute vers le Bad Boy si ESI < 2
J'espère qu'avec les couleurs c'est plus compréhensible ! :-) J'ai fait du mieux
que j'ai pu.
Donc pour résumer en rose/rouge c'est la routine qui va d'abord mettre à chaque
passage un caractère (le 1er puis le 2ème...etc) dans AL. Ensuite si le
caractère est 00 (en hexa) il sort de la routine. Il trouvera un 00 quand il n'y
aura plus de caractères. Après cela si le caractère = 01h il ne saute pas et
incrémente ESI, sinon pour tout autre caractère il n'incrémente pas ESI car il
saute par dessus. Et enfin il incrémente EBX pour passer au caractère suivant.
Quand vous êtes sur la ligne du MOV, vous pouvez voir la valeur du caractère étudié, le premier sera le "1", mais attention "1" est un caractère ascii et non une valeur. Sa valeur est 31h.
Donc pour éviter le mauvais message il faut que ESI soit égal ou supérieur à 2. C'est à dire qu'il y ait au moins deux caractères 01h dans le serial. On va donc mettre un serial valide. Pour mettre 01h il faut donc utiliser l'éditeur hexadécimal. Vous ne pourrez pas modifier le .dat à cet instant car le crackme est en exécution dans OllyDbg, on va donc noter à quel ligne on repartira (004010D6) et on réinitialise OllyDbg avec CTRL+F2. Si un message s'affiche faîtes "Oui", une erreur peut survenir mais c'est normal dans ce cas les breakpoints et les modifications seront annulés mais ce n'est pas grave. Une fois initialisé on peut modifier le .dat. On va donc mettre 18 caractères dont deux 01h. Dans HexDecCharEditor pour ajouter des bytes il faut faire "Edit" --> "Insert Bytes" ou la touche Ins.
On va donc mettre par exemple (en ascii) : 123456 789ABC DEFG (les 01h, ne
pouvant pas être représentés en ascii, correspondent ici aux espaces).
Ce qui donne en hexa : 31|32|33|34|35|36|01|37|38|39|41|42|43|01|44|45|46|47
Je vous déconseille de mettre des caractères identiques dans le serial car après vous aurez
des difficultés à savoir sur quel caractère travaille le crackme dans ses
routines et ses tests. Bon donc on enregistre et on va regarder ce qu'il nous veut
encore, ce crackme.
On pose un BreakPoint en 004010D6 comme on l'avait noté, car les tests d'avant doivent être corrects. On Run, ça break normalement et sur le JL on voit : "Jump is NOT taken" (enfin si vous avez bien rempli correctement le .dat). Le crackme a donc bien compté les deux 01h. On avance en pas à pas toujours : XOR EBX, EBX et XOR ESI, ESI : mise à 0 de ces deux registres. Et ensuite juste avant le deuxième mauvais message on rencontre une routine quasi-semblable à la précédente :
004010DC MOV AL,BYTE PTR DS:[EBX+40211A]
--> met le caractère dans AL
004010E2 CMP AL,0
--> compare le caractère à 00h
004010E4 JE SHORT
due-cm2.004010EF --> sort de la routine si le caractère =
00h
004010E6 CMP AL,1
--> compare le caractère à 01h
004010E8 JE SHORT
due-cm2.004010EF --> sort de la routine si le caractère =
01h
004010EA ADD ESI,EAX
--> ajoute la valeur du caractère à ESI
004010EC INC EBX
--> caractère suivant
004010ED JMP SHORT due-cm2.004010DC
--> boucle au début de la routine : et c'est reparti pour un tour :-p !
004010EF CMP ESI,1D5
--> compare ESI à 1D5h
004010F5 JE SHORT
due-cm2.00401114 --> saute au dessus du bad boy si
ESI = 1D5h
Explication pour ceux qui ne comprennent toujours pas mes belles routines
colorées :-) : chaque caractère est placé tour à tour dans AL (donc dans EAX car
je répète que AL est une partie de EAX). Si le caractère est 00h ou 01h alors il
sort de la routine. Sinon il ajoute la valeur hexa du caractère dans ESI. Ainsi
on aura dans ESI, à la fin de la routine, la somme des caractères situés avant le
premier 01h ou 00h qui joue le rôle de séparateur (pour notre .dat ce sera 01h
qui arrivera avant le 00h). Après la routine il vérifie que ESI (donc la somme
des caractères) soit égale à 1D5h. Et si ce n'est pas le cas : PAF! Un "Your keyfile
is invalid..." vous sautera à la figure ! Pour résumer : il faut donc que la somme des caractères situés
avant le premier 01h soit égale à 1D5h.
Bon on note où on en est (004010F5) si on ne veut pas se
perdre, on
réinitialise tout ça et on va faire les modifications nécessaires. Pour notre
exemple nous avons mis 6 caractères avant le 01h. On va donc prendre la
calculette pour se débrouiller à avoir une somme de 1D5h (et oui car de tête
c'est pas pratique le calculs hexadécimaux :-/ ). Pour la calculette mettez vous
en Mode Scientifique et en "Hex". On va faire 1D5/6 ce qui donne 4E. Mais en
hexa il n'y a pas de décimal donc 4E correspond à la partie entière. Pour
trouver le reste vous devez savoir faire je pense ! :-) Bon je vais vous aider
quand même ! ;-) On fait 4E*6 on trouve 1D4. Et 1D5-1D4=1 (cette soustraction à
été calculée de tête là !
Balèze hein ? :-P ) donc on aura cinq 4E et un 4F (car 4E+1=4F). Ce qui nous fait un total de 1D5 !
Le compte est bon ! :-) On
a donc dans l'éditeur hexa : 4E|4E|4E|4E|4E|4F|01|37|38|39|41|42|43|01|44|45|46|47.
On retourne à OllyDbg en posant un breakpoint sur 004010F5, on Run, ça break et
on voit sur le JE : "Jump is taken" ! Ouais ça marche ! Et là on se dit que c'est
fini car on saute après le deuxième mauvais message, mais comme on l'a vu en
première partie il reste encore des vérifications ! Donc on avance prudemment en
faisant attention aux sauts qui, tels des mines, peuvent vous faire sauter
n'importe où ! :-P On passe par dessus le bad boy, on tombe sur une mise à 0 de ESI et une incrémentation de EBX. Pourquoi changer EBX ? Car EBX correspond
toujours au numéro de position des caractères et donc il passe au caractère suivant. Rappelez vous
on s'était arrêté au 01h à la routine précédente ! Et là à partir du INC EBX on
tombe encore une routine, c'est donc reparti pour une routine colorée par mes
soins :-) :
00401116 INC EBX
--> caractère suivant
00401117 MOV AL,BYTE PTR DS:[EBX+40211A]
--> met le caractère dans AL
0040111D CMP AL,0
--> compare le caractère à 00h
0040111F JE SHORT
due-cm2.00401139 --> sort de la routine si le caractère =
00h
00401121 CMP AL,1
--> compare le caractère à 01h
00401123 JE SHORT
due-cm2.00401139 --> sort de la routine si le caractère =
01h
00401125 CMP ESI,0F
--> compare ESI à 0Fh
00401128 JNB SHORT
due-cm2.00401139 --> sort de la routine si ESI > ou
= 0Fh
0040112A XOR AL,BYTE PTR DS:[ESI+40211A]
--> XOR entre ESI+40211A et le caractère (cf. explication plus bas)
00401130 MOV DWORD PTR DS:[ESI+402160],EAX
--> met le résultat dans ESI+402160
00401136 INC ESI
--> incrémente ESI = caractère suivant (cf. explication plus bas)
00401137 JMP SHORT due-cm2.00401116
--> boucle au début de la routine
00401139
Bon donc on commence avec le caractère qui est situé après le premier 01h. Si le
caractère est 00h ou 01h (qui servent encore de séparateur) on sort de la
routine. On la quitte également si ESI >= 0Fh.
Avant d'expliquer ce qu'il fait avec le XOR, voyons ce qui correspond à quoi.
40211A est l'adresse où est situé le serial. Donc EBX+40211A correspond au
caractère placé en "EBX position" (donc au départ la position juste après le 01h,
donc, ici, en 7ème position). Pour ESI+40211A, il faut savoir que juste avant la
routine, ESI a été mis à 0. Donc ESI+40211A correspond au 1er caractère. Ainsi
pour le XOR AL,BYTE PTR DS:[ESI+40211A] cela
signifie qu'il fait une opération XOR entre le Nème caractère après le 01h
(donc AL) et le Nème caractère du serial (ESI+40211A). Par exemple lors de
la première boucle on aura un XOR entre le 1er caractère et le 7ème. Puis à la
deuxième boucle : 2ème caractère XOR 8ème caractère. C'est compliqué à expliquer
donc voici pour vous aider la correspondance en couleur :
4E|4E|4E|4E|4E|4F|01|37|38|39|41|42|43|01|44|45|46|47.
Les XOR se font entre les valeurs de même couleur.
Le résultat de calcul est mis à l'adresse : ESI+402160.
Si vous voulez voir ce que cela donne vous pouvez regarder dans la fenêtre en
bas à l'adresse 402160 (+ESI mais comme celui-ci a varié de 0 à 6 les résultats
commencent en 402160+0 donc bien 402160 et finissent en 402166) ce que cela donne
(ici "yvw│││"). Nous verrons par la suite
à quoi cela sert.
On continue notre pas à pas (pour éviter de faire en pas à pas les 6 boucles de la routine vous pouvez poser un BP juste à la sortie de la routine en 00401139 et appuyez sur Run pour s'y rendre immédiatement). Par la suite on trouve un INC EBX (pour passer au caractère suivant, donc caractère suivant le deuxième 01h) et une mise à 0 de ESI. Et ensuite encore une routine qui est strictement identique à la deuxième rencontrée à une différence près : CMP ESI,1B2 et non 1D5. Il faut donc que la somme des caractères après le deuxième 01h soit égale à 1B2h. On a 4 caractères après le deuxième séparateur. On fait donc 1B2/4 = 6C et il reste 2. Donc on aura trois fois 6C et une fois 6C+2 soit 6E : 4E|4E|4E|4E|4E|4F|01|37|38|39|41|42|43|01|6C|6C|6C|6E. On fait donc les modifications dans l'éditeur hexa (pensez à réinitialiser OllyDbg et noter à quelle ligne on est). On teste en lançant le .exe. Et là : BINGO ! Ça marche ! Mais il y a un petit détail qui ne va pas ! Notre nom ne ressemble à rien ! :-( On va donc voir comment il trouve le nom. On va donc breaker là où en était (00401155) et continuer à analyser le code. Une fois le programme breaké, on va regarder un peu en dessous pour avoir une vue globale de la suite. Et on remarque :
004011F4 > \68 60214000 PUSH due-cm2.00402160 ; /Text
= "yvw│││"
004011F9 . 6A 01 PUSH 1 ; |ControlID = 1
004011FB . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
004011FE . E8 5E010000 CALL <JMP.&USER32.SetDlgItemTextA>
; \SetDlgItemTextA
Le texte qu'il va afficher : yvw│││ ressemble
étrangement à ce qu'on avait aperçu en
402160, là où on avait le résultat du XOR. Cette routine servait donc à trouver
le nom. Maintenant il n'y a plus qu'à calculer pour arriver à afficher notre
nom. On va essayer d'afficher "Deamon" (le nom fera forcément 6 caractères donc
6 lettres au maximum). On va donc décomposer mon nom "Deamon" en valeur hexa (pour cela
soit vous prenez une table ascii qui est disponible dans HexDecCharEditor
(l'icône du carré jaune :-) ) ou soit vous tapez dans la partie ascii de l'éditeur et sa
correspondance en hexa s'affiche).
D|e|a|m|o|n donne donc 44|65|61|6D|6F|6E. Mais pour trouver les lettres le crackme calcule en faisant un XOR entre les caractères après le premier 01h et ceux avant. On a : 4E|4E|4E|4E|4E|4F|01|X1|X2|X3|X4|X5|X6|01|6C|6C|6C|6E. Pour avoir 44 en pour la première lettre ("D") il faut que 4E XOR X1 = 44 (la variable X1 correspondant au nombre recherché pour la première lettre, X2 pour la seconde...) donc X1 = 4E XOR 44 = 0A. Faites le calcul du XOR avec la calculatrice de windows. Donc voici ce que ça donne :
X1 = 4E XOR 44 = 0A
X2 = 4E XOR 65 = 2B
X3 = 4E XOR 61 = 2F
X4 = 4E XOR 6D = 23
X5 = 4E XOR 6F = 21
X6 = 4F XOR 6E = 21
Ce qui nous donne : 4E|4E|4E|4E|4E|4F|01|0A|2B|2F|23|21|21|01|6C|6C|6C|6E
Si votre nom est plus court ou plus long vous pouvez déplacer les séparateurs et augmenter le nombre total de caractères. Mais le nom ne dépassera pas les 15 caractères à cause du CMP ESI,0F. Et veillez à mettre le même nombre de lettres avant et après le 1er séparateur. Mais respectez la valeur des sommes des caractères du début et de la fin.
Attention : Si par exemple vous souhaitiez afficher un "N" dans votre nom, cela n'aurait pas marché car "N" = 4E et comme 4E XOR 4E = 00h cela aurait donc posé un problème (c'est la même chose pour 00h ou 01h). Il aurait donc fallu changer le 4E du départ par exemple en mettant 40 (ce qui fait 4E-40=E) et donc rajouter Eh à un autre pour que la somme soit toujours égal à 1D5h. Exemple : 40|5C|4E|4E|4E|4F|01|0E|39|2F|23|21|21|01|6C|6C|6C|6E. Ce qui revient au même car 4D+5C=4E+4E. Et ainsi le "N" en première place aurait donné X1 = 40 XOR 4E = 0E. Et donc la deuxième lettre si on garde le "e" (65h) donnerait : X2 = 5C XOR 65 = 39. Bon j'espère avec ça que vous aurez compris.
Récapitulatif :
- Nom du keyfile : due-cm2.dat
- 18 caractères minimum
- 2 caractères 01h minimum qui servent de séparateur
- 00h ou un vide pour indiquer la fin du keyfile
- La somme des caractères avant le premier 01h fait 1D5h
- La somme des caractères après le deuxième 01h fait 1B2h
- Les caractères du nom sont donnés par l'opération XOR entre les caractères
d'avant et d'après le premier 01h
- Exemple valide avec le nom "Deamon" : 4E|4E|4E|4E|4E|4F|01|0A|2B|2F|23|21|21|01|6C|6C|6C|6E
Et avec ça, ça sera tout ! :-) Le crackme est cracké et avec la manière ! Et en plus vous savez désormais utiliser OllyDbg.
Deamon, le 02 et 03 mars 2004