Tut sur le crackme1 de rd116 (http://www.multimania.com/rd116/)
---------------------------------------------------------------


Ce crackme1 niveau débutant est idéal pour ceux qui commencent dans le cracking.
Il faut entrer un nom/sérial et le but est de pouvoir s'enregistrer avec n'importe quel sérial...

outils:

*crackme1
*wdasm 8.93
*SoftIce

go!:

- on lance le crackme1, on rentre un nom et un sérial bidon et on obtient le message: "mauvais serial, essaye encore...

- on désassemble le crackme avec wdasm, on regarde ce qui est intéressant dans les strings data et on trouve: "c'est cracké" (très intéressant) "raté", "réesaye encore"

- pour que le prog s'enregistre toujours --> on double clik sur "c'est cracké" et on arrive ici:

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004015C1(U)                                     <---here!!!
|
:004015D9 6A00                    push 00000000

* Possible StringData Ref from Data Obj ->"Bien jou"
                                  |
:004015DB 6834304000              push 00403034

* Possible StringData Ref from Data Obj ->"C' est crack"
                                  |
:004015E0 6820304000              push 00403020
:004015E5 8B4DE0                  mov ecx, dword ptr [ebp-20]

on voit que la string est appelée par un saut inconditionnel (le u entre parenthèses)
- double clik avec le bouton droit sur 004015c1 et on arrive ici:

:004015B0 33C9                    xor ecx, ecx
:004015B2 8A18                    mov bl, byte ptr [eax]
:004015B4 8A0A                    mov cl, byte ptr [edx]
:004015B6 3AD9                    cmp bl, cl
:004015B8 7509                    jne 004015C3   <--- nous envoie vers "mauvais sérial"
:004015BA 40                      inc eax
:004015BB 42                      inc edx
:004015BC 803800                  cmp byte ptr [eax], 00
:004015BF 75EF                    jne 004015B0   <--- une boucle
:004015C1 EB16                    jmp 004015D9   <--- on arrive ici!

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00401503(U), :0040151C(U), :004015B8(C)
|
:004015C3 6A00                    push 00000000

* Possible StringData Ref from Data Obj ->"Rat"
                                  |
:004015C5 686C304000              push 0040306C

* Possible StringData Ref from Data Obj ->"Mauvais serial, essaye encore... "
                                        ->"       "
                                  |
:004015CA 6840304000              push 00403040
:004015CF 8B4DE0                  mov ecx, dword ptr [ebp-20]


- bon, vous feriez quoi ici?...

- je résume, on doit arriver au jump 004015d9 pour que le sérial soit toujours bon mais le méchant jne 004015c3 nous en empêche... on pourrait nopper le saut, l'inverser ou le transformer...ici on va le transformer:

- si on noppe le saut, il pourrait boucler plus bas...

- une solution serait de remplacer le jne par un je (changer le 75 par 74), mais dans ce cas, en entrant le bon sérial( par le plus grand des hasard mais on sait jamais...)le prog nous enverrait vers mauvais sérial :((.

- la meilleur solution est de transformer le jne en un jmp qui nous enverrait vers le fameux jmp que nous voulons atteindre, voici comment faire:

vous comptez combien de byte sépare le jmp initial au jump final (ici il y en a 7)... et avec votre éditeur héxa, vous changez le 7509 en eb07 :-))).
et voila, it's cracked!!!

ce crackme1 est idéal pour les débutants, ils peuvent se faire la main dessus :-)

tombeur4

tombeur4@caramail.com
http://www.multimania.com/tombeur4/

Une autre approche, bien que ce ne soit pas l'objectif de ce crackme, serait de trouver comment est généré le sérial.
Commençons pas délimiter notre "espace de travail"...
Celui ci va être compris, à priori, entre la saisie des champs du crackme et l'affichage de la messagebox "code invalide".

La saisie des messages, au moins dans les programmes écrit en C (ces API ne sont pas sollicitées par les programmes écrits en Delphi ou en VB), se fait généralement par le biais des API GetWindowTextA et GetDlgItemTextA. (according to Rhayader).
Pour commencé, j'ai utilisé le breakpoint "action" suivant:
GetWindowtext, déclaré comme ceci:

