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 |
|
Tools required |
|
Target's URL/FTP |
|
Program History |
|
Essay |
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)