Keygenerator de Winzip
Par Papow
|
Winzip32 v6.x & v7.0
(générateur de Reg Codes) |
| Introduction |
Ce tutoriel à pour but de comprendre ce qu'il y a derrière les routines
que l'on shunt pour cracker un programme, avec l'aboutissement évident de réaliser un générateur
de Reg Codes.
J'ai donc choisi un programme très répandu (qui n'a pas Winzip ?) et
assez démonstratif : le patch est quasi immédiat et la routine de vérification du Reg Code
assez facile à identifier (elle n'est pas dispersée en cinq ou six fois).
La première des choses à faire est d'essayer notre programme pour avoir une idée des fonctions qu'il propose, connaître son principe d'enregistrement et repérer des informations qui pourraient nous être utiles ultérieurement:
On lance Winzip ... quel superbe "Unregistered" écran (pas pour
longtemps, je l'espère) ... et nous voila dans l'application principale.
On y apprend rien de particulier, tout à l'air de fonctionner correctement.
On va dans le menu [Help] >> [About Winzip], et on découvre enfin qu'on
peut enregistrer (légalement) notre magnifique version de Winzip. Essayons donc !
Nous devons remplir deux TextBox, la première pour notre nom
(j'utiliserai papaow), et la deuxième pour notre reg code (12345).
Evidemment le Reg # n'est pas bon et nous obtenons un message très
interressant : "Incomplete or incorrect information ...", qui poura peut être nous servir ultérieurement.
Le fichier d'aide lui par contre ne nous aide pas beaucoup, la seul
information que j'y ai trouvée concerne le prix d'une copie de Winzip ($29).
| Travail initial |
On va commencer à partir de la seul indication valable que
nous avons sur Winzip: le message d'erreur en cas d'enregistrement invalide. On part d'abord sur l'hypothèse
qu'ils sont un peu couillons chez Winzip (et ça va marcher ...)
_ Faites un dead-listing de Winzip32.exe à l'aide de W32Dasm.
_ Ouvrez le menu [Search] de W32Dasm et essayez de retrouver
la chaîne "Incomplete or incorrect
information".
Normalement vous devriez obtenir
* Reference To: USER32.GetDlgItemTextA, Ord:00F5h
|
:00409D6D FF15C86A4700
Call dword ptr [00476AC8]
:00409D73 0FB60558124700
movzx eax, byte ptr [00471258]
:00409D7A 85C0
test eax, eax
:00409D7C 7414 je 00409D92
:00409D7E 0FB60578F54600
movzx eax, byte ptr [0046F578]
:00409D85 85C0
test eax, eax
:00409D87 7409
je 00409D92
:00409D89 E85CF9FFFF
call 004096EA
:00409D8E 85C0
test eax, eax
:00409D90 7541
jne 00409DD3 ; renvoie plus loing
dans le programme
(saute la routine "mauvaise clef...").
* Referenced by a (U)nconditional or (C)onditional
Jump at Addresses:
|:00409D7C(C), :00409D87(C)
|
:00409D92 E805020000
call 00409F9C
* Possible Reference to String Resource ID=00654: "Incomplete or incorrect information"
|
:00409D97 688E020000
push 0000028E
:00409D9C E82EB10100
call 00424ECF
:00409DA1 59
pop ecx
Si on regarde juste au-dessus de notre chaîne de référence on peut observer qu'il y a trois possibilités pour le programme d'afficher ce message : la réalisation des deux premiers sauts (c-à-d eax=0), et la non réalisation du dernier (eax non nul).
Nous allons donc travailler sur cette piste afin de savoir si la modification de ces conditions de saut ne nous permettrait pas d'éviter la routine destinée à celui qui s'est trompé de numéro.
Dans ce but je vous propose un rappel de toutes les instructions d'assembleur que nous allons utiliser dans ce tutotiel. (Vous pouvez evidemment sauter le chapitre suivant si vous n'en avez pas besoin).
| Opcodes |
J'ai fait des copier-coller pour ce passage et je n'ai absolument pas envie de m'ammuser à le traduire. (j'ai déja mis en avant les points importants pour chaque instruction, ce qui évidemment dépend beaucoup de votre niveau).
MOV - Move Byte
or Word
Usage: MOV
dest, src
Modifies
flags: None
Copies byte or word
from the source operand to the destination operand. If the destination is SS
interrupts
are disabled except on early buggy 808x CPUs. Some CPUs disable interrupts if the
destination
is any of the segment registers
MOVZX - Move with
Zero Extend (386+)
Usage: MOVZX dest, src
Modifies flags: None
Copies the value of the source operand to the destination register with
the zeroes extended.
SUB - Subtract
Usage: SUB dest, src
Modifies
flags: AF CF OF PF SF ZF
The source is subtracted from the destination and the result is stored in the destination.
ADD - Arithmetic
Addition
Usage: ADD
dest, src
Modifies
flags: AF CF OF PF SF ZF
Adds "src"
to "dest" and replacing the original contents
of "dest". Both operands are binary.
INC - Increment
Usage: INC dest
Modifies
flags: AF OF PF SF ZF
Adds one to destination unsigned binary
operand.
IMUL - Signed Multiply
Usage: IMUL
dest, src
Modifies flags: CF OF (AF,PF,SF,ZF
undefined)
Variations
of this instruction allow specification of source and destination registers as well as a third immediate factor.
CMP - Compare
Usage: CMP
dest, src
Modifies
flags: AF CF OF PF SF ZF
Subtracts source from destination
and updates the flags but does not save result.
Flags
can subsequently be checked for conditions.
TEST - Test For
Bit Pattern
Usage: TEST dest, src
Modifies
flags: CF OF PF SF ZF (AF undefined)
Performs
a logical AND of the two operands updating the flags
register without saving the result.
JE -Jump if Equal
Jump
condition : ZF=1 (Zero Flag, the n° 6)
JNE - Jump if Not Equal
Jump
condition : ZF=0 (Zero Flag, the n° 6)
JGE - Jump if Greater or Equal
(signed)
Jump condition
: SF=OF (Sign Flag, the n° 7)
LEA - Load Effective
Address
Usage: LEA
dest, src
Modifies flags: None
Transfers offset address of "src" to the destination register.
PUSH - Push Word
onto Stack
Usage: PUSH
src
Modifies
flags: None
Decrements
SP by the size of the operand (two or four, byte values are sign extended) and transfers
one word from source to the stack top (SS:SP).
SHL - Shift Logical
Left
Usage: SHL dest, count
Modifies flags: CF OF PF SF ZF (AF undefined)
Shifts the destination left by
"count" bits with zeroes shifted in on right. The Carry Flag contains the last bit shifted out.
AND - Logical And
Usage:
AND dest, src
Modifies flags: CF OF PF SF ZF (AF undefined)
Performs a logical AND of the
two operands replacing the destination with the result.
XOR - Exclusive
OR
Usage: XOR dest, src
Modifies
flags: CF OF PF SF ZF (AF undefined)
Performs a bitwise
exclusive OR of the operands and returns the result
in the destination.
LEAVE - Restore
Stack for Procedure Exit (80188+)
Usage: LEAVE
Modifies flags: None
Releases the local variables created by the previous ENTER instruction by restoring SP and BP to their
condition before the procedure stack frame was initialized. (Before exit procedure).
RET - Return From
Procedure
Usage:RET nBytes
Modifies flags: None
Transfers
control from a procedure back to the instruction address saved on the stack.
"n bytes" is an optional number of bytes to release. Far returns pop the IP followed by
the CS, while near returns pop only the IP register.
| Le crack |
On va donc tester notre première intuition à propos des trois instructions
de saut avec un débuggeur.
Retournez sous l'écran d'enregistrement de Winzip et remplissez les deux TextBox
:
Name: Papaow
Registration #: 7777777
Ensuite pressez Ctrl - D ... nous somme dans Softice.
Nous allons mettre un point d'arrêt qui stoppera l'exécution du programme
juste après la validation de notre reg code, donc tapez bpx GetDlgitemTexta
pour mettre en place le point d'arrêt et Ctrl
- D pour quitter Softice.
Pressez le bouton 'OK'.
Softice interromp Winzip et nous nous retrouvons dans la fonction GetDlgItemTextA.
Tapez 'BD 00'
pour désactiver le point d'arrêt et ' F12' pour retourner dans la section de code de Winzip (pour être sûr vérifiez
en bas de l'écran correspondant au segment de code, vous devez voir "Winzip32!.text" et non pas
celui de modules ou diverses dll de Windobe).
Vous verrez alors le segment de code suivant :
:00409D58 FF15C86A4700
Call dword ptr [00476AC8]
:00409D5E 6A0A
push 0000000A
:00409D60 6878F54600
push 0046F578
Oh que c'est joli
:00409D65 68810C0000
push 00000C81
tout ce code !
:00409D6D FF15C86A4700
Call dword ptr [00476AC8]
:00409D73 0FB60558124700
movzx eax, byte ptr [00471258]
:00409D7A 85C0
test eax, eax
:00409D7C 7414
je 00409D92
:00409D7E 0FB60578F54600
movzx eax, byte ptr [0046F578]
:00409D85 85C0
test eax, eax
:00409D87 7409
je 00409D92
:00409D89 E85CF9FFFF
call 004096EA
Ne vous inquiétez pas
:00409D8E 85C0
test eax, eax On va essayer de l'ellucider.
:00409D90 7541
jne 00409DD3
:00409D92 E805020000
call 00409F9C
:00409D97 688E020000
push 0000028E
:00409D9C E82EB10100
call 00424ECF
:00409DA1 59
pop ecx
Maintenant essayons de comprendre tout cela.
Quelques instructions de Softice :
wd ## pour voir ## lignes du segment de données.
watch eax pour voir le registre eax.
d #### pour voir ce qu'il y a à l'adresse #### .
ed eax pour éditer la donnée pointé par eax (avec la traduction ascii
!).
r eax pour modifier eax.
F10 pour parcourir le code (en l'exécutant).
F8 pour parcourir les calls.
A chaque étape nous observerons le code, les registres et le segment de données.
:00409D58 FF15C86A4700
Call dword ptr [00476AC8] ; GetDlgItemTexta
(User 32).
:00409D5E 6A0A
push 0000000A ; RAS dans le segment de données.
:00409D60 6878F54600
push 0046F578
; 7777777 dans le segment
de données !!!
:00409D65 68810C0000
push 00000C81
; RAS dans le segment de données.
:00409D6D FF15C86A4700
Call dword ptr [00476AC8] ; GetDlgItemTexta (User 32).
:00409D73 0FB60558124700
movzx eax, byte ptr [00471258] ;
Papaow dans le segment de
données!
eax=50 : le P de "Papaow".
:00409D7A 85C0
test eax, eax
; eax & eax <>0 ?
(y a-t-il une lettre dans
Name ?)
:00409D7C 7414
je 00409D92 ; s'il
y en une, on continu.
:00409D7E 0FB60578F54600
movzx eax, byte ptr [0046F578] ; eax=37 :
le 7 de "7777777".
:00409D85 85C0
test eax, eax ; eax & eax <>0 ?
(y a-t-il une lettre dans
Reg# ?)
:00409D87 7409
je 00409D92 ; s'il
y en une, on continu.
:00409D89 E85CF9FFFF
call 004096EA ; Ce saut modifie eax:
maintenant eax=0 !!
:00409D8E 85C0
test eax, eax ; évidemment eax & eax =0 ... pas de saut!
:00409D90 7541
jne 00409DD3 ;
|
:00409D92 E805020000
call 00409F9C ;
| On
:00409D97 688E020000
push 0000028E ;
| a
:00409D9C E82EB10100
call 00424ECF ;
| perdu.
:00409DA1 59
pop ecx ;
|
Mais si vous stoppez l'exécution juste avant l'instruction jne (n'appuyez
plus sur F10) vous voyez :
eax = 0 dans la fenêtre des registres (dans le cas contraire tapez watch
eax).
Nous allons modifier la valeur de eax pour obtenir un saut vers la bonne routine d'enregistrement : nous devons donc modifier eax avec une valeur non nulle, 1 par exemple.
Tapez r eax 1 ,
maintenant eax=1 et nous pouvons exécuter le saut.
On appui sur F10
.... et voila!
On se trouve en face d'un magnifique écran nous demandant confirmation quant à la validité de notre numéro d'enregistrement et si nous l'avons obtenu de la part de revendeurs autorisés ... ils sont vraiment stupides ... on appuira donc sur le bouton Cancel
Maintenat on va s'intéresser à ce call qui modifie eax et à la façon dont notre numéro d'enregistrement est contrôlé.
| Le générateur de Reg Code |
Nous allons maintenant parcourir la fonction qui met eax à zéro en regardant les manipulations suspectes faites avec notre nom (00471258), notre Reg Code (0046F578), ou le registre eax.
... nous avons là beaucoup de travail ... il faut penser aux écritures en mémoire ... aux arguments des calll ... et au reste !
Une sous fonctin de ce call est tout particulièrement intéressante :
:004097DE 8D85F8FDFFFF
lea eax, dword ptr [ebp+FFFFFDF8]
:004097E4 50
push eax
:004097E5 6858124700
push 00471258
; notre nom.
:004097EA E8D4000000
call 004098C3
Si vous observez ce qu'il y a en mémoire à l'adresse pointé
par eax juste avant le call vous trouverez à [EBP+FFFFFDF8], un segment de huit zéros.
Quel peut bien être l'intéret de passer ce segment en argument à une fonction
?
Tapez F10 et regardez à
la même adresse juste après le call !
Nous avons un nouveau segment de huits chiffres un peu bizards qui ne ressenblent
certainement pas à un compteur ou à une adresse en mémoire ...
Avez vous deviné ?
Oui, [38B60673] est mon numéro personnel d'enregistrement !
Mais notre travail n'est pas uniquement de trouver notre code personnel (on aurrait tout aussi bien pu patcher le programme pour arriver au même résultat, ce qui n'aurrait nécessité que quelques minutes).
Nous voulons trouver une solution pour pouvoir enregistrer Winzip, à tous les coups, sans même toucher à l'exécutable
Nous allons donc être obligés d'étudier cette routine et de la comprendre.
:004098C3 55
push ebp
:004098C4 8BEC
mov ebp, esp
:004098C6 83EC10
sub esp, 00000010
:004098C9 668365F400
and word ptr [ebp-0C], 0000
:004098CE 668365F800
and word ptr [ebp-08], 0000
:004098D3 8B4508
mov eax, dword ptr [ebp+08] ; Récupère
Name.
:004098D6 8945FC
mov dword ptr [ebp-04], eax ; le sauve
localement à
[ebp-04].
:004098D9 668365F000
and word ptr [ebp-10], 0000 ; initialisation
d'un compteur
à l'adresse [ebp-10].
:004098DE EB13 jmp 004098F3 @2
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00409915(U)
|@1
:004098E0 668B45F0
mov ax, word ptr [ebp-10] ; récupère le compteur.
:004098E4 66050100
add ax, 0001
; l'incrémente et
:004098E8 668945F0
mov word ptr [ebp-10], ax ; le sauve.
:004098EC 8B45FC
mov eax, dword ptr [ebp-04] ; récupère Name.
:004098EF 40
inc eax
; décallage d'une lettre
:004098F0 8945FC
mov dword ptr [ebp-04], eax ; dans le nom et sauvegarde.
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004098DE(U)
|@2
:004098F3 8B45FC
mov eax, dword ptr [ebp-04] ; notre nom est dans eax.
:004098F6 0FB600
movzx eax, byte ptr [eax] ; récupère la première lettre
du nom.
:004098F9 85C0
test eax, eax ; y en
a-t-il une ?
:004098FB 741A
je 00409917 @#
; oui : on continu, non : jump .
:004098FD 8B45FC
mov eax, dword ptr [ebp-04] ; récupère de nouveau la première
:00409900 0FB600
movzx eax, byte ptr [eax] ; lettre de notre nom.
:00409903 0FB74DF0
movzx ecx, word ptr [ebp-10] ; ecx = compteur.
:00409907 0FAFC1
imul eax, ecx ; la lettre est multipliée
par le compteur.
:0040990A 668B4DF4
mov cx, word ptr [ebp-0C] ; charge le buffer1 dans cx.
:0040990E 6603C8
add cx, ax
; ajoute le résultat de la
multiplication à buffer1.
:00409911 66894DF4
mov word ptr [ebp-0C], cx ; sauvegarde de buffer1.
:00409915 EBC9
jmp 004098E0 @1
Les 5 premières lettre de Papaow ont donc été utilisées
(y a-t-il une lettre?,oui ... non)
A ce niveau du code nous avons 4 des 8 chiffres de notre reg Code, ils sont situés
à [ebp-0C].
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004098FB(C)
|
:00409917 C7055C1C470001000000 mov dword ptr [00471C5C],
00000001
:00409921 8B4508
mov eax, dword ptr [ebp+08] ;
récupère Name.
:00409924 8945FC
mov dword ptr [ebp-04], eax ;
le sauvegarde localement
à [ebp-04].
:00409927 EB07
jmp 00409930 @2
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00409956(U)
|@1
:00409929 8B45FC
mov eax, dword ptr [ebp-04] ;
récupère Name.
:0040992C 40
inc eax
; décallage d'une lettre, sauvegarde
:0040992D 8945FC
mov dword ptr [ebp-04], eax ; de la nouvelle position.
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00409927(U)
|@2
:00409930 8B45FC
mov eax, dword ptr [ebp-04] ; place Name dans eax.
:00409933 0FB600
movzx eax, byte ptr [eax] ; récupère une lettre.
:00409936 85C0
test eax, eax ;
y a-t-il quelque chose?
:00409938 741E
je 00409958 @3
; oui : continu, non : jump .
:0040993A 6821100000
push 00001021 ;
push une constante.
:0040993F 8B45FC
mov eax, dword ptr [ebp-04] ; notre nom se trouve dans eax.
:00409942 660FB600
movzx ax, byte ptr [eax] ; une
lettre est placée dans ax.
:00409946 50
push eax
;
push eax.
:00409947 FF75F8
push [ebp-08] ; push buffer2.
:0040994A E831000000
call 00409980 ; un call !
:0040994F 83C40C
add esp, 0000000C ; pas d'utilité.
:00409952 668945F8
mov word ptr [ebp-08], ax ;
sauvegarde du résultat dans buffer2.
:00409956 EBD1
jmp 00409929 @1
Une fois de plus on a utilisé toute les lettres de notre nom, et nous obtenons (pas exactement) les 4 autres chiffre de notre Reg Code (à [EBP-08]), mais il reste une sous-routine que nous n'avons toujours pas étudiée (on va le faire juste après la fin de celle là).
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00409938(C)
|@3
:00409958 668B45F8
mov ax, word ptr [ebp-08] ; place les quatres autres chiffres dans ax.
:0040995C 66056300
add ax, 0063
; ax = ax+63.
Exactement
maintenant !
:00409960 668945F8
mov word ptr [ebp-08], ax ; sauvegarde du résultat.
:00409964 0FB745F4
movzx eax, word ptr [ebp-0C] ; eax = 0000 ####
(où ####= buffer2 + 63).
:00409968 50
push eax
; push eax.
:00409969 0FB745F8
movzx eax, word ptr [ebp-08] ; eax = 0000 ####
(où ####= buffer1).
:0040996D 50
push eax
; push eax.
* Possible StringData Ref from Data Obj ->"%04X%04X"
|
:0040996E 6854394600
push 00463954 ;
pas d'importance.
:00409973 FF750C
push [ebp+0C] ;
pas d'importance.
:00409976 E865B90300
call 004452E0 ;
Est ce que notre Reg Code est
bon ?
; bien sûr que non ...
=> eax=0!
:0040997B 83C410
add esp, 00000010
:0040997E C9
leave
:0040997F C3
ret
; retour au point de départ.
Maintenant nous avons une bonne connaissance de la routine de test, mais nous ne pouvons toujours pas fabriquer de générateurs de Reg Code, car il nous reste encore une sous-routine à étudier, la dernière! (mais la plus dure).
:00409980 55
push ebp
; on place à [EBP+8]
la valeur qui
:00409981 8BEC
mov ebp, esp
; se trouve à [EBP-08].
:00409983 51
push ecx
; buffer3=buffer2
:00409984 668B450C
mov ax, word ptr [ebp+0C] ; on récupère la
lettre.
:00409988 66C1E008
shl ax, 08
; ax = lettre * 256 .
:0040998C 6689450C
mov word ptr [ebp+0C], ax ; sauvegarde du résultat à
l'adresse [EBP+0C] (L2).
:00409990 8365FC00
and dword ptr [ebp-04], 00000000 ; compteur=0.
:00409994 EB07 jmp 0040999D @2
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004099DE(U)
|@1
:00409996 8B45FC
mov eax, dword ptr [ebp-04] ; incrémente
:00409999 40
inc eax
; le compteur et
:0040999A 8945FC
mov dword ptr [ebp-04], eax ; le sauvegarde.
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00409994(U)
|@2
:0040999D 837DFC08
cmp dword ptr [ebp-04], 00000008 ; est ce que
compteur <= 8 ?
:004099A1 7D3D
jge 004099E0 @5
; oui : continu.
:004099A3 0FB74508
movzx eax, word ptr [ebp+08] ; récupère buffer3.
:004099A7 0FB74D0C
movzx ecx, word ptr [ebp+0C] ; récupère L2.
:004099AB 33C1
xor eax, ecx
; eax XOR L2.
:004099AD 2500800000
and eax, 00008000
; if eax
>= 8000
:004099B2 85C0
test eax, eax
; continu,
else
:004099B4 7412
je 004099C8 @3
; jump to @3
:004099B6 0FB74508
movzx eax, word ptr [ebp+08] ; récupère buffer3.
:004099BA D1E0
shl eax, 1
; eax= buffer3 * 2.
:004099BC 0FB74D10
movzx ecx, word ptr [ebp+10] ; 1021.
:004099C0 33C1
xor eax, ecx
; eax= eax XOR 1021.
:004099C2 66894508
mov word ptr [ebp+08], ax ; sauvegarde de eax
dans buffer3.
:004099C6 EB0B
jmp 004099D3 @4
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004099B4(C)
|@3
:004099C8 668B4508
mov ax, word ptr [ebp+08] ; récupère
buffer3.
:004099CC 66D1E0
shl ax, 1
; ax= buffer3 * 2.
:004099CF 66894508
mov word ptr [ebp+08], ax ; sauvegarde
de buffer3.
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004099C6(U)
|@4
:004099D3 668B450C
mov ax, word ptr [ebp+0C] ; récupère
L2.
:004099D7 66D1E0
shl ax, 1
; L2= L2 * 2 .
:004099DA 6689450C
mov word ptr [ebp+0C], ax ; sauvegarde
de L2.
:004099DE EBB6
jmp 00409996 @1 ; on repare au début.
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004099A1(C)
|@5
:004099E0 668B4508
mov ax, word ptr [ebp+08] ; ax = buffer3.
:004099E4 C9
leave
; fin du
:004099E5 C3
ret
; call.
La valeur de buffer3 est replacée dans buffer2 à la fin de chaque exécution de la sous-routine, en fait buffer3 est buffer2, mais il y a un décallage de ebp à chaque appel de la sous-routine :
mov ebp, esp
Cette instruction à pour résultat que [ebp+8] n'a pas la même valeur à l'intérieur et à l'extérieur de la sous-routine, et c'est la raison de "buffer3".
Rq : le nom de L2 signifie "nouvelle lettre" car on part d'une
lettre que l'on modifie à chaque passage... évidemment!
| Conclusion |
Nous avons maintenant tout en main pour créer un générateur de Reg Code, qui fait la différence entre celui qui comprend vraiment ce qu'il fait et celui qui fille directement à la fin dans les tutoriels (cf Le Patch).
Létape de la création du générateur doit être considérée comme un aboutissement et non comme une fin (il n'y à cas aller dans la rubrique "Crack" et vous y trouverez le programme compilé), ausi je ne ferai que vous conseiller pour sa réalisation, mais je ne vous donnerai pas le code source (ou pas tout de suite ...)
for (int k=1;k<=Name.Length();k++)
{
c=Name[k];
buf += (c * (k-1));
}
J'espère que ce tutoriel n'est pas trop flou et qu'il vous a permis, pour
ce qui en avait besoin, de comprendre ce qu'il y avait derrière un call de vérification de Reg Code.
Pour ceux qui savent programmer en assembeur j'attend vos commentaires, vos remarques (et aussi vos sugestions).
Pour tout complément d'information : Boite01@hotmail.com