int GetWindowtext (HWND hWnd, LPTSTR IpString, int MaxCount),

Au moment du break, la pile (stack) ressemblera à ceci:

[ESP+0Ch]    - contiendra nMaxCount
[ESP+08h]    - contiendra IpString
[ESP+04h]    - contiendra hwnd
[ESP+00h]    - contiendra le Return EIP

Au retour de la fonction API appelée, GetWindowText placera le texte saisie à l'adresse pointée par IpString, si bien qu'il faudra utiliser le caractère * pour lire le contenu de l'adresse pointée. (D *ESP+8)

En posant un point d'arrêt de ce genre, vous obtiendrez un break avec affichage du contenu du champ saisi:

Bpx GetWindowTextA Do "D esp->8;", et en appuyant sur F12 vous reviendrez à la fonction appelante.
Pour vous épargner le F12, vous pouvez aussi entrer:

Bpx GetWindowTextA Do "D esp->8;P RET;" qui vous ferra retourner au call appelant automatiquement.

L'API GetDlgItemText fonctionne sur le même principe, mais les adresses ne seront pas les mêmes. Une autre différence sera nIDDlgItem, qui est l'ID du contrôle qui saisie le texte de la pile:

[ESP+10h]    - contiendra nMaxCount
[ESP+0Ch]    - contiendra IpString
[ESP+08h]    - contiendra nIDDlgItem
[ESP+04h]    - contiendra hwnd
[ESP+00h]    - contiendra le Return EIP

Le breakpoint à poser sera alors:

Bpx GetDlgItemTextA Do "D esp->C;P RET;"

Au break sur le BPX posé, vous aurez le contenu du buffet de saisi d'affiché dans la fenêtre des Data. Il ne suffira plus que de poser un BPR adresse_de_debut adresse_de_fin du champ saisi pour pouvoir suivre à la trace ce qu'il va advenir du contenu de cette adresse (vous pouvez vous attendre à ce que ce contenu soit transvaser dans une [mémoire] de travail du genre [EBP-20], sur laquelle il faudra poser un nouveau BPR).

Cette "méthode" est finalement sensiblement équivalente à la recherche du sérial entré en mémoire par un:
S 0 L FFFFFFFF "sérial entré", à la différence que vous pouvez mieux suivre les manipulations faites sur le contenu du champ saisi.

Dans le cas de notre crackme, voici la totalité de la routine:

