Ecrire un KeyGénérator
Inintiation à l'assembleur
For Newbies Only
Modem Booster 1.2
By Christal & X-or
Part I
Tripatouillage autour d'un serial
Commençons par définir les éléments qui vont permettre de trouver comment le serial
est généré.
Chaque programme est un cas particulier. Beaucoup d'entre eux peuvent se ranger dans tels ou tels catégories,
d'autres sont atypiques...
Modem Booster n'est en rien radicalement inhabituel des autres, à ceci prés que la méthode
que j'ai utilisée est un peu différente de mes habitudes.
Recherche des chaînes intéressantes, éventuellement d'un serial en clair, avec SuperSnooper
:
Modem Booster Support Questions
support@inKlineGlobal.net
htt://www.inKlineGlobal.com
{b7094990-a244-11d3-b67d-0000b4915dc9}
Location of CompuServe Browser
CompuServe 2000 Setup
{b7094990-a244-11d3-b67d-0000b4915dc9}
La clé {b7094990-a244-11d3-b67d-0000b4915dc9} revient un très grand nombre de fois dans le fichier retourné par SuperSnooper
(Drag and Drop).
Un petit tour dans la base de registre va donner :
[HKEY_CLASSES_ROOT\CLSID\{b7094990-a244-11d3-b67d-0000b4915dc9}]
[HKEY_CLASSES_ROOT\CLSID\{b7094990-a244-11d3-b67d-0000b4915dc9}\ProgID]
"ProgID"=dword:39c1d1cb
[HKEY_CLASSES_ROOT\CLSID\{b7094990-a244-11d3-b67d-0000b4915dc9}\Reg]
"Reg"=dword:00000000
[HKEY_CLASSES_ROOT\CLSID\{b7094990-a244-11d3-b67d-0000b4915dc9}\Name]
"Name"="christal"
[HKEY_CLASSES_ROOT\CLSID\{b7094990-a244-11d3-b67d-0000b4915dc9}\Company]"Company"=" "
Je vous le donne émile, que va t il se passer en mettant 00000001 dans REG
?
Bingo !
Le prog est Registered...
Valà, valà, par ici la sortie pour ceux qui voulait juste cracker cette cible...
Pour ceux qui ont du mal à s'endormir, voici la suite :
Un p'tit coup de ProcDump va nous donner quelques infos intéressantes :
Name V Size V Offset Raw Size Raw Offset Characteristics .text 00075207 00001000 00076000 00001000 60000020 .rdata 0001746E 00077000 00018000 00077000 40000040 .rsrc 0006ED88 0009B000 0006F000 00096000 40000040
Bien, bien...
Nous avons des sections alignées (Virtual offset/Raw offset), ce qui va être très pratique
pour trouver la relation entre les adresses du programme et les offsets de l'éditeur hexadécimal
(offset + ImageBase (00400000) = adresse).
Ensuite, la section .rdata commence à l'offset 77000 (adresse 477000). C'est dans cette section que vont
se retrouver les chaînes de caractères (DB et autres) que le programmeur aura définie.
Un rapide traçage dans un hex-éditeur va donner deux choses amusantes :
004907C4 37 37 39 38 47 35 33 35-36 00 00 00 38 38 39 39 7798G5356...8899 004907D4 54 33 33 39 35 00 00 00-4C 61 75 6E 63 68 69 6E T3395...Launchin
Et notamment deux trucs qui ressemblent furieusement à des sérials...
En continuant un peu, vous trouverez deux espèces de tables des plus louches, dont voici un exemple:
491830: 72 6F 67 72-65 73 73 00-2E 00 00 00-39 58 38 37 rogress . 9X87 491840: 33 00 00 00-38 47 36 38-36 00 00 00-32 46 37 37 3 8G686 2F77 491850: 37 00 00 00-39 55 32 36-34 00 00 00-32 4B 34 35 7 9U264 2K45 491860: 39 00 00 00-37 44 32 33-35 00 00 00-35 58 36 36 9 7D235 5X66 491870: 35 00 00 00-39 59 37 38-34 00 00 00-38 46 33 38 5 9Y784 8F38 491880: 37 00 00 00-34 48 39 39-38 00 00 00-38 54 32 32 7 4H998 8T22 491890: 33 00 00 00-33 33 34 34-38 00 00 00-33 34 32 37 3 33448 3427 4918A0: 32 00 00 00-32 39 36 39-39 00 00 00-33 35 34 39 2 29699 3549 4918B0: 39 00 00 00-38 37 39 38-38 00 00 00-38 38 31 36 9 87988 8816 4918C0: 38 00 00 00-34 39 38 35-35 00 00 00-39 32 32 38 8 49855 9228 4918D0: 32 00 00 00-36 33 33 37-33 00 00 00-43 4C 53 49 2 63373 CLSI 4918E0: 44 00 00 00-50 72 6F 67-49 44 00 00-52 65 67 00 D ProgID Reg 4918F0: 43 6F 6D 70-61 6E 79 00-4E 61 6D 65-00 00 00 00 Company Name
Glop ! Glop !
Aurions nous affaire à un programme qui comparera le serial entré avec l'une ou l'autre de ces valeurs
étranges ?
Et bien pour le savoir, rien ne vaut un bon petit débuggeur, et de judicieux BreakPoints on memory Range
;
BPR 004907C4 004907D8 R (pour
Read)
BPR 00491830 004918D8 R
BPR 00491178 004911F8 R
Si ma petite idée est bonne, je devrais avoir un break dès que ces tables seront utilisées
en lecture, et peut-être lors d'une comparaison avec le serial entré ? ? ?
Héhé,
Rien ne se passe tant qu'un serial n'est pas entré dans la boite d'enregistrement, mais dès un chatouilli
sur le bouton, on tombe sur un REPNZ SCASB en 0040A562. On relève la nouvelle adresse dans laquelle le serial
repéré a été déplacé, et on place un nouveau BPR, qui va nous faire arriver
ici :
1er test
0040A59D MOV ECX,[004781CC] > serial repéré 0040A5A3 MOV EDX,[ESP+0C] > serial entré
004907C4 37 37 39 38 47 35 33 35-36 00 00 00 38 38 39 39 7798G5356...8899 004907D4 54 33 33 39 35 00 00 00-4C 61 75 6E 63 68 69 6E T3395...Launchin 014AD244 34 38 39 33 33 35 00 20-53 65 72 69 66 00 43 6F 489335. Serif.Co 014AD254 6E 74 72 6F 6C 53 65 74-5C 53 65 72 76 69 63 65 ntrolSet\Service
0040A5A7 PUSH ECX > poussés tous les deux 0040A5A8 PUSH EDX > sur la pile 0040A5A9 CALL 0043CAF5 > comparaison 0040A5AE ADD ESP,08 0040A5B1 TEST EAX,EAX > c'est Glop Glop? 0040A5B3 JZ 0040A628 > oui ou non 0040A5B5 PUSH 00008007 0040A5BA LEA ECX,[ESP+0C] > pousse serial entré 0040A5BE CALL 00451E27 0040A5C3 MOV EAX,[ESP+08] 0040A5C7 PUSH 00 0040A5C9 PUSH 10 0040A5CB PUSH EAX 0040A5CC CALL 00459F97 > affiche Pas Glop!
Oui ou Non ?
Je vous laisse deviner...
Mais un R FL Z (inversion du Zéro Flag) va permettre de continuer sans passer par le call affichant le message
"Pas glop! pas Glop!" (jamais rien compris à l'Anglais, on traduit comme on peu...)
En continuant à tracer, on break (1ere table) en passant sur un call à un endroit fort plaisant :
00441006 MOV ESI,[EBP+0C] > charge table dans ESI 00441009 MOV EDI,[EBP+08] > place serial dans EDI 0044100C LEA EAX,[00498790] 00441012 CMP DWORD PTR [EAX+08],00 > serial présent? 00441016 JNZ 00441053 > go out 00441018 MOV AL,FF > al = -1 0044101A MOV EDI,EDI > replace serial dans EDI 0044101C OR AL,AL > plus de caractères? 0044101E JZ 0044104E > Goto End 00441020 MOV AL,[ESI] > caractère de ESI dans AL 00441022 INC ESI > caractère suivant 00441023 MOV AH,[EDI] > caractère de EDI dans AH 00441025 INC EDI > caractère suivant 00441026 CMP AH,AL > on trouve une correspondance? 00441028 JZ 0044101C > non -> loop
C'est ce call qui va se charger, pour les trois séries repérées
(les deux serials en goguette, et les deux tables), de comparer la concordance entre caractères.
Le programme va tourner jusqu'à avoir trouvé une correspondance entre deux caractères identiques
(un de la table, l'autre du serial), ou jusqu'à épuisement des caractères.
00491178 31 35 64 63 39 7D 00 00-35 36 39 38 52 00 00 00 15dc9}..5698R... 00491188 33 34 36 37 4B 00 00 00-39 37 37 38 54 00 00 00 3467K...9778T... 00491198 34 39 36 39 4E 00 00 00-34 39 38 35 50 00 00 00 4969N...4985P... 004911A8 35 32 32 33 58 00 00 00-38 38 36 36 4E 00 00 00 5223X...8866N... 004911B8 36 33 33 37 48 00 00 00-33 35 34 39 42 00 00 00 6337H...3549B... 004911C8 33 33 34 34 56 00 00 00-38 47 36 38 41 00 00 00 3344V...8G68A... 004911D8 33 46 37 37 48 00 00 00-39 55 39 36 4D 00 00 00 3F77H...9U96M... 004911E8 32 4B 34 35 4B 00 00 00-37 44 34 33 42 00 00 00 2K45K...7D43B...
Et en continuant à tracer encore un peu, vous aurez le loisir d'avoir un
troisième Break sur la dernière des tables.
A la sortie de ce call (et de quelques autres, le point d'entrée étant le call 0043CAF5), vous arrivez
ici :
00415738 CALL 0043CAF5 > contrôle serial entré 0041573D ADD ESP,08 00415740 TEST EAX,EAX > si EAX=-1 alors Pas Glop! 00415742 JZ 00415779 > si saut inversé alors "Registered"
Et si vous n'inversez pas le saut, une boite "Pas Glop!" apparaîtra en traçant sur un call
en 0040A7A1.
Ce qu'il va y avoir de fâcheux, avec ce programme, c'est qu'à chaque bon serial entré, ou à
chaque inversion des deux branchements PAS GLOP !, il faudra retourner dans la base de registre pour enlever le
01 dans REG...
Hypothèse :
Le serial entré, puisqu'il y a comparaison avec la série des deux valeurs SCASBées, puis les
deux tables, pourrait bien être composé d'un petit morceau de chaque ?
Non ?
J'ai donc tenté ma chance en entrant le serial suivant (et en faisant attention à prendre une série
de chiffres dans les tables en fonction de leur ordre d'apparition, me réservant la possibilité de
les inverser si le programme s'amusait à commencer par la fin/1er table)
7798G53563467K8G686
1er test : Glop ! Glop
7798G5356 (serial entré) est comparé avec une valeur identique.
2ème test : Pas Glop ! Pas Glop !
Le programme voulait à tout pris comparer 467KG8 (mon serial) avec 3467K (sa table), et le dernier contrôle
sur 686 et 8G686.
Bref, rien ne va plus...
Aurais-je loupé quelque chose ?
Vi, bien sur !
Pourquoi que le programmeur qu'il se serait amusé à créer un ensemble de trois séries
de chiffres, si que ce n'était pas pour les voir joliment séparé par des tirets ? ? ?
Hein ?
Pourquoi ?
Ben en fait, des espaces, ou n'importe quoi d'autre fait aussi bien l'affaire, mais j'ai plutôt penché
pour des zolis tirets...
7798G5356-3467K-8G686
Et un serial qui va bien, UN !
Ensuite toutes les combinaisons sont acceptées. Indifféremment le programme accepte le 1er ou le
second serial (série1), avec n'importe lequel des serials de la table 1, et celui qui vous plaît de
la table 2.
Et la dedans, votre nom et votre compagnie ne servent rigoureusement à rien...
Quel Gâchis !
Voyons si il n'y aurait pas moyen de "personnaliser" votre serial, autrement qu'en utilisant une quelconque
fonction aléatoire, pour bricoler un chtit KeyMaker...
Le détails concernants l'utilisation des APIs sont de X-or (http://www.multimania.com/w32asm/ )
;========================================================================= ; ce source ASM est dérivé de celui de Win Zip codé par Papaow ;=========================================================================
.386 ; les fonctions utilisées sont compatibles
; avec l'architecture du 386 (vous avez
; aussi le 486 et le 586 pour les Pentium
.model flat, stdcall
option casemap :none ; case sensitive
; il différenciera majuscule et minuscule
;=========================================================================
Les includes (inc) et les library (lib) vont êtres choisis en fonctions des APIs que vous utiliserez.
Les fonctions de l'api Windows sont contenues dans des librairies
chargées dynamiquement au démarrage du programme. Ces librairies (les DLLs) les plus couramment utilisées
sont kernel32.dll pour la gestion des processus et de la mémoire, user32.dll pour l'interface et gdi32.dll
pour les fonctions graphiques. Le problème est que le programme ne charge pas toutes les dlls au démarrage.
Il faut donc préciser à MASM que vous utilisez une fonction externe mais en plus qu'elle doit être
importée d'une dll précise. Pour indiquer qu'une fonction externe est utilisée dans le module
(le fichier source asm) il faut ajouter au début du module les lignes qui suivent :
include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\gdi32.inc include \masm32\include\masm32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib includelib \masm32\lib\gdi32.lib includelib \masm32\lib\masm32.lib
Il y a encore un point à préciser sur les fonctions
de l'api win32. Dans la documentation officielle vous trouverez le nom standard des fonctions, mais dans les dlls
vous aurez des fonctions du même nom succédées d'un A ou d'un W (par ex MessageBoxA ou MessageBoxW.
Le A signifie ANSI et le W signifie UNICODE, ces deux lettres précisent donc le format des chaînes
de caractères utilisées par les fonctions donc celles que vous passez en paramètre et celles
que vous recevez en retour de fonction.
;=========================================================================
; Et pour commencer, il faut déclarer les constantes et les variables dont
; on aura besoin :
.const
;========================================================================= Prog equ "Modem Booster 1.2" ; Prog et Auteur seront automatiquement Auteur equ "Christal" ; reconnus comme des chaînes de caractères ;=========================================================================
IDC_Gen equ 3001 ; le bouton "Generate" se voit affecter la valeur 3001 IDC_Exit equ 3002 ; mais vous pouvez choisir celle qui vous plaît... IDC_About equ 3003 ; l'avantage est de rendre plus lisible le source.
.data
;========================================================================= ; Données dont vous aurez besoin ;=========================================================================
; chacunes de ces strings vont être stockées à une adresse précise,
lors de
; la compilation. Elles doivent impérativement se terminer par un 0
; (NULL Terminated String)
ClassName db "WinClass",0
NameApp db Prog," KeyMaker by [Christal]",0
EditClass db "edit",0
StaticClass db "static",0
ButtonClass db "button",0
DlgName db "Dialog",0
IconName db "Icon2",0
TextAbout db "Keymaker pour Modem Booster 1.2",0Dh,0Ah,0Ah,0Ah
db "Coded by Christal et Papaow",0
; TextAbout s'affiche sur deux lignes, dans la boite About, avec un CR
Caption db Prog,0
; Prog sera remplacé par la chaîne déclaré plus haut
TextNom db "Enter your name here:",0 TextKey db "Your serial number:",0 TextCoded db "cODED bY [Christal]",0 ButtonAbout db "&About",0 ButtonExit db "&Exit",0 ButtonGen db "&Generate",0 FontString db "Arial", 0 serial0 db "7798G5356",0 serial1 db "8899T3395",0 serial2 db "5698R3467K9778T4969N4985P5223X8866N6337H3549B3344V8G68"db A3F77H9U96M2K45K7D43B5X66U9Y78X8F38K4H99R8T33E",0 serial3 db "9X8738G6862F7779U2642K4597D2355X6659Y7848F3874H9988T223"db "334483427229699354998798888168498559228263373",0
.data?
;=========================================================================
; Variables dont vous aurez besoin
; c'est variables, n'ayant pas par avance une taille définie, sont déclarées
; à part, pour que le compilateur leur réserve une place suffisante
;=========================================================================
buffer db 512 dup (?) ; place réservée pour Buffer : 512 octets
buffer2 db 512 dup (?)
hInstance HINSTANCE ? ; très important, c'est l'identifiant que votre
; application va se voir définir (en fonction
; des autres tâches) et qui permettra à l'OS
; de savoir qui est "propriétaire" des
; événements qui se produiront
Le paramètre hInstance est un nombre identifiant le programme en cours d'exécution. hInstance signifie handle Instance, sachez qu'un handle est une sorte d'index dans un tableau contenant des caractéristiques. Ici Windows tient un tableau d'info sur le programme et notre numéro d'handle lui permet de savoir ou regarder dans son tableau pour avoir le numéro qu'on lui donne (on peut connaître le handle d'un programme, sous SoftIce, en faisant TASK pour trouver le nom de la tâche recherchée, puis HWND pour obtenir le tableau des différents événements qui s'y rattache)
:task
TaskName SS:SP StackTop StackBot StackLow TaskDB hQueue Events
Loader32 0000:0000 007FB000 00800000 1246 11E7 0000
Modemboo 0000:0000 0063E000 00640000 3C36 4577 0000
Spool32 0000:0000 0063E000 00640000 45F6 454F 0000
:hwnd Modemboo
Window Handle hQueue SZ QOwner Class Name Window Procedure
05A8(1) 4577 32 MODEMBOOS WinClass 1427:00000BD2
05AC(2) 4577 32 MODEMBOOS Edit (1er champ) 177F:00000CC6
05B0(2) 4577 32 MODEMBOOS Edit (2d champ) 177F:00000CC6
05B4(2) 4577 32 MODEMBOOS Static (Enter) 178F:00005C20
05B8(2) 4577 32 MODEMBOOS Static (Your) 178F:00005C20
05BC(2) 4577 32 MODEMBOOS Static (cODED) 178F:00005C20
05C0(2) 4577 32 MODEMBOOS Button (Gen) 178F:00001040
05C4(2) 4577 32 MODEMBOOS Button (Exit) 178F:00001040
05C8(2) 4577 32 MODEMBOOS Button (About) 178F:00001040
CommandLine LPSTR ? ; pointeur
hwnd HWND ? ; c'est l'instance propre à une fenêtre
hwndButton HWND ? ; ou à un bouton, un champ...
hwndEdit1 HWND ? ; c'est grâce à ces instances que le prog
hwndEdit2 HWND ? ; saura où et à qui attribuer un événement
hwndStatic1 HWND ? ; au sein d'une fenêtre
hwndStatic2 HWND ? ; Edit1 pour le champ1, static1 pour du texte
hwndStatic3 HWND ? ; etc...
hwndButton1 HWND ? ; toutes ces Instances vont être réclamées par
hwndButton2 HWND ? ; le système lors de l'appel des API
hwndButton3 HWND ?
La notion de fenêtre est extrêmement large pour le programmeur sous Windows. En effet une fenêtre peut très bien être la fenêtre d'une application, mais aussi tout composant de cette fenêtre : edit box, text box, bouton, etc...
Key DWORD ? ; les DWORD sont des adresses réservées, de
Key1 DWORD ? ; taille non définie, mais dont j'aurais
Key2 DWORD ? ; besoin pour stocker mes informations
Font1 DWORD ?
;=========================================================================
; fin de la déclaration des constantes et des variables,
; et début du programme
;=========================================================================
.code
start:
; Le programme doit obligatoirement commencer par START.
; Il deviendra l'EntryPoint de l'application
invoke GetModuleHandle, NULL ; on récupère le handle de l'application mov hInstance, eax ; différent à chaque nouveau lancement du prog
La première étape va être de récupérer
le handle du programme. Pour cela on utilise la fonction GetModuleHandleA avec comme paramètre
0. Le handle correspondant au process principal.
Notez l'utilisation de la macro call GetModuleHandle, 0
Le handle est dans eax... mov hInstance, eax va le stocker.
Je rappelle qu'en retour d'une fonction win32, c'est dans eax que se trouve la valeur de retour.
En ce qui concerne les registres, eax, ecx et edx ne sont pas préservés lors des appels aux fonctions
de l'api win32. Prenez donc vos précautions lorsque vous utilisez ces registres...
Pour les registres ebx, edx, edi, esi et ebp, c'est le contraire, ils sont toujours préservés et
Windows attend de vous que vous en fassiez de même....
Ensuite on récupère le pointeur vers la ligne de commande avec la fonction GetCommandLineA
invoke GetCommandLine mov CommandLine, eax
LPTSTR GetCommandLine(VOID)
C'est une fonction sans paramètre dont la valeur de retour est un pointeur vers la chaîne Command-line
du process en cours.
;=========================================================================
; puis on lance la procédure principale...
; son nom est obligatoirement suivi de PROC et doit se terminer par ENDP
;=========================================================================
WinMain
proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
int WINAPI WinMain( HINSTANCE hInstance, // handle de l'instance en cours HINSTANCE hPrevInstance, // handle de l'instance précédente LPSTR lpCmdLine, // pointe vers command line int nCmdShow // montre l'état de la fenêtre );
Notez que sous win32, Handle=hInstance.
Le paramètre hPrevInstance est le handle de l'instance précédente de l'application. Ce paramètre
n'a de sens que sous win 16 et doit être égal à NULL (=0) sous win32.
Le paramètre lpCmdLine est un pointeur vers la ligne de commande ayant exécuté l'application
(du style "c:\monapp.exe /v"), ce qui est utile pour récupérer des paramètres en
ligne de commande. Enfin le paramètre nCmdShow représente l'état de la fenêtre de l'appli
au démarrage (cf doc officielle).
Cette fonction WinMain ne retourne (cad fini) que lorsque l'application est fermée. Il ne reste alors plus
qu'à quitter pour revenir à Windows.
LOCAL wc:WNDCLASSEX ; wc et msg sont des variables locales. Elles ne LOCAL msg:MSG ; seront reconnues qu'au sein de WinMain PROC
La structure WNDCLASSEX est similaire à la structure WNDCLASS. Il y a deux différences, WNDCLASSEX inclu le membre cbSize, qui spécifie la taille de la structure, et le membre hIconS, qui contient l'handle d'une petite icône associée avec la window class.
;========================================================================= ; Traçage de la boite de dialogue ;=========================================================================
mov wc.cbSize,SIZEOF WNDCLASSEX ; spécifie la taille, en bytes, de la structure mov wc.style, CS_DBLCLKS ; spécifie le style de la Class
; CS_DBLCLKS Envoi un message double-click à la procédure
de fenêtre
; quand l'utilisateur double clic avec la souris.
mov wc.lpfnWndProc, OFFSET WndProc ; pousse l'adresse de la procédure mov wc.cbClsExtra,NULL ; Specifie le nombre d'extra bytes
; à allouer suivant la structure Window-class. L'OS initialise les Bytes à zéro.
mov wc.cbWndExtra,NULL ; idem pour l'Instance Window.
push hInstance ; pousse l'Instance sur la pile
pop wc.hInstance ; et la récupère dans WC.INSTANCE
mov wc.hbrBackground,COLOR_BTNFACE+1 ; définie la couleur de fond
mov wc.lpszClassName,OFFSET ClassName ; Pointe vers une sting null-terminated
; qui spécifie la nom de la window class
invoke LoadImage,hInstance,ADDR IconName,IMAGE_ICON,32,32,LR_LOADMAP3DCOLORS
La fonction LoadImage charge une icône, un curseur ou un bitmap.
HANDLE LoadImage(
HINSTANCE hinst, // handle de l'instance qui contient l'image
LPCTSTR lpszName, // nom de l'identificateur de l'image
UINT uType, // type de l'image
int cxDesired, // largeur désirée (32, ici)
int cyDesired, // hauteur désirée
UINT fuLoad // charge Drapeau
);
mov wc.hIcon,eax
mov wc.hIconSm,NULL
invoke LoadCursor,NULL,IDC_ARROW
La fonction LoadCursor charge un curseur spécifié dans les ressources de l'exécutable (.EXE) associé avec l'instance de l'application.
HCURSOR LoadCursor(
HINSTANCE hInstance, // handle de l'apllication
LPCTSTR lpCursorName // nom de l'identificateur de la ressource liée au curseur
mov wc.hCursor,eax invoke RegisterClassEx, addr wc
RegisterClassExA (respectivement RegisterClass) permet définir
une classe de fenetre en lui attribuant une fonction gérant les messages, et d'autres caractères
comme les icones...
. Vous créez la fenêtre avec la fonction CreateWindowExA, vous l'affichez avec la fonction ShowWindow et vous rentrez dans la
boucle des messages. Cette dernière est arrêtée par l'envoi d'un message de fermeture à
l'application. Le rôle de cette boucle est de demander à Windows de lui envoyer un message de la liste
d'attente (par la fonction GetMessageA), de le traduire (par la fonction TranslateMessage qui par exemple traduit
les scan codes du clavier en codes ASCII) et de l'envoyer à la fenêtre de l'application à laquelle
elle est destinée (par la fonction DispatchMessageA).
INVOKE CreateWindowExA,WS_EX_WINDOWEDGE,\ ADDR ClassName,ADDR NameApp,\ WS_SYSMENU or WS_MINIMIZEBOX or WS_DLGFRAME or WS_BORDER\ or WS_CLIPCHILDREN or WS_CLIPSIBLINGS or WS_VISIBLE,\ 280,200,385,162,NULL,NULL,hInst,NULLmov hwnd,eax
INVOKE ShowWindow, hwnd,SW_SHOWNORMAL INVOKE UpdateWindow, hwnd
La fonction ShowWindow met en place l'état d'affichage déterminé pour la fenêtre.
BOOL ShowWindow( HWND hWnd, // handle de la fenêtre int nCmdShow // montre l'état de la fenêtre );
SW_MINIMIZE et SW_SHOWNORMAL vont permettre les manipulations de la fenêtre à travers l'une des 3 petites icônes en haut et à droite de celle ci : la réduction (le tiret) de l'application qui ira se loger dans la barre des tâches, et la restitution à la taille précédente de celle ci, si vous cliquez sur l'application en attente dans la barre des tâches.
.WHILE TRUE INVOKE GetMessage, ADDR msg,NULL,0,0
Récupère tout msg adressé à notre application et le stocke dans la structure Msg (les msg sont récupérés 1 par 1). Si eax=0 en sortie, c'est la fin de la boucle. Eax=0 si le message est WM_QUIT et si eax=-1 il y a une erreur
BOOL GetMessage( LPMSG lpMsg, // adresse de la structure avec message HWND hWnd, // handle de la fenêtre UINT wMsgFilterMin, // premier message UINT wMsgFilterMax // dernier message );
.ENDW mov eax,msg.wParam ret
WinMain endp ; fin de la procédure principale
;=========================================================================
WndProc
proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
wparam et lparam, sont des paramètres utilisés
ou non par les messages:
mov eax, uMsg ; message à traiter.IF eax==WM_DESTROY ; si fermeture de la fenêtre demandée invoke PostQuitMessage,NULL ; Envoie le msg WM_QUIT à l'appli ; ce qui va nous sortir de la boucle ; des message et donc de la fonction ; principale et donc de l'application !
Vous avez du remarquer dans WNDCLASSEX le champ lpfnWndProc, définissant
la procédure chargée de la fenêtre définie par la classe. Cette fonction nommée
WindowProc est appelée par Windows lorsque la fonction DispatchMessage lui envoie un message. Vous comprenez
donc qu'elle a pour rôle de traiter les messages reçus. On appelle cette fonction WindowProc un CALLBACK. Ce qui signifie
que c'est une fonction dont vous donnez l'adresse à Windows pour qu'il puisse l'appeler tout seul (cad sans
vous le demander), contrairement à une fonction que vous appelleriez entre deux fonctions win32 par exemple...
Ici ce callback gère les messages envoyés à la fenêtre. Ces messages peuvent être
très divers, du mouvement de la souris au besoin de redessiner un zone de l'écran en passant par
le clavier....
Maintenant vous devez connaître les messages susceptibles de vous être envoyés par Windows.
Pour cela cherchez dans la documentation officielle (le win32.hlp) la liste vous intéressant. Pour une fenêtre
ce sera "Window messages", pour la souris "mouse input messages" pour une boite de dialogue
"dialog box messages" etc...
Ensuite, vous devez dans votre fonction WindowProc redéfinir la gestion du message et ensuite renvoyer 0
ou bien appeler la fonction DefWindowProc, qui fera un traitement standard du message. Ce système est très
pratique car il permet d'intercepter les messages du système et les redéfinir pour nous servir. Par
exemple, lorsque vous clicker sur la croix pour fermer une appli, le msg WM_CLOSE est envoyé, qui par le
traitement standard appelle la fonction DestroyWindow qui elle envoie le message WM_DESTROY à la fenêtre
provoquant la fermeture de l'application. Si vous redéfinissez le traitement du msg WM_CLOSE dans votre
appli en le prenant en charge dans WindowProc, vous pouvez empêcher la fermeture de l'appli ou demander une
confirmation...
Mais quant on parle de messages, ca ne s'arrêtent pas à la seule réception de messages...
Vous pouvez en envoyer, à l'aide de la fonction SendMessage. Ceci a de l'intérêt par exemple
lorsque vous ne voulez pas ré écrire un bout de code qui gérait un autre message il suffit
de vous envoyer le message qui exécutera le code en question. Vous pouvez aussi interagir avec les fenêtres,
par exemple, en envoyant le message WM_SETTEXT à une appli, avec en paramètre une chaîne de
caractères, vous pourrez changer le titre de l'appli, en l'envoyant à une boite d'édition,
vous modifierez le texte qu'elle contient
.ELSEIF eax==WM_CREATE ; sinon création des différentes fenêtre associées
; création du 1er champ.
; les paramètres WS_VISIBLE, ES_AUTOHSCROLL... vont déterminer le style de
; champ : visible, permettant l'écriture, autorisant le scroll...
invoke CreateWindowExA,WS_EX_CLIENTEDGE, ADDR EditClass,NULL,\
WS_CHILDWINDOW or WS_MAXIMIZEBOX or WS_VISIBLE or\
ES_AUTOHSCROLL,12,30,261,23,hWnd,NULL,hInstance,NULL
mov hwndEdit1,eax
HWND CreateWindowEx( DWORD dwExStyle, // style de fenêtre étendue LPCTSTR lpClassName, // pointe vers le nom de classe enregistrée LPCTSTR lpWindowName, // pointe vers le nom de la fenêtre DWORD dwStyle, // style de fenêtre int x, // cordonnée horizontale de la fenêtre int y, // cordonnée verticale de la fenêtre int nWidth, // largeur de la fenêtre int nHeight, // hauteur de la fenêtre HWND hWndParent, // handle de la fenêtre parent ou propriétaire HMENU hMenu, // handle du menu ou de l'identifiant de la fenêtre fille HINSTANCE hInstance, // handle de l'applicationinstance LPVOID lpParam // point vers les données de la fenêtre de création );
la fonction CreateWindow spécifie la Class Window, le window title, le window style, et (optionnel) les coordonnées
initiales et la taille de la fenêtre. La fonction spécifie également le handle de la fenêtre
parent ou propriétaire.
; création du second champ. Ici le champ sera grisé, et en lecture seul
invoke CreateWindowExA,WS_EX_CLIENTEDGE, ADDR EditClass,NULL,\ WS_CHILDWINDOW or ES_READONLY or WS_VISIBLE or\ ES_AUTOHSCROLL,12,80,261,23,hWnd,NULL,hInstance,NULL
mov hwndEdit2,eax
; création du texte " Enter your Name here "
invoke CreateWindowExA,WS_EX_NOPARENTNOTIFY, ADDR StaticClass,ADDR TextNom,\ WS_CHILD or WS_VISIBLE or ES_LEFT ,\ 12,15,180,15,hWnd,NULL,hInstance,NULL
mov hwndStatic1,eax
; création du texte " Your serial number is "
invoke CreateWindowExA,WS_EX_NOPARENTNOTIFY, ADDR StaticClass,ADDR TextKey,\ WS_CHILD or WS_VISIBLE or ES_LEFT ,\ 12,65,180,15,hWnd,NULL,hInstance,NULL
mov hwndStatic2,eax
; création du texte " cODED by... "
invoke CreateWindowExA,WS_EX_NOPARENTNOTIFY, ADDR StaticClass,ADDR TextCoded,\ WS_CHILD or WS_VISIBLE or ES_LEFT ,\ 12,110,180,15,hWnd,NULL,hInstance,NULL
mov hwndStatic3,eax
; création du bouton " ABOUT "
invoke CreateWindowExA,NULL, ADDR ButtonClass,ADDR ButtonAbout,\ WS_CHILDWINDOW or WS_MAXIMIZEBOX or WS_VISIBLE or WS_GROUP or WS_TABSTOP,\ 295,100,75,23,hWnd,IDC_About,hInstance,NULL
mov hwndButton1,eax
; création du bouton " EXIT "
invoke CreateWindowExA,NULL, ADDR ButtonClass,ADDR ButtonExit,\ WS_CHILDWINDOW or WS_VISIBLE or WS_CLIPSIBLINGS,\ 295,40,75,23,hWnd,IDC_Exit,hInstance,NULL
mov hwndButton2,eax
; création du bouton " GENERATE "
invoke CreateWindowExA,NULL, ADDR ButtonClass,ADDR ButtonGen,\ WS_CHILDWINDOW or WS_VISIBLE or WS_CLIPSIBLINGS\ or BS_DEFPUSHBUTTON,\ 295,10,75,23,hWnd,IDC_Gen,hInstance,NULL
mov hwndButton3,eax
invoke SetFocus, hwndEdit1
; création du type de font (ici ARIAL) qui va être choisi. Par défaut c'est
; la font " TIME NEW ROMAN " qui est utilisée
invoke CreateFontA, -11, -5, 0, 0, 150, FALSE, FALSE, FALSE,\ DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,\ DEFAULT_QUALITY, DEFAULT_PITCH,ADDR FontString
mov Font1, eax
; Font1 va être utilisé pour les textes des champs et des boutons
invoke PostMessageA, hwndStatic1, WM_SETFONT, Font1, TRUE invoke PostMessageA, hwndStatic2, WM_SETFONT, Font1, TRUE invoke PostMessageA, hwndButton1, WM_SETFONT, Font1, TRUE invoke PostMessageA, hwndButton2, WM_SETFONT, Font1, TRUE invoke PostMessageA, hwndButton3, WM_SETFONT, Font1, TRUE invoke PostMessageA, hwndStatic3, WM_SETFONT, Font1, TRUE
La fonction PostMessage place un message dans la file d'attente associée avec le thread qui a créé la fenêtre spécifiée.
BOOL PostMessage(
HWND hWnd, // handle de la fenêtre concernée
UINT Msg, // message à placer (poster)
WPARAM wParam, // paramètre du premier message
LPARAM lParam // paramètre du second message
);
.ELSEIF eax==WM_COMMAND ; gestion des événements (clic, enter...) mov eax,wParam .IF ax==IDC_About ; si bouton ABOUT concerné shr eax,16 .IF ax==BN_CLICKED ; et si il s'agit d'un clic/souris invoke MessageBoxA, NULL,addr TextAbout, addr Caption,040h
(voir texte de Morgatte sur les MessageBox)
.ENDIF .ENDIF .IF ax==IDC_Gen ; si bouton GENERATE concerné shr eax,16 .IF ax==BN_CLICKED ; et si il s'agit d'un clic/souris invoke GetWindowTextA,hwndEdit1,ADDR buffer,512
; saisie le texte se trouvant dans le 1er champ pour le placer dans
; l'adresse pointée par BUFFER, limité à 512 caractères.
La fonction GetWindowText copie le texte du champ pointé par hwndEdit1 (the specified window's title bar) dans un buffer
(définie dans .const).
INT GetWindowText ( HWND hwnd, // handle de la fenêtre ou du contrôle contenant le texte LPTSTR lpsz, // pointeur vers un tampon qui recevra le texte int nChars // nombre maximum de caractères à copier dans le tampon.
Les caractères dépassant la limite seront tronqués ) ;
;========================================================================= ; KeyGeneraor ;=========================================================================
Xor ebx,ebx ; met EBX à zéro
lea eax, buffer ; charge Name dans EAX
mov bl, byte ptr [eax] ; prend 1er caractère du Name
cmp bl, "A" ; le compare avec "A"
jl defaut1 ; si inférieur -> valeur par défaut
cmp bl, "Z" ; si supérieur à "Z"
jg defaut1 ; valeur par défaut
lea esi, serial0 ; place la série définie (1ère)dans ESI
jmp suite0 ; saute par dessus choix /défaut
defaut1:
lea esi, serial1 ; place série définie (2ème) dans ESI
suite0:
lea edi, Key ; adresse serial à créer dans EDI
mov ecx, 09 ; 9 caractères à déplacer
rep movsb ; de serial(0 ou 1) vers KEY
mov byte ptr [edi], 2dh ; et finit par un "-"
suite1:
mov bl, byte ptr [eax+1] ; 2eme caractère du Name
lea esi, serial2 ; charge table serial2 dans ESI
mov eax, "A" ; EAX = A -> limitateur bas
mov ecx, "M" ; ECX = M car pas plus de 20 séries
mov edx, 41h ; EDX va servir à déterminer la place
; de la lettre choisie dans l'alphabet
call glop ; routine de pointage
mov byte ptr [edi], 2dh ; et place un "-" à la fin de la série déplacée
lea eax, buffer ; charge Name dans EAX
mov bl, byte ptr [eax+2] ; 2eme caractère du Name
lea esi, serial3 ; charge table serial2 dans ESI
mov eax, "A" ; et on rétablie les valeurs,
mov ecx, "M" , de EAX, ECX et EDX
mov edx, 61h
call glop
;=========================================================================
; Affichage du résultat
;=========================================================================
; affiche le serial généré dans le second champ
invoke SetWindowTextA,hwndEdit2,ADDR Key
La fonction SetWindowText change le texte du champ spécifié (the specified window's title bar).
BOOL SetWindowText(
HWND hWnd, // handle de la fenêtre
LPCTSTR lpString // adresse de la string qui devra être affichée
);
Fin:
;========================================================================= ; Fin ;=========================================================================
.ENDIF .ENDIF .IF ax==IDC_Exit ; si le bouton EXIT shr eax,16 .IF ax==BN_CLICKED ; est clické invoke DeleteObject, Font1
La fonction DeleteObject efface une fonte, un bitmap, une zone ou une palette, libérant tout le système de la ressource associée à l'objet. Après que l'objet ait été effacé, l'handle spécifié n'est plus valide.
invoke DestroyWindow,hWnd
La fonction DestroyWindow détruit la fenêtre spécifiée. La fonction envoie un message à
WM_DESTROY et WM_NCDESTROY pour que la fenêtre soit désactivée. La fonction détruit
également le menu Window (s'il y en a), efface les messages dans la file d'attente, détruit les timers...
Si la fenêtre spécifiée est parent ou propriétaire (je vous rappelle qu'une fenêtre
peut être un bouton, un champ...), DestroyWindow détruit automatiquement les enfants associés
en même temps que le parent.
.ENDIF .ENDIF .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam
La fonction DefWindowProc appelle la procédure Window par défaut. En fait elle rend la main à la fonction WinMain en effaçant tous les messages en attente.
ret .ENDIF xor eax,eax ret WndProc endp
;========================================================================= ; Pointage dans les serials 2 et 3 ;=========================================================================
glop proc
cmp bl, al ; Name en majuscule ?
jl defaut2 ; si chiffre alors valeur par défaut
cmp bl, cl ; si supérieur à "M", le Name est
jg minus ; considéré comme étant en minuscule
sub EBX,EDX ; ascii - 65 = position dans alphabet
imul ebx, 5 ; pointe sur la série de 5 caractères définie
inc edi ; se positionne après le "-"
add esi, EBX ; se positionne sur la série définie
mov ecx, 05 ; série de 5 caractères
rep movsb ; déplace la série définie dans le buffet Key
ret
minus:
add eax,20h ; passe des majuscules aux minuscules
add ecx,20h ; A=41+20=> 61 => " a ". idem pour " m "
sub EBX,EAX ; valeur ascii - EAX = position dans alphabet
cmp bl,0 ; si inférieur à "a"
jl defaut2 ; -> prend la valeur par défaut
cmp bl,109 ; si supérieur à "m" (il n'y a que 20 séries)
jg defaut2 ; -> prend la valeur par défaut
imul ebx, 5 ; pointe sur la série de 5 caractères définie
inc edi ; se positionne après le "-"
add esi, EBX ; se positionne sur la série définie
mov ecx, 05 ; série de 5 caractères
rep movsb ; déplace la série définie dans le buffet Key
ret
defaut2:
inc edi ; se positionne après le "-"
mov ecx, 05 ; série de 5 caractères
rep movsb ; déplace la série définie dans le buffet Key
ret
glop endp
end start