Un essai pour comprendre comment fonctionnent les menus et les messages, pour être capable de renverser les fonctions désactivées ou grisées

Theory and practice of menus reversing

Written by +Spath.

Introduction


Ce texte est une introduction au fonctions désactivées; je l'ai écrit parce que je n'avais trouvé aucun essai décrivant à la fois les menus liés et les messages liés. Mon but ici était d'expliquer les bases du fonctionnement des menus et de donner un certains nombre d'éléments et d'exemples, de façpon a ce que tout débutant puisse commencer à chercher dans la bonne direction


I) MENUS
1) Créer un menu
2) Modifier un menu
3) Reversing a menu definition
II) WINDOWS MESSAGES
1) Messages et fenêtres
2) Messages vers une application
III) REVERSING D'UNE FONCTION DESACTIVEE
1) Avant que vous ne commenciez
2) Quelques éléments
IV) HANDS ON
1) CAP 1.0
2) Sigmastat 2.0
3) Bestwin
4) Flipper

Tools required


Un œil et un cerveau sont nécessaires. Ce texte est long, aussi un peu de Thé pourra vous aider

Target's URL/FTP


Voir IV)

Program History


Aucun.

Essay


I) WINDOWS MENUS

1) Créer un menu

Les menus peuvent être créés en utilisant soit une fonction de calibrage soit une fonctions de création.

- Créer un menu en utilisant une fonction de calibrage:

C'est la méthode la plus commune; la plupart des formats des menus sont définis comme des ressources et sont contenus dans un fichier séparé du code réel. Un format de menu définit la totalité d'un menu, incluant toutes les fonctions, les séparateurs et les sous-menus. Voici un exemple typique de calibre de menu C/ASM:

IDR_MENU1 MENU DISCARDABLE 
BEGIN
    POPUP "File"
    BEGIN
        MENUITEM "&Open...", ID_BROWSE 
        MENUITEM "&Execute", ID_EXECUTE GRAYED 
        MENUITEM SEPARATOR
        MENUITEM "E&xit", ID_CANCEL
    END
END

Une barre de menu est définie et contient un menu popup, appellé File (de
here on, les popup menus s'y référeront). Dans ce menu, quelques items sont définis, da la même façon que les séparateurs (et qui déssine une ligne horizontale entre deux items d'un menu). Chaque item est décrit en utilisant ce format:

MENUITEM  String,  MenuID   [Options]

String  : Les caractères des items du menu sont compris entre des " ". 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, GRAYED (inactive et grisé), 
          INACTIVE, MENUBARBREAK, MENUBREAK (chaque item étant placé sur une 
          nouvelle ligne).


- Création de menus au lancement de l'application :

En employant des fonctions de création de menu, vous pouvez créer des menus au lancement de l'application, ou ajouter des items à un menu existant. Vous pouvez d'abord utiliser la fonction CreateMenu()pour créer une barre de menu vide, et la fonction CreatePopupMenu()pour créer un menu vide. Puis, pour ajouter des items à un menu, AppendMenu(), InsertMenuItem() and InsertMenu() peuvent être utilisés : AppendMenu() ajoute uniquement un item à la fin d'un menu ou sous-menu, tandis que les deux autres vous permettent d'insérer un item à une position spécifique. Toutes les fonctions vous permettent de spécifier les attributs de l'item, incluant si il est activé, désactivé, grisé, séléctionné ou déselectionné.

Après qu'une barre de menus est été chargée ou créée, elle doit être affectée à une fenêtre.
La première méthode est de le faire dans l'enregistrement de la class : avant la création de la fenêtre, un programme doit enregistrer la class des fenêtres via RegisterClass()ou RegisterClassEx(). Le paramêtre de cette fonction est un pointeur vers la structure de WNDCLASS ou de WNDCLASSEX:

typedef struct _WNDCLASS {        typedef struct _WNDCLASSEX {  
    UINT    style;                    UINT    cbSize; 
    WNDPROC lpfnWndProc;              UINT    style;  
    int     cbClsExtra;               WNDPROC lpfnWndProc; 
    int     cbWndExtra;               int     cbClsExtra;
    HANDLE  hInstance;                int     cbWndExtra; 
    HICON   hIcon;                    HANDLE  hInstance; 
    HCURSOR hCursor;                  HICON   hIcon; 
    HBRUSH  hbrBackground;            HCURSOR hCursor; 
    LPCTSTR lpszMenuName;             HBRUSH  hbrBackground;
    LPCTSTR lpszClassName;            LPCTSTR lpszMenuName;
} WNDCLASS;                           LPCTSTR lpszClassName; 
                                      HICON   hIconSm; 

} WNDCLASSEX;

Le paramêtre lpszMenuName de la structure indique quel menu sera associé à une fenêtre de cette class (créée par CreateWindow() ou CreateWindowEx() ). Les autres méthodes sont d'assigner un menu après la création de la fenêtre, ce qui peut être fait avec SetMenu().

Juste une remarque au sujet des menus contextuels (aussi appellé menu flottant) qui sont ceux activés par le bouton droit de la souris : ils sont la plupart du temps envoyé via TrackPopupMenu() ou TrackPopupMenuEx() quand un WM_CONTEXTMENU est utilisé.


2) Modifier un menu

Plusieurs fonctions permettent de changer un menu ou les items d'un menu après qu'ils aient été chargés.

- SetMenuItemInfo() permet de changer les attributs d'un item de menu existant; les attributs incluent le type, l'état, l'identifieur, le sous-menu, les bitmaps, les items data, et le texte. Avant de modifier un menu, GetMenuItemInfo() peut être utilisé pour récupérer des informations sur un item. Cette fonction retourne une stucture MENUITEMINFO, qui spécifie l'attribut à récuperer et recoit les valeurs nécessaires.

- ModifyMenu() remplace l'item du menu visé par un nouvel item. Il peut être utilisé pour activer, désactiver, griser, sélectionner ou déselectionner la chaine de texte ou le bitmap d'un item de menu.

- EnableMenuItem(), le plus celèbre: cette fonction existe spécifiquement pour activer, désactiver, ou grisé un item de menu.

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

Le drapeau de l'item de menu contient deux informations, l'action à appliquer à l'item et la méthode de sélection:

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 message d'envoyé).

MF_BYCOMMAND (00h)   : uIDEnableItem donne le menuID de l'item 
                       (par défaut).
MF_BYPOSITION (400h) : uIDEnableItem donne le zero-based 

pour griser un item, vous utiliserez généralement 03h (MF_GRAYED + MF_DISABLED)

- DeleteMenu() ou RemoveMenu() pour effacer l'item d'un menu.
Si l'Item qui doit être éffacé et l'un de ceux qui ouvre un sous-menu,
DeleteMenu() efface les sous-menus associés et libère la mémoire utilisée par le sous-menu. La fonction RemoveMenu() efface un itm de menu, mais si l'item ouvre un sous-menu, la fonction ne détruira pas le sous-menu ou son handle, autorisant le sous-menu à être utilisé de nouveau.


3) Reversing a menu definition

Prenons comme exemple le fichier ressource de 1) et compilons le: vous verrez ici comment il se présente dans un fichier exécutable:

10 00 46 00 69 00 6C 00-65 00 00 00 00 00 E8 03  ..F.i.l.e.......
26 00 4F 00 70 00 65 00-6E 00 2E 00 2E 00 2E 00  &.O.p.e.n.......
00 00 00 01 EA 03 26 00-45 00 78 00 65 00 63 00  ......&.E.x.e.c.
75 00 74 00 65 00 00 00-00 00 00 00 00 00 80 00  u.t.e...........
02 00 45 00 26 00 78 00-69 00 74 00 00 00 00 00  ..E.&.x.i.t.....