:00401533  E866030000    CALL      0040189E      > saisie du 1er champ
(retour à cette adresse après un F12, qui suit le break dans SoftIce)
:00401538  8B4DE0        MOV       ECX,[EBP-20]  
:0040153B  81C1E4000000  ADD       ECX,000000E4  
:00401541  51            PUSH      ECX           > D * ECX = "nom entré"
:00401542  8B4DE0        MOV       ECX,[EBP-20]
:00401545  83C160        ADD       ECX,60
:00401548  E851030000    CALL      0040189E      > saisie du 2eme champ
:0040154D  8B55E0        MOV       EDX,[EBP-20]
:00401550  81C2E0000000  ADD       EDX,000000E0
:00401556  52            PUSH      EDX           > D * EDX = "sérial entré"
:00401557  8D4DE4        LEA       ECX,[EBP-1C]  > [EBP-1C]= nom -> ECX
:0040155A  E839030000    CALL      00401898      > call MFC42
:0040155F  8B45E0        MOV       EAX,[EBP-20]
:00401562  05E4000000    ADD       EAX,000000E4
:00401567  50            PUSH      EAX           > D * EAX = "sérial entré"
:00401568  8D4DF0        LEA       ECX,[EBP-10]  > [EBP-10]= sérial -> ECX
:0040156B  E828030000    CALL      00401898      > call MFC42
:00401570  33C0          XOR       EAX,EAX       > mise à 0 des registres
:00401572  33DB          XOR       EBX,EBX       > qui vont être utilisés
:00401574  33C9          XOR       ECX,ECX       > pour le cryptage 
:00401576  B901000000    MOV       ECX,00000001  > initialisation de la clé de cryptage
:0040157B  33D2          XOR       EDX,EDX       > mise à 0 du registre EDX
:0040157D  8B45E4        MOV       EAX,[EBP-1C]  > place le nom dans EAX
:00401580  8A18          MOV       BL,[EAX]      > met un caractère du nom dans BL
:00401582  32D9          XOR       BL,CL         > crypte ce caractère
:00401584  8818          MOV       [EAX],BL      > le place dans EAX
:00401586  41            INC       ECX           > ajoute 1 à la clé de cryptage
:00401587  40            INC       EAX           > caractère suivant
:00401588  803800        CMP       BYTE PTR [EAX],00 > reste-t-il des caractères?
:0040158B  75F3          JNZ       00401580      > oui -> boucle
:0040158D  33C0          XOR       EAX,EAX       > non, passe au sérial
:0040158F  33DB          XOR       EBX,EBX       > en mettant les 
:00401591  33C9          XOR       ECX,ECX       > registres à 0
:00401593  B90A000000    MOV       ECX,0000000A  > initialisation de la clé de cryptage
:00401598  33D2          XOR       EDX,EDX       
:0040159A  8B45F0        MOV       EAX,[EBP-10]  > place le sérial dans EAX
:0040159D  8A18          MOV       BL,[EAX]      > 1 caractère du sérial dans BL
:0040159F  32D9          XOR       BL,CL         > crypte ce caractère
:004015A1  8818          MOV       [EAX],BL      > le place dans EAX
:004015A3  41            INC       ECX           > ajoute 1 à la clé de cryptage
:004015A4  40            INC       EAX           > caractère suivant
:004015A5  803800        CMP       BYTE PTR [EAX],00 > encore des caractères ?
:004015A8  75F3          JNZ       0040159D      > oui -> boucle
:004015AA  8B45E4        MOV       EAX,[EBP-1C]  > place le nom crypté dans EAX
:004015AD  8B55F0        MOV       EDX,[EBP-10]  > place le sérial crypté dans EDX
:004015B0  33C9          XOR       ECX,ECX       > ECX mis à 00
:004015B2  8A18          MOV       BL,[EAX]      > 1 caractère du nom dans BL
:004015B4  8A0A          MOV       CL,[EDX]      > 1 caractère du sérial dans CL
:004015B6  3AD9          CMP       BL,CL         > compare ces 2 caractères
:004015B8  7509          JNZ       004015C3      > si <> saute en bad boy
:004015BA  40            INC       EAX           > caract suivant du nom
:004015BB  42            INC       EDX           > caract suivant du sérial
:004015BC  803800        CMP       BYTE PTR [EAX],00 > encore des caractères pour le nom ?
:004015BF  75EF          JNZ       004015B0      > oui -> boucle
:004015C1  EB16          JMP       004015D9      > non -> le code est OK

:004015C3  6A00          PUSH      00            > routine bad Boy
:004015C5  686C304000    PUSH      0040306C      > pousse sur la pile, les
:004015CA  6840304000    PUSH      00403040      > infos de la messagebox
:004015CF  8B4DE0        MOV       ECX,[EBP-20]
:004015D2  E8BB020000    CALL      00401892      > affichage messagebox
:004015D7  EB14          JMP       004015ED

:004015D9  6A00          PUSH      00            > routine good boy
:004015DB  6834304000    PUSH      00403034     
:004015E0  6820304000    PUSH      00403020
:004015E5  8B4DE0        MOV       ECX,[EBP-20]
:004015E8  E8A5020000    CALL      00401892
etc....


Analysons un peu ces lignes:

· Le sérial et le nom sont saisis par l'API GetWindowTextA
· Le contenu du 1er champ est placé à l'adresse pointée par EAX
· Les caractères sont chargés 1 a 1 dans BL
· le Xor BL, CL va crypter ce caractère: en début de routine, CL va recevoir la valeur 01 qui va être la clé de cryptage. Cette clé changera à chaque fois par incrémentation.
· Puis le caractère crypté va être replacé dans EAX, et remplacera le caractère entré.

Le principe va être le même pour le sérial, à ceci prés que la clé de cryptage sera égale à 10d (0Ah) au démarrage. Puis les caractères du sérial et du nom vont être comparés un à un, jusqu'à concurrence du nombre de caractères contenus dans le 1er champ du formulaire de saisie (le cmp byte ptr [EAX], 00 vérifie que le dernier byte saisie n'est pas un 00). Le sérial peut donc avoir n'importe quel taille supérieur à celle du nom.

Le principe de la validation du sérial est donc basé sur ce schéma:

1er champ Xor CL = 2eme champ Xor CL+10

avec CL qui variera de 01 à {01+ nombre de caractères entrés dans le champ}.

A titre d'exemple, en entrant "Christal" dans le premier champ, vous allez obtenir "bjqmvrfd" après cryptage, et le second champ donnera ">35>=:" pour "489335" d'entré.
Ensuite, "b" -> 6A sera comparé à ">" -> 3E, et vous irez en routine Bad Boy.
Tombeur 4 a montré comment patcher le programme pour obliger celui ci à aller systématiquement en routine Good Boy:

:004015B8 7509                    jne 004015C3

Il peut y avoir d'autres solutions:

:004015AA  8B45E4        MOV       EAX,[EBP-1C]
:004015AD  8B55F0        MOV       EDX,[EBP-10]
:004015B0  33C9          XOR       ECX,ECX
:004015B2  8A18          MOV       BL,[EAX]
:004015B4  8A0A          MOV       CL,[EDX]
:004015B6  3AD9          CMP       BL,CL
:004015B8  7509          JNZ       004015C3

En 004015AD vous pouvez remplacer EBP-10 (sérial crypté -> 8B55F0) par EBP-1C (nom crypté -> 8B55E4), si bien qu'en fait le nom que vous aurez entré sera comparé avec lui même, et vous irez en routine Good Boy.

Vous pouvez aussi supprimer les deux clés de cryptage (Xor BL, CL) et entrer le même sérial (ou nom) dans les deux champs. Là encore vous irez en routine good Boy. Pas très intéressant...

Le plus simple est quand même de trouver quel devrait être le sérial à entrer pour le nom de votre choix:
Dans le cas de cryptage comme celui ci, on ne peut pas établir une grille de proportion (du genre si "4" -> 52 Ascii = ">" -> 62 Ascii, alors "8" -> 56 Ascii = valeur ascii du bon sérial).

Comme on sait que le nom crypté (clé 01) va être comparé au sérial crypté (clé 0A), le plus simple (mais qui oblige à utiliser un débuggeur, ou à écrire une routine dans le langage de son choix) va être d'entrer votre nom dans le premier champ, et votre nom crypté dans le second, soit:

1er  champ:   Christal
2eme champ:   bjqmvrfd

Puis de provoquer un break en 004015AD pour lire le sérial crypté par un " d EDX".
Dans mon cas, j'obtiens "ha}`x}vu". Il ne reste plus qu'à remplir la dialogbox ainsi:

1er  champ:   Christal
2eme champ:   ha}`x}vu

"Christal" et "ha}`x}vu", une fois crypté vont donner "bjqmvrfd", et la comparaison dans la boucle en 004015B6 nous enverra vers la routine Good Boy!
CQFD!

Dans le cas de ce crackme, le programme ne s'occupe pas de vous enregistrer en plaçant les nom et sérial dans la base de registre, ou dans un fichier de type *.INI.
Si c'était le cas vous pourriez suivre ce qui se passe au niveau de la saisie (ou de l'interrogation) du contenu d'une clé dans la base de registre en posant un:
BPX RegQueryValueA, avant de lancer l'application.
Le problème que vous allez rencontrer est que ce breakpoint risque de provoquer de nombreux breaks dans SI, sans pour autant que ceux ci soit en lien avec la saisie (éventuelle) d'un sérial.

Pour obtenir un break intéressant, il va falloir poser un bpx conditionnel.
RegQueryValueEx est déclaré ainsi:

LONG RegqueryValueEx (
HKEY hKey                      handle de la clé interrogée
LPTSTR IpValueName             adresse du nom de la valeur interrogée
LPDWORD IpReserved             comme c'est écrit: Réservé!
LPDWORD IpType                 adresse du buffet pour la valeur Type
LPBYTE IpData                  adresse du data Buffer
LPDWORD IpcbData               adresse de la taille du Data Buffer)

Quand SoftIce va breaker, la pile contiendra ceci:

[ESP+18h]   - IpcbData
[ESP+14h]   - IpData
[ESP+10h]   - IpType
[ESP+0Ch]   - IpReserved
[ESP+08h]   - IpValueName   > le nom de la chaîne intérrogée
[ESP+04h]   - hKey
[ESP+00h]   - return EIP

En utilisant un outil comme RegMon, vous pourrez connaître les clés de la base de registre consultées par l'application-cible, comme par exemple:

[HKEY_LOCAL_MACHINE\SOFTWARE\NuMega\SoftICE]
InstallDir      = C:\\Program Files\\NuMega\\SoftIce95
User            = votre nom
Company         = bonne année
Serial          = 1900-0000DD-9B
Current Version = 3.23

Vous pouvez alors placer un breakpoint de ce type:

BPX RegQueryValueExA IF *(ESP->8) == 'Seri' DO "D ESP->14;"

Et SoftIce ne breakera que quand le RegQueryExA lira la clé commençant par "Seri". Gain de temps et efficacité garantie...

Qui c'est il passé?
Quand Windows passe de LPTSTR (pointant vers une chaîne non nul) à RegQueryExA, la condition posée sur ESP->8 regarde si l'adresse pointée par le contenu de [ESP+08] est bien égale à la chaîne visée. (pour plus d'informations, regardez le Chapitre 7 du Guide d'utilisation de SoftIce: Stack in Conditional BreakPoint).

L'opérateur d'indirection de SoftIce (*) va indiquer à Softice que nous avons besoin de la valeur stockée à l'adresse pointée par le contenu de [esp+08].
L'expression *(esp+08)== 'Seri' va évaluer si la condition est Vraie, mais ne le fera que sur la valeur d'un Dword (32 bits). En conséquence, la comparaison ne peut se faire que sur les premiers 32-bits soit quatre caractères, qui seront convertis en leur valeur hexadécimale 0x53657269 (Case sensitive).

Dans le cas de retours attendus sur 16 bits, le problème doit être abordé différemment. Par exemple si les clés visées sont de ce type:

HKEY_LOCAL_MACHINE\SOFTWARE\NuMega\SoftICE\sn
HKEY_LOCAL_MACHINE\SOFTWARE\NuMega\SoftICE\rv

Dans la mesure ou l'opérateur * retourne une valeur sur un Dword, nous n'avons pourtant besoin cette fois ci que d'un retour sur un Word. Pour nous en sortir, nous allons avoir besoin de la fonction Word() de SoftIce, encore que ce cas de figure se présente rarement. Notre breakpoint va ressembler à ceci:

BPX RegQueryValueExA IF (*(ESP->8)== 'sn') DO "D ESP->14;" || (*(ESP->8) == 'rv') DO "D ESP->14;"

L'opérateur logique OR (||) évaluera l'expression comme étant VAIE (TRUE) si l'une des conditions (ou les deux) sont Vraies.
Sachant que les commandes à entrer dans SoftIce sont limitées à 80 caractères, il faudra créer une macro que nous pourrions déclarer ainsi:

BPX RegQueryValueExA IF (*(ESP->8)== '%1') DO "D ESP->14;" || (*(ESP->8) == '%2') DO "D ESP->14;"

et l'appeler, par exemple, REGSN sn rv.

Bonne journée
Christal

merci Tombeur4