Défi Main Rouge
Avril 2000
By Christal

Avant propos :

Ce défi, original en soit même, pose un petit problème.
Comme il s’agit de transformer une version FreeWare en version ShareWare, le code doit, par la force des choses, être rajouté là où c’est possible.
L’un des inconvénients majeurs de cette obligation, réside dans le fait que dès que vous tenez un bout de fil du schéma de protection, toute la pelote vient avec...
Pour embrouiller les choses, j’ai essayé d’avoir recours à quelques subterfuges, sans être certain que vous en serez dupes, mais en faisant aussi en sortes que les différents éléments d’une routine soient rapides à trouver dans l’idée de « récompenser » vos recherches et rendre la suite stimulante. J’aurais pu m’amuser à mieux cacher mes petites chausse-trappes, mais ça n’aurais fait que retarder le moment de la mise à mort...

Let’s Go On :

Ma « version shareware » est équipée des accessoires suivants :

- Une message Box ShareWare
- Une Box "Registered Version"
- Un Item de Menu désactivé
- Une boite d’enregistrement
- Un générateur de serial
- Un anti SoftIce et un anti FrogsIce
- Un anti Wdasm (pour version 8X...)
- Un check des branchements les plus délicats
- Un nombre d’utilisations limitées à 16
- Une message box "Time Out"

Il n’y a que deux boites de messages (ce sont des points d’entrées trop faciles), et toute « faute » est sanctionnée par la fermeture immédiate de la fenêtre en cours (ExitProcess ou EndDialog suivant le cas). Pas très élégant, mais fichtrement désagréable!

Pour empoisonner Wdasm, j’ai fait en sorte de ne pas créer de références supplémentaires, et j’ai rajouté des boucles folles histoires d’essayer de faire tourner un désassembleur en bourrique...

Le reste des réjouissances, vous les découvrirez au fur et à mesure.


La première chose à laquelle je me suis attaqué, a été de créer mes nouvelles ressources.

1- créer des ressources.

La boite de Dialogue

En fait j'ai simplement modifié la boite "A propos" d'origine à l'aide d'un éditeur de ressources en réutilisant les deux champs EDIT et les STATICS.

Bien sur, j’en ai profité pour récupérer les identificateurs des boutons et des champs d’édition.

NVIEWABOUT DIALOG 22, 17, 221, 128
STYLE DS_MODALFRAME | WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU
CAPTION "A propos de XNview"
FONT 8, "MS Sans Serif"
"AboutIcon", 2008, "STATIC"
"XNVIEW pour Windows", -1, "STATIC
"", 1000, "STATIC
"", 1001, "STATIC
"Copyright 1991-99 Pierre-e Gougelet", -1, "STATIC
"",
100, "EDIT
"",
66, "EDIT
"Cette application est un contribuciel", -1, "STATIC
"Sérial", -1, "STATIC
"Name", -1, "STATIC
"Merci de vous enregistrer", -1, "STATIC »
"Register",
1000, "BUTTON", BS_DEFPUSHBUTTON

A savoir, 100 pour le champ Name, 66 pour le champ Serial, et 1000 pour le bouton (soit respectivement 64h, 42h et 03E8h).
Pour le bouton "REGISTER", un ID comme 03E8 sera facile à trouver dans le Dead Listing, c'est une valeur peu courante. Pour plus de discrétion, j'aurais pu mettre 10 (toujours en utilisant un éditeur de ressources), ce qui aurait rendu la localisation de la gestion de la boite de dialogue plus difficile...

Puis je suis passé aux menus et à leurs Items :

NVIEWDEFAULTMENU MENU
{
POPUP "&Fichier"
{
MENUITEM "&Ouvrir...\tCtrl+O", 1
MENUITEM "&Ouvrir tout...", 2
MENUITEM "Voir en &hexa...\tCtrl+H", 3
MENUITEM "&Parcourir...\tCtrl+B", 4
MENUITEM SEPARATOR
MENUITEM "Importation Clipboard", 12, GRAYED
MENUITEM "Capture...", 13
MENUITEM "Slide Show...\tCtrl+L", 16,
GRAYED
MENUITEM "Conversion de S&équence...\tCtrl+N", 14
MENUITEM "Conversion M&ultiple...\tCtrl+U", 15
MENUITEM SEPARATOR
MENUITEM "&Quitter\tCtrl+Q", 18

Sous ExeEscope, rien n’est plus simple que de les bidouiller, et par exemple de griser (Grayed) l’Item « Slide Show).


2- Créer de la place :

Ceci étant fait, je me suis occupé de trouver de la place pour mes futures routines.

Oups !
Si vous éditez les sections à l'aide d'un outils comme ProcDump, vous verrez que les virtual Sizes sont à 00000000. Ca ne va pas être commode pour calculer la place disponible, c’est à dire la différence entre la Virtual Size et la Raw Size de chaque section qui aurait pu m’indiquer quelle place il restait dans chacune d’elle par la grâce du Padding (remplissage de la différence par des 0000).
En balayant l'exécutable avec un éditeur hexadécimal, je me suis rendu compte qu'il y avait très peu de places disponibles et fiables, où j'aurais été certain de ne pas être dérangé...

Mais je dois reconnaître que je n’ai pas cherché longtemps.
Nody ayant réalisé un adorable agrandisseur d’exécutable, j’ai tout simplement utilisé son programme.
Bien sur j’aurais pu agrandir manuellement une section, voir en rajouter une, mais autant par flemme que pour noyer le poisson, j’ai fait ce choix :
De plus, le programme de Nody utilise une routine de VIROGEN dont je vais me servir, bien qu'en la modifiant, et récupérer une partie de la place allouée à cette routine pour quelques branchements particuliers...


3- Trouver la DialogProcedure

Il est possible d’aborder la recherche de la gestion de l’Item About, puisque c’est celle ci qui mènera à ma boite de dialogue, de plusieurs façons :

> à partir du Dead Listing sur une recherche de la référence « A propos », ou sur son ID, 000000BF

 * Possible Ref to Menu: BROWSERMENU, Item: "A propos..."
                                   |
 :00421B99 3DBF000000              cmp eax, 000000BF
 :00421B9E 0F84F6010000            je 00421D9A
 :00421BA4 31C0                    xor eax, eax
 :00421BA6 C3                      ret 

> ou en mettant la main directement sur la DialogBox

 :004010F8  51                  PUSH    ECX
 :004010F9  52                  PUSH    EDX
 :004010FA  6A00                PUSH    00       > dwInitParam 
 :004010FC  68D8104000          PUSH    004010D8 > adresse DialogProc
 :00401101  50                  PUSH    EAX      > hWndParent
 :00401102  687C104C00          PUSH    004C107C > template
 :00401107  6A00                PUSH    00 
 :00401109  2EFF15D0F84B00      CALL    CS:[KERNEL32!GetModuleHandleA]
 :00401110  50                  PUSH    EAX      > hInstance
 :00401111  2EFF159CF64B00      CALL    CS:[USER32!DialogBoxParamA]
 :00401118  5A                  POP     EDX
 :00401119  59                  POP     ECX
 :0040111A  C3                  RET  

int DialogBoxParam(

 
     HINSTANCE hInstance,           // handle to application instance
     LPCTSTR lpTemplateName,       // identifies dialog box template
     HWND hWndParent,             // handle to owner window
     DLGPROC lpDialogFunc,       // pointer to dialog box procedure  
     LPARAM dwInitParam         // initialization value
    );

La gestion des messages envoyés par la DialogBox va être traitée à partir de l’adresse 004010D8.
Le bidouillage au niveau de cette adresse ne sera pas pratique, et j’ai opté pour ma propre routine de gestion.
Pour me faciliter la tache, je l’ai d’abord écrite en assembleur sous MASM, puis je l’ai compilée avant de faire un coupé/collé directement du petit programme généré à la cible :

 WndProc proc hWin :HWND, uMsg :UINT, wParam :WPARAM, lParam :LPARAM,
         
       .if uMsg == WM_COMMAND
         je Main_Command
       .elseif uMsg == WM_CLOSE
         invoke EndDialog,hWin,0
       .endif
     ret
 
 Main_Command:
 
         mov eax,wParam
         cmp ax,IDC_REGISTER
         jne suite
                 invoke GetDlgItemText,hWin,IDC_TEXT,offset buffer,16
                 invoke GetDlgItemText,hWin,IDC_TEXT2,offset buffer,16    
                 cmp buffer,NULL
                 je exit_Button
                 cmp buffer,NULL
                 je exit_Button
                 nop                ; place pour implanter
                 nop                ; un call ou un jmp 
                 nop
                 nop
                 nop
                 
 suite:   
         cmp  ax,IDC_EXIT
         je   exit_Button
         ret
 
 exit_Button:
         invoke EndDialog,hWin,0
         ret 

Ce qui donne, après modification de l'adresse de la DialogBoxProcédure,

 :004010FC  68E4025100          PUSH    005102E4  > vers ma nouvelle DialogProc
 :00401101  50                  PUSH    EAX
 :00401102  687C104C00          PUSH    004C107C 

pour arriver ici :

 :005102E4  55                  PUSH    EBP
 :005102E5  8BEC                MOV     EBP,ESP
 :005102E7  817D0C11010000      CMP     DWORD PTR [EBP+0C],00000111 > WM_COMMAND
 :005102EE  7504                JNZ     005102F4  > message arrivé ?
 :005102F0  7418                JZ      0051030A  > Oui -> traitement de l'événement
 :005102F2  EB12                JMP     00510306  > boucle
 :005102F4  837D0C10            CMP     DWORD PTR [EBP+0C],10 > clic sur le X (fermeture)
 :005102F8  750C                JNZ     00510306              > non -> boucle
 :005102FA  6A00                PUSH    00                    > oui -> push hDlg
 :005102FC  FF7508              PUSH    DWORD PTR [EBP+08]    > nResult
 :005102FF  2EFF15B8F64B00      CALL    CS:[USER32!EndDialog]
 :00510306  C9                  LEAVE
 :00510307  C21000              RET     0010 
 BOOL EndDialog(
 
     HWND hDlg,     // handle to dialog box
     int nResult   // value to return
    ); 
 :0051030D  8B4510              MOV     EAX,[EBP+10] > message dans EAX
 :00510310  663DE803            CMP     AX,03E8      > est-ce un appui sur Register ? 
 :00510314  7548                JNZ     0051035E     > va voir si appui sur un autre bouton 
 :00510316  BE44055100          MOV     ESI,00510544 > buffet de stockage
 :0051031B  90                  NOP                  > Oups!
 :0051031C  6A10                PUSH    10           > nb de caractères maxi
 :0051031E  56                  PUSH    ESI          > buffet de stockage
 :0051031F  6A64                PUSH    64           > ID 1er champ
 :00510321  FF7508              PUSH    DWORD PTR [EBP+08] > Handle DBox
 :00510324  2EFF15F4F64B00      CALL    CS:[USER32!GetDlgItemTextA]  
 UINT GetDlgItemText(
 
     HWND hDlg,            // handle of dialog box
     int nIDDlgItem,      // identifier of control
     LPTSTR lpString,    // address of buffer for text
     int nMaxCount      // maximum size of string
    ); 
 :00510331  6A10                PUSH    10       > et fait de même pour 2d champ
 :00510333  56                  PUSH    ESI
 :00510334  6A66                PUSH    66       > Identificateur 2d champ
 :00510336  FF7508              PUSH    DWORD PTR [EBP+08]
 :00510339  2EFF15F4F64B00      CALL    CS:[USER32!GetDlgItemTextA]
 :00510340  83C604              ADD     ESI,04   > au moins 4 caractères entrés
 :00510343  803E00              CMP     BYTE PTR [ESI],00
 :00510346  7421                JZ      00510369 > EndDialog si pas le cas
 :00510348  803D4805510000      CMP     BYTE PTR [00510548],00
 :0051034F  7418                JZ      00510369 > idem champ 2
 :00510351  EB28                JMP     0051037B > vers Générateur de code
 :00510353  E800000000          CALL    00510358 > code bidon pour 
 :00510358  58                  POP     EAX      > empoisonner les 
 :00510359  A348055100          MOV     [00510548],EAX > crackers
 :0051035E  663DE903            CMP     AX,03E9  > test pour un éventuel 2d bouton
 :00510362  7405                JZ      00510369 > fermeture DialogProc
 :00510364  61                  POPAD            > quitte la procédure
 :00510365  C9                  LEAVE
 :00510366  C21000              RET     0010 

Avec juste les Call API à corriger et implanter le saut vers le générateur de code.
Sur votre OS, sous SoftIce, si vous entrez un Call MessageBoxA, le débuggeur se chargera automatiquement de calculer l'adresse de branchement dans la DLL (KERNEL32, USER32, SHELL32...) où devra se brancher l'appel à la fonction API souhaité.
Si sur mon PC, le programme tourne sans histoire, sur un autre Micro ces DLL peuvent se trouver à une adresse différente et le programme plantera tout bêtement parce qu'il ne trouvera rien correspondant au call rencontré.

Facheux!

Pour Trouver les adresses des APIs, et remédier à mon problème, j’ai vu deux possibilités (peut être y en a-t-il d’autres...) :


4- La routine de VIROGEN

La routine de VIROGEN tel que l'a utilisé NODY est basé sur le principe de certains Virus.
Cette routine, en l'état, est adaptable à tous types de programmes. En fait elle est totalement autonome et indépendante du programme.
En la rendant plus dépendante, il y a moyen de l'optimiser et de la raccourcir considérablement, sachant que:

- Les APIs peuvent être placées dans KERNEL32, USER32, SHELL32 etc… (table (0))
- Le nom des APIs à catcher devra être placé dans une table (1)
- A la sortie de cette routine les adresses trouvées doivent venir se ranger dans une seconde table (2)

Dans l'exemple qui va suivre, je récupère les adresses des APIs _lopen dans KERNEL32.dll et ShellAboutA dans SHELL32.dll

En tout premier lieu, il convient de trouver les adresses des DLL en utilisant un éditeur hexadécimal. Nous dirons que ces adresses font partie de la Table (0).

000BEDE0 400D 0C00 0000 0000 4144 5641 5049 3332 @.......ADVAPI32
000BEDF0 2E64 6C6C 006F 6C65 3332 2E64 6C6C 0047 .dll.ole32.dll.G
000BEE00 4449 3332 2E64 6C6C 0053 4845 4C4C 3332 DI32.dll.SHELL32
000BEE10 2E64 6C6C 0055 5345 5233 322E 646C 6C00 .dll.USER32.dll.
000BEE20 4B45 524E 454C 3332 2E64 6C6C 0043 4F4D KERNEL32.dll.COM
000BEE30 4354 4C33 322E 646C 6C00 4D50 522E 646C CTL32.dll.MPR.dl
000BEE40 6C00 636F 6D64 6C67 3332 2E64 6C6C 0000 l.comdlg32.dll..

Puis d'implanter à une adresse disponible le nom des API à catcher:

Adresses (Offset) de la table (1)

001098D0 5F6C 6F70 656E 0000 0000 0000 0100 0000 _lopen..........
001098E0 5368 656C 6C41 626F 7574 4100 0000 0000 ShellAbout .....

Voici la routine en question:

:00510074  60             PUSHAD                 > sauvegarde des registres
:00510075  BE09FA4B00     MOV     ESI,004BFA09   > pousse l'@ de SHELL32.dll
:0051007A  BF800D4C00     MOV     EDI,004C0D80   > pousse l'@ de ShellAboutA
:0051007F  E81B000000     CALL    0051009F       > choppe l'adresse de l'API
:00510084  A3700D4C00     MOV     [004C0D70],EAX > et la stock en mémoire
:00510089  BE20FA4B00     MOV     ESI,004BFA20   > pousse l'@ de KERNEL32.dll
:0051008E  BF700D4C00     MOV     EDI,004C0D70   > pousse l'@ de _lopen
:00510093  E807000000     CALL    0051009F       > choppe l'adresse de l'API
:00510098  A3740D4C00     MOV     [004C0D74],EAX > la stocke dans la Table (2)
:0051009D  EB12           JMP     005100B1       > et continue


Le call qui s'occupe de catcher l'adresse de l'API est très court. Au retour de ce call, EAX contient l'adresse de l'API dans la DLL

:0051009F  56             PUSH    ESI   > pousse l'@ de la dll
:005100A0  2EFF1518F94B00 CALL    CS:[KERNEL32!LoadLibraryA]
:005100A7  57             PUSH    EDI   > pousse le nom de l'API
:005100A8  50             PUSH    EAX   > eax contient l'adresse de la dll
:005100A9  2EFF15E0F84B00 CALL    CS:[KERNEL32!GetProcAddress]
:005100B0  C3             RET           > eax contient l'adresse de l'API dans la dll

Au retour du Call GetProcAdress, le registre EAX contiendra l’adresse de l’API désirée, ou la valeur 00 si la fonction a échouée.
Vous remarquerez le Call Dword Ptr CS:[KERNEL32!LoadLibraryA]!
En fait, c'est SoftIce qui remplace l'adresse du call par le nom de l'API appelée (KERNEL32!LoadLibraryA), pour ma part, j'avais entré:

Call Dword Ptr Cs:[004BF8E0].

Pour trouver cette adresse d'une API déjà utilisée par le programme, j'ai regardé la fin du Dead Listing:

* Reference To: KERNEL32.WriteFile, Ord:0062h
                                  |
:004BE5B8 FF259CF94B00            Jmp dword ptr [004BF99C]

* Reference To: KERNEL32.LocalFileTimeToFileTime, Ord:0042h
                                  |
:004BE5BE FF251CF94B00            Jmp dword ptr [004BF91C]

* Reference To: KERNEL32.DosDateTimeToFileTime, Ord:000Bh 

Il suffit de repérer dans cette liste, le nom de l’API que vous voulez appeler, et de faire un :

Call Dword Ptr cs :[004BF99C], à la place d’un Call WriteFile

Il est maintenant facile de récupérer autant de nouvelles APIs dont vous pourriez avoir besoin, sur le principe suivant:

Mov ESI, adresse de la DLL
Mov EDI, adresse du nom de l'API
Call 0051009F
Mov [adresse table (2)],EAX

Et de mettre EAX de coté dans la table (2) pour pouvoir réutiliser l'adresse de l'API dans la DLL par la suite.

Mais dans l'exemple ci dessus, la routine n'est plus relogeable à n'importe quelle adresse, comme à l'origine. Pourtant, il est possible d'écrire des routines qui pourront se caser là ou vous aurez de la place, sans avoir à s'inquiéter de corriger les JUMP, ou les adresses des PUSH pointant vers l'@ d'un message...
Sur le principe des Virus et de ce que PULSAR appelle les codes relogeables, le relais vers le programme d'origine, dans l'exécutable écrit par NODY, était passé ainsi:

:005100B1  61             POPAD                > restaure la pile
:005100B2  E800000000     CALL    005100B7     > place l'adresse 5100B7 sur la pile
:005100B7  58             POP     EAX          > récupère cette adresse dans EAX
:005100B8  2D9C224000     SUB     EAX,0040229C > soustrait cette valeur à celle 
:005100BD  8B80AA224000   MOV     EAX,[EAX+004022AA] > d'origine
:005100C3  FFE0           JMP     EAX

Le Call 005100B7 va simplement servir à pousser sur la pile l'adresse qui servirait de retour après un RET. Le POP EAX va récupérer cette adresse sur la pile, et le SUB EAX, 0040229C va calculer la différence entre les adresses de cette routine, et celle que vous aviez écrites au départ:
A l'origine le JMP EAX branchait sur l'adresse 004022AA, et EAX valait 00
Cette routine, une fois greffée dans une autre exécutable va calculer la différence (placée dans EAX) entre 004022AA et l'adresse réelle où se trouve désormais la routine à atteindre.
Ainsi, cette routine peut venir se loger par un coupé/collé à peu prés n’importe où dans un exécutable avec un minimum de, voir aucune, correction à apporter !


5- Implantation du générateur de code

Pour le générateur de code, j’ai tout simplement fait (encore!) un coupé/collé à partir de la routine d’un Crackme que j’avais eu l’occasion d’étudier, et dont voici ce que j’en avais noté :

(la routine suivante ce trouve normalement à l’adresse :005102D1, et il n’y a que très peu de différences entre ces lignes et celles que vous verrez dans le programme)

:00401570  33C0          XOR       EAX,EAX       > mise à 0 des registres
:00401572  33DB          XOR       EBX,EBX       > qui vont être utilisés
:00401574  33C9          XOR       ECX,ECX       > pour le cryptage 
:00401576  B901000000    MOV       ECX,00000001  > initialisation de la clé de cryptage
:0040157B  33D2          XOR       EDX,EDX       > mise à 0 du registre EDX
:0040157D  8B45E4        MOV       EAX,[0051053E]> place le nom dans EAX
:00401580  8A18          MOV       BL,[EAX]      > met un caractère du nom dans BL
:00401582  32D9          XOR       BL,CL         > crypte ce caractère
:00401584  8818          MOV       [EAX],BL      > le place dans EAX
:00401586  41            INC       ECX           > ajoute 1 à la clé de cryptage
:00401587  40            INC       EAX           > caractère suivant
:00401588  803800        CMP       BYTE PTR [EAX],00 > reste-t-il des caractères?
:0040158B  75F3          JNZ       00401580      > oui -> boucle
:0040158D  33C0          XOR       EAX,EAX       > non, passe au sérial
:0040158F  33DB          XOR       EBX,EBX       > en mettant les 
:00401591  33C9          XOR       ECX,ECX       > registres à 0
:00401593  B90A000000    MOV       ECX,0000000A  > initialisation de la clé de cryptage
:00401598  33D2          XOR       EDX,EDX       
:0040159A  8B45F0        MOV       EAX,[00510127]> place le sérial dans EAX
:0040159D  8A18          MOV       BL,[EAX]      > 1 caractère du sérial dans BL
:0040159F  32D9          XOR       BL,CL         > crypte ce caractère
:004015A1  8818          MOV       [EAX],BL      > le place dans EAX
:004015A3  41            INC       ECX           > ajoute 1 à la clé de cryptage
:004015A4  40            INC       EAX           > caractère suivant
:004015A5  803800        CMP       BYTE PTR [EAX],00 > encore des caractères ?
:004015A8  75F3          JNZ       0040159D      > oui -> boucle
:004015AA  8B45E4        MOV       EAX,[EBP-1C]  > place le nom crypté dans EAX
:004015AD  8B55F0        MOV       EDX,[EBP-10]  > place le sérial crypté dans EDX
:004015B0  33C9          XOR       ECX,ECX       > ECX mis à 00
:004015B2  8A18          MOV       BL,[EAX]      > 1 caractère du nom dans BL
:004015B4  8A0A          MOV       CL,[EDX]      > 1 caractère du sérial dans CL
:004015B6  3AD9          CMP       BL,CL         > compare ces 2 caractères
:004015B8  7509          JNZ       004015C3      > si <> saute en EndDialog
:004015BA  40            INC       EAX           > caract suivant du nom
:004015BB  42            INC       EDX           > caract suivant du sérial
:004015BC  803800        CMP       BYTE PTR [EAX],00 > encore des caractères pour le nom ?
:004015BF  75EF          JNZ       004015B0      > oui -> boucle

Analysons un peu ces lignes:

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

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

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

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

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

A titre d'exemple, en entrant "Christal" dans le premier champ, vous allez obtenir "bjqmvrfd" après cryptage, et le second champ donnera ">35>=:" pour "489335" d'entré.
Ensuite, "b" -> 6A sera comparé à ">" -> 3E, et vous irez en routine EndDialog (sans message « Bad Boy »)

Mais reprenons avec les bonnes adresses à l’avant dernières lignes :

:00510420  803800              CMP     BYTE PTR [EAX],00
:00510423  75EB                JNZ     00510510     > oui -> boucle
:00510425  B8D0D71000          MOV     EAX,0010D7D0 > pointeur
:0051042A  40                  INC     EAX          > pour ne pas mettre
:0051042B  48                  DEC     EAX          > de NOP, trop
:0051042C  40                  INC     EAX          > rapide à voir
:0051042D  48                  DEC     EAX
:0051042E  C6051009510036      MOV     BYTE PTR [00510910],36 > Flag UEnregistré
:00510435  E883000000          CALL    005104BD    > enable menu Item
:0051043A  E846000000          CALL    00510485    > modifie la KeyFile
:0051043F  E925FFFFFF          JMP     00510369

Le Call qui s’occupe de modifier la KeyFile va réécrire celle ci en utilisant le flag qui a été mis à 36 (caractère ASCII du chiffre « 6 »).

:00510486  C7805030400076696577MOV     DWORD PTR [EAX+00403050],77656976 = view
:00510490  66C7804D304000786E  MOV     WORD PTR [EAX+0040304D],6E78      = xn

EAX joue le rôle de pointeur pour permettre au programme de trouver les adresses des différents PUSH. Ainsi je me réservais la possibilité de déplacer une procédure entière en ayant juste à modifier le pointeur.

:00510499  8D8850304000        LEA     ECX,[EAX+00403050]  > eax est le pointeur
:0051049F  8D904D304000        LEA     EDX,[EAX+0040304D]
:005104A5  8DB020314000        LEA     ESI,[EAX+00403120]
:005104AB  8DB89E304000        LEA     EDI,[EAX+0040309E]
:005104B1  57                  PUSH    EDI                > Path FileName
:005104B2  56                  PUSH    ESI                > Buffet
:005104B3  52                  PUSH    EDX                > Key (xn)
:005104B4  51                  PUSH    ECX                > Section (view)
:005104B5  2EFF15A0F94B00      CALL    CS:[KERNEL32!WritePrivateProfileStringA]

Je reviendrais plus tard sur l’activation de l’Item « Slide Show ».
Les lignes qui suivent le call 0051067A vont écrire dynamiquement le nom de la section (nx) et de la clé (view) à un emplacement disponible dans le code, et une valeur va être mise à 36 en cas de bon code.
Petite précision : la protection peut être crackée par deux simples R FL Z, sans qu’il y ait besoin de patcher le programme. Et vous verrez pourquoi...

BOOL WritePrivateProfileString(

    LPCTSTR lpAppName,     // pointer to section name 
    LPCTSTR lpKeyName,    // pointer to key name 
    LPCTSTR lpString,    // pointer to string to add 
    LPCTSTR lpFileName  // pointer to initialization filename    );

lpFileName est le Path pour un fichier bidon nxview.dll qui va être utilisé pour lire ou écrire deux types d’informations, dont le statut utilisateur. En utilisant un éditeur hexadécimal, vous pourrez trouver:

0010A020 0000 0000 0041 4244 3547 3852 3144 3548 .....ABD5G8R1D5H
0010A030 3135 3446 3435 4434 4635 3447 4834 4831 154F45D4F54GH4H1
0010A040 5744 3534 4635 3447 4831 3346 3138 3751 WD54F54GH13F187Q
0010A050 4552 5353 4B48 4A4B 4C4C 4735 4539 3656 ERSSKHJKLLG5E96V
0010A060 3254 3542 3251 4659 4851 5948 3800 633A 2T5B2QFYHQYH8.c:
0010A070 5C77 696E 646F 7773 5C73 7973 7465 6D5C \windows\system\
0010A080 786E 7669 6577 2E64 6C6C 0063 3A5C 7769 xnview.dll.c:\wi

lpString pointe vers un buffet dans lequel le caractère ASCII 35 (le chiffre « 5 ») a été remplacé par le caractère 36 (le chiffre « 6 »).
Ce buffet à été écrit sous éditeur hexa en tapant n’importe quoi sur le clavier , les yeux fermés !
WritePrivateProfileStringA va se charger de recopier ce buffet dans le fichier nxwiew.dll avec le byte 00BFFD0 modifié. Vous êtes enregistré !

Voici à quoi ressemble le fichier nxview.dll dans sa version enregistrée

[view]
xn=ABD5G8R1D5H154F45D4F54GH4H1WD54F54GH13F187QERSSKHJKLLG5E96V2T5B2QFYHQYH8

Ce fichier est sollicité au lancement de l’application lors du test « Is_Shareware ? ».
Comme les lignes suivantes se trouvent juste après la routine de VIROGEN, j’ai essayé de leurs donner un peu le même look

:005100B2  E830010000          CALL    005101E7     > test Shareware
:005100B7  B814824300          MOV     EAX,00438214 > pousse l’OEP
:005100BC  90                  NOP
:005100BD  8DB8AA224000        LEA     EDI,[EAX+004022AA] > code bidon
:005100C3  FFE0                JMP     EAX          > et Zou ! 


6- La Routine de Vérification Shareware

Elle commence par la récupération du fichier xnview.dll

:00510445  C7805030400076696577MOV     DWORD PTR [EAX+00403050],77656976 = view
:0051044F  8D8850304000        LEA     ECX,[EAX+00403050]
:00510455  66C7804D304000786E  MOV     WORD PTR [EAX+0040304D],6E78      = xn
:0051045E  8D904D304000        LEA     EDX,[EAX+0040304D]
:00510464  8D9820314000        LEA     EBX,[EAX+00403120]
:0051046A  8DB09E304000        LEA     ESI,[EAX+0040309E]
:00510470  56                  PUSH    ESI     > lpFileName
:00510471  6A50                PUSH    50      > nSize
:00510473  53                  PUSH    EBX     > lpReturnedString
:00510474  6A00                PUSH    00      > lpDefault
:00510476  52                  PUSH    EDX     > lpKeyName
:00510477  51                  PUSH    ECX     > lpAppName
:00510478  2EFF15DCF84B00      CALL    CS:[KERNEL32!GetPrivateProfileStringA
DWORD GetPrivateProfileString(

    LPCTSTR lpAppName,          // points to section name 
    LPCTSTR lpKeyName,         // points to key name 
    LPCTSTR lpDefault,        // points to default string 
    LPTSTR lpReturnedString, // points to destination buffer 
    DWORD nSize,            // size of destination buffer 
    LPCTSTR lpFileName     // points to initialization filename    );	

Puis par les tests d’usage :

:005101F2  686E085100          PUSH    0051086E > pousse paramètre API
:005101F7  803DF008510000      CMP     BYTE PTR [005108F0],00 > 1er lancement de xnview?
:005101FE  7507                JNZ     00510207 > si oui, création de xnview.dll
:00510200  6825085100          PUSH    00510825 > sinon pousse le buffet
:00510205  EB1D                JMP     00510224 > et continue
:00510207  803D1009510036      CMP     BYTE PTR [00510910],36 > version enregistrée ?
:0051020E  7425                JZ      00510235 > saute toutes les autres vérifications
:00510210  FE0D1A095100        DEC     BYTE PTR [0051091A] > Time Limit
:00510216  803D1A09510041      CMP     BYTE PTR [0051091A],41 > si = « A » 
:0051021D  741D                JZ      0051023C               > Out Of Time !
:0051021F  68F0085100          PUSH    005108F0  > réécrit la KeyFile
:00510224  681D085100          PUSH    0051081D  > pour enregistrer la
:00510229  6820085100          PUSH    00510820  > modification apportée
:0051022E  2EFF15A0F94B00      CALL    CS:[KERNEL32!WritePrivateProfileStringA]

Sur le même principe qu’en écriture, cette routine va lire le fichier nxview.dll et l’écrire (adresse du Buffer) juste en dessous des codes de la KeyFile trouvables avec un éditeur hexadécimal


7- Version Shareware -> diminution du nombre d’utilisation

Cette fois ci, c’est le caractère (« Q ») en 0051091A (de la KeyFile qui vient d’être lue) qui va être concerné, et décrémenté de 01. Le « Q » va donc devenir un « P », et ainsi de suite, avant d’être réécrit avec la modification. Arrivé à la lettre « A », le programme affiche une MessageBox :

Out Of Time ! (création Dynamique)

:0051023C  C780D33040004F757420MOV     DWORD PTR [EAX+004030D3],2074754F > Out
:00510246  C780D73040006F662054MOV     DWORD PTR [EAX+004030D7],5420666F > Of T
:00510250  C780DB304000696D6521MOV     DWORD PTR [EAX+004030DB],21656D69 > ime!
:0051025A  8D88D3304000        LEA     ECX,[EAX+004030D3]
:00510260  8D901E304000        LEA     EDX,[EAX+0040301E]
:00510266  6A00                PUSH    00   > uType -> MB_OK
:00510268  52                  PUSH    EDX  > lpCaption
:00510269  51                  PUSH    ECX  > lpText
:0051026A  6A00                PUSH    00   > hWnd
:0051026C  2EFF1584F74B00      CALL    CS:[USER32!MessageBoxA]
:00510273  EB67                JMP     005102DC  > vers ExitProcess
int MessageBox(

    HWND hWnd,            // handle si fenêtre propriétaire
    LPCTSTR lpText,      // adresse du texte de la message box
    LPCTSTR lpCaption,  // address du titre de la message box  
    UINT uType         // style du bouton et/ou de la boite   );

Version Shareware

:00510276  8D880C304000        LEA     ECX,[EAX+0040300C]
:0051027C  8D901E304000        LEA     EDX,[EAX+0040301E]
:00510282  6A00                PUSH    00    > bouton OK
:00510284  52                  PUSH    EDX   > Version Shareware
:00510285  51                  PUSH    ECX   > Enregistrez-vous !
:00510286  6A00                PUSH    00    > handle
:00510288  2EFF1584F74B00      CALL    CS:[USER32!MessageBoxA]

Le titre et le message de cette Box sont lisibles sous éditeur hexadécimal.


8- Les Anti XXX

Afin de ne pas faciliter une recherche de la routine Anti Sice en Dead Listing, j’ai écrit la routine à une adresse différente de celle où elle commencera à être lue:

:0051028F  BE03055100          MOV     ESI,00510503 > Adresse de Départ
:00510294  BF40075100          MOV     EDI,00510740 > Adresse d’arrivée
:00510299  B941000000          MOV     ECX,00000041 > Nb d’octets à déplacer
:0051029E  F3A4                REPZ    MOVSB        > Zou !
:005102A0  E89B040000          CALL    00510740     > appelle la routine déplacée
:005102A5  40                  INC     EAX          > +1 pour éviter un FFFFFFFF
:005102A6  7513                JNZ     005102BB     > SoftIce Detected?
:005102A8  6A00                PUSH    00           > prépare Anti FrogsIce
:005102AA  68F00A5100          PUSH    00510AF0     > \\.\FROGSICE
:005102AF  B830015100          MOV     EAX,00510130 > charge _lopen
:005102B4  FF10                CALL    [EAX]        > Call Kernel32!_lopen
:005102B6  40                  INC     EAX          > +1 pour éviter un FFFFFFFF
:005102B7  7502                JNZ     005102BB     > FrogsIce Detected
:005102BB  68FB045100          PUSH    005104FB     > pousse sur la pile l'@ d'ExitProcess
:005102C0  C3                  RET                  > et simule un retour sur Call

Le Call 00510740 commence ainsi:

Création dynamique de \\.\SICE et \\.\FROGSICE

:00510740  C705D00A51005C5C2E5CMOV     DWORD PTR [00510AD0],5C2E5C5C
:0051074A  C705D40A510053494345MOV     DWORD PTR [00510AD4],45434953
:00510754  C705F00A51005C5C2E5CMOV     DWORD PTR [00510AF0],5C2E5C5C
:0051075E  C705F40A510046524F47MOV     DWORD PTR [00510AF4],474F5246
:00510768  C705F80A510053494345MOV     DWORD PTR [00510AF8],45434953
:00510772  C3                  RET

Après avoir écrit dynamiquement les chaînes qui vont être à trouver, le programme va s’occuper de les chercher.

:005107D5  6A00                PUSH    00            > iReadWrite
:005107D7  68D00A5100          PUSH    00510AD0      > lpPathName
:005107DC  B830015100          MOV     EAX,00510130  > charge _lopen
:005107E1  FF10                CALL    [EAX]         > appel _lopen
:005107E3  C3                  RET
HFILE _lopen(

    LPCSTR lpPathName,  // pointer to name of file to open  
    int iReadWrite     // file access mode 
   );

La fonction _lopen ouvre un fichier existant et place un pointeur au début de celui ci. Elle reste utilisée pour la compatibilité entre les versions 16 et 32 bits de Windows. Les versions 32 bits utilisent en général l'API CreateFileA.
A son retour, cette fonction renvoie la valeur FFFFFFFF dans EAX si le fichier recherché n'a pas été trouvé.

HANDLE CreateFile(

    LPCTSTR lpFileName,                 // pointer to name of the file 
    DWORD dwDesiredAccess,             // access (read-write) mode 
    DWORD dwShareMode,                // share mode 
    LPSECURITY_ATTRIBUTES lpSecurityAttributes,	// pointer to security attributes 
    DWORD dwCreationDistribution,   // how to create 
    DWORD dwFlagsAndAttributes,    // file attributes 
    HANDLE hTemplateFile          // handle to file with attributes to copy  
   );

La première fois que j'ai vu _lopen d'utilisé dans un schéma de protection, c'était dans ASProtect, pour éviter le traditionnel BPX CreateFileA dans le cas de détection MeltIce.

Un peu plus loin, et partant du principe que la majorité des crackers utilisent W32dasm version 8.X, je vais dépister la présence éventuelle du fichier INI généré par le désassembleur :

Anti-Wdasm

Routine de déplacement et de décryptage

:0051037B  BE98055100   MOV     ESI,00510598 > adresse de départ
:00510380  BF98035100   MOV     EDI,00510398 > adresse d'arrivée
:00510385  B930000000   MOV     ECX,00000030 > taille du bloc
:0051038A  8A06         MOV     AL,[ESI]     > place un caractère dans AL
:0051038C  3435         XOR     AL,35        > le décrypte avec la clé 53h
:0051038E  8807         MOV     [EDI],AL     > place caractère crypté à l'@ d'arrivée
:00510390  46           INC     ESI          > incrémente l'adresse de départ
:00510391  47           INC     EDI          > puis celle d'arrivée
:00510392  49           DEC     ECX          > décrémente le compteur
:00510393  83F900       CMP     ECX,00       > bloc entier lu?
:00510396  75F2         JNZ     0051048A     > non -> boucle

La routine décryptée commence à la ligne suivante. Ainsi vous pourrez la voir apparaître, et vous n'aurez pas à la chercher:

68A4045100          PUSH    005104A4     > c:\windows\w32dasm8.ini
6803000000          PUSH    00000003     > lecture de trois caractères
6847055100          PUSH    00510547     > à mettre dans un Buffet
6A00                PUSH    00           > default
68CC045100          PUSH    005104CC     > clé « w »
68C5045100          PUSH    005104C5     > section « screen »
E81573A6BF          CALL    KERNEL32!GetPrivateProfileStringA
3D00000000          CMP     EAX,00000000 > le fichier existe ?
7405                JZ      0051053B     > non -> continue
E8BDCFA7BF          CALL    KERNEL32!ExitProcess > oui -> Bye Bye
C3                  RET

Le path de Wdasm, et les noms de clé et section ont été écrits sous éditeur hexadécimal.
En cas de succès (fichier trouvé) la fonction GetPrivatePrifileStringA retourne la valeur 01 dans EAX, 00 dans le cas contraire.

J’ai bien été tenté de m’amuser à réduire à 1 pixel la taille de Wdasm (W=1, H=1). Ainsi vous auriez lancé Wdasm...
Et rien, pas d’affichage (visible), bien que pour autant l’application aurait tourné en tache de fond (CTRL+ALT+Suppr obligatoire pour la quitter)

[SCREEN]
W=800
H=553

Imaginez votre tête, avant de trouver la solution...
Mais je ne suis pas certain que moi même j’aurais apprécié la plaisanterie, si on me l’avait appliquée !


Dernier Contrôle :

8- La Vérification de la modification des conditions

Plutôt qu’un checksum sur l'ensemble des codes du programme, j’ai rajouté un petit vérificateur (en mémoire) de certains des sauts conditionnels les plus sensibles.
Dans l’hypothèse que le cracker ait pu patcher ces sauts (au lieu de se contenter d’un R FL Z pour générer la KeyFile), je me suis amusé à vérifier que les conditions de nombres d’utilisation, de version Shareware, d’Anti XXX n’avaient pas été corrompues.

Faisant appel à une procédure (Call) pour détecter les modifications, il est très facile de faire des contrôles réguliers, et dans la mesure ou la procédure est très petite, de la dupliquer (couper/coller) à plusieurs endroits pour éviter qu'une modification de celle ci ne shunte tous mes contrôles.

Pour cela, j’ai utilisé une table qui peut être complétée par autant de tests que souhaité :

.00510040:  04 00 00 33-C0 40 C3 1F-C3 B7 02 51-00 75 A6 02    3+@++ÀQ uª
.00510050:  51 00 75 7B-05 51 00 75-F1 04 51 00-75 19 04 51  Q u{Q u±Q u
Q
.00510060:  00 85 1D 02-51 00 74 0E-02 51 00 74-0D 02 51 00   àQ tQ t

Par exemple, je vais vérifier qu’à l’adresse 005102B7 j’ai bien un 75, puis un autre à l’adresse 005102A6, et ainsi de suite…

:005104DF  60                  PUSHAD
:005104E0  B904000000          MOV     ECX,00000004 > nombre de contrôle
:005104E5  BE50314000          MOV     ESI,00403150 > 1ere adresse de la table
:005104EA  8B3E                MOV     EDI,[ESI]    > mise dans EDI
:005104EC  8A4604              MOV     AL,[ESI+04]  > récupère OpCode de la condition
:005104EF  3807                CMP     [EDI],AL     > et vérifie que tout baigne
:005104F1  7508                JNZ     005104FB     > Bad Boy Exit Process
:005104F3  83C605              ADD     ESI,05       > passe à la 2d adresse
:005104F6  49                  DEC     ECX          > décrémente le compteur
:005104F7  75F3                JNZ     005105EC     > boucle si différent de NULL
:005104F9  61                  POPAD                > restauration des registres
:005104FA  C3                  RET                  > continue

Dans une telle boucle, il suffit juste de changer la valeur du compteur, et de rajouter des adresses sensibles avec leurs OpCodes, sans que ce soit plus long que ça...


9- Réactivation d'un Item


Lors de la validation du sérial entré, l’Item « Slide Show » est réactivé.
Pour des questions de flemme, je n’ai pas voulu m’empoisonner la vie à la réactiver en cas de test favorable sur la KeyFile. Pourtant le principe était simple, il suffisait qu'au lancement du programme je démarre normalement, et dès que le handle de l'application est catché par le programme, détourner celui ci vers le routine Is_Registered et en cas de résultat positif, réactiver l'Item en question. Pour des problèmes de gestion de la pile (Pushad/Popad), je n'ai pas insisté. Sorry...

Une solution efficace dans ce cas est de réactiver manuellement l'Item avec un éditeur hexadécimal :

00101F90 7500 7200 6500 2E00 2E00 2E00 0000 0100 u.r.e...........
00101FA0 1000 5300 6C00 6900 6400 6500 2000 5300 ..S.l.i.d.e. .S.
00101FB0 6800 6F00 7700 2E00 2E00 2E00 0900 4300 h.o.w.........C. 

Le 01 (= Grayed), 4 bytes avant la première lettre du nom de l’Item va indiquer son statut. En le remplaçant par 00 vous retrouverez l’Item Slide Slow intact. Un éditeur de ressources comme ExeEscope fait la même chose…

Normalement le programme est sensé le faire à votre place si vous êtes reconnu comme Utilisateur Enregistré, mais cette fois ci, l' activation de l’Item n’est effective que lors de la validation d’un code entré :

Item du menu

Pour activer un Item de menu, il est possible d’utiliser l’API suivante :

BOOL EnableMenuItem(

    HMENU hMenu,          // handle to menu
    UINT uIDEnableItem,  // menu item to enable, disable, or gray
    UINT uEnable        // menu item flags
   ); 

Pour pouvoir l’utiliser, il faut connaître le handle des menus.
L’API GetMenu permet de le récupérer :

HMENU GetMenu(

    HWND hWnd	// handle of window  
   );

Cette fois ci, c’est le Handle de la fenêtre qu’il faut catcher. Il va falloir trouver un Appel à API ayant également besoin de ce handle, et par lequel le programme passe assez rapidement pour que l’on puisse la catcher et l’utiliser dès que possible. En partant à la recherche de ce type d’API, je me suis rendu compte que le programme utilisait de nombreuses [mémoires] différentes pour stocker ce handle, et que bien souvent c’est la valeur NULL qui s’y trouvait (rendant l’appel à l’API inopérant). A force de recherches et de tests sous SI, j’ai fini par coincer une partie du code où j’avais ce fameux handle :

:004232D5 893560934E00            mov dword ptr [004E9360], esi > ici
:004232DB E80CFEFEFF              call 004130EC

* Possible StringData Ref from Data Obj ->"NviewViewAcc"
                                  |
:004232E0 686C294C00              push 004C296C
:004232E5 8B2D78934E00            mov ebp, dword ptr [004E9378]
:004232EB 55                      push ebp

A l’aide d’un petit détournement, j’ai mis le handle au chaud dans une autre [mémoire] que j’aurais ensuite possibilité d’appeler quand il me plaira :

:004232D5  893560934E00        MOV     [004E9360],ESI
:004232DB  E8AECF0E00          CALL    0051028E  > détour

et catchage du handle

:0051028E  89370005100         MOV     [00510070],ESI > choppe hWnd
:00510294  E8532EF0FF          CALL    004130EC       > restauration
:00510299  C3                  RET                    > et continue

Au moment opportun un JUMP sur cette adresse va réactiver l’Item :

:005104C3  61                  PUSHAD                       > sauve les registres
:005104C4  FF3570005100        PUSH    DWORD PTR [00510070] > récupère hWnd
:005104CA  2EFF1500F74B00      CALL    CS:[USER32!GetMenu]  > Handle menu placé dans EAX
:005104D1  6A00                PUSH    00                   > Menu Flag (00 = enabled)
:005104D3  6A10                PUSH    10                   > ID du menu
:005104D5  50                  PUSH    EAX                  > Handle des menus
:005104D6  2EFF15B0F64B00      CALL    CS:[USER32!EnableMenuItem]
:005104DD  61                  POPAD                        > restauration des registres
:005104DE  C3                  RET                          > continue


10- La Boite "Utilisateur Enregistré"

Une fois le bon sérial entré, outre la réactivation de l'Item "Slide Show", il est plus joli de faire disparaître l'accès à la boite d'enregistrement.
Utilisant le texte "Registered Version", trouvable sous éditeur hexadécimal, rien n'est plus facile que de relever son Offset, et de chercher ensuite dans le Dead Listing la procédure qui va s'en occuper. Vous aurez ceci:

:00510574  803D1009510036      CMP     BYTE PTR [00510910],36  > Flag U.Registered
:0051057B  7516                JNZ     00510593                > non -> saute
:0051057D  6A00                PUSH    00                      > hIcon
:0051057F  6807085100          PUSH    00510807                > "Registered Version"
:00510584  68F0164C00          PUSH    004C16F0                > Titre: XNVIEW
:00510589  6A00                PUSH    00                      > handle
:0051058B  A135015100          MOV     EAX,[SHELL32!ShellAboutA] > charge @ API
:00510590  FFD0                CALL    EAX                     > appel API
:00510592  C3                  RET

L'API ShellAboutA a pour seule fonction d'afficher une "Shell About Dialog Box".
En fait, à partir de la version 98 de Windows (me semble-t-il), cette API permet de proposer une DialogBox dont les paramètres sont les suivants:

int ShellAbout (

    HWND hWnd,                 // handle of parent window
    LPCTSTR szApp,            // title bar and first line text
    LPCTSTR szOtherStuff,    // other dialog text
    HICON hIcon             // icon to display
   );

Bien souvent, elle sera précédée par un call à l'API LoadIcon qui se chargera de récupérer l'icône spécifiée dans les ressources de l'exécutable.
Par défaut, c'est l'Icône standard de Windows qui sera affichée si hIcon est égal à NULL.

J'ai choisi l'adresse de SzApp en recherchant la première chaîne XNVIEW qui se présentait, et écrit le texte "REGISRED VERSION" sous éditeur hexa.
HWnd peut être NULL si vous ne souhaitez pas associer la Shell About Box à une fenêtre en particulier.

Well!
Mais comment le programme arrive-t-il ici?

Si vous mettez un BPX sur le RET de l'appel à la DialogBox d'enregistrement, un F10 vous fait arriver à la source de l'appel de la DialogBox:

:0042220C  E8E7EEFDFF          CALL    004010F8 > vers DialogBoxProc
:00422211  31C0                XOR     EAX,EAX
:00422213  C3                  RET

Le F10 suivant vous montre comment le programme gère les différentes sélections d'Items.

:0040DE62  6689DA              MOV     DX,BX
:0040DE65  89C3                MOV     EBX,EAX
:0040DE67  89F8                MOV     EAX,EDI
:0040DE69  FF5604              CALL    [ESI+04] > appel procédure
:0040DE6C  EB19                JMP     0040DE87

Un "D EDI" affichera 000000BF, l'ID de l'Item "A Propos" dans la fenêtre des Datas.

:004CEE78 BF 00 00 00 74 05 51 00-C0 00 00 00 14 22 42 00  ....t.Q......"B.

et un "D EDI+4", l'adresse de la procédure de branchement

:004CEE7C 0C 22 42 00 C0 00 00 00-14 22 42 00 C1 00 00 00  t.Q......"B.....

En fait l'ID de l'Item va être utilisé pour pointer dans une table sur l'adresse de début de la gestion de la procédure associé à cet Item.

Une rapide recherche dans un éditeur hexadécimal sur 0042220C vous donnera ceci:

000CE070 1200 0000 F821 4200 BF00 0000 0C22 4200 .....!B......"B.
000CE080 C000 0000 1422 4200 C100 0000 1C22 4200 ....."B......"B.

Rien de plus facile maintenant que de changer cette valeur par celle qui branchera sur le test Is_Registered avant d'aiguiller vers l'une ou l'autre des procédures d'affichage DialogBoxRegistration, ou ShellAboutBox.

Bonne Journée
Christal