Les Menus

Hommage à +Spath

Intoduction :

Il est absolument inutile que vous lisiez le pavé qui suit. Non seulement il est indigeste, mais il est en plus redondant avec l'exceptionnel texte de +spath sur la gestion des menus et des messages :
Theory and practice of menus reversing
Je vous invite donc à en cesser la lecture, et à vous reporter au tutoriel de +spath

Christal



Encore là !
Vous l'aurez voulu...
En fait si j'ai écrit ce texte, c'est en cherchant à mieux comprendre et pouvoir mettre en application les enseignements de +spath. L'approche que j'ai eu n'est pas radicalement différente, bien que j'ai essayé d'aborder le problème des menus de la manière la plus concrète possible, à partir d'un
petit programme joint.

Mais commençons par le début :


Les Ressources:

Elles se trouvent regroupées dans une même section, en général .rsrc, mais qui peut être rebaptisée de n'importe quel nom sans aucune incidence. Les différents outils à notre disposition vont pouvoir nous donner quelques informations:

Wdasm:

Number of Objects = 0004 (dec), Imagebase = 00400000h
.text    RVA: 00001000 Offset: 00000400 Size: 00001200 Flags: 60000020
.rdata   RVA: 00003000 Offset: 00001600 Size: 00000600 Flags: 40000040
.data    RVA: 00004000 Offset: 00001C00 Size: 00000400 Flags: C0000040
.rsrc    RVA: 00005000 Offset: 00002000 Size: 00000800 Flags: C0000040

ProcDump:

Directory Name                          VirtAddr  VirtSize
--------------------------------------  --------  --------
Export                                  00000000  00000000
Import                                  000030C0  0000008C
Resource                                00005000  00000608

Les ressources se trouvent dans la section .rsrc dont les caractéristiques ne permettent pas toujours l'écriture, ce qui n'est pas la cas ici (Data, Readable, Writeable)

04  .rsrc   Virtual Address         00005000  > adresse de début
            Virtual Size            00000608  > taille réel
            Raw Data Offset         00002000  > offset de début
            Raw Data Size           00000800  > taille avec Padding
            Characteristics         C0000040  
                 Initialized Data
                 Readable
                 Writeable

La Virtual size de la section est 00000608, ce qui veut dire que la mémoire qui sera allouée pour cette section sera de 0000608 bytes. Le Virtual Offset de la section est de 00005000, additionné à l'Image base qui vaut 00400000, ce qui donne une adresse virtuelle de 00005000 + 00400000 = 00405000, l'adresse à laquelle la section .rsrc débute.
La taille sur disque (Raw Size) est de 00000800, ce qui veut dire que le code n'utilise que 00000800 bytes dans le fichier .exe. Autrement dit le code s'étale de l'adresse 00405000 jusqu'à 00405000 + 0000800 = 00405800.
Virtual Size et Raw Size étant différent, vous aurez un peu de place disponible à la fin de cette section.
Dernière chose, la Virtual Address (5000) et la Raw data offset (2000) étant différent les adresses seront différentes quand vous éditerez le fichier avec un éditeur hexa (fichier sur Disque Dur) de celles que vous verrez avec SoftIce (fichier en mémoire).

Début de la section Ressources (.rsrc) dans Hiew à l'adresse 00405000

.00405000:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 03 00
.00405010:  03 00 00 00-28 00 00 80-04 00 00 00-40 00 00 80
.00405020:  0E 00 00 00-58 00 00 80-00 00 00 00-00 00 00 00

Et dans HexworkShop, à l'offset 2000

0000 0000 0000 0000 - 0000 0000 0000 0300 
0300 0000 2800 0080 - 0400 0000 4000 0080 
0E00 0000 5800 0080 - 0000 0000 0000 0000 


Les Menus et les items des Menus:

C'est, entre autres ressources, dans cette section que vous trouverez les informations liées aux menus. Un éditeur hexadécimal permet de mettre la main facilement dessus (particulièrement quand on connaît l'adresse de début de la section...), mais encore faut-il savoir décoder ce qui va être vu.

Trouver les items des menus:

Trouver leurs noms ne posent pas de problème. Il suffit de les avoir relevés lors de la phase découverte de l'application, et d'utiliser ensuite la fonction "Find" d'un éditeur hexa. Vous obtiendrez ceci:

01 00 04 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ...............
00 00 00 00 01 00 26 00 - 46 00 69 00 6C 00 65 00 ......&.F.i.l.e.

Si vous attaquez le programme par une recherche sur le nom d'un Item, pensez à le faire en respectant le Wide Char Format.
Le symbole "&" placé avant une lettre la fera apparaître souligné (raccourci clavier).

Mais vous ne serrez pas plus avancé pour autant, il va falloir:

Trouver l'identificateur ID:

Et des outils comme ExeEscope vont être particulièrement adaptés pour ce type de recherche:

0,&File
        1000,&Open          > 1000d = ID
        0,                  > séparateur
        1001,&Save
        0,                  > séparateur
        1010,&Exit
0,&Window
        1800,&Tile
        1801,&Cascade
        1802,&Arrange Icons
        1803,&Next Window$09F6 > touche [F6]
        1804,C&lose All
0,&Help
        1900,&About
0,SoftIce
        32001,Enable
        32002,Disable


Mais qu'est qu'un Menu, ou un Item de Menu:

Une barre de menu, lorsqu'elle est définie, contient un menu popup. Dans ce menu, des items sont définis, (ainsi que les séparateurs qui dessinent une ligne horizontale entre deux items d'un menu). Chaque item est décrit en utilisant ce format:

MENUITEM String, MenuID [Options]

String : Le symbole '&' placé devant une lettre signifie que cette lettre sera soulignée dans le menu (la plupart du temps pour indiquer un raccourci clavier)

MenuID : Un nombre qui va devenir la partie basse du word de wParam qui est envoyé avec le message WM_COMMAND (j'en parlerai un peu plus tard).

Options : comment l'item doit apparaître: CHECKED (08h) , GRAYED (01h ->inactive et grisé), INACTIVE (02h), MENUBARBREAK (20h), MENUBREAK(40h) (-> chaques Items étant placés sur une nouvelle ligne).

La plupart du temps, pour des questions de facilité, un éditeur de ressources (Type Ressources Editeur de Symantec) a été utilisé pour créer ces menus (ou les boites de dialogues...). Voici un extrait du script du fichier rsrc.rc tel qu'il a été édité par ResEdit . Vous remarquerez qu'à partir du fichier exécutable, ExeEscope a réussi à retrouver les mêmes informations. Nous verrons comment...

Source du script .RC

BEGIN
    POPUP "&File", , , 0
    BEGIN
        MENUITEM "&Open", 1000
        MENUITEM ""           , , 0x0800 /*MFT_SEPARATOR*/
        MENUITEM "&Save", 1001
        MENUITEM ""           , , 0x0800 /*MFT_SEPARATOR*/
        MENUITEM "&Exit"   , 1010
    END    

POPUP "&Window", , , 0 > le Menu PopUp est Normal (ni grisé, ni désactivé)

Un éditeur hexa va nous donner:

Normal (00)
0000 0000 0000 0000 0100 2600 5700 6900 ..........&.W.i.
6E00 6400 6F00 7700 0000 0000 0000 0000 n.d.o.w.........

Mais nous aurions pu avoir:

Grayed (01)
0100 0000 0000 0000 0100 2600 5700 6900 ..........&.W.i.
6E00 6400 6F00 7700 0000 0000 0000 0000 n.d.o.w.........

Disabled (02)
0200 0000 0000 0000 0100 2600 5700 6900 ..........&.W.i.
6E00 6400 6F00 7700 0000 0000 0000 0000 n.d.o.w.........

Vous remarquerez que le statut du Menu-PopUp (Grayed, Disabled...) se trouve 8 bytes avant le nom du menu en question.

BEGIN
MENUITEM "&Tile", 1800

Normal (00)
0000 0000 0000 0000 0807 0000 0000 2600 ..............&.
5400 6900 6C00 6500 0000 0000 0000 0000 T.i.l.e.........

mais si le script avait été celui ci:

MENUITEM "&Tile", 1800, , 0x0001 /*MF_GRAYED*/ > Menu Grisé

Grayed (01)
0000 0000 0100 0000 0807 0000 0000 2600 ..............&.
5400 6900 6C00 6500 0000 0000 0000 0000 T.i.l.e.........

Ou encore:

Checked (08)
0000 0000 0800 0000 0807 0000 0000 2600 ..............&.
5400 6900 6C00 6500 0000 0000 0000 0000 T.i.l.e.........
Grayed et Checked (09)
0000 0000 0900 0000 0807 0000 0000 2600 ..............&.
5400 6900 6C00 6500 0000 0000 0000 0000 T.i.l.e.........

On retrouve le même principe pour les Items de Menus que pour les menus PopUp
Les options des items sont les suivantes:

MF_ENABLED  (00h) : l'action peut être utilisée. 
MF_GRAYED   (01h) : l'action est grisée et ne peut être utilisée.
MF_DISABLED (02h) : l'action n'est pas grisée, mais un clic ne donne rien
                    (pas de message WM_COMMAND d'envoyé).

Et elles peuvent être combinées entres-elles, par exemple, pour griser et désactiver un item, vous utiliserez généralement 03h (MF_GRAYED + MF_DISABLED)

Une autre chose qu'il est indispensable de connaître, est l'identificateur (ID) de l'item de menus. C'est grâce à lui que le programme saura traiter l'événement concernant cet item. L'ID se trouve 4 bytes avant le nom de l'item.

        MENUITEM "&Cascade", 1801
        MENUITEM "&Arrange Icons", 1802
        MENUITEM "&Next Window\tF6", 1803 = 0x070B -> identificateur 070B
0B07 0000 0000 2600 4E00 6500 7800 7400 ......&.N.e.x.t.
2000 5700 6900 6E00 6400 6F00 7700 0900  .W.i.n.d.o.w...

Une recherche dans le listing désassemblé de Wdasm sur la valeur de cet identificateur va permettre de trouver le début de son traitement:

:00401C0F 817D100C070000          cmp dword ptr [ebp+10], 0000070C
:00401C16 7555                    jne 00401C6D
MENUITEM "C&lose All",              1804  = 0x070C
    END
00002350 0C07 0000 8000 4300 2600 6C00 6F00 7300 ......C.&.l.o.s.
00002360 6500 2000 4100 6C00 6C00 0000 0000 0000 e. .A.l.l.......
:00401C6D 817D106C070000          cmp dword ptr [ebp+10], 0000076C
:00401C74 0F8537020000            jne 00401EB1
 POPUP "&Help", , , 0
    BEGIN
        MENUITEM "&About", 1900 = 0x076C
0000 0000 6C07 0000 8000 2600 4100 6200 ....l.....&.A.b.
6F00 7500 7400 0000 0000 0000 0000 0000 o.u.t...........


La gestion des menus:

Prenons un peu de recul pour regarder comment la gestion des menus a été programmée:

Pour commencer, il faut faire apparaître la fenêtre avec ses menus:

invoke WinMain,hInstance,NULL,CommandLine,SW_SHOWDEFAULT
invoke ExitProcess,eax
> fin du programme

Ca va être le boulot de la procédure WinMain (Cf source ASM) qui va faire directement appel aux APIs suivantes:

RegisterClassEx > c'est l'API la plus intéressante sur laquelle poser un BPX pour mettre la main sur la procédure de fenêtre (WinMain). Il y a aussi RegisterClassExA, CreateWindowA, CreateWindowExA...
GetSystemMetrics
CreateWindowEx
ShowWindow
UpdateWindow

Puis une boucle va être créée pour la saisie des événements, par le biais de messages. c'est elle qu'il faut essayer de trouver, dans la mesure du possible, et qui va mener vers le traitement des événements.

StartLoop:
      invoke GetMessage,ADDR msg,NULL,0,0
      cmp eax, 0
      je ExitLoop
      invoke TranslateMessage, ADDR msg
      invoke DispatchMessage,  ADDR msg
      jmp StartLoop
    ExitLoop:

> valeur retournée: msg.wParam

Au retour de cette boucle, vous aurez le message (si un événement est survenu -> EAX <> 0) qui va être traité:

Ensuite c'est la procédure WinProc qui va prendre le relais, et s'occuper, entre autre, de gérer ces messages reçus, dont le très intéressant à repérer:

;======== toolbar commands ========

.if uMsg == WM_COMMAND

:00401610 817D0C11010000          cmp dword ptr [ebp+0C], 00000111
:00401617 0F85A6060000            jne 00401CC3

WM_Command est facilement identifiable par sa valeur 00000111. C'est le début de la gestion des événements (mais qui peuvent aussi bien être liés à une Dialog Box...). c'est aussi un excellent indicateur, facile à trouver par une recherche sur 00000111...

A partir de là, il n'y a plus qu'à suivre le Dead Listing, et mettre la main sur la routine recherchée, comme par exemple l'ouverture d'une Dialog Box pour la sauvegarde d'un fichier:

.elseif wParam == 1001
  jmp @F
   szTitleS   db "Save file as",0  > cette partie du code se traduira 
   szFilterS  db "All files",0,"*.*",0,"Text files",0,"*.TXT",0,0
                                   > par des opcodes semblant ne pas avoir de sens
                                   > On trouve souvent des BYTE 065h...
                                  
           @@:    
     invoke FillBuffer,ADDR szFileName,length szFileName,0
     invoke SaveFileName,hWin,ADDR szTitleS,ADDR szFilterS
  cmp szFileName[0],0            > si [cancel] est sélectionné 
je @F                            > fin du traitement de l'événement
                                 > sinon le nom du fichier retourné est dans szFileName

:00401946 817D10E9030000  cmp dword ptr [ebp+10], 000003E9  > Item (1101d) sélectionné ?
:0040194D 756F            jne 004019BE                      > non -> suite 
:0040194F EB2D            jmp 0040197E                      > oui -> traitement 

Traitement de la fonction " Save as "

:0040197E 6A00                    push 00000000  > NULL
:00401980 6804010000              push 00000104  > longueur de szFileName 
:00401985 68B7404000              push 004040B7  > adresse de szFileName
:0040198A E882FAFFFF              call 00401411  > récupère le nom du fichier 

* Possible StringData Ref from Code Obj ->"All files"
                                  |
:0040198F 685E194000              push 0040195E   > "All files"
:00401994 6851194000              push 00401951   > "Save File As"
:00401999 FF7508                  push [ebp+08]   > Handle de la DialogBox
:0040199C E80FFAFFFF              call 004013B0   > vers GetSaveFileNameA 
:004019A1 803DB740400000          cmp byte ptr [004040B7], 00 > Buffer Vide ?
:004019A8 7414                    je 004019BE     > jump si [cancel] est sélectionné

A la sortie de cette routine, nous avons le path et le nom du fichier à enregistrer.
Mais revenons à nos Items de Menus.
Ce qui va nous intéresser, c'est de pouvoir réactiver un menu Grayed et/ou Disabled, éventuellement trouver comment un Item de Menu est supprimé d'un menu PopUp.
Imaginons, par exemple, un Item "Register" qui serait désactivé ou supprimé après enregistrement, ou un menu "Save As" non sélectionnable...
Autant de (bonnes) pistes à suivre pour trouver une solution.

Commençons par le plus facile:

Réactiver un Menu PopUp et/ou un Item de menu.

Le plus facile, oui! A condition que la routine existe, ce qui n'est pas toujours le cas...
De l'Item dégrisé à l'Item branchant sur une routine, il peut y avoir une marge...
Certaines versions Démo ont la fonction "Save as" de désactivée tout simplement parce que la fonction n'existe pas! (il y a moyen de corriger ce bug...).

En premier lieu, il faut réactiver l'accès à l'item. Le plus rapide est d'utiliser un éditeur hexa, de rechercher le nom de l'item dans la section ressource, et de remettre en place la "bonne" valeur. Si vous ne trouvez rien d'autre que des 00, c'est qu'au lancement de l'application le menu est activé, et ce n'est qu'après un éventuel test "Utilisateur Enregistré" que l'item est désactivé.

Or il a bien fallu dans ce cas désactiver l'Item.
Le plus classique pour ceci, est l'utilisation de:

invoke EnableMenuItem,hWmenu,32001,MF_ENABLED

:00401BD8 6A00                    push 00000000             > option (0 = menu activé)
:00401BDA 68017D0000              push 00007D01             > ID du menu (32001)
:00401BDF FF75F8                  push [ebp-08]             > handle (hWmemu)
:00401BE2 E8DD040000              Call USER32.EnableMenuItem

dont voici ce qu'en donne le Win32Help:

La fonction
EnableMenuItem active, désactive, ou grise le menu item spécifié.

EnableMenuItem(

    HMENU hMenu,            // handle du menu
    UINT uIDEnableItem,    // menu item à activer, désactiver ou griser
    UINT uEnable          // statut de l'item de menu (flag)
   );

Paramètres:

hMenu
Identifie le menu

uIDEnableItem
Spécifie le menu item qui doit être activé, désactivé ou grisé comme défini par le paramètre uEnable. Ce paramètre défini un item dans une barre de menu, un menu ou un sous-menu.

uEnable
Spécifie le drapeau qui contrôle l'interprétation du paramètre uIDEnableItem et indique si le menu item est activé, désactivé ou grisé. Ce paramètre peut être une combinaison de MF_BYCOMMAND ou MF_BYPOSITION et de MF_ENABLED, MF_DISABLED, ou MF_GRAYED.

La valeur retournée spécifie l'état précédent de l'item de menu (en général MF_DISABLED, MF_ENABLED, ou MF_GRAYED).

Une recherche sur EnableMenuItem a donc de forte chance, en remontant dans la hiérarchie du Dead Listing, de vous permettre de trouver le Call/Test/Jump vers cette routine, et/ou le test utilisateur enregistré!

Un autre bon indicateur de manipulations sur les Menus et les Items est la fonction qui va permettre au programme de récupérer le Handle du menu en question

:00401623 E8AE0A0000              Call USER32.GetMenu
:00401628 8945F8                  mov dword ptr [ebp-08], eax (-> handle)

HMENU GetMenu (  HWND hWn   // handle qui identifie la fenêtre    );

De la même façon, vous avez également SetMenu et GetSubMenu qui peuvent être utilisés.

:00401D0A FF3504404000            push dword ptr [00404004] > handle du menu
:00401D10 FF7508                  push [ebp+08]             > handle de la fenêtre
:00401D13 E812040000              Call USER32.SetMenu
:00401D18 6A01                    push 00000001             > position du menu (1er rang)
:00401D1A FF3504404000            push dword ptr [00404004] > handle du menu
:00401D20 E8BD030000              Call USER32.GetSubMenu
:00401D25 89856CFFFFFF            mov dword ptr [ebp+FFFFFF6C], eax


SetMenu

La fonction SetMenu assigne un nouveau menu à la fenêtre spécifiée.

SetMenu(

    HWND hWnd,	       // handle de la fenêtre
    HMENU hMenu        // handle du menu
   );	


hWnd
Identifie la fenêtre pour laquelle un menu va être ajouté.

hMenu
Identifie le nouveau menu. Si ce paramètre est NULL, le menu de la fenêtre est replacé.


GetSubMenu

La fonction GetSubMenu récupère le handle du drop-down menu ou sous-menu activé par l'item de menu spécifié.

HMENU GetSubMenu(

    HMENU hMenu,       // handle du menu
    int nPos          // position du menu Item
   );	

hMenu
Identifie le menu.

nPos
Spécifie la " zero-based relative position " dans un menu ou un sous-menu.

Pour les menus de type "Register" qui sont parfois supprimés au moment de l'enregistrement, vous avez :

DeleteMenu

La fonction DeleteMenu efface un item d'un menu spécifié.

DeleteMenu(

    HMENU hMenu,         // handle du menu
    UINT uPosition,     // menu item identifier ou position
    UINT uFlags        // menu item flag
   );	

hMenu
Identifie le menu qui doit être modifié.

uPosition
Spécifie l'item de menu à effacer, ou défini par le paramètre uFlags.

uFlags
Spécifie comment le paramètre uPosition est interprété. Ce paramètre doit être l'une des valeurs suivantes :

Value Meaning:
MF_BYCOMMAND Indicates that uPosition gives the identifier of the menu item. The MF_BYCOMMAND flag is the default flag if neither the MF_BYCOMMAND nor MF_BYPOSITION flag is specified.
MF_BYPOSITION Indicates that uPosition gives the zero-based relative position of the menu item.

RemoveMenu

La fonction RemoveMenu efface un menu d'un menu spécifié.

BOOL RemoveMenu(

    HMENU hMenu,         // handle du menu
    UINT uPosition,     // menu item identifier ou position
    UINT uFlags        // menu item flag
   ); 

Les paramètres sont identiques à la fonction précédente.



Reverse Engineering

Vous me direz que de reverser un programme dont on dispose du source c'est quand même un peu facile. C'est vrai !
Mais laisser moi rêver...
Et puis c'est fou ce que j'arrive à apprendre ainsi!

Bien !
Maintenant comment pourrais-je reverser mon propre programme ?

Réactiver le menu SoftIce ?
Bof !
Un ch'tit coup d'éditeur hexa au bon endroit, et ça roule…
Pas du reverse, ça.

Par contre, je peux essayer d'effacer l'un des Items du menu SoftIce...
Ensuite je vais voir comment modifier les items à ma convenance, puis coller la fonction " Save As " à l'un des PushBouttons.

Mais commençons par recenser la place disponible.

Virtual Size            00000608
Raw Data Offset         00002000  
Raw Data Size           00000800

Nous avons vu dans la première partie que le code de la section .rsrc commence à l'offset 0x2000 pour finir à l'offset 0x2800, mais que la taille du code étant de 0x0608, il doit être possible de squatter quelques octets à partir de l'offset 0x2610

Un coup d'œil dans hexWorkShop montre qu'à partir de cet Offset, il y a effectivement de la place disponible, et un bpr 405610 405800 va confirmer que rien ne viendra nous y déranger.
405800 - 405610 = 01F0, soit 496 octets. Ca devrait aller, sinon il y aura certainement moyen de squatter entre deux sections...
Vive le Padding !


Suppression d'un Item de Menu :

Pour supprimer un Item de menu, on peux utiliser l'API DeleteMenu.
Mais il va me falloir le handle du menu, et pour cela je vais utiliser l'API GetMenu.
Je vais aussi avoir besoin de récupérer le handle de la fenêtre. Il y a certainement mieux à faire que ce que j'ai trouvé, mais à défaut...

Cherchons dans la liste des APIs données par Wdasm, l'une d'entres-elles qui aurait également besoin de ce Handle.

Par exemple
GetClientRect :

GetClientRect(
    HWND hWnd,         // handle de la fenêtre
    LPRECT lpRect     // adresse de la structure pour les coordonnées clientes 
   );	 

Maintenant, jetons un coup d'œil dans Wdasm :

:00401DDE FF3508404000            push dword ptr [00404008] > ha ! ha !
:00401DE4 E8E7020000              Call USER32.GetClientRect

Il y a fort à parier que le Handle de la fenêtre s'y trouve (Oui, je sais...)

Il y a un autre endroit où il est courant de pouvoir capturer le handle de la fenêtre. En tout début de programme, au moment de l'affichage de la fenêtre, vous trouverez des codes de ce genre :

:0040159A E8250B0000              Call USER32.CreateWindowExA
:0040159F A308404000              mov dword ptr [00404008], eax    > ici

" mov hWnd, eax " pour HandleWindow. Donc [00404008] va contenir le Handle de la fenêtre et ce tant que l'application ne sera pas fermée...

La fonction
CreateWindow crée des overlapped, des pop-up, ou des child windows. Elle spécifie la window class, le window title, le window style, et (éventuellement) la position d'origine et la taille de la fenêtre

HWND CreateWindow(

    LPCTSTR lpClassName,        // pointeur pour enregistre la " class name "
    LPCTSTR lpWindowName,      // pointeur vers le nom de la fenêtre 
    DWORD dwStyle,            // window style
    int x,                   // horizontal position of window
    int y,                  // vertical position of window
    int nWidth,            // largeur de la fenêtre
    int nHeight,          // hauteur de la fenêtre
    HWND hWndParent,     // handle to parent or owner window
    HMENU hMenu,        // handle to menu or child-window identifier
    HANDLE hInstance,  // handle to application instance
    LPVOID lpParam    // pointer to window-creation data
   );

Au retour de cette fonction, EAX contient le handle de la fenêtre.

Il va falloir aussi que je trouve d'où faire glisser le programme vers le patch envisagé. Je décide, cette fois, d'affecter l'un des boutons de la barre d'outils à cette tâche.

Retrouver l'ID des boutons n'est pas très compliqué dans ce programme. Toujours en partant de WM_Command (00000111), il suffit de tracer tranquillement jusqu'aux comparaisons d'ID :

:0040163B 837D1033                cmp dword ptr [ebp+10], 00000033 > ID 3eme bouton
:0040163F 754E                    jne 0040168F
:00401641 EB11                    jmp 00401654

Les 2 sauts suivants étant codés sur 4 octets alors qu'il m'en faut 5, j'opte pour écraser le cmp dword [ebp+10].
Bien sur il faudra que je pense à le remettre...

Et voici le patch en question :

:00405610  837D1034            CMP     DWORD PTR [EBP+10],34 > restauration
:00405614  0F85C9C0FFFF        JNZ     004016E3              > saut si <>
:0040561A  FF3508404000        PUSH    DWORD PTR [00404008]  > handle fenêtre
:00405620  E8F4CDB4BF          CALL    USER32!GetMenu        > récupère handle menus
:00405625  6A00                PUSH    00                    > push NULL
:00405627  6668027D            PUSH    7D02                  > Item " disable "
:0040562B  50                  PUSH    EAX                   > push handle menus
:0040562C  E835CDB4BF          CALL    USER32!DeleteMenu     > efface Item
:00405631  E972C0FFFF          JMP     004016A8              > retour à la normale

Et Valà !
En poussant sur le troisième bouton de ma fenêtre, j'efface le second Item du menu SoftIce.

Deuxième petit exercice:

Modifier un Menu et ses Items:

Cette fois ci, l'idée est de modifier le texte d'un menu, puis le texte d'un Item en détournant vers un patch le traitement des événements lui arrivant.

Le menu

SoftIce       (désactivé)
    Enable    (désactivé)
    Disable   (Activé)

va devenir

Cracked        (Activé)
    &Gretz     (Activé)
    Key Gen    (Activé)

Avec traitement des événements survenant à Gretz et Key Gen.
Le plus simple pour la modification des menus et de leurs Items est d'utiliser un outils genre exeEscope. Pour l'avoir testé, il est facile d'enlever des menus, d'en rajouter, de modifier, supprimer ou rajouter des items (en pensant à leur affecter une valeur ID, si ça n'est pas automatique, et surtout à la noter quelque part !). Il y a aussi moyen de réaliser les modifications souhaitées à la main en utilisant un éditeur hexadécimal.

Pour éviter de semer la pagaille dans les ressources, il semble préférable de ne pas modifier le nombre de caractères composant le texte des menus ou des Items. Si le ressource Menu est la dernière de la section, il n'y aura pas d'incidence, mais si vous avez d'autres ressources (String table, Dialog, etc…) qui suivent, vous mettrez une belle pagaille dans les adresses. Tout au plus vous pouvez vous autoriser un à deux caractères supplémentaires avant d'empiéter sur les informations de la ressource suivante.

Pour ma petite bidouille, j'ai modifié les textes suivant mes souhaits, en n'oubliant pas de rendre le menu et les items Actifs.

En relançant le programme, tout fonctionne.
Les identificateurs des menus n'ayant pas changé, il va juste être nécessaires de ré-adresser les branchements vers un patch, ou si la place est suffisante, modifier la routine assurant le traitement de l'événement.

Enable -> &Gretz

La routine traitant l'événement survenu à l'(ex)Item Enable commence à l'adresse 00401B1B et fini à l'adresse 00401B8A.

:00401B12 817D10017D0000          cmp dword ptr [ebp+10], 00007D01   > événements ?
:00401B19 7574                    jne 00401B8F                       > non -> continue
:00401B1B 833D5642400000          cmp dword ptr [00404256], 00000000 > début traitement
Snips---------- Snips------------------------------
:00401B80 C7054940400000000000    mov dword ptr [00404049], 00000000 > Flag
:00401B8A E922030000              jmp 00401EB1                       > fin de la routine

00401B80 - 00401B1B = 0x65. Je dispose de 101 octets pour m'exprimer.
Comme j'ai opté pour une MessageBox, je ne vais pas avoir besoin de beaucoup de place, sauf peut être pour le message à passer.
Celui ci, je vais l'écrire dans la dernière partie de la section .rsrc en 004057D0, et le titre de ma boite de message en 004057C0

0137:00401B1B  6A00                PUSH    00       > MB_OK
0137:00401B1D  68C0574000          PUSH    004057C0 > titre
0137:00401B22  68D0574000          PUSH    004057D0 > message
0137:00401B27  6A00                PUSH    00       > handle de la fenêtre propriétaire
0137:00401B29  E8AB1DB6BF          CALL    USER32!MessageBoxA
0137:00401B2E  E97E030000          JMP     00401EB1

Il me reste 77 octets de disponibles, et en fait il serait plus "économique" d'y loger mes deux chaînes de caractères (titre et message) en remplacant 004057C0 par 00401B33 et 004057D0 par 00401C33...

Passons au second Item:

On va s'amuser à activer et à désactiver la détection SoftIce. Si j'ai appelé ce second Item " Key Gen ", c'est en me disant que je pourrais bien m'en servir pour " retourner " une éventuelle boite d'enregistrement de façon à ce qu'elle me donne le bon sérial (voir à ce sujet le tutoriel sur PC Screen Tools. Même collection, même auteur).

Et par exemple en partant de cette référence :

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401A3E(C)
* Possible StringData Ref from Data Obj ->"Glop! Glop! No SoftIce"
                                  |
:00401A4F 6843424000              push 00404243
:00401A54 E88B040000              call 00401EE4
:00401A59 E96B040000              jmp 00401EC9

Il pourrait être intéressant de savoir pourquoi ce programme se réjouit tant de ne pas avoir SoftIce...

:00401A32 E80B070000              Call 00402142
:00401A37 833D5A42400001          cmp dword ptr [0040425A], 00000001
:00401A3E 750F                    jne 00401A4F

Une comparaison de [0040425A] avec 1 ?
Tiens tiens...

(Oui, je sais...)

Cherchons commet SoftIce est détecté :

* Possible StringData Ref from Data Obj ->"\\.\SICE"
                                  |
:00401F12 680B424000              push 0040420B             > \\.\SICE
:00401F17 E844020000              Call KERNEL32.CreateFileA
:00401F1C 83F8FF                  cmp eax, FFFFFFFF         > si Good EAX = -1
:00401F1F 7402                    je 00401F23               > pas Glop !
:00401F21 33C0                    xor eax, eax              > Glop ! Glop !

Un classique Meltice (Quelle originalité !).

En traçant sous SoftIce, on sort ici :

:00401624  833D4D40400000      CMP     DWORD PTR [0040404D],00       > test
:0040162B  7513                JNZ     00401640                      > non -> saute 
:0040162D  E8E6080000          CALL    00401F18        > call Sice_Detection
:00401632  85C0                TEST    EAX,EAX         > EAX = 0 ?
:00401634  750A                JNZ     00401640        > pas Glop ! 
:00401636  C7055A42400001000000MOV     DWORD PTR [0040425A],00000001 > Glop ! Glop !
:00401640  817D0C11010000      CMP     DWORD PTR [EBP+0C],00000111   > WM_Command

Donc pour désactiver la détection Sice (et pouvoir la réactiver) il va falloir s'intéresser de très prés à [0040404D]. On voit ici que si ce flag est à 1, le programme n'ira pas vérifier la présence de SoftIce !

Mettons la main sur la routine de gestion de l'événement " Key Gen ".
C'est l'ancien Disable dont l'ID est égal à 32001, soit 0x7D01.

Petite recherche sur cette valeur, et...

:00401B2A 817D10017D0000          cmp dword ptr [ebp+10], 00007D01
:00401B31 7574                    jne 00401BA7

Voilà l'entrée du traitement de cet Item dont le rôle actuel n'est que de désactiver la détection SI.
Le traitement de l'événement en lui-même commence ici :

:00401BF4  6A00                PUSH    00                    > MB_OK
:00401BF6  681C404000          PUSH    0040401C              > titre
:00401BFB  68D71B4000          PUSH    00401BD7              > message
:00401C00  FF7508              PUSH    DWORD PTR [EBP+08]    > handle fenêtre
:00401C03  E83A050000          CALL    USER32!MessageBoxA    > SI désactivé
:00401C08  6A00                PUSH    00                    > actif
:00401C0A  68017D0000          PUSH    00007D01              > Item visé
:00401C0F  FF75F8              PUSH    DWORD PTR [EBP-08]    > handle menu
:00401C12  E8E3040000          CALL    USER32!EnableMenuItem > item activé
:00401C17  6A01                PUSH    01                    > inactif
:00401C19  68027D0000          PUSH    00007D02              > Item visé
:00401C1E  FF75F8              PUSH    DWORD PTR [EBP-08]    > Handle menu
:00401C21  E8D4040000          CALL    USER32!EnableMenuItem > Item désactivé


notre patch va donner ceci :

.si Flag_contrôle_SI = 0
            message "Détection SoftIce Activée"
            mov Flag_SI-détection , 01
            mov Flag_contrôle_SI, 01
      .sinon 
            message "Détection SoftIce Désactivée"
            mov Flag_SI-détection , 00
            mov Flag_contrôle_SI ,00
.endif

mais comme il y a besoin de plus de place de disponible (ne serait-ce que pour les deux Message Box), que celle qu'il est possible de prendre, on va squatter à la suite de notre précédente modification, en 00405636.

Comme les deux messages à afficher existent déjà, il va suffire de récupérer leurs adresses :
00401BD7 Détection SoftIce Désactivée
00401B5A Détection SoftIce Activée
0040401C Softice Disabled
0040403D Softice Enabled

Le Flag_SI-détection est déjà connu : [0040425A]
Le Flag Contrôle présence SI : [0040404D]

Le Patch va ressembler à ceci :

:00405636  803D4D40400000      CMP     BYTE PTR [0040404D],00
:0040563D  7526                JNZ     00405665
:0040563F  6A00                PUSH    00
:00405641  681C404000          PUSH    0040401C
:00405646  68D71B4000          PUSH    00401BD7
:0040564B  6A00                PUSH    00
:0040564D  E8DCEAB4BF          CALL    USER32!MessageBoxA
:00405652  C6055A42400000      MOV     BYTE PTR [0040425A],00
:00405659  C6054D40400001      MOV     BYTE PTR [0040404D],01
:00405660  E97CC8FFFF          JMP     00401EE1
:00405665  6A00                PUSH    00
:00405667  683D404000          PUSH    0040403D
:0040566C  685A1B4000          PUSH    00401B5A
:00405671  6A00                PUSH    00
:00405673  E8B6EAB4BF          CALL    USER32!MessageBoxA
:00405678  C6054D40400000      MOV     BYTE PTR [0040404D],00
:0040567F  E95DC8FFFF          JMP     00401EE1

Et vous voici avec un bel interrupteur ON/OFF sur l'item " Key Gen "...


Dernière bidouille envisagée :

Permettre la sauvegarde d'un fichier à partie d'un des PushButton de la barre des outils.
Il est tentant de vouloir réutiliser la routine existante dans le programme...

L'appui sur le troisième bouton va être traité ici :

:004016BF  837D1034            CMP     DWORD PTR [EBP+10],34
:004016C3  754E                JNZ     00401713
:004016C5  EB11                JMP     004016D8  > procédure de traitement de l'événement

et la sélection de l'Item " Save " nous fait atterrir ici :

:004018FD  817D10E8030000      CMP     DWORD PTR [EBP+10],000003E8
:00401904  7570                JNZ     00401976
:00401906  EB2C                JMP     00401934 > procédure de traitement

Il est alors on ne peut plus facile de brancher de 004016C5 directement vers 00401934 par un jmp long.

Merci à +Spath pour l'aide qu'il nous apporte à travers ses écrits de grande qualité

Bonne journée

Christal