qui peut être décodé de cette façon, si vous rétablissez le widechar format :

10h  "File" 
00h, 03E8h, "&Open..."
01h, 03EAh, "&Execute"
80h
00h, 0002h, "E&xit"

la principale modification de cette ressource (conformément au format wide char) est l'inversion des paramêtres (l'ordre sera le suivant: Options, MenuID, et String).

Avant chaque option, vous voyez la partie basse du word de la valeur de wParam du message WM_COMMAND qui sera envoyé à l'application quand la fonction sera sélectionnée; cette valeur est égal à celle définie pour MenuID dans le fichier ressource.

La valeur de l'option documentée correspond aux flags des l'items qui y sont associés: ils sont grisés(01h), inactives(02h), sélectionnés(08h), menubarbreak (20h), menubreak (40h). Aucune option ne correspond à la valeur 00h, qui indique que l'item est active. Comme vous voyez, chaque option utilise seulement un bit de l'octet, pour chaque combinaison (par exemple, 09 est grisé et selectionné). Les autres valeurs sont:

04h (MF_BITMAP) pour utiliser un bitmap comme un item de menu.
10h (MF_POPUP) doit être utilisé pour la définition d'un menu, il crash USER.EXE quand il est utilisé avec un item.
80h (MF_HILITE) est utilisé pour les séparateurs de menuitem.

Avec le fichier ressources de 1), l'option Execute est grisée; si bien que pour l'activer, vous aurez juste à remplacer 01 (2 octets avant le "&Execute") par des 00.

Regardons maintenant la définition d'un menu VB:

...mnuFile.....&  05 07 00 6D 6E 75 46 69-6C 65 00 13 03 05 00 26   
File.....&......  46 69 6C 65 00 07 FF FF-02 26 00 00 00 06 0B 00  
mnuFileSave.....  6D 6E 75 46 69 6C 65 53-61 76 65 00 13 03 0A 00  
&Save Text......  26 53 61 76 65 20 54 65-78 74 00 08 13 00 FF 02  
.......mnuFileSe  1D 00 00 00 07 0C 00 6D-6E 75 46 69 6C 65 53 65  
p_1.....-.....!.  70 5F 31 00 13 03 01 00-2D 00 06 FF FF 02 21 D1  
.....mnuFileExit  00 00 08 0B 00 6D 6E 75-46 69 6C 65 45 78 69 74  
.....E&xit......  00 13 03 05 00 45 26 78-69 74 00 08 11 00 FF 03  
........mnuAbout  02 1E 00 00 00 09 08 00-6D 6E 75 41 62 6F 75 74  
.....&About.....  00 13 03 06 00 26 41 62-6F 75 74 00 07 FF FF 02  
"......mnuAboutR  22 00 00 00 0A 0B 00 6D-6E 75 41 62 6F 75 74 52  
eg.....&Register  65 67 00 13 03 09 00 26-52 65 67 69 73 74 65 72  
..........mnuAbo  00 FF 02 1F 00 00 00 0B-0B 00 6D 6E 75 41 62 6F  
utAbo.....A&bout  75 74 41 62 6F 00 13 03-06 00 41 26 62 6F 75 74  
..........+@....  00 FF 03 03 04 06 00 00-00 DC 2B 40 00 01 00 01  

Il peut être interprété de cette façon:

05 0700 "mnuFile"00      1303 0500 "File"00       07FFFF02 2600     0000
06 0B00 "mnuFileSave"00  1303 0A00 "&Save Text"00 081300FF 021D00   0000
07 0C00 "mnuFileSep_1"00 1303 0100 "-"00          06FFFF02 21D1     0000
08 0B00 "mnuFileExit"00  1303 0500 "E&xit"00      081100FF 03021E00 0000
09 0800 "mnuAbout"00     1303 0600 "&About"00     07FFFF02 2200     0000
0A 0B00 "mnuAboutReg"00  1303 0900 "& Register"00 FF021F00          0000
0B 0B00 "mnuAboutAbo"00  1303 0600 "A&bout"00     FF030304 0600     0000

Et voici une description des champs:
1 : le numéro de l'item qui sera défini.
2 : taille de la prochaine chaine
3 : nom du menuitem comme il a été entré durant sa création dans VB, c'est une
chaine se terminant par des 00.
4 : délimitateur spécial entre les 2 chaines; ces valeurs sont utilisées pour
contrôler que la taille des chaines est correcte.
5 : taille de la prochaine string.
6 : nom de l'item qui sera affiché. Notez que le "-" est une valeur
particulière, reservée pour les séparteurs du menuitem; qui lui aussi est
une chaine se terminant par de 00.

Les autres champs sont laissés à la curiosité des reversers:)

Hey, mais ou sont les valeurs de MenuID? Et bien, ils ne sont pas stockés ici, nous verrons plus tard qu'elles ne sont pas stockées du tout...


II) WINDOWS MESSAGES

1) Messages et Windows

Quand vous tapez une clé, ou que vous bougez la souris, le device driver correspondant converti cette entrée en messages et placent ceux ci dans la file d'attente du message. Puis Windows prend ces messages dans cette file, détermine quelle fenêtre est active, et envoie le message à la file d'attente qui a créé cette fenêtre.

Un message est une stucture déclarée comme suit:

typedef struct MSG {
   HWND hwnd;     // handle de la fenêtre pour qui le message est destiné
   WORD message;  // type du message (WM_COMMAND, WM_PAINT,...)
   WPARAM wParam; // permier parametre, depend du type de message
   LONG lParam;   // second parametre, depend du type de message
   DWORD time;    // temps d'arrivée du message
   POINT pt;      // localisation (X,Y) de la souris quand le message arrive}

Selon l'API employée (Win32 or Win16) le contenu de wParam, de lParam et la taille de wparam peuvent varier. Par exemple, un message WM_COMMAND est :
dans Win16 : wParam(16-bits) - Identificateur pour la commande
lParam(32-bits) - LOWORD: Window handle, HIWORD: Special modifier

dans Win32 : wParam(32-bits) - LOWORD: Identifier, HIWORD: modifier
lParam(32-bits) - Window Handle

Notez que Softice ne gère pas ces handles, et qu'il reconnaît parfois mal les valeures de wParam et de lParam (avec certain accelerateurs par exemple, voir 2) ).

Chaque file de chacune des applications à une file d'attente interne, où Windows peut déposer un ou plusieurs messages; puis l'application traite les messages de la file d'attente (dans l'ordre ou ils sont arrivés) et les traitent.
Cela signifie que l'application

- lit le message de la file d'attente en utilisant GetMessage().

- traduit les messages du clavier (key up/down) en char messages par
TranslateMessage().

- les expédie à la fenêtre concernée en utilisant DispatchMessage().

En réalité, Windows peut envoyer un message de deux façons:
Il peut ou bien placer le message dans la file d'attente avec PostMessage()
(ils sont appelés des messages alignés) ou être envoyé directement à une fenêtre par la procédure SendMessage() (ils sont appellés messages non alignés).

Les messages alignés incluent tous les messages d'entrées utilisateur (comme WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_KEYDOWN, WM_CHAR, ...) et quelques messages système (WM_TIMER, WM_PAINT, WM_QUIT). Tous ces messages utilisent une structure MSG.

Pour les messages non alignés, seul le premier des quatre arguments (c'est à dire not time et la position de la souris) sont passés, mais si la procédure de la fenêtre a beoin d'eux, il peut les obtenir via GetMessageTime() et GetMessagePos(). Windows habituellement envoi les messages non alignés pour indiquer à une fenêtre l'événement qui va arriver (WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR, ...) ou quand une application appelle certaines fonctions de Windows (par exemple, Windows envoi directement le mesage WM_WINDOWPOSCHANGED
après qu'une application ait utilisée la fonction SetWindowPos() pour déplacer une fenêtre).

Notez aussi que WM_PAINT est une exception dans la pile FIFO des messages en attente. Ce message spécial a la plus basse des priorité, ce qui veut dire qu'il est envoyé à la procédure de la fenêtre quand la file d'attente ne contient pas d'autre message.


2) Messages vers une application.

Mainteant la question est: comment une application sait quand Windows a placé un nouveau message dans sa file d'attente? Et bien elle ne le sait pas vraiment. En réalité, l'application est juste placée dans une boucle sans fin d'ou elle continue à envoyer et recevoir des messages. On appelle ceci la "pompe à messages" ou "la boucle de message", et sa forme la plus simple est:

; Process messages, quitte quand WM_QUIT est reçu.
;
msg_loop:
      push   0                  ; la valeur maximale du message à filtrer
      push   0                  ; la valeur minimale du message à filtrer
      push   0                  ; handle de la fenêtre (0 = all messages) 
      push   offset msgbuffer   ; où stocker le message
      call   GetMessageA        ; prend le message de la file d'attente et le stock 
      or     eax,eax            ; si eax=0, WM_QUIT a été envoyé 
      je     end_loop           ; dans ce cas, on quitte l'application
      push   offset msgbuffer   ; où le mesage est stocké
      call   DispatchMessageA   ; envoi un message à la procédure de fenêtre
      jmp    msg_loop           ; boucle 

; Termine le programme.
end_loop:
      push   [msgbuffer.msgWparam]
      call   ExitProcess

Une procedure fenêtre est une fonction qui reçoit et traite les messages envoyés à celle ci. Chaque window class a une procédure fenêtre, et chaque fenêtre crée avec cette class utilise la même procedure pour répondre au message.
Parce qu'une procédure de fenêtre est partagée par toutes les fenêtres appartenant à la même class, elle peut traiter des mesages pour plusieurs fenêtres différentes. Pour identifier la fenêtre précise concernée par le message, une procédure fenêtre peut examiner le handle de la fenêtre passé en premier paramètre d'un message.
Les Window procedures sont générallement des grandes déclarations de "cas", où tous les types de messages sont envoyé à différentes parties du code:

; La procédure de fenêtre, vers laquelle le message est envoyé.
;
MainWndProc:
        mov    eax,[esp+8]        ; prend le message type
        cmp    eax, 0000000F      ; est ce que c'est WM_PAINT ?
        je     paint_client       ; oui, va et déssine quelque chose
        cmp    eax, 00000111      ; est ce que c'est WM_COMMAND ?
        je     execute_command    ; oui, va à la commande procédure
        cmp    eax, 00000201      ; est ce que c'est WM_LBUTTONDOWN ?
        je     LeftMouseDown      ; fait quelque chose
        cmp    eax, 00000001      ; est ce que c'est WM_CREATE ?
        je     création_fenêtre
        cmp    eax, 00000002      ; est ce que c'est WM_DESTROY
        je     commence_destroy
        jmp    DefWindowProcA     ; delegate other message processing

Notez que par défaut, il y a un call vers DefWindowProcA(). C'est la procédure Wondows par défaut d'une procédure de fenêtre qui DOIT être appellée pour saisir tous messages qu'une procédure de fenêtre ne peut pas saisir. Un programme peut avoir plusieurs fenêtres et par la même occasion plusieurs procédures de fenêtres: quand un message n'est pas envoyé par la procédure de fenêtre principale, le message est passé à la suivante via DispatchMessage().
Nous avons vu qu'une combinaison de messages WM_KEYDOWN/WM_KEYUP peut être traduite en un message WM_CHAR (et renvoyée vers sa propre file d'attente) TranslateMessage(). Mais un WM_KEYDOWN peut aussi être traduit en un message WM_COMMAND par TranslateAccelerator(), la fonction qui permet les raccourci clavier du menu commands (comme Ctrl+C pour copy).
Egalement, si une combinaison clavier correspond à un menu, WM_INITMENUPOPUP
est envoyée à l'application et le menu popup est ouvert. Notez que vous pouvez différencier un message WM_COMMAND traduit d'une entrée clavier, d'un message envoyé par le souris., jusqu'à ce que TranslateAccelerator() donne le mot d'ordre supérieur du paramètre wParam à 1; dans ce cas, SoftIce pense à tord que c'est un hiword lParam qui a été envoyé.

En plus des APIs que nous avons rencontrées, Windows a d'autres APIs dont vous devez vous méfier qui permettent d'effectuer différentes actions avec les messages et la file d'attente des messages:

PeekMessage() - lit les messages sans les remettre dans la file.
RegisterWindowMessage() - déclare un message particulier pour l'appliction
WaitMessage() - suspend l'attente avant qu'un message ne soit placé dans la file d'attente.


C) Messages et Menus

Quand l'utilisateur active un item de la barre de menu, la fenêtre propriétaire recoit un message WM_SYSCOMMAND. Ce message inclut un drapeu qui indique si l'utilisateur a activé le menu en employant le clavier (SC_KEYMENU) ou la souris (SC_MOUSEMENU).

Ensuite, avant qu'aucun menu de soit affiché, Windows envoi le message WM_INITMENU à la procédure de fenêtre pour que l'application puisse modifier les menus avant que l'utilisateurs ne les voit. Windows envoi le message WM_INITMENU une seule fois par activation d'un menu.

Quand l'utilisateur pointe vers l'item d'un menu qui ouvre un sous-menu, Window envoie le message WM_INITMENUPOPUP avant d'afficher le sous-menu. De nouveau, ce message permet à l'application de modifier le sous-menu avant son affichage. A chaque fois que l'utilisateur déplace le highlighting d'un item à un autre, Windows envoie un message WM_MENUSELECT à la procédure de le fenêtre dont dépnd le menu. Ce message identifie l'item de menu concerné; certaines applications (Words, Wordpad,...) saisissent ce message pour afficher des informations sur l'item de menu sélectionné à la fin de leur fenêtre principale.

Quand l'utilisateur choisi un item d'un menu, Windows envoie un message WM_COMMAND à la procédure de fenêtre. La partie basse du Word du paramêtre d'un message WM_COMMAND contient l'identificateur de l'item choisi. La procédure de fenêtre examinera cet identificateur et traitera le message en conséquence.

Pour les menus contexctuels (ouvert par un clic sur le bouton droit de la souris) un message WM_CONTEXTMENU est envoyé, pour que l'application puisse le traiter et afficher ce menu.


III) REVERSING D'UNE FONCTION DESACTIVEE

1) Avant que vous ne commenciez

Avant que vous ne pensiez seulement à cracker la fonction désactivée d'un programme, vous devez vérifier certaines choses:

- est ce la meilleure solution? Si vous pouvez vous enregistrer avec un code ou une keyfile, essayez en premier cette solution, cela enlevera toutes traces visibles (et également invisibles par la même occasion) de limitations d'un seul coup.

- est ce que le code de la fonction désactivée est dans le programme? Si ce n'est pas le cas, vous aurez à l'ajouter, et c'est un autre challenge...
Il y a de nombreux signes qui peuvent vous ammener à une conclusion, comme un schéma d'enregistrement, les API importées, certaines String data references, etc

Ok, pour une raison ou une autre vous décidez de cracker le programme de cette fçon. Vous devez maintenant gardez à l'esprit où vous en êtes et ce que vous voulez faire, premierement vous aurez à:
- dé-griser la fonction désactivée (ou la faire apparaître) dans le menu.
- linker cette fonction vers la bonne partie du code.

La première partie est en général relativement facile, et peut conduire à une erreur classique: vous posez un breakpoint sur EnableMenuItem, vous trouvez où se trouve le flag et vous le changez en MF_ENABLED. La fonction est désormais dé-grisée, si bien que vous voilà relativement confiant, mais quand vous cliquez dessus... rien n'apparait :(

C'est la seconde partie, qui peut être plus ou moins compliquée, suivant la façon dont le programme a été concu: vous devez trouver en premier la partie du code qui correspond à la fonction, ensuite vous devez trouver et modifier la boucle du message ou/et la procédure de fenêtre.


2) Quelques trucs

c'est relativement facile de dé-griser un item, regardez les API importées, et trouvez celles qui sont utilisées pour créer et peut être modifier les menus. La difficultée principale est de trouver où se trouve la boucle du message et la procédure de fenêtre.

Quelques trucs pour trouver la boucle d'un message:

- via des messages API specifiques, comme GetMessageA(), DispatchMessageA() ou DefWindowProcA(), qui sont utilisés dans la majeure partie des cas pour chaque boucle de message.

- via les valeurs du message: certains messages sont toujours utilisés comme WM_COMMAND (0111h) ou WM_DESTROY (0002h). Si bien que vous pouvez chercher des 00000111 dans un listing désassemblé ("cmp xxxx, 00000111h"), qui pourrait être des 'case WM_COMMAND statement'.

- dans SoftIce, avec la commande BMSG.
SoftIce's BMSG breakpoint est vraiment pratique pour provoquer un braek sur un messgae spécifique.
Vous pouvez aussi enregistrer les messages sans faire apparaître le message allant à une procédure de fenêtre avec l'option 'L'; notez que le message est sont enregistré dans le "history buffer" de Softice, donc soyez sur d'avoir une liste de messages reconnus par SoftIce en utilisant WMSG.

Vous pouvez réaliser un "BPX-style breakpoint" équivalent en utilisant une expression conditionnelle. Utilisez la commande HWND pour obtenir l'adresse de la procédure de fenêtre, puis utilisez la commande BPX suivante:

bpx postmessagea if @(ss:esp+8)==WM_COMMAND


Quelques façons de trouver une procédure de fenêtre:

- via les fonctions RegisterClass() et RegisterClassExA().
L'adresse de la procédure de fenêtre Windows est le second paramètre (for WNDCLASS) et le troisième parametre (for WNDCLASSEX), ainsi, certains

BPX RegisterClassA do "u @(@(ss:(esp+4))+4) ; d @(@(ss:(esp+4))+24)"
BPX RegisterClassExA do "u @(@(ss:(esp+4))+8) ; d @(@(ss:(esp+4))+28)"

montreront la "registered class" et desassembleront la partie du code de la procedure de fenêtre correspondante. Regardez aussi CreateWindowA() et CreateWindowExA(). Pour le moment, ceci est intéressant uniquement si vous trouvez la procédure de fenêtre qui appartient à la cible; la plupart du temps, ce ne sera pas le cas, parce que l'application utilisera le systeme ou des class d'application globale (de Windows, MFC,...).

- dans Softice, avec HWND et CLASS : la première fonction donne des informations sur toutes les fenêtre d'un process, incluant les adresses des procédures de fenêtres:

:hwnd
Window Handle   hQueue  SZ  QOwner    Class    Name     Window Procedure
0080(0)         206F    32  MSGSRV32  #32769 (Desktop)   17CF:00004C44
                                                         ^^^^^^^^^^^^^  
Le seconde fonction donne des informations sur les Class utilisées par le process:

:class notepad
Handle  Class Name               Owner           Wndw Proc       Styles
-------------------------- Application Private -----------------------------
403C    Notepad                  NOTEPAD         1467:00000350   07001000
--------------------------- Application Global -----------------------------
401C    SysAnimate32             COMCTL32        1467:000000FE   03004008
400C    msctls_hotkey32          COMCTL32        1467:000000E8   03004000
3A04    msctls_progress32        COMCTL32        1467:000000D2   03004003
...

Une commande très sous estimée est "HWND -X", qui vous donnera toutes les informations dont vous avez besoin au sujet d'une fenêtre, en voici un exemple:

:hwnd -x notepad
Window Handle : (01B8) Level (1)
    Parent        : 16E7:000204CC
    Child         : 16E7:000235D8
    Next          : 16E7:00024F7C
    Owner         : NULL
    Window RECT   : (-4,-4) - (804,576)
    Client RECT   : (0,38) - (800,572)
    hQueue        : 238F
      Size        : 32
      QOwner      : NOTEPAD
    hrgnUpdate    : NULL
    wndClass      : 16E7:4184
    Class         : Notepad
    hModule       : NOTEPAD (1A07)
    lpfnWndProc   : 1467:0000033A
      ...

- avec un BPR sur le code de l'application: quand la fonction DispatchMessage() est appelée, vous laissez le message faire sa boucle et executer quelques codes de USER32 DLL. La pluspart du temps le morceau de code suivant de l'application sera exécuté dans la procédure de fenêtre. Même si l'entry point de la procédure de fenêtre n'est pas compris dans l'application, ce type de breakpoints sur une application est toujours intéressant.
Notez que vous pouvez facilement trouver l'espace mémoire utilisé par une file d'attente d'un message tant qu'il utilise son propre selecteur. En voici un exemple:

:task
TaskName   SS:SP      StackTop  StackBot  StackLow  TaskDB  hQueue  Events
Stat32d    0000:0000  00CFD000  00D00000            2D1E    2B27    0000
...                                                         ^^^^

:ldt 2b27
Sel.  Type      Base      Limit     DPL  Attributes
2B27  Data16    80155B80  0000009F  3    P   RW

La file d'attente pour l'application stat32d commence à l'adresse 80155B80
et fait 160 octets de long. Pour des détails sur la manière dont les messages sont sockés dans la file d'attente, lisez le livre de Matt Pietrek.

Trouver la partie du code coresspondant à une fonction manquante peut être la part la plus compliquée de ce job, parce qu'ici vous aurez à utiliser majoriatirement votre intuition. Heureusement, la plupart des applications utilisent les boites de dialogue standards de Windows pour ouvrir, imprimer ou sauver un fichier. Il y a DLG32 DLL's GetOpenFileName (for Open) , GetSaveFileName (for Save and SaveAs), PrintDlgA (for Print).


IV) HANDS ON

1) Cryptographic Analysis Program (CAP) 1.0

Emplacement: www.plu.edu/~spillmrj/cap.html
Description: a basic cryptanalyis tool
Limitations: Save, SaveAs et les fonctions d'ipression sont désactivées
Language: delphi

Pour s'enregistrer, vous devez envoyer à l'auteur un nombre (dérivé de celui de votre HD) et il vous renverra en retour le bon serial: une bonne chose pour nous, parce que nous serons sur d'avoir déjà tous les codes.

Jetons un coup d'œil aux APIs importées: pour le partie concernant les " menus", vous avez tout ce qu'il vous est nécessaire: CreateMenu(), EnableMenuitem(), InsertMenu(), RemoveMenu(), SetMenu(),... Cette partie du message est intéressante: nous avons no GetMessage() d'importé, mais la file d'attente du message est testée avec PeekMessage() (peut être une spécificitée du delphi ?)

Posons un breakpoint sur EnableMenuItem() pour voir quelles fonctions sont désactivées: comme la valeur de MF_GRAYED est de 01, nous avons juste a provoquer un break sur tous les drapeaux dont le bit est mis à 0, ca peut être fait avec un:

:BPX EnableMenuItem IF ((@(ss:(esp+0c))&1)==1)

Ici se trouve les calls à EnableMenuItem() qui déclenche le breakpoint:

 MenuId  0588 058C 058C 058C 05D4 05D4 0614 0614 0620 0620
 FctId    20   04   05   07   49   4A   51   52   56   57
 Flag     03   03   03   03   03   03   03   03   03   03                   

Dans ce cas, toutes ces fonctions sont grisées et désactivées (03h).
Nous trouvons seulement 3 fonctions désactivées dans le menu 058C dont les valeurs FctId (04, 05, 07) correspondent à l'ordre des 3 fonctions désactivées et qui nous intéressent (Save, SaveAs, Print). Avec le commande STACK, nous trouvons que EnableMenuItem est appelé par 42639B pour ces 3 fonctions.

:00426383  mov    eax, dword ptr [4*eax+004996D8]   ; flag
:0042638A  or     eax, 00000000
:0042638D  push   eax
:0042638E  movzx  eax, word ptr [esi+34]
:00426392  push   eax                               ; function id
:00426393  mov    eax, edi
:00426395  call   00426194
:0042639A  push   eax                               ; menu handle
:0042639B  Call   EnableMenuItem

A la ligne 426383, un coup d'œil semble montrer que la table est utilisée pour obtenir le flag de chacune de nos 3 fonctions désactivées.
eax=0 et ds:[4996D8]=03 sont utilisées à chaque fois. Prenons Hiew pour changer cet octet à l'offset .996D8 en 00 : maintenant toutes les fonctions sont activées.

Après avoir désassemblé le programme, cherchons une ligne contenant 0111 (WM_COMMAND) dans le listing obtenu; voici ce que nous trouvons:

:00423177  cmp   dword ptr [edx+04], 00000111  ; est-ce WM_COMMAND ?
:0042317E  je    004231A0                      ; oui: go on
:00423180  mov   esi, dword ptr [edx+04]
:00423183  cmp   esi, 00000200
:00423189  jle   00423193
:0042318B  cmp   esi, 0000020A
:00423191  jle   004231A0

Avez vous noté comme le 'range' 200-20A a une signification spéciale pour cette partie du code? Et bien un rapide coup d'œil à notre référence de message montre que ce 'range' contient tous les messages relatifs à la souris. Cela veut dire que cette portion du code est très probablement une partie du "message processing". Posons un breakpoint pour poper tous les messages WM_COMMAND:

:bpx 42317E if (ZFL==true)

Maintenant, on clic sur Save... Le breakpoint est activé.
Traçons un peu pour voir comment le message va être traité. Comme prévu nous trouvons rapidement une procédure DispatchMessageA(), où le message est envoyé à la procédure de fenêtre concernée.

Pour trouver cette procédure, faisons quelque chose d'éléguant, c'est à dire un peu de "breakpoint combo" :

:bc *
:map32 cap_prj          

Owner     Obj Name  Obj#  Address        Size      Type
CAP_PRJ   CODE      0001  0137:00401000  0009753C  CODE  RO
...

Donc nous savons que le code du programme est stocké en mémoire entre 137:401000 et 137:49853C; J'ai alors placé un bpr sur cette zone pour que toute pièce du code de CAP_PRJ qui serait exécuté déclenche ce breakpoint. puis,

:bpr cs:401000 cs:49853c r
:bd 0
:bpx 42317E if (ZFL==true) do "g DispatchMessageA ; be 0; g"

Le breakpoint en 42317E se déclenchera en premier quand un message WM_COMMAND message sera reçu. Alors il traitera la procédure DispatchMessageA suivante, activant le premier breakpoint et continuera. A partir d'ici seul le code de DispatchMessageA() et de ses sous procédures sera exécuté, et BP0 se déclanchera au premier opcode exécuté par CAP_PRJ, qui doit être le point d'entrée de la procédure de fenêtre.

Le breakpoint se déclenche en 428F48; si nous traçons un peu, nous trouvons cette partie du code:

:004266B4  push  ebx
:004266B5  cmp   byte ptr [eax+2D], 00      ; teste une valeur
:004266B9  je    004266CC                   ; quitte si 0
:004266BB  cmp   word ptr [eax+62], 0000    ; teste une autre valeur
:004266C0  je    004266CC                   ; quitte si 0
:004266C2  mov   ebx, eax
:004266C4  mov   edx, eax
:004266C6  mov   eax, dword ptr [ebx+64]
:004266C9  call  [ebx+60]
:004266CC  pop   ebx
:004266CD  ret

regardons ce qui arrive en 4266B4 quand nous essayons quelques options du menu:

Function  Status      EAX     [EAX+2D]    [EAX+62] 
---------------------------------------------------
Open     enabled    101ABD0      01          48
Save     disabled   101AEDC      00          48    
SaveAs   disabled   101AE58      00          48
Print    disabled   1019698      00          48
Exit     enabled    1018108      01          48

C'est relativement clair maintenant, nous avons juste à changer les valeurs en [eax+2D] de 0 en 1 pour activer les fonctions d'enregistrement.


2) SigmaStat 2.0

Emplacement:   www.spss.com
Description:   Utilitaire de statistiques
Limitations:   Fonction d'enregistrement désactivée
Language:      C++ / MFC

C'est un programme typique du C++, mais il utilise le fameux Microsoft
Fundation Classes (MFC 3.0 ici), et ça change tout! MFCs ajoute un véritable environnement autout de votre application pour compléter les interactions entre l'utilisateur et le handle: le message boucle, la procédure de fénêtre et même toutes les fonctions communes d'un fichier menu (New, Save, Print...) sont exécutées sans la Dll.

Vous avez ici la façon dont les menus sont saisi par MFC: à chaque fois que vous cliquez sur un menu,
- MFC saisi le handle du menu via GetMenu(), puis le nombre d'items dans ce
menu via GetMenuItemCount().
- pour chaque item de ce menu, il vérifie si celui ci lance un sous-menu via
GetSubMenu(). Si un sous-menu est présent, une autre sub-loop est lancée,
dont le point de départ se fait par la saisie du nombre d'items via GetMenuItemCount().
- pour chaque menu/submenu, MFC prend le messageID qui est associé
à cette item via GetMenuItemID() et le désactive si besoin par
EnableMenuItem().

Voici la dernière partie (la procédure 0D_3BF à l'offset 6854h du segment de texte MFC30) ; cette partie du code est éxécutées pour chaque item

Pour chaque menu, et à chaque fois que vous séléctionnez ce menu:

:7FB27848  cmp    dword ptr [esp+10],01   ; faut il désactiver ?
:7FB2784D  sbb    eax,eax                 ; yes / no     
:7FB2784F  and    eax,03                  ; garde les 2 bits les plus bas
:7FB27852  mov    ah,04                   ; prend MF_BYPOSITION
:7FB27854  push   eax                     ; passe le flag
:7FB27855  push   dword ptr [esi+08]      ; choisie la fonction
:7FB27858  push   dword ptr [ecx+04]      ; choisie le menu
:7FB2785B  Call   EnableMenuItem          ; active ou désactive le menu

Vous voyez ce qu'il se passe? Si la valeur en [esp+10] est zero, alors le drapeau de retenu est mis et l'instruction SBB EAX,EAX met EAX à 0 moins 1 = FFFFFFFFh.
Si la valeur en [esp+10] n'est pas zero, alors le drapeau de retenu n'est pas activé et EAX est mis à 0. Le résultat fait que la valeur EAX utilisée comme drapeau est égale à 0403h (si [esp+10] = 0) ou 0400h ; comme d'habitude, les bits les plus bas sont les plus importants pour nous.

En traçant en arrière là où la valeur [esp+10] est mise à 0, nous trouvons rapidement cette partie de code intéressante:

:0040D599  mov    [ebp-04],ecx
:0040D59C  push   00              ; met le flag ici !
:0040D59E  mov    eax,[ebp+08]    
:0040D5A1  mov    eax,[eax]
:0040D5A3  mov    ecx,[ebp+08]
:0040D5A6  call   [eax]           ; call 7FB27836 (to EnableMenuItem)               
:0040D5A8  jmp    0040D5AD        ; stupide compilateur flush le PIQ :(
:0040D5AD  pop    edi
:0040D5AE  pop    esi
:0040D5AF  pop    ebx

Cette partie du code peut être trouvée trois fois dans le code de Stat32, c'est réellement trois fois les mêmes codes, séparés par un paquet d'octets inutiles CCh; évidemment le compilateur Microsoft utilise une procédure de désactivation standard, qui est placé autant de fois que nécéssaire dans le code de l'application. Mainteant,il y a quelque chose de vraiement intéressant à ce sujet: si nous remplaçons le "push 00" par un "push 01", non seulement les items seront activés, mais les fonctions correspondantes seront exécutées... le boulot est fait :)

Juste pour s'amuser (et pour apprendre), regardons quels messages sont traités par MFC ;
Un breakpoint sur GetMessageA() nous donnes la position de la boucle de message (dans MFC30 DLL):

:7FB24CEF  push   edi
:7FB24CF0  lea    esi,[ecx+30]
:7FB24CF3  mov    edi,ecx
:7FB24CF5  push   00                          ; (0,0,0) <=> prend tous
:7FB24CF7  push   00                          ; les messages
:7FB24CF9  push   00
:7FB24CFB  push   esi                         ; message buffer offset
:7FB24CFC  call   GetMessageA                 ; prend le message de la file d'attente
:7FB24D02  test   eax,eax                     ; WM_QUIT reçu ?
:7FB24D04  jz     7FB24D31                    ; oui : quitte le programme
:7FB24D06  cmp    dword ptr [edi+34],0000036A ; message 36A reçu ?
:7FB24D0D  jz     7FB24D29                    ; oui : ignore le
:7FB24D0F  push   esi
:7FB24D10  mov    eax,[edi]
:7FB24D12  mov    ecx,edi
:7FB24D14  call   [eax+38]                    ; traitement de 201h et 202h
:7FB24D17  test   eax,eax                     ; (mouse-related messages)
:7FB24D19  jnz    7FB24D29
:7FB24D1B  push   esi
:7FB24D1C  call   TranslateMessage            ; traduit les entrées du clavier
:7FB24D22  push   esi
:7FB24D23  call   DispatchMessageA            ; envoi le message à wndproc
:7FB24D29  mov    eax,00000001
:7FB24D2E  pop    edi
:7FB24D2F  pop    esi
:7FB24D30  ret

Essayons d'intérrompre cette boucle de message quand un message WM_COMMAND est reçu; nous verrons le "message buffer". La valeur ESI à la ligne 7FB24CFB est toujours 5D7048, et pointe vers une section Data de Stat32d. Ils débutent une structure MSG où chaque message prit dans la file d'attente est temporairement stocké avant d'être traité; à l'offset 4 de cette stucture se trouve placé wParam. Donc pour obtenir un break sur un message WM_COMMAND nous pouvons utiliser:

:bpx 7FB24D02 if (@(ds:(5D704C))==0111)

Maintenant, si nous essayons le même combo que dans CAP, nous nous égarrons parce que nous avons seulement sauté entre le code de MFC30.DLL et la table de consultation.

Essayons la méthode du dead-listing: nous desactivons MFC30.DLL et utilisons la même méthode, c'est à dire rechercher des "00000111" ; voici le premier retour sur cette recherche:

:7FB21074  cmp   ebx, 00000111    ; 111h = WM_COMMAND
:7FB2107A  je    7FB2110C
:7FB21080  cmp   ebx, 0000004E    ;  4Eh = WM_NOTIFY
:7FB21083  je    7FB21138
:7FB21089  cmp   ebx, 00000006    ;  06h = WM_ACTIVATE
:7FB2108C  je    7FB21176

Nous pouvons nous arréter ici, c'est le bon. Pourquoi ? Parce que dans chaque application MFC, le message est traité entre CCmdTarget objet qui saisi les handles de WM_COMMAND, de WM_NOTIFY et de WM_ACTIVATE (CN_UPDATE_COMMAND_UI)
et CWin objet qui traite la plupart des autres message de fenêtre. La valeur des trois premiers messages et trouvée içi, et doit être la partie du code que nous recherchons. Si nous regardons 7FB2110C, c'est tout à fait prometteur:

:7FB2110C  mov    ecx,[ecx]
:7FB2110E  mov    esi,[ebp+10]   ; lParam est le premier paramètre
:7FB21111  push   esi            ; 
:7FB21112  mov    edi,[ebp+0C]   ; wParam est le second
:7FB21115  push   edi            ; 
:7FB21116  mov    [ebp-14],ecx
:7FB21119  mov    ecx,[ebp-10]
:7FB2111C  mov    eax,[ebp-14]
:7FB2111F  call   [eax+48]       ; call WM_COMMAND processing

Ok, traçons un peu dans le traitement de WM_COMMAND pour savoir où tous les différents messages sont envoyé, et nous trouvons rapidement cette partie du code:

:7FB29DBF  push   00
:7FB29DC1  mov    eax,[esi]      ; get adress
:7FB29DC3  push   00
:7FB29DC5  mov    ecx,esi
:7FB29DC7  push   ebp
:7FB29DC8  push   edi
:7FB29DC9  call   [eax+14]       ; execute la fonction

regardez cette belle table d'allocation, c'est le cœur du mécannisme de traitement du message :

:005E7664 72 87 5A 00 32 82 5A 00-C0 F4 44 00 2C 82 5A 00  r.Z.2.Z...D.,.Z.
:005E7674 26 82 5A 00 20 82 5A 00-1A 82 5A 00 14 82 5A 00  &.Z. .Z...Z...Z.
:005E7684 6C 87 5A 00 10 F1 44 00-02 82 5A 00 FC 81 5A 00  l.Z...D...Z...Z.
:005E7694 F6 81 5A 00 66 87 5A 00-EA 81 5A 00 E4 81 5A 00  ..Z.f.Z...Z...Z.
:005E76A4 90 29 40 00 60 87 5A 00-DE 81 5A 00 5A 87 5A 00  .)@.`.Z...Z.Z.Z.
:005E76B4 D2 81 5A 00 CC 81 5A 00-C6 81 5A 00 80 EF 44 00  ..Z...Z...Z...D.
:005E76C4 BA 81 5A 00 54 87 5A 00-AE 81 5A 00 A8 81 5A 00  ..Z.T.Z...Z...Z.
:005E76D4 4E 87 5A 00 9C 81 5A 00-48 87 5A 00 42 87 5A 00  N.Z...Z.H.Z.B.Z.

Il est possible de reconnaître, avec un peu d'habitude, cette table uniquement à l'aide d'un éditeur hexadécimal. En effet, puisque les offsets sont tous alignés dans le code de l'application, si bien que vous trouverez toujours 4 paires de colonnes égales à "XX 00" (où XX est le premier byte de l'offset) à l'offset 2, 6, 10 et 14 de cette table.

:map32 stat32d
Owner     Obj Name  Obj#  Address        Size      Type
STAT32D   .text     0001  0137:00401000  001D5784  CODE  RO

Ici, XX peut avoir n'importe quelle valeur comprise entre 40h et 5Dh, qui sont toutes composées de caractères imprimables, mais comme vous pouvez le voir, majoritairement vous trouvez des paires de ("Z",00h)
Le reversing de la cible désactivée de MFC peut être très laborieux, car le gros du travail s'effectue dans la DLL, même si vous commutez bien souvent de/vers l'application. Ne pas indiquer que le code de la DLL de MFC et le code inséré par MFC dans votre application est peu éléguant.


3) BestWin

Emplacement:  fravia.org/jn_essay.htm
Description:  Utilitaire d'encryptage
Limitations:  la fonction de décryptage est désactivé (c'est ennuyeux :)
Language:     C (compilé avec une version française de lcc-win32 2.4 dans 
              C:\lcc\bestwin directory :)

Avant l'attaque de la forteress de Fravia, Jeremy Likness proposait en défi de cracker un software, tout à fait prétentieusement appélé "New Chaos Protection", pour protéger son algorithm d'encryptage (qui est tout aussi faible, mais c'est une autre histoire). Suit ici une déscription et un rapide crack de sa méthode.

Un rapide désassemblage dans W32dasm donne ces références intéressantes à des menus:

MenuID_0258
      File {Popup}
           Encrypt  [ID=00C8h]
           Decrypt  [ID=00D2h]
           Set Key  [ID=00DCh]
           Exit     [ID=012Ch]

La valeur MenuID pour "Decrypt" est D2h, donc cherchons des "000000D2" dans le listing désassemblé:

:00402677  cmp    dword ptr [ebp+0C], 000000D2  ; Decryptage demandé ?
:0040267E  je     004026B1                      ; yes : continuer
 ...
:004026B1  push   755647E4                      
:004026B6  call   PROT._pGetFuncbyCode          ; quel code doit être lancé?
:004026BB  add    esp, 00000004                 
:004026BE  mov    dword ptr [ebp-04], eax       ; EAX = code offset
:004026C1  call   [ebp-04]                      ; execute notre code:004026C4  jmp    004026EA

Quand "Decrypt" est séléctionné, BestWin appel PROT.DLL (via
PROT._pGetFuncbyCode) partie du code associée à cette fonction, puis il la lance.

Chargeons BestWin dans SoftIce pour jetter un œil à ce _pGetFuncbyCode...
Quoi ? SoftIce demande les fichiers sources ??
Regardez comme Jeremy à oublier de supprimer les symboles :)

:sym
.text(014F:00401000, 00002B88 bytes)
      0137:00401ABA _Decrypt
      0137:004028A4 _DefaultFunc
      0137:00401E4F _EnableDecrypt
      0137:00401CC4 _SetKey
        ...

Héhéhé, notre boulot est fini : L'étiquette de _Decrypt en 401ABA est le commencement de la routine de décryptage. Donc, nous avons seulement besoin d'associer cette partie du code à la fonction "Decrypt" du menu dans le traitement du message WM_COMMAND, comme cela:

004026BE  E8F7F3FFFF    call 401ABA  ; procédure _Decrypt
004026C3  90   



Transfer interrupted!

Nous avons aussi besoin d'activer la fonction de décryptage dans le menu; _pGetFuncbyCode est aussi utilisé pour "cacher" les fonctions qui n'appartiennent à aucun menu. Par exemple, quand vous mettez la clé, cette partie du code est lancé:

:00401E1C  push   75564714
:00401E21  call   PROT._pGetFuncbyCode
:00401E26  add    esp, 00000004
:00401E29  mov    dword ptr [ebp+FFFFFBAC], eax
:00401E2F  call   dword ptr [ebp+FFFFFBAC]

Si nous sommes enregistré, la fonction _EnableDecrypt est exécutée, et dé-grise "Decrypt" dans le menu. Si ce n'est pas le cas, un message infamant est affiché.

Comme vous le voyez, nous n'avons même pas besoin d'étudier PROT.DLL pour annuler la protection, nous avons juste à remplacer le call relatif par celui qui convient, comme ceci :

00401E2F  E81B000000    call 401E4F  ; _EnableDecrypt procedure  
00401E34  90            nop 

Sa protéction est basée sur un dialogue entre le programme principal (BestWin) et la DLL de protection (PROT.DLL): Premièrement, la boite de message "you must register" est envoyée comme fonction par défaut (via PROT._SetDefaultFunc).puis quand un item enregistré est séléctionné, BestWin demande à PROT.DLL (via
PROT._pGetFuncbyCode) quelle partie du code doit être lancée. Si nous ne sommes pas enregistré, l'offset de la fonction par défaut est retourné, et le message diabolique est affiché.

Une autre erreur majeur est que toutes les fonctions sont d'abord définies (via PROT._AddFunc) avec l'offset de la bonne partie du code (ainsi _pGetFuncbyCode connaît le bon offset à retourner pour les utilisateurs enregsitrés).

Une idée assez intéressante, mais horriblement mise en œuvre: tout à fait décevant pour un telle frénésie.


4) Flipper

Emplacement:  crackmes.cjb.net
Description:  crackme
Limitation:   Le menu fichier est grisé durant les 10 premières secondes
Language:     Visual Basic 5.0

Bien, ce n'est pa une réelle protection "fonctions désactivées", mais c'est petit et fait tout ce dont nous avons besoin pour comprendre comment fonctionnent les menus VB.
Les handles Visual Basic appelle le GUI par lui même, ainsi le codeur n'a aucune idée ce de qui est fait; aussi nous pouvons supposer que tous les programmes VB définissent les menus de la même manières. Tout se passe dans la fonction __vbaI4ErrVar.

Premièrement quelques menus vides sont créés avec CreateMenu(). Ensuite chaques menus et chaques items sont ajouté avec InsertMenuA(); notez que tous les paramètres de InsertMenuA() sont stocké dans une structure basée sur EBP, EBP prend uniquement 2 valeurs dans cette partie du code, une pour tous les items, et une pour tous les menus.

:0F05191A  push  [ebp-10]                ; adresse du menuitem text string
:0F05191D  push  [ebp-0C]                ; handle du nouvel item
:0F051920  mov   eax, dword ptr [ebp-08] ; prend le flag
:0F051923  or    ah, 04                  ; add MF_BYPOSITION
:0F051926  push  eax                     ; passe le bouveau flag  
:0F051927  push  [ebp+18]                ; la position du nouvel item
:0F05192A  push  [ebp+0C]                ; menu handle
:0F05192D  call  InsertMenuA

Le premier paramètre (menu handle) est retourné par un précedent call à
CreateMenu(). Le second paramètre (position) est toujours 0FFFFFFFFh,
pour que chaque nouvel item soit ajouté au menu existant.
Le troisième paramètre (flag) a une valeur par défaut fixée à 0, qui est re-loaders après chaque insertion d'items. Le cinquième paramètre (menuitem string) est juste copié du menu définition (regardez "Reversing a menu definition").
Le paramètre le plus intéressant est évidemment le quatrième, le message ID de l'item. Comme nous l'avons déjà vu, il n'est pas stocké dans la définition des menus contrairement à ce que font la plupart des autres languages. Ici, VB est rusé parce qu'il distribue les messages ID au moment de l'exécution, par ces sections du code:

:0F051834  mov   eax, dword ptr [ebx]         ; Donne le précedent message ID
:0F051836  inc   eax                          ; incremente 
:0F051837  mov   word ptr [edi+000000D0], ax  ; 
:0F05183E  mov   dword ptr [ebx], eax         ; stocke le nouveau message ID

Par exemple, voici le message distribué par cette application:

1 : Menu "File"     
2 :   Item "SaveText" 
3 :   Item Separator
4 :   Item "Exit"
5 : Menu "About"
6 :   Item "Register"
7 :   Item "About"  

C'est clair et simple (quelque chose d'étonnant pour du VB).

Quand vous démarrez l'application, le menu "File" est grisé pour 10 secondes.
Un breakpoint sur InsertMenuA() montre que quand le menu est créé, le drapeau utilisé est 400h (MF_BYPOSITION), ainsi le menu est activé; plus tard, le menu est grisé en utilisant EnableMenuItem() avec un drapeau 403h. Après 10 secondes, la même fonction APIs est utilisé à nouveau pour re-griser le menu. Pour le moment concentrons nous sur la partie re-grisée et focalisons nous sur la routine du message (vous verez pourquoi plus tard):

:hwnd app1
Window Handle   hQueue  SZ  QOwner    Class Name            Window Procedure
 0E2C(1)        0D87    32  APP1      ThunderRT5Form        1497:000007DE
  0E30(2)       0D87    32  APP1      Edit                  1497:000007DE
  0E34(2)       0D87    32  APP1      ThunderRT5Timer       1497:000007DE
  ...

Toutes les adresses des procédures de fenêtres sont pointées vers le KERNEL DLL, donc pour trouver quelle partie de l'application est impliqué dans le menu handling, nous allons utiliser à nouveau un breakpoint sur les codes de app1:

:map32 app1
Owner     Obj Name  Obj#  Address        Size      Type
APP1      .text     0001  014F:00401000  000069A4  CODE  RO

:bmsg 02EC WM_COMMAND do "bd * ; bpr 14f:401000 14f:408000 r ; g"

Le breakpoint est déclenché dans MSVBVM50.DLL, en F03A2FD, où l'adresse de la procédure de fenêtre est allé chercher dans la table de app1:

:0F03A2F8  mov    ecx,[eax]                ; get table base address
:0F03A2FA  mov    eax,[ebp+14]             ; get item
:0F03A2FD  push   dword ptr [eax*4+ecx+0C] ; calculate address
:0F03A301  call   0F03A546

et si nous traçons un peu, nous trouvons cette partie de code:

:0F01E597  mov    eax, dword ptr [ebp+08]  ; prend l'adresse sur la pile
...
:0F01E5A7  call   eax                      ; go !

Toutes les valeurs EAX conduisent à une sorte de table de structure, d'où nous sautons à la vraie procédure de fenêtre, après que le premier paramêtre de la pile soit ajusté à une valeur fixée (4114C5):

:004024FB  sub   dword ptr [esp+04], 00000033  ; About item
:00402503  jmp   00403B0A
:00402508  sub   dword ptr [esp+04], 0000004F  ; Register item
:00402510  jmp   00403CC9
:00402515  sub   dword ptr [esp+04], 00000057  ; Exit item
:0040251D  jmp   00403DDF
:00402522  sub   dword ptr [esp+04], 00000047  ; SaveText item
:0040252A  jmp   00403E43
:0040252F  sub   dword ptr [esp+04], 0000003F  ; triggered each second
:00402537  jmp   004040A1                      ; during 10 s countdown
:0040253C  sub   dword ptr [esp+04], 0000004B  
:00402544  jmp   0040425E

Pour l'item SaveText, nous sautons en 402508, puis en 403CC9.

Maintenant cherchons quelle partie du code nous aimerions exécuter: bien, rtcMsgBox est appellé en 404030, et juste un peu au dessus, nous trouvons un call intéressant à une API:

:00403FE0  call  __vbaFileOpen
...
:0040400F  call  __vbaPrintFile
...
:00404029  call  __vbaCloseFile

and again a bit upper, this part :

:00403E85  mov   ax, word ptr [00408030]  ; registered flag
...
:00403E8D  cmp   ax, 03FB    ; bigger than 3FBh ? 
:00403EC4  jnl   00404035    ; yes : do nothing 
:00403ECA  cmp   ax, 001B    ; smaller than 1Bh ?
:00403ECE  jle   00404035    ; yes : do nothing           
:00403ED4  cmp   ax, 00FE    ; min registered value 
:00403ED8  jle   00404030    ; yes : go to rtcMsgBox

Changeons juste [408030] en FFh, et c'est bon.

Ok, maintenant dé-grisons le menu fichier: nous avons vu que l'état initial de cet item est "activé", et que il est grisé après. Et maintenant? C'est fait en utilisant la même table:

:004024D4  sub   dword ptr [esp+04], 00000037   ; gray File menu  
:004024DC  jmp   00403A04

C'est un beau petit endroit pour patcher, remplacons juste le JMP de la fonction "grisante", par une autre inofensive. Oh, mais regardons comme la ligne suivante semble intéressante:

:004024E1  sub   dword ptr [esp+04], 0000FFFF
:004024E9  jmp   00403B00
:004024EE  sub   dword ptr [esp+04], 0000FFFF
:004024F6  jmp   00403B05

Pas uniquement la valeur FFFFh qui semble un peu étrange, mais entre les deux JMP, il y a seulement 5 bytes; regardons quel code se trouve en 403B00:

:00403B00  xor   eax, eax
:00403B02  ret   0004

Et bien, ca me semble suffisament inofensif à mon goût, et nous avons juste à patcher en 4024DC pour forcer le saut à cette adresse, et tout matche à la perfection.

La plupart du temps, les programmes qui affichent une message box pour les fonctions désactivées sont plus faciles à reverser que ceux qui utilisent un item grisé. De tout façon, dans ce cas, des "protectionists" flemmards utiliseront un switch entre le vrai et le faux code après que le message soit décodé au lieu d'essayer de (par exemple) modifier la definition du menu.


Final Notes


J'espère que vous avez maintenant une bonne vue d'ensemble sur la façon dont les menus fonctionnent, bien sur, commentaires et corrections sont les bienvenus. C'est réellement un vaste sujet et il y a toujours plein de choses intéressantes dont on peut parler dans ce cadre (subclassing, superclassing,
adding functions, ...) ; si vous êtes intéressé par ce sujet, je vous sugère de regarder le +HCU projet n°6 et l'essai de Fravia sur Filemon.
Bonne nuit, cher lecteur.


+Spath. (Spath@iname.com) (05/99)


Remerciements:
+ORC, Fravia+, +Greythorne and all +HCU members.
+Frog's Print, BeLZeBuTH, Ethan, CyberbobJr, KellogS, Jeff, Rhayader, Eternal Bliss, CrackZ, Iczelion, Virogen.

Toute mon admiration va à Razzia+, Stone, mammon_, Iceman, Quine et particulièrement à The Owl. Merci pour m'avoir donné l'inspiration.


(Traduction français par Christal. décembre 99)