Sérials et Généralités: les premiers Pas


1- Pourquoi un sérial
2- Forme usuelle des sérials
3- Les boites d'enregistrement
4- Architecture

· La saisie du ou des champs
· La vérification du format
· La génération du code
· La vérification du code
· La recherche du code
· Le drapeau utilisateur


1- Pourquoi un sérial

Dans notre bon vieux système d'économie de marché, les notions d'altruisme est de gratuité sont des valeurs perdues. Tout auteur d'un programme va alors chercher à pouvoir obtenir de l'argent à partir d'un travail qu'il a effectué. Le mode de diffusion des programmes en version shareware s'est beaucoup développé dès lors que la démocratisation des lecteurs de CD-Rom, et de l'accès à Internet s'est généralisé.
Il faut distinguer la version d'essai transformable en version complète, de la version de démonstration, qui n'est (théoriquement) que tout ou partie de la version licenciée.
L'entrée d'un sérial dans une boite d'enregistrement va permettre de transformer un programme shareware en version définitive, faisant de cette méthodes l'une des plus courantes, et des plus faciles, pour un auteur d'enregistrer un utilisateur:

· le sérial peut se communiquer par téléphone, courrier, mail...
· il ne demande pas de modification physique des fichiers du programme
· le schéma de protection est facile à rajouter une fois que le programme est achevé

L'entrée d'un sérial valide dans une boite d'enregistrement va offrir différents avantages, suivant les cas:

· Les versions d'essai à durée déterminée: votre programme restera utilisable au delà de la période limite (souvent 30 jours, 1E en hexadécimal)
· Les fonctions désactivées ou manquantes: vous pourrez utilisez le programme dans son intégralité, sans limitations.
· Les mises à jour: sur le principe du "UpDate On-Line", certains programmes permettent, pour une licence acquise, de pouvoir bénéficier des versions futures du programme
· Aide "On Line": l'utilisateur licencié pourra bénéficier d'une aide à la résolution des problèmes qu'il pourrait rencontrer lors de l'utilisation du programme
· etc...


2- Forme usuelle des sérials

Le sérial peut être une combinaison numérique, alphabétique, alphanumérique. Il peut être composé d'une seule série de X caractères, ou de séries séparées par des tirets (-).
Il peut être unique et commun à toutes les licences, ou personnalisé à partir des critères Nom et parfois Company: pour un utilisateur donné, il y aura un code particulier.

Il existe des petits outils très pratique, comme Super Snooper, qui va " lire " toutes les chaînes alphanumériques contenues dans un exécutable, et éviter une laborieuse recherche avec un éditeur hexadécimal.


Super Snooper sur DreamWeaver.exe

Selected Browser
DWW200-05987-98274-58597
Receive Error: %d.
CWindowDC32
CPaintDC32

Le code est en toute lettre, non codé, dans l'exécutable. A défaut de Super Snooper, un désassemblage de DreamWaever.exe avec Wdasm me donne ce sérial dans l'une des " String data references " !

Dans certain cas, le programme va attendre l'entrée d'un numéro de série comportant des caractères spéciaux, comme les séparateurs (-):

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00409850(U)
|
:0040983A 3BCF    cmp ecx, edi           -> edi = nombre de caractères du code
:0040983C 7314    jnb 00409852	         -> va vers contrôle du code
:0040983E 803A2D  cmp byte ptr [edx], 2D -> recherche de  " - "
:00409841 7506    jne 00409849           -> sinon continue

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00409847(C)
|
:00409843 42       inc edx                -> caractère suivant
:00409844 803A2D   cmp byte ptr [edx], 2D -> [edx] = notre code
:00409847 74FA     je 00409843            -> boucle

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00409841(C)
|
:00409849 8A02       mov al, byte ptr [edx]-> 1 caractère ds AL
:0040984B 41         inc ecx               -> compteur de boucle
:0040984C 42         inc edx               -> caractère suivant
:0040984D 8841FF     mov byte ptr [ecx-01], al -> réécrit le code ailleurs 
:00409850 EBE8       jmp 0040983A          -> boucle

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040983C(C)
|
:00409852 53         push ebx
:00409853 C60100     mov byte ptr [ecx], 00
:00409856 6A32       push 00000032
:00409858 8D4E44     lea ecx, dword ptr [esi+44]
:0040985B E885CF0100 call 004267E5
:00409860 50         push eax        -> notre UserName
:00409861 8BCD       mov ecx, ebp
:00409863 E8088DFFFF call 00402570   -> calcul du n° de série
:00409868 8BF8       mov edi, eax
:0040986A 6AFF       push FFFFFFFF
:0040986C 8D4E44     lea ecx, dword ptr [esi+44]
:0040986F E8B2CF0100 call 00426826   -> comparaison avec notre code
:00409874 85FF       test edi, edi
:00409876 0F84C200000je 0040993E     -> vers " code invalide "
:0040987C 53                      push ebx début de code valide
:0040987D 8BCD                    mov ecx, ebp
:0040987F C785F400000001000000    mov dword ptr [ebp+000000F4], 00000001
:00409889 C785F800000000000000    mov dword ptr [ebp+000000F8], 00000000
* Possible StringData Ref from Data Obj ->"RegistrationKey"

En 0040983A, la cible va vérifier que ce code comprend bien des "-" -> cmp byte ptr [edx],2D
(2D étant égal au symbole "-"). Donc notre code doit en comporter (2 en fait). Si vous entrez un numéro de série du genre "1234-1234-1234", vous passerez tous ces tests avec succès.

Un peu plus loin un call 00402570 va calculer le numéro de série d'après le nom que vous aurez entré. (si vous êtes à la recherche du numéro de série, il y a des chances que vous en trouviez un écho dans cette routine...)

A suivre, vous verrez un call 00426826. C'est lui qui va contrôler la validité de votre code, et en cas
d'inexactitude, créer un branchement par un je 0040993E -> code invalide
Si vous inversez ce saut par un R FL Z, vous aurez le plaisir de voir le bel écran "Code accepté". Pour autant vous n'aurez pas trouvé la solution, mais seulement comment ne pas arriver dans la routine "Code Invalide".


3- Les boites d'enregistrement

La saisie du sérial se fait majoritairement par le biais d'une boite d'enregistrement comportant autant de champs que de données à entrer (Nom, Compagnie, Numéro de série...).
Bien souvent, un message "Code invalide "sanctionnera l'entrée d'un sérial erroné.
L'accès à cette boite d'enregistrement pourra être proposée de façon systématique, et apparaîtra à chaque lancement de l'application, être accessible par l'un des menus de la barre des tâches du programme, soit directement, soit via le menu "A propos", ou par une combinaison de touche (ex: CTRL-R). L'accès à cette boite pourra dépendre de l'état d'un drapeau Utilisateur Licencié ou non.
Les boites d'enregistrement les plus usuelles peuvent être classées en quatre catégories:

- les boites à champ unique
- les boites à champs multiples
- les boites sans bouton de validation
- les boites sans écran de validation


4- Architecture

· La saisie du ou des champs

Une fois les informations demandées entrées dans les champs de la boite d'enregistrement, le programme va s'occuper d'en saisir le contenu. Pour cela, il va utiliser des API Windows comme

Hmemcpy
GetWindowText (A)
GetDlgItemText (A)

Reference To: USER32.GetDlgItemTextA, Ord:00EDh |

:0040980B FF15DCAA4400  Call dword ptr [0044AADC] -> saisie d'un champ
:00409811 83F80E        cmp eax, 0000000E -> 14 caractères
:00409814 8BF8          mov edi, eax      -> pousse nb caractères entré dans edi
:00409816 0F8C35010000  jl 00409951       -> vers " code invalide "

"* Possible StringData Ref from Data Obj  ->"0123456789ABCDEF-" |

:0040981C 6854624400    push 00446254     -> pousse le bon sérial
:00409821 53            push ebx          -> pousse notre code
:00409822 E8BD020100    call 00419AE4     -> les compare
:00409827 83C408        add esp, 00000008 -> dépile
:0040982A 3BC7          cmp eax, edi      -> code ok?
:0040982C 0F851F010000  jne 00409951      -> vers " code invalide "

Dans cet exemple, la saisie se fait par GetDlgItemTextA, et à la sortie de cette API, le programme contrôle que le code comprend bien 14 caractères (0E en hexadécimal), et sinon file en procédure code_invalide. Ensuite, les push ebx et 446254, qui poussent notre code et le bon sérial sur la pile, vont être comparés dans le call suivant...

Dans un listing désassemblé, une recherche sur la chaîne GetWindowTexta (par exemple), ou en consultant la liste des fonctions importées, peut permettre de localiser la routine de saisie du sérial, tout en sachant que cette API peut également être utilisée par d'autres boites de dialogue.

Le code que vous avez entré va être saisi par le programme. Pour cela il fait presque toujours appel à Hmemcpy, l'une des API Windows, qui elle même peut-être appelée par GetdlgItemText(A), ou GetWindowText(A)… Des breakpoints posés sur ces deux dernières fonctions Windows donnent souvent de bons résultats, mais Hmemcpy à l'avantage. Quand vous tapez un Bpx hmemcpy, vous vous "enfoncez" loin dans les procédures Windows, et il n'est pas rare d'avoir à taper une douzaine de fois sur F12 pour finir par quitter le dernier Kernel, le dernier User, et enfin revenir au programme sur lequel votre intérêt c'est porté.
Une fois saisi, votre code va être mis dans un registre, et plus souvent dans une adresse mémoire du genre [00407632], ou [ebp-14] etc… Il faut toujours être curieux de ce qui peut se trouver dans ces adresses, et ne pas hésiter à taper un "d ebp-14", pour en interroger le contenu qui s'affichera dans la fenêtre des Datas de Soft-Ice.


Il y a ensuite les API de traitement des Strings:

lstrcmpiA
GetFileAttributesA
lstrcpyA
DeleteFileA
lstrcmpA
(Suivant la structure de l'application (16 ou 32 bits), il faudra, ou non, inscrire le "A" à la fin)

Sans chercher à détailler ce que font chacune de ces API, voici un exemple pour la fonction lstrcpyA. Elle compares deux chaînes de caractères ("Case Sensitive"), et retourne une valeure.

int lstrcmp
LPCTSTR lpString1 // address de la première string
LPCTSTR lpString2 // address de la seconde string

Paramètres
lpString1 -> Pointe sur la première chaine non nulle à comparer.
lpString2 -> Points sur la seconde chaine non nulle à comparer.

Cela se traduira par une routine de ce genre ci:

0157:004011DD 50             PUSH EAX      <- pousse votre code sur la pile
0157:004011DE 6830604000     PUSH 406030   <- pousse le bon code sur la pile
0157:004011E3 FF1520924000   CALL [KERNEL32!lstrcmp] <- les compare 
0157:004011E9 85C0           TEST EAX, EAX <- vérifie si ils sont identiques 
0157:004011EB 0F8580000000   JNZ 00401271  <- va en code invalide

je vous laisse imaginer l'intérêt qu'il y a à faire un "d eax" et un "d 406030" avant le call lstrcmp...

· La vérification du format

Suivant la forme des caractères attendus dans les champs de la boite d'enregistrement, le programme va vérifier la validité des chaînes saisies. Ainsi, il pourra contrôler la présence, ou non, d'informations dans ces champs, et leurs formats. Comme il est rare que votre "vrai" nom contienne des ! ou des chiffres, la routine de contrôle se branchera sur la procédure adéquat, et pourra générer un message d'erreur. Il en sera de même si le numéro de série attendu est essentiellement de type alphabétique alors que vous avez entré un sérial numérique. Il peut également être attendu un ou plusieurs séparateurs (-), et finalement la longueur de votre nom, ou du numéro de série peut être contrôlé.

:0040788D
:0040788F
:00407891
:00407893
:00407895
:00407897
:00407899
:0040789B
:0040789C
:0040789D
:0040789E
:004078A0
3C61
7206
3C7A
7702
2C20
8806
42
46
4B
85DB
75EB
BA02
mov al, byte ptr [edx]
cmp al, 61
jb 00407899
cmp al, 7A
ja00407899
sub al,20
mov byte ptr [esi], al
inc edx
inc esi
dec ebx
test ebx, ebx
jne
Prend une lettre dans [edx], et met là dans al
compare al avec 61h (soit le a minuscule ASCII)
dégage si inférieur vers la procédure 00407899
compare al avec 7Ah ( le z minuscule en ASCII)
fiche le camp si supérieur en procédure 00407899
enlève les espaces si tu en trouves ( 20h = " ")
place al dans [esi] = reconstruit une chaîne ailleurs
edx=edx+1 (incrémente edx pour la lettre suivante)
esi=esi+1 (incrémente pour la deuxième chaîne)
ebx=ebx-1 (ebx = nombre de caractères du code )
ebx est il égale à zéro (plus de lettres à lire ?)
Recommence si ca n'est pas le cas, ou continue

Vous aurez remarqué que votre code est réécrit ailleurs, et qu'il est probable que ce soit sur cette nouvelle adresse que les contrôles suivants s'effectueront.

· La génération du code

L'ingéniosité des auteurs dans la génération des codes peut être des plus navrantes jusqu'au plus paranoïaque.

Dans l'exemple suivant, et grâce à VBDis, un décompilateur Visual basic, vous aurez l'intégralité du source saisissant, générant (en gras) et vérifiant votre sérial

If Texte1.Text = "" Or Texte2.Text = "" Then
MsgBox ("Renseignez les deux cadres S.V.P."), 24, "ENREGISTREMENT"
Exit Sub
End If
Texte1.Text = UCase$(Texte1.Text)
ReDim l0028(40)
l002E = 0: l0028(l0032) = 0
For l0032 = 1 To Len(Texte2.Text)
l0028(l0032) = Int((Asc(Mid$(Texte2, l0032, 1))) * l0032 * Len(Texte2.Text) + 46397& / Len(Texte2.Text) + 6) * 152
l002E = l002E + l0028(l0032)
Next l0032
Etiquette2.Caption = l002E + Left$(Texte2.Text, 2)
If UCase$(Texte1.Text) <> UCase$(Etiquette2.Caption) Or Texte1.Text = "" Then
MsgBox ("ERREUR DE CODE"), 64, ("ENREGISTREMENT")
Texte1.Text = ""
Texte2.Text = ""
Open "cdgratis.ini" For Output As #2
Print #2, "LICENCE NON ENREGISTREE"
Print #2, "76614840LI"
Close #2
Unload Me
Else
l0026 = "LE CODE EST CORRECT. "
l0026 = l0026 + Chr$(10)
l0026 = l0026 & UCase$(Texte2.Text)
l0026 = l0026 + Chr$(10)
l0026 = l0026 & " S'EST ENREGISTRE CONVENABLEMENT."
Open "cdgratis.ini" For Output As #2
Print #2, Texte2.Text
Print #2, Texte1.Text
Close #2
MsgBox l0026, 48, "BIENVENUE A CD GRATIS!!!"
l003C = "RELANCEZ CD GRATIS POUR METTRE A JOUR"
l003C = l003C + Chr$(10)
l003C = l003C & "VOTRE ENREGISTREMENT"
MsgBox l003C, 64, "VERSION ENREGISTREE"
End
End If

Dans un autre exemple, voici la partie basse de la routine de génération du sérial:

:0040281B B81F85EB51    mov eax, 51EB851F  > pousse 51EB851F dans EAX
:00402820 F7E7          mul edi            > ???????????????
:00402822 8BF2          mov esi, edx       > EDX sauvé dans ESI
:00402824 8B542414      mov edx, dword ptr [esp+14]
:00402828 C1EE05        shr esi, 05        > décalage à droite de 5
:0040282B 8BFE          mov edi, esi       > ESI sauvé dans EDI
:0040282D 52            push edx           > pousse notre code sur la pile
:0040282E 81F721332153  xor edi, 53213321  > ou logique entre code(h) et 53213321
:00402834 E817390100    call 00416150
:00402839 8B8D94000000  mov ecx, dword ptr [ebp+94]> place un code dans ECX
:0040283F 83C404        add esp, 00000004  > rétablie la pile
:00402842 3BC1          cmp eax, ecx       > compare notre code(h) avec ECX
:00402844 7411          je 00402857        > saute vers code valide
:00402846 8B442414      mov eax, dword ptr [esp+14] > notre code en décimal
:0040284A 50            push eax           > pousse notre code sur la pile
:0040284B E800390100    call 00416150      > conversion en hexa
:00402850 83C404        add esp, 00000004  > rétablie l'état de la pile
:00402853 3BC6          cmp eax, esi       > compare les 2 sérials
:00402855 757B          jne 004028D2       > saute vers code invalide
:00402857 E8508F0300    call 0043B7AC      > début de la proc code valide 

Analysons de plus prés

Si vous avez suivi le chemin qui mène à une validation du sérial, vous aurez remarqué qu'il y avait deux portes de sortie: en 00402844 et 00402855.
Regardons la dernière:
ESI est manipulé en 00402828, et prend la valeur du bon sérial. Auparavant, ce registre à déjà reçu la valeur de EDX en 00402822. C'est donc celui ci qui va nous intéresser. Il vaut à ce stade 0045C662. Mais d'ou lui vient cette valeur?

Après quelques recherches, je suis tombé sur un mov edx, dword ptr [0045C658]. La valeur 0045C662 vient donc d'une adresse [mémoire]. Mais comment est elle arrivée là?

En fait c'est au lancement de l'application que cette valeur est placée dans [0045C658]:

:0041FDE8 B862C64500    mov eax, 0045C662             > ici, dans EAX
:0041FDED A358C64500    mov dword ptr [0045C658], eax > et ici dans [0045C658]
:0041FDF2 A35CC64500    mov dword ptr [0045C65C], eax

Donc EDX contiendra toujours cette même valeur 0045C662, comme EAX contiendra toujours 51EB851F. C'est le MUL EDI en 00402820 qui va modifier EAX et EDX, dont le contenu deviendra 356F6947, puis le SHR ESI,5 qui va finir le travail est établir définitivement le sérial.

Regardons d'un peu plus prés comment ce boutique la génération du sérial:
L'instruction MUL multiplie la destination par l'opérande source. La taille de l'opérande source fixe la taille de la destination. Trois cas peuvent se présenter, la taille de l'opérande source est :

1. un octet: le registre AL ( 8 bits ) est multiplié par la source et le résultat, pouvant tenir sur 16 bits, est placé dans AX

2. un mot : le registre AX ( 8 bits ) est multiplié pas la source et le résultat est placé dans les registre DX (mot de poids fort ) et AX ( mot de poids faible )

3. un double mot : le registre EAX est multiplie par la source et le résultat est placé dans les registres EDX et EAX

Nous sommes dans le troisième cas de figure, et donc Eax contient les 32 premiers bits de la réponse et EDX contient les 32 derniers bits.

La seule variable dans ce système est EDI, c'est donc lui qui va permettre au code de varier suivant des critères que l'on va essayer de décoder, à partir des lignes qui précédent le MUL EDI, la partie haute du générateur de sérial:

01:004027F5 0FBE0406    movsx eax, byte ptr [esi+eax] 
02:004027F9 50          push eax
03:004027FA E8013A0100  call 00416200
04:004027FF 83C404      add esp, 00000004
05:00402802 85C0        test eax, eax
06:00402804 8B442418    mov eax, dword ptr [esp+18]
07:00402808 7409        je 00402813
08:0040280A 0FBE0C06    movsx ecx, byte ptr [esi+eax]
09:0040280E 03CE        add ecx, esi
10:00402810 0FAFF9      imul edi, ecx
11:00402813 8B48F8      mov ecx, dword ptr [eax-08]
12:00402816 46          inc esi
13:00402817 3BF1        cmp esi, ecx
14:00402819 7CDA        jl 004027F5

Avant l'arrivée dans ces lignes, la valeur de EDI est fixé par:
:004027CB 8BBD90000000 mov edi, dword ptr [ebp+00000090]
et EDI devient égale à 11FF0 (h) soit 4592 (d), et ce de façon invariable.

Détaillons un peu ces lignes:

01: EAX reçoit un caractère du Name entré
02: ce caractère est poussé sur la pile
03: pour être contrôlé dans le call
04: on dépile
05: EAX sera = 0 si le caractère entré n'est pas alphabétique, ou si le champ Name est vide
06: EAX contient l'ensemble des caractères entrés dans le champ Name
07: saut si caractères non alphabétiques
08: ECX reçoit un caractère du Name entré (ECX= EAX+ESI), Esi jouant le rôle de pointeur
09: la valeur du pointeur ESI est ajoutée à la valeur ASCII de ECX dans le Name (pointeur ESI)
10: EDI est multiplié ( multiplication signée) avec ECX
11: ECX reçoit de [EAX-08], le nombre de caractères qui composent le Name
12: on incrémente de 01 le Pointeur ESI
13: on compare si la valeur du pointeur est égale au nombre de caractères du Name
14: on boucle en 01 si ca n'est pas le cas, ou on continue

Imaginons que vous rentriez un Name composé de 0000000 (> 6 obligatoirement), la valeur de EDI restera toujours égale à 11FF0 (puisque le Name est numérique et < à 41 (h)), vous aurez donc un MUL EDI qui s'effectuera avec les 3 valeurs 11FF00 (h) pour EDI, 45C662 (h) pour EDX et 517B851F (h) pour EAX. Après la multiplication, EDX vaudra 5C23 (h), puis sera placé dans ESI, il va y avoir un décalage de 5 à droite (SHR ESI,05), et la valeur de ESI va se changer en 2E1 (h), soit 737, le code à entrer pour le Name 0000000.

Voilà de quoi réaliser un générateur de code, il me semble...

· la verification du code

A titre d'exemple, voici un contrôle_de_code ne portant que sur une partie de celui ci, et une méthode pour en localiser la routine:

1- remplir la boite d'enregistrement
2- CTRL-D pour poser un BPX hmemcpy sous Softice
3- F5
4- F10 pour tracer pas à pas jusqu'à la boite "code invalide" en 0001.53C6 "Are you trying to crack my programm…" nan nan!
5- Dans les lignes précedantes, vous aviez une procédure contrôle_du_code qui commencait en 0001.52BE. Si vous éditiez cette procédure, vous découvririez que le code devait comprendre 11 caractères (en 52FE). Votre code est placé dans [bp-26] et s'étale jusqu'à [bp-1B]. Le programme va tester si [bp-1F]=38h, [bp-22]=39h, [bp-23]=35h, [bp-22]=39h, et [bp-26]=36h. Donc seulement 6 des 11 chiffres entrés sont testés, et à des endroits bien précis (rang 1, 2, 4, 5, 7 et 8). Les autres rangs peuvent être complétés avec n'importe quel autre valeur. Donc notre code pourra ressembler à D4159168111.

· La validation du code

Votre sérial ayant été reconnu comme correcte, le programme peut avoir utilisé plusieurs solutions pour s'en "rappeler":

· en plaçant une clé dans la base de registre, ou en y écrivant vos noms et numéro de série
· en faisant la même chose dans un fichier INI (qui lui est propre, ou qui est commun comme le Win.ini), dans une dll, en patchant son propre exécutable...

A chaque fois qu'il en aura l'utilité, il pourra soit vérifier votre statut utilisateur en consultant la clé mise en place, soit vérifier l'état d'un drapeau utilisateur initialisé au lancement de l'application. Cette initialisation de drapeau peut avoir été faite par la mise en place d'un valeur dans une adresse [mémoire], souvent 01, après consultation de la clé mise en place. Si cette clé est sous la forme d'un nom et d'un numéro de série, vous avez des chances que le MEME call que celui qui a servi à valider votre enregistrement soit sollicité, ou un call identique (Couper/Coller), placé à une adresse différente du premier.

Le schéma le plus courant de validation d'un sérial est sous la forme d'un Call/Test/Branchement Conditionnel. Vous rencontrerez souvent des schémas de ce type:

· Call validité du sérial
· Test
· jne -> vers code invalide
· Call génération du sérial
· Call comparaison
· Test
· jne -> vers code invalide
· Call validation
· Test
· jne -> code invalide

Sous cette forme (appel à des Call), ou en ligne, on retrouve peu ou prou ce type de schéma.
Pour mettre la main dessus, voici ma proposition:

Lors d'un tracing avec F10, en partant de la saisie des champs (Bpx Hmemcpy), vous allez faire surgir la message box "code invalide". En remontant de quelques lignes, vous allez repérer le test branchant ou évitant la procédure "Bad boy". Le test conditionnel qui précède peut être soit la comparaison de votre code avec le bon sérial, soit un test plus anodin du genre Test EAX,EAX. Le call qui se trouve au dessus va être celui dans lequel on valide votre statut utilisateur. Le bon numéro de série n'y sera pas obligatoirement, si bien que vous aurez une chance supplémentaire en visitant les call précédants, tout particulièrement si avant ceci, vous voyez un push quelque_chose, dans lequel vous reconnaissait le code que vous avez entré.

Voici un exemple de "mouvements" autour de drapeaux Utilisateurs Reg/Unreg:

:0040284D E8633F0500             call 004567B5 
:00402852 A16C224800             mov eax, dword ptr [0048226C]
:00402857 C784248400000000000000 mov dword ptr [esp+00000084], 00000000 
:00402862 85C0                   test eax, eax
:00402864 0F849E000000           je 00402908
:0040286A A1F8814700             mov eax, dword ptr [004781F8] 
:0040286F 85C0                   test eax, eax
:00402871 0F8591000000           jne 00402908
:00402877 E8EAB20500             call 0045DB66
:0040287C 8B4004                 mov eax, dword ptr [eax+04]
:0040287F 8D4C240C               lea ecx, dword ptr [esp+0C]
:00402883 51                     push ecx
:00402884 8BC8                   mov ecx, eax
:00402886 E815F0FFFF             call 004018A0 
:0040288B 8BF0                   mov esi, eax
:0040288D C684248400000001       mov byte ptr [esp+00000084], 01


Les deux adresses mémoires [0048226C] et [004781F8] ressemblent forts à ce genre d'adresses que l'on interroge pour savoir si l'utilisateur est enregistré ou non, et les deux tests qui les suivent nous envoie au delà de la routine code valide…
De plus, vous aurez sans doute remarqué le [esp+84] d'abord mis à 0, puis mis à 1 si vous passez ces deux tests. C'est assez typique d'une forme: "si l'utilisateur est enregistré, alors [esp+84]=1"
Dans les deux cas, les valeurs de [0048226C] et [004781F8] doivent doit être différentes de 0 pour passer ces tests. En toute logique, le programme doit forcement y mettre quelque chose à un moment ou à un autre: il suffit de trouver où, quand et comment, à défaut de savoir pourquoi...


La recherche du bon sérial ou de son écho

dans ce paragraphe, l'idée est de trouver l'endroit au votre code vas être comparé avec le bon sérial, et à défaut, le moment ou il va être sollicité. Avec un peu de chance l'écho du bon sérial n'est pas très loin:

Lors de la saisie du code, par Hmemcpy éventuellement, celui ci va être écrit dans une adresse en mémoire. Le programme viendra y chercher votre numéro de série à chaque fois qu'il en aura besoin pour en vérifier sa validité. Voilà le principe. Dans les faits, le nom et le numéro de série que vous aurez entrés seront placés dans deux adresses mémoires, souvent proche l'une de l'autre. Le programme viendra d'abord chercher le nom pour l'utiliser avec son générateur de code, puis comparera le résultat avec votre numéro de série. Lors de ces différentes manipulations, le nom et le sérial entrés peuvent se voir placés dans d'autres adresses mémoires, si bien que l'on en trouve parfois 3 à 4 occurrences avant le test qui branchera sur code valide/code invalide. Pour le Vrai code, dans le meilleur des cas, vous en trouverez une seule trace, que la plupart des programmeurs s'empressent de faire disparaître. Je vous propose la méthode suivante pour essayer d'en trouver la trace.

Après avoir rempli les champs de la boite d'enregistrement, et avant de les valider par l'appui sur [enter], ou un clic de souris, faites CTRL-D pour lancer Soft-Ice, posez un bpx hmemcpy, relancez par F5, puis validez la boite d'enregistrement. Au break qui suivra, tapez sur la ligne de commande de soft-ice S 0 L FFFFFFFF "le_numéro_que_vous_avez_entré". S pour search, 0 pour le début de la recherche en mémoire, et FFFFFFFF pour la fin. Soft-Ice va vous retourner la première adresse où il aura trouvé une trace de votre code, sous cette forme:

Pattern Found at 0030:00018D1

Vous poserez alors un BPR 18D1 18D1+(longueur Héxa de votre numéro de série) RW. La commande BPR provoquera un break à chaque sollicitation de cette zone en mémoire. La longueur des adresses à surveiller commence à la première lettre de votre code, et fini après la dernière (en prévision d'une comparaison byte à byte, ou d'une comparaison sur un ou plusieurs bytes à des endroits précis), RW pour Read and Write, lecture ou écriture. Tapez à nouveau la lettre "s" pour que Soft-ice continue ses recherches. Faites la même chose pour les adresses commençants par 800, et laissez tomber celles débutants par C000 (ce sont des adresses miroirs). Posez également des BPR sur votre nom, ainsi quand le générateur de code le saisira pour calculer le Bon sérial, vous en serez averti. Puis relancez le programme.

A chaque break, interrogez le contenu des différents registres et adresses mémoires ([ebp-14], [408269]…) que vous verrez autour de l'instruction qui aura provoqué l'arrêt du logiciel. Il faut penser que les breaks se font sur votre code_entré, pas sur le Vrai Code. Lui peut se trouver dans une autre instruction. Il faut fouiller. Tapez "d eax", et tous les "d" qui vous ferrons plaisir, utilisez l'ascenseur de la fenêtre des datas pour chercher l'écho du Vrai Code +/- 90 lignes au dessus et en dessous du résultat que Soft-Ice vous aura donné pour votre "d quelque_chose" ,. Notez tout ce qui peut ressembler de près ou de loin à un code. Certains programmeurs, pour noyer le poisson, gênèrent des codes qui n'y ressemblent pas vraiment (du genre: "804 < t", ou "r,45,ad"). D'autre fois, le Vrai code est venu se superposer sur de précédentes écritures (par exemple: 804697DF631Z7, pour un code Vrai = 97DF63).

Le cas
CdManager: L'un des break se fera dans l'une des API Windows, Kernel (01), en repZ movsB. Ce genres d'instructions sont très intéressantes pour nous, ce sont des manipulations de chaînes de caractères sur les registres Esi et Edi. Un "d esi" vous donnera votre code, et un "d edi", le Vrai code. Nom:Christal For Free n°:6086555554
Le cas
Audio Compositor: Cette fois ci le bon numéro de série sera visible dans l'éxécutable, en 004337DB8 cmp al, [esi]. Faîtes un "d al", et vous obtiendrez pour "christal" le code suivant: "AsiUqdBP"
Le cas
WebEdit: Pour le nom "christal", le numéro de série sera donné dans l'exécutable en 0D17:117A Push Edi: 1FDBAAFB3BDI

Voici une façon de récupérer le bon sérial:

:004A2074 5A                      pop edx
:004A2075 8950FC                  mov dword ptr [eax-04], edx
:004A2078 C740F801000000          mov [eax-08], 00000001
:004A207F C6041000                mov byte ptr [eax+edx], 00
:004A2083 C3                      ret

Un " d eax+edx " va vous afficher votre sérial dans la fenêtre des Datas, et en dessous de celui ci un sérial du même genre, mais encore incomplet, en pointillé dirons-nous…
Voilà qui semble prometteur. Alors que faire ?
On vous la dit et répété, " dans le doute ne t'abstient jamais ", et posez un break point sur 004A207F, puis relancer par F5… Et ca va devenir magique !
A chaque break que fera Softice sur cette adresse, vous verrez, sans avoir rien à faire, le numéro de série se modifier dans la fenêtre des Data, jusqu'à avoir son état définitif. C'est un vrai plaisir de regarder apparaître petit à petit la solution sous vos yeux, et il n'est pas courant qu'un crack soit aussi visuel…
Pour ma part, j'obtiens : Christal EZCDDAX3-A1E3A18A-5611D793-550


La comparaison directe en décimal

00403EDB cmp eax, edx -> compare votre code avec le bon code
00403EDD je 00403F72  -> saute en procédure "code valide" si OK

Le contenu des registres EAX et EDX aura été initialisée comme ceci:

004AA361 mov eax, dword ptr [ebp-18] -> votre code dans eax
004AA364 mov edx, dword ptr [ebp-0c] -> le bon code dans edx
004AA367 call 00403ED4               -> vérifie la conformité
004AA36C jne 004AA3E8                -> vers code invalide


La comparaison directe en hexadécimale

Entrez vos informations dans les champs Name, Company (Optional), et Reg. Code.

1- Posez un bpx GetWindowTextA (ou un bpx Hmemcpy)
2- Appuyez une fois sur F12
3- Tracez jusqu'à faire apparaître la boite " The registration information you have entered…"

Et vous serez ici:

:004028D0 EB0E                    jmp 004028E0
:004028D2 6A00                    push 00000000
:004028D4 6A00                    push 00000000
:004028D6 685C714500              push 0045715C
:004028DB E87CE30200              call 00430C5C   -> ici

Comme il y a un jmp en 004028D0, il y a eu probablement un saut conditionnel qui a envoyé à la ligne suivante.

4- Remontez dans les codes de SoftIce, à sa recherche:

:0040284A 50                      push eax        > notre code(d) dans EAX
:0040284B E800390100              call 00416150   > conversion décimal/hexa
:00402850 83C404                  add esp, 04     > rétablie la pile
:00402853 3BC6                    cmp eax, esi    > notre code (h) dans EAX
:00402855 757B                    jne 004028D2    > vers code invalide

Et si le programme compare notre code (dans EAX sous sa forme hexadécimale) avec ESI, il y a de forte probabilité pour que ESI contienne le bon code…

Voici un petit truc: le code que je rentre à chaque fois que je rencontre une Reg Box, est 489335. Converti en hexadécimal, vous obtenez 77777. Dans le cas de TOGGLE Disk Space, en 00402853, la comparaison cmp EAX, ESI, a immédiatement retenue mon attention, dans la mesure ou, dans la fenêtre des Registres, je voyais le 77777 affiché pour EAX


La comparaison caractère par caractère

Commençons par un code unique qui sera valable pour n'importe quel programme:

:00403222 83F805                  cmp eax, 00000005
:00403225 0F8596000000            jne 004032C1
:0040322B 8A0F                    mov cl, byte ptr [edi]
:0040322D 80F932                  cmp cl, 32
:00403230 0F858B000000            jne 004032C1
:00403236 807F0237                cmp byte ptr [edi+02], 37
:0040323A 0F8581000000            jne 004032C1
:00403240 8A4704                  mov al, byte ptr [edi+04]
:00403243 3C36                    cmp al, 36
:00403245 757A                    jne 004032C1
:00403247 384F01                  cmp byte ptr [edi+01], cl
:0040324A 7575                    jne 004032C1
:0040324C 384703                  cmp byte ptr [edi+03], al
:0040324F 7570                    jne 004032C1
:00403251 8D442408                lea eax, dword ptr [esp+08]
:00403255 68EE580000              push 000058EE
:0040325A C7462401000000          mov [esi+24], 00000001

Voici quelques éléments qui pouvaient permettre d'identifier la routine de validation du code entré:

- le cmp eax, 00000005 -> le nombre de caractères entrés est-il égale à 5
- après la majorité des tests: un jne 004032C1 -> si pas égale va en code invalide
Vous aurez remarqué le nombre de jne 004032C1, significatifs dans un endroit comme celui ci…
- à la fin des tests: mov [esi+24], 00000001 -> si tout Ok alors [esi+24] = 1
c'est le classique si_le_code_est_ok alors met une valeur (drapeau utilisateur) à 1

Mais quel est le code exactement?

- [edi] contient votre numéro de série. Le premier caractère est mis dans cl, et comparé avec 32
-> avec un convertisseur hexadécimale 32 (héxa) = 2 (décimal)
- le troisième caractère [edi+2] est comparé avec 37 (3 décimal)
- le cinquième caractère [edi+4] est mis dans al, et comparé avec 36 (5 décimal)
- le deuxième caractère est comparé avec cl (qui est sensé valoir [edi]=32h)
- le quatrième caractère est comparé avec al (qui est sensé valoir [edi+4]=36h)
Vous aurez donc un numéro de série égale à 2 2 7 6 6.

· le drapeau Utilisateur

Histoire de se rappeler qu'un utilisateur a entré dans la boite d'enregistrement un code valide, le programme va laisser une trace quelque part qui lui permettra de s'en souvenir. A chaque fois qu'il aura besoin de connaître votre statut (enregistré ou non?), il consultera sa "mémoire". En fonction, il désactivera (écrans shareware, trial période…) ou au contraire activera (version complète, nom de l'utilisateur dans une boite "version"…) certaines parties du programme. Bien souvent, afin de ne pas se faire repérer, cette "trace" sera déposée dans un ebp-04 par exemple. Le jeu du cracker est de trouver où et comment un utilisateur valable se ferait enregistrer, à défaut de trouver le bon sérial.

Suivant les programmeurs, vous pourrez trouver des schémas de protection du type suivant:

Call appel_du_code_entré
Call contrôle_du_code_entré
Test eax,eax
saut vers_procédure_code_OK et affichage de l'écran "merci de vous être…"

Avec un peu de chance, " l'enregistrement " de votre statut d'utilisateur autorisé se fera dans le procédure Code_OK. Votre crack est fini.

Le principe est simple: si le code est bon, le programme initialise soit l'un des registres (par ex: eax, ebx...), soit une adresse mémoire, en y mettant 1 la plupart du temps (0=faux, 1=bon).

Par exemple:   mov eax,01
                      mov byte ptr [ebp-14],01
                      mov byte ptr [ebp-14],eax
                      mov byte ptr [0049A750],01  etc...

Donc, quand vous voyez une mise à 01, ou des manipulations sur des adresses [mémoires], soyez vigilant, et tout particulièrement à des parties de programme du genre

:00495143       mov edx, dword ptr [ebp-08]  met [ebp-08] dans edx
:00495146 	call 004038F8                contrôle la validité du code
:0049514B 	jne 00495156                 si faux, va en "code invalide"
:0049514D 	mov byte ptr [0049A750], 01  si ok, [0049A750]=1
:00495154 	jmp 004951A5                 va vers proc "code valide"
:00495156 	mov byte ptr [0049CAB6], 01  début proc "code invalide"

j'espère qu'avec ces premières idications vous pourrez commencer à aborder la recherche des sérials. Il reste une catégorie de programme qui n'a pas été abordé, ce sont ceux écrit en Visual basic, pour lesquels je vous invite à lire "Comment cracker du Visual Basic" (même auteur, même édition).

Dans l'un de ses textes, +ORC faisait une remarque du genre: "apprenez à reconnaître ces types classiques de schémas et vous ne serrez plus un chercheur, mais un historien…".

Pour ma part, je ne suis pas très fort dans la recherche des codes. J'ai une nette préférence, par facilité, à rechercher les drapeaux utilisateurs, mais certains dont vous lirez les textes sont particulièrement doué pour y arriver, et savent ensuite écrire les générateurs de code qui vont donneront les sérials dont vous réviez.
A tout ceux là, je leur tire mon chapeau...

Bonne Journée

Christal