Dialog Box :

Initiation à l'Assembleur et au Reverse Engineering

For Newbies Only

By christal

Avant propos

Les sections

Le Header

Reverse Engineering

La documentation



Avant Propos :

Dans le texte qui suit, j'ai cherché à mieux comprendre comment fonctionnaient les Boites de Dialogues, à travers différentes approches, dans l'idée de pouvoir plus facilement les reverser le cas échéant.
Pour cela j'ai écrit un petit programme avec un simulacre de protection, qui m'a permis de mieux appréhender les APIs DialogBoxParam, MessageBox, SendMessage, GetDlgItemTextA, EndDialog et SetWindowText.
Et à fin de ce texte, je me suis essayé à reverser ma propre boite de dialogue...
Mais qu'est qu'une DialogBox?

Ce n'est ni plus ni moins qu'une fenêtre ordinaire qui fonctionne avec des "child window controls", des objets "enfants" de fenêtres (les boutons, les champs de saisie de textes...). La procédure DialogBox communiquera avec ses enfants par le jeu de messages, fonction des événements qui lui arriveront.
Pour plus de détails sur la gestion des évènements et des messages, je recommande de lire le texte de +Spath :
Theory and practice of menus reversing


Les sections :

Nous allons commencer par regarder un peu les informations qu'il est possible de récupérer sur ce programme :

Informations sur les sections:

Name               Virt Size           Virt Offset          Raw Size         Raw Offset       Characteristics
.text      00000158    00001000    00000200    00000400     60000020
.rdata     0000010E    00002000    00000200    00000600     40000040
.data      00000060    00003000    00000200    00000800     C0000040
.rsrc      00000218    00004000    00000400    00000A00     C0000040

Traduisons un peu :

Section Table
-------------

.text   Virtual Address         00001000   > + image base = adresse
        Virtual Size            00000158   > taille réelle
        Raw Data Offset         00000400   > emplacement différent Ram et HDD
        Raw Data Size           00000200   > taille avec Padding
        Characteristics         60000020   > Code Executable et Readable

Cette section contient les codes du programme en tant que tel.
Le padding auquel je fais allusion est une série de bytes NULL (00 00) qui complète la taille d'une section entre la fin des bytes utiles et l'alignement des sections qui se fait ici sur 0x200 bytes. Une section qui " mesure " 158 bytes aura donc une taille réelle de 200 bytes pour rester un multiple de 200. Cet espace peut être intéressant à récupérer pour y glisser un patch...

.rdata   Virtual Address         00002000
         Virtual Size            0000010E
         Raw Data Offset         00000600
         Raw Data Size           00000200
         Characteristics         40000040  > Initialized Data, Readable

Ici, les import functions sont au beau milieu d'autres données. Souvent la section des imports s'appelle .idata

.data   Virtual Address         00003000
        Virtual Size            00000060
        Raw Data Offset         00000800
        Raw Data Size           00000200
        Characteristics         C0000040  > Initialized Data, Readable, Writeable

La section .data va rassembler les variables déclarées :

.data
  dlgname       db "TESTWIN",0                  > nom de la boite de dialogue
  hInstance     dd 0                            > déclaration de variable
  Num1          db "Unregistered version",0     > Titre d'une messagebox
  Num2          db "Version Non Enregistrée!",0 > idem
  Num3          db "Registered version",0       > idem
  Statut        byte 0                          > flag Utilisateur Registered
.data?
  buffer db 16 dup(?) > taille allouée pour le Buffer de saisie des entrées clavier
.rsrc   Virtual Address         00004000
        Virtual Size            00000218
        Raw Data Offset         00000A00
        Raw Data Size           00000400
        Characteristics         C0000040  > Initialized Data, Readable, Writeable

Section des ressources (Icônes, boites de dialogues, Strings table...). c'est ici que se trouvent les informations qui vont permettre au programme d'afficher la DialogBox (Template).

C'est dans la dernière section qu'il est le plus judicieux de rajouter du code, pour évier d'éventuels problèmes.
La Virtual Size étant de 218 bytes, la Raw Size, pour s'aligner sur 0x200 bytes, est de 0x400, soit 0x400 - 0x218 = 0x01E8. On dispose donc d'un espace de 01E8h (488 d) octets pour y glisser du code supplémentaire, comme un éditeur hexadécimal pourra vous le confirmer. L'adresse où il sera possible de s'installer sera en 00400000h (Image Base) + 4000h (Virtual Address) + 0218h (Virtual Size) = 00404218h.

Lors de sa création avec un éditeur de ressources (utiliser un éditeur de textes est possible, mais confine à la folie furieuse !), un certain nombre de critères ont été définis et notamment le nom des objets (Edit, Button, static).

Informations données par Wdasm : Uniquement le nom de la boite de dialogue : TESTWIN

+++++++++++++++++ DIALOG INFORMATION ++++++++++++++++++
Number of Dialogs = 1 (decimal)
Name: TESTWIN, # of Controls=000, Caption:"Dialog", ClassName:""

Cette boite de dialogue a été crée ainsi :

TESTWIN DIALOG 0, 2244, -28472, 10
STYLE DS_ABSALIGN | WS_ICONICPOPUP | WS_MINIMIZE | WS_VISIBLE | WS_DISABLED |
 WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_MAXIMIZE | WS_CAPTION | WS_VSCROLL | WS_HSCROLL |
 WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX
CAPTION "Dialog"
MENU "\n\nÍz"
{
}
Dialog: TESTWIN
Edit                   100    > 1er Champ à remplir (name)
Edit                   102    > 2ème Champ à remplir (Comp)
PushButton: Register  1000    > 1er bouton
PushButton: Exit      1001    > second bouton
Static: Name            20    > Texte " Name "
Static: Comp            30    > Texte " Comp "
Static: Serial          50    > Texte " Serial "
Static:                       > Cadre pour faire joli
Static:                       > Cadre vide en face de " Serial "

Les 100, 102 etc… sont les identificateurs des objets crées tel qu'ils seront reconnu dans les codes assemblés, comme vous les verrez plus tard (Attention! 100 est en décimal. L'identificateur apparaitra sous la fomre 64h).

Ce qui nous donne dans SoftIce en faisant un
TASK (pour connaître le nom de la tache : RESDLG), et un HWND Nom-de_la_Tâche qui va nous donner la liste suivante:

Window Handle   hQueue  SZ  QOwner    Class Name            Window Procedure
 0F74(1)        37B7    32  RESDLG    #32770 (Dialog)       178F:000050B1
  0B38(2)       37B7    32  RESDLG    Edit   (1er champ)    177F:00000CC6
  0C00(2)       37B7    32  RESDLG    Edit   (2eme champ)   177F:00000CC6
  0EFC(2)       37B7    32  RESDLG    Button (Register)     178F:00001040
  0C84(2)       37B7    32  RESDLG    Button (Exit)         178F:00001040
  0C7C(2)       37B7    32  RESDLG    Static (Name)         178F:00005C20
  0C74(2)       37B7    32  RESDLG    Static (Comp)         178F:00005C20
  0C64(2)       37B7    32  RESDLG    Static (Serial)       178F:00005C20
  0394(2)       37B7    32  RESDLG    Static (Cadre 1)      178F:00005C20
  0EE8(2)       37B7    32  RESDLG    Static (Cadre 2)      178F:00005C20

Avec une informations utilisable : le Handle de chaque objet, mais qui ne pas le même d'une session à l'autre.

En faisant un BMSG 0F74 (handle) WM_Command, vous provoquerez un arrêt du programme dès que DialogBox reconnaîtra un événement (Clic de souris, déplacement de la fenêtre, Enter…)

BPSG (handle) WM_LPButton sur les Boutons " Exit " et " Register " vous donnera un break plus ciblé, ainsi que WM_GetText pour les champs (Edit).

Et voici le listing désassemblé de Wdasm dans lequel j'ai inclus des extraits du source ASM. Il m'a paru intéressant de pouvoir comparer les deux. J'ai également joint des extraits du fichier WIN32HLP pour compléter les informations concernant les APIs (que vous trouverez à la fin de ce texte), dont on retrouve les traces dans les codes donnés par un éditeur hexadécimal.

Quelques infos sur le PE directory

Directory Name                          VirtAddr  VirtSize
--------------------------------------  --------  --------
Import                                  00002024  0000003C
Resource                                00004000  00000260
Import Address Table                    00002000  00000024

000000C0 5045 0000 4C01 0400 C31F D438 0000 0000 PE....4 sections
000000D0 0000 0000 E000 0F01 0B01 050C 0002 0000 ....size of code
000000E0 0008 0000 0000 0000 0010 0000 0010 0000 ................
000000F0 0020 0000 0000 4000 0010 0000 0002 0000 . ....Image Base
00000100 0400 0000 0000 0000 0400 0000 0000 0000 ................
00000110 0050 0000 0004 0000 0000 0000 0200 0000 .P... Image Size
00000120 0000 1000 0010 0000 0000 1000 0010 0000 ................
00000130 0000 0000 1000 0000 0000 0000 0000 0000 ................
00000140 2420 0000 3C00 0000 0040 0000 1802 0000 $ ..<....@......

Import Table : (ou plutôt image Import descriptor. Courtesy of El.CaRaCoL, muy grand amigo)
· On retrouve la RVA (virtual offset) de l'import table : 2024
· La size de l'import table (image import descriptor) est de 3C bytes. C'est à dire qu'en 2060 se termine la liste des informations pour chaque DLL importée. (l'ensemble des DLL et des infos permettant de pointer vers les fonctions de ces DLL)

Import address Table :

· L'Import address table commence en 2000 : c'est à dire qu'ici on va trouver les adresses d'appel des fonctions de chaque DLL. Jusqu'en 2000+0024 = 00002024
· Il y a 24 bytes d'adresses donc 0024 / 04 = 0009 bytes. Cela donne un ordre de grandeur du nombre de fonctions qui sont importées. (la fin d'une liste d'adresses pour les fonctions d'une DLL est signalée par dword null, donc il y a moins de 9 (dec) fonctions importées).

Les voici, livrées par hexWorkShop :

00000680 0000 0000 9200 4469 616C 6F67 426F 7850 ......DialogBoxP
00000690 6172 616D 4100 B800 456E 6444 6961 6C6F aramA...EndDialo
000006A0 6700 0201 4765 7444 6C67 4974 656D 5465 g...GetDlgItemTe
000006B0 7874 4100 BB01 4D65 7373 6167 6542 6F78 xtA...MessageBox
000006C0 4100 1002 5365 6E64 4D65 7373 6167 6541 A...SendMessageA
000006D0 0000 5553 4552 3332 2E64 6C6C 0000 7500 ..USER32.dll..u.
000006E0 4578 6974 5072 6F63 6573 7300 1101 4765 ExitProcess...Ge
000006F0 744D 6F64 756C 6548 616E 646C 6541 0000 tModuleHandleA..
00000700 4B45 524E 454C 3332 2E64 6C6C 0000 0000 KERNEL32.dll....

Et par Wdasm

+++++++++++++++++++ IMPORTED FUNCTIONS ++++++++++++++++++
Number of Imported Modules =    2 (decimal)

   Import Module 001: USER32.dll
   Import Module 002: KERNEL32.dll

+++++++++++++++++++ IMPORT MODULE DETAILS +++++++++++++++

   Import Module 001: USER32.dll

 Addr:00002084 hint(0092) Name: DialogBoxParamA
 Addr:000020B4 hint(01BB) Name: MessageBoxA
 Addr:000020A2 hint(0102) Name: GetDlgItemTextA
 Addr:00002096 hint(00B8) Name: EndDialog
 Addr:000020C2 hint(0210) Name: SendMessageA

   Import Module 002: KERNEL32.dll

 Addr:000020EC hint(0111) Name: GetModuleHandleA
 Addr:000020DE hint(0075) Name: ExitProcess

Le listing à proprement parler, désassemblé par Wdasm :

//******************** Program Entry Point ********
:00401000 6A00                    push 00000000

GetModuleHandle récupère l'adresse de l'ImageBase dans EAX et la place dans [00403008]

* Reference To: KERNEL32.GetModuleHandleA, Ord:0111h
                                  |
:00401002 E84B010000              Call KERNEL32.GetModuleHandleA
:00401007 A308304000              mov dword ptr [00403008], eax
invoke GetModuleHandle, NULL
mov    hInstance, eax
Création de la DialogBox :

:0040100C 6A00                    push 00000000             > NULL    (dwInitParam)
:0040100E 682B104000              push 0040102B             > WndProc (lpDialogFunc)
:00401013 6A00                    push 00000000             > NULL    (hWndParent)

* Possible StringData Ref from Data Obj ->"TESTWIN" (dlgname)
                                  |
:00401015 6800304000              push 00403000             > dlgname (Template)
:0040101A FF3508304000            push dword ptr [00403008] > hInstance

* Reference To: USER32.DialogBoxParamA, Ord:0092h
                                  |
:00401020 E809010000              Call USER32.DialogBoxParamA 
:00401025 50                      push eax
invoke DialogBoxParam,hInstance,ADDR dlgname,0,ADDR WndProc,0

Cette ligne appelle la fonction DialogBoxParam et nécessite 5 paramètres: l'instance handle, le nom du template de la dialog box, le handle de la fenêtre parent, l'adresse de la procédure de traitement de la Dialog box, et les dialog-specific data. DialogBoxParam crée une modal dialog box, et bloquera l'application tant qu'elle n'aura pas été détruite.

(Voir les paramètres de la DialogBox dans le chapitre Documentation)

* Reference To: KERNEL32.ExitProcess, Ord:0075h
                                  |
:00401026 E821010000              Call KERNEL32.ExitProcess
invoke ExitProcess,eax
:0040102B 55                      push ebp
:0040102C 8BEC                    mov ebp, esp
:0040102E 817D0C10010000          cmp dword ptr [ebp+0C], 00000110 (WM_INITDIALOG)

00000110 est au centre de la gestion des DialogBox. C'est un excellent indicateur du moment ou les événements liés à la boite de Dialogue vont commencer à être traités (déplacement de la souris, entrées de textes, clics, appui sur ENTER…)
La procédure dialog box ressemble à une procédure de fenêtre, excepté qu'elle ne reçoit pas un message WM_CREATE. Le premier message qu'elle recevra est WM_INITDIALOG (00000110). Normalement, vous pouvez placer les codes d'initialisation ici.

      .if uMsg == WM_INITDIALOG

:00401035 7526                    jne 0040105D   > va traiter le message
:00401037 EB11                    jmp 0040104A   > vous remarquerez qu'il n'y a pas
                                                 > d'adresse 0040104A !
szText dlgTitle,"Dialog Box Param"
invoke SendMessage,hWin,WM_SETTEXT,0,ADDR dlgTitle

En fait c'est une appréciation du débuggeur, et/ou du désassembleur, car les codes suivants correspondent au texte " Dialog Box param ", le titre de la DialogBox :

:00401039 44 69 61 6C 6F 67 20 42-6F 78 20 50 61 72 61 6D  Dialog Box Param
:00401039 44                      inc esp
:0040103A 69616C6F672042          imul esp, dword ptr [ecx+6C], 4220676F
:00401041 6F                      outsd
:00401042 7820                    js 00401064
:00401044 50                      push eax
:00401045 61                      popad
:00401046 7261                    jb 004010A9
:00401048 6D                      insd
:00401049 006839                  add byte ptr [eax+39], ch
:0040104C 104000                  adc byte ptr [eax+00], al

Les codes à l'adresse 0040104A commencent ainsi :

0040104A                    loc_0040104A:

:0040104A 6839104000              push 00401039   > pousse le texte (dlgTitle)
:0040104F 6A00                    push 00000000   > NULL
:00401051 6A0C                    push 0000000C   > hWin (handle de la Box)
:00401053 FF7508                  push [ebp+08]   > SendMessage

* Reference To: USER32.SendMessageA, Ord:0210h
                                  |
:00401056 E8EB000000              Call USER32.SendMessageA
:0040105B EB1D                    jmp 0040107A

(voir SendMessage)

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401035(C)
|
:0040105D 817D0C11010000          cmp dword ptr [ebp+0C], 00000111

La valeur 00000111! Très importante à repérer, c'est à partir de celle ci que les messages qui nous intéressent le plus vont être traités (appuis sur les boutons, entrée de texte dans un champ, fermeture de l'application...)

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401042(C)
|
:00401064 7504                    jne 0040106A  > évènement lié à WM_Command?
:00401066 7416                    je  0040107E  > oui -> va traiter le message
.elseif     uMsg == WM_COMMAND > comme pour les BMSG !
je          Main_Command
:00401068 EB10                    jmp 0040107A
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401064(C)
|
:0040106A 837D0C10                cmp dword ptr [ebp+0C], 00000010 > = WM_Close
:0040106E 750A                    jne 0040107A                     > Exit ou pas
.elseif uMsg == WM_CLOSE
:00401070 6A00                    push 00000000  > NULL
:00401072 FF7508                  push [ebp+08]  > hWin

* Reference To: USER32.EndDialog, Ord:00B8h
                                  |
:00401075 E8BA000000              Call USER32.EndDialog
invoke EndDialog,hWin,0

L' internal dialog box manager n'envoi pas à notre procédure boite de dialogue le message WM_DESTROY par défaut quand WM_CLOSE est sélectionné (fermeture de la Box). Donc si vous voulez obtenir la fermeture de celle ci, il faudra envoyer un message WM_CLOSE.

(Voir EndDialog)


* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0040105B(U), :00401068(U), :0040106E(C)
|
:0040107A C9                      leave
:0040107B C21000                  ret 0010

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401066(C)
|
:0040107E 8B4510          mov eax, dword ptr [ebp+10]
:00401081 663DE803        cmp ax, 03E8   > est ce le bouton Register qui a été sollicité ?
:00401085 7563            jne 004010EA   > sinon continu
:00401087 6A10            push 00000010  > le buffer est limité à 16 caractères

(Il ne faut pas confondre le nombre de caractères entrés au clavier de ceux qui vont être stockés dans la variable Buffer. C'est une façon indirecte de faire un cmp eax, 10 !)

:00401089 6850304000      push 00403050  > adresse du Buffer où vont être stockées les entrées.
:0040108E 6A64            push 00000064  > = 100 d. C'est l'identificateur du 1er champ 
:00401090 FF7508          push [ebp+08]  > handle de la DialogBox 
(cf le tableau de SI)
* Reference To: USER32.GetDlgItemTextA, Ord:0102h   
:00401093 E8A2000000      Call USER32.GetDlgItemTextA  > saisie au clavier
:00401098 803D5030400000  cmp byte ptr [00403050], 00  > pas d'entrée dans le champ 1?
:0040109F 7415            je 004010B6                  > va voir dans le champ 2
:004010A1 6A00            push 00000000
Main_Command:            
	mov eax,wParam
        cmp ax,IDC_REGISTER
        jne suite
      	invoke GetDlgItemText,hWin,100,offset buffer,16
            cmp buffer,NULL
            je suite1

(Voir GetDlgItemText)

Et sinon affiche la messagebox " Unregistered Version ".

:004010A1 6A00                    push 00000000   > bouton simple (MB_OK)
* Possible StringData Ref from Data Obj ->"Unregistered version"
                                  |
:004010A3 680C304000              push 0040300C   > titre de la boite (Num 1)
:004010A8 6850304000              push 00403050   > texte  entré dans le champ " Name "
:004010AD 6A00                    push 00000000   > fenêtre concernée 
(au cas où la messageBox serait attribuée à une fenêtre en particulier, ce qui n'est pas le cas ici.)

* Reference To: USER32.MessageBoxA, Ord:01BBh
                                  |
:004010AF E88C000000              Call USER32.MessageBoxA
:004010B4 EB34                    jmp 004010EA
invoke MessageBox,NULL,offset buffer,ADDR Num1,MB_OK 
jmp    suite 

(Voir MessageBox)

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040109F(C)
|
:004010B6 6A10             push 00000010  > limitation à 16 caractères
:004010B8 6850304000       push 00403050  > adresse du buffer
:004010BD 6A66             push 00000066  > identificateur du second champ
:004010BF FF7508           push [ebp+08]  > handle de la dialogBox

* Reference To: USER32.GetDlgItemTextA, Ord:0102h
                                  |
:004010C2 E873000000      Call USER32.GetDlgItemTextA
:004010C7 803D5030400000  cmp byte ptr [00403050], 00 > le Buffer est vide?
:004010CE 741A            je 004010EA                 > continue
suite1:            
        invoke GetDlgItemText,hWin,102,offset buffer,16    
            cmp buffer,NULL
            je suite

Si le second champ contient un texte, et seulement lui, la messageBox " registered version " va être affichée avec le texte que vous aurez entré dans le champ " Comp ", et un flag va être mis à 01. C'est le drapeau Utilisateur enregistré.

:004010D0 6A00                    push 00000000  > bouton OK

* Possible StringData Ref from Data Obj ->"Registered version"
                                  |
:004010D2 683A304000              push 0040303A  > Titre de la fenêtre
:004010D7 6850304000              push 00403050  > texte entré (limité à 16 caractères)
:004010DC 6A00                    push 00000000  > handle de la fenêtre

* Reference To: USER32.MessageBoxA, Ord:01BBh
                                  |
:004010DE E85D000000              Call USER32.MessageBoxA
:004010E3 C6054D30400001          mov byte ptr [0040304D], 01 > Flag Good Boy
invoke MessageBox,NULL,offset buffer,ADDR Num3,MB_OK 
mov Statut,01
* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00401085(C), :004010B4(U), :004010CE(C)
|
:004010EA 663DE903                cmp ax, 03E9  > bouton Exit ?
:004010EE 7404                    je 004010F4   > Terminé !
:004010F0 C9                      leave
:004010F1 C21000                  ret 0010
suite:	
        cmp   ax,IDC_EXIT
        je	exit_Button
        ret
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004010EE(C)

L'utilisateur est il enregistré ?

:004010F4 803D4D30400001          cmp byte ptr [0040304D], 01 > 01 = Oui
:004010FB 740A                    je 00401107                 > saute la MessageBox

* Possible StringData Ref from Data Obj ->"Version Non Enregistr"

Ici, la boite de Message n'est pas " immédiate " mais fait appel à une forme de macro :
                                  |
:004010FD 6821304000              push 00403021  > ->Version Non Enregistrée
:00401102 E80E000000              call 00401115  > appel de la macro
exit_Button:
	cmp   Statut,01
    je    fin 
	push	offset Num2
	call	BoiteMessage
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004010FB(C)
|
:00401107 6A00                    push 00000000
:00401109 FF7508                  push [ebp+08]

* Reference To: USER32.EndDialog, Ord:00B8h
                                  |
:0040110C E823000000              Call USER32.EndDialog > détruit la DialogBox
:00401111 C9                      leave
:00401112 C21000                  ret 0010              > et Bye bye...
fin:

      invoke EndDialog,hWin,0
      ret
WndProc endp

La macro qui s'occupe d'afficher la MessageBox:
L'ennui avec ce type de Routine, c'est qu'elle peut servir à l'ensemble des boites de messages, et rendre la localisation de la procédure recherchée plus difficile. A l'entrée de cette macro, vous avez le texte qui sera affiché dans la MessageBox :

* Referenced by a CALL at Address:
|:00401102   
|
:00401115 55                      push ebp         
:00401116 8BEC                    mov ebp, esp
:00401118 6A00                    push 00000000    > bouton OK

* Possible StringData Ref from Data Obj ->"Unregistered version"
                                  |
:0040111A 680C304000              push 0040300C      > titre de la boite
:0040111F FF7508                  push [ebp+08]      > message
:00401122 6A00                    push 00000000      > handle de la fenêtre

* Reference To: USER32.MessageBoxA, Ord:01BBh
                                  |
:00401124 E817000000              Call USER32.MessageBoxA
:00401129 C9                      leave
:0040112A C20400                  ret 0004           > retour vers l'appelant
BoiteMessage	proc	Message:DWORD
	push	MB_OK	
	push	offset Num1
	push	Message	
	push	00
	call	MessageBoxA
	ret
BoiteMessage	endp


Petit essai pour reverser une DialogBox :

Bien évidement, il n'est plus question, ici, de toucher au source de la Dialog box.

Prenons une Hypothèse.
Le générateur de sérial a été identifié, ou du moins un registre ou une adresse dans lequel se trouvera le bon sérial à un Moment X.
A l'instant ou vous appuyez sur le bouton " Register " le message renvoyé par la Dialog Box va obligatoirement amener le programme à aller faire un tour du coté de la routine liée au sérial, et à le contrôler (si les paramètres entrés sont valides) avant de proposer, le cas échéant la boite de message qui va bien (Good Boy/Bad Boy).

Mon idée est de capturer le message lié au clic sur le bouton Register, et de le détourner pour que le programme puisse passer tous les contrôles de validité (nombre de caractères entrés par exemple) en se branchant directement sur le générateur qui va pondre le bon sérial, et renvoyer celui ci dans le bandeau de la Dialog box. En fait transformer la boite d'enregistrement en Key Generator...

Pour faire Zoli, je vais commencer par modifier la bouille de ma DialogBox en utilisant Exescope, un éditeur de ressources, pour que le bouton " register " devienne un bouton " Generate ". Puis avec un éditeur hexadéciaml, ma dialog Box va prendre le titre de " Key Generator " au lieu du " Dialog Box Param " d'origine.

Ceci étant fait, il faut maintenant mettre la main sur la procédure de gestion de cette DialogBox, et trouver le test lié à l'appui sur le bouton " Generate ". Bien sur dans un aussi petit programme, rien n'est plus facile, mais essayons de procéder comme si nous avions affaire à un 4 Mo !

Tout d'abord, il faut connaître le nom exacte de la DialogBox.
Exescope va permettre de trouver celle qui nous intéresse, en affichant les différentes boite de dialogue, et en précisant leurs noms :

+   Header
+   Import
+   Resource
              Dialog
                 TESTWIN

Ensuite, Wdasm va nous la localiser dans le Dead Listing

+++++++++++++++++ DIALOG INFORMATION ++++++++++++++++++
Number of Dialogs =    1 (decimal)
Name: TESTWIN, # of Controls=000, Caption:"Dialog", ClassName:""

En double cliquant sur la StringData Ref qui lui correspond :

StringData Ref de Wdasm :

"Registered version"
"TESTWIN"
"Unregistered version"
"Version Non Enregistr"

Pour arriver ici :

* Possible StringData Ref from Data Obj ->"TESTWIN"
                                  |
:00401015 6800304000              push 00403000
:0040101A FF3508304000            push dword ptr [00403008]

* Reference To: USER32.DialogBoxParamA, Ord:0092h
                                  |
:00401020 E809010000              Call 0040112E
:00401025 50                      push eax

Pour autant, à moins de faire une recherche empirique sur l'ensemble des 00000111 du listing, il serait souhaitable de mettre de suite la main sur la procédure de traitement des messages liés à DialogBoxParamA.

:0040102E 817D0C10010000 cmp dword ptr [ebp+0C], 00000110

Il semblerait que la valeur 00000110 ne soit jamais éloignée de l'appel à DialogBoxParam. De là, il est facile de mettre la main sur le traitement des messages qui seront envoyés à la boite de message.

Une fois localisé l'adresse de 00000111 :

:0040105D 817D0C11010000 cmp dword ptr [ebp+0C], 00000111

un BreakPoint intelligent va vous permettre de n'être dérangé que si le type d'événements qui vous intéresse est provoqué:

BPX 0040105D if ((EBP->0xC) == 0x111)

Et par exemple au moment de l'appui sur le bouton " Generate ". Une interrogation des registres va vous donner l'identificateur de celui ci :

DD EBP+0C :

:0063FB50 00000111  000003E8  00000FB8  8B96112F      ............/...
:0063FB60 00000187  0063FB78  BFF94407  3EB78BBC      ....x.c..D.....>

ou 03E8 est l'identificateur du bouton " Generate ".

:0040107E 8B4510                  mov eax, dword ptr [ebp+10] > 03E8
:00401081 663DE803                cmp ax, 03E8                > identificateur de Generate
:00401085 7563                    jne 004010EA                > suite
:00401087 6A10                    push 00000010               > traitement

A partir de l'adresse 004010EA, la routine de traitement de l'événement lié à l'appui du bouton Generate va commencer

:00401087 6A10            push 00000010                > le buffer est limité à 16 caractères
:00401089 6850304000      push 00403050                > adresse du Buffer
:0040108E 6A64            push 00000064                > identificateur du 1er champ
:00401090 FF7508          push [ebp+08]                > handle de la DialogBox 
:00401093 E8A2000000      Call USER32.GetDlgItemTextA  > saisie au clavier
:00401098 803D5030400000  cmp byte ptr [00403050], 00  > pas d'entrée dans le champ 1 ?
:0040109F 7415            je 004010B6                  > va voir ce qui se passe dans le 2d champ

C'est à dire, dans notre cas, la saisie du nom entré dans le premier champ qui pourrait servir de base à la génération d'un sérial.
On va donc prendre ce nom et l'envoyer à la supposée procédure de génération du sérial, puis afficher le résultat dans le bandeau de la boite d'enregistrement (qui sera en fait le contenu du 1er champ).

Récupérer l'adresse du contenu du champ NAME n'est pas bien compliqué : 00403050, de même que le handle de la DialogBox qui est en EBP+08.
Si après la génération du sérial cette valeur, codée sur un Word, a changé, il faudra la sauver dans l'une des adresses disponible à la fin de la section .rsrc, bien qu'il serait aussi simple de la pousser sur la pile.

Maintenant, il faut que le programme aille faire un détour par notre Patch. Un JMP 00401218 en 004010A1 va très bien faire l'affaire, puis :

:00404218  6850304000      PUSH    00403050               > le " bon sérial "
:0040421D  FF7508          PUSH    DWORD PTR [EBP+08]     > handle de la Box
:00404220  E8AFD9B4BF      CALL    USER32!SetWindowTextA  > affichage sérial
:00404225  C6054D30400001  MOV     BYTE PTR [0040304D],01 > drapeau Utilisateur =1
:0040422C  E9B9CEFFFF      JMP     004010EA               > jump en Full Option.


On ne peut pas vraiment parler de reverse Engineering pour une aussi petite " intervention ", mais l'idée est là...

Christal


La Documentation

DialogBoxParam

int DialogBoxParam(

    HINSTANCE hInstance,       // handle de l'application instance
    LPCTSTR lpTemplateName,   // identifie le template de la dialog box
    HWND hWndParent,         // handle de la fenêtre propriétaire
    DLGPROC lpDialogFunc,   // pointe vers la procédure de la Dialog box  
    LPARAM dwInitParam     // initialization value
   );

hInstance
Identifie l'instance du module ou le fichier exécutable qui contient le template de la Dialog Box.

lpTemplateName
Identificateur (type ICD_Bouton) de la Dialog Box. Ce paramètre est aussi le pointeur vers une chaîne null-terminated ( se terminant par 00) qui spécifie le nom de la DialogBox, ou une valeur entière pour l'identificateur de la ressource de la Boite de Dialogue.

hWndParent
Identifie la fenêtre à qui appartient la dialog box.

lpDialogFunc
Pointe vers la procédure de la dialog box.

dwInitParam
Spécifie la valeur à passer de la dialog box aux paramètres lParam du message WM_INITDIALOG.

Valeur retournée

La valeur retournée est la valeur du nResult parameter spécifiée dans le call à la fonction EndDialog utilisée pour détruire la Dialog Box.

SendMessage

La fonction SendMessage envoie un message spécifique à une ou plusieurs fenêtres. La fonctions appelle la procédure de fenêtre pour la fenêtre concernée et ne se termine que quand celle ci a traité le message.

LRESULT SendMessage(

    HWND hWnd,             // handle de la fenêtre de destination
    UINT Msg,             // message à envoyer
    WPARAM wParam,       // paramètre du 1er message
    LPARAM lParam       // paramètre du second message 
   );

Paramètres

hWnd
Identifie la fenêtre dont la procédure a reçue un message.

Msg
Spécifie le message à envoyer.

wParam
Spécifie une information particulière supplémentaire.

lParam
Spécifie une information particulière supplémentaire.

EndDialog

La fonction EndDialog détruit la modal dialog box, et met fin à la procédure la concernant. Il y a deux types de dialogBox, les modal (application et système) et les modeless (appelées par CreateDialogParam).

BOOL EndDialog(

    HWND hDlg,     // handle de la dialog box
    int nResult   // valeur to return
   );	

Paramètres

hDlg
Identifie la DialogBox à détruire.

nResult
Spécifie la valeur qui doit être retournée à la fonction qui a créée cette Dialog Box.

GetDlgItemText

La fonction GetDlgItemText récupère le titre, ou le texte associé à un Champ, dans une DialogBox (de type IDC_Text).

UINT GetDlgItemText(

    HWND hDlg,		   // handle de la dialog box
    int nIDDlgItem,	  // identificateur du champ
    LPTSTR lpString,     // adresse du buffer pour le texte
    int nMaxCount 	// taille maximum de la chaîne de caractères
   );	

En fait le principe est simple, le contenu de la zone de saisie (le champ) pointée par " identifier of control " (défini lors de la création de la DialogBox) va être stocké dans un Buffer (une adresse mémoire) avec une taille bien définie. (maximum size of string)

Paramètres

hDlg
Identifie la dialog box qui contient le Champ à remplir.

nIDDlgItem
Spécifie si l'identificateur du Champ doit récupérer un Texte ou un Titre.

lpString
Pointe vers le buffer qui reçoit le Titre ou le Texte.

nMaxCount
Spécifie la longueur de la chaînes, en caractères, à copier dans le buffer pointé par lpString. Si la longueur de la chaîne excède la limite fixée, la chaîne est tronquée.

Valeurs retournées dans la table du registre ESP au retour de GetDlgItemTextA

[ESP+10h] - contiendra nMaxCount 
[ESP+0Ch] - contiendra IpString 
[ESP+08h] - contiendra nIDDlgItem 
[ESP+04h] - contiendra hwnd 
[ESP+00h] - contiendra le Return EIP

MessageBox

int MessageBox(

    HWND hWnd,           // handle de la fenêtre propriétaire (en général 00)
    LPCTSTR lpText,     // adresse du message apparaissant dans la boite
    LPCTSTR lpCaption, // adresse du Titre de la message box  
    UINT uType        // style de la message box (type de bouton choisi...)
   );

SetWindowText

La fonction SetWindowText change le texte de la barre des titres de la fenêtre spécifiée (si il y en a une). Si c'est le Champ de la fenêtre qui est spécifié, le texte du Champ est modifié.

BOOL SetWindowText(

    HWND hWnd,          // handle de la fenêtre ou du champ
    LPCTSTR lpString   // adresse de la chaîne
   );	

Paramètres

hWnd
Identifie le champ de le fenêtre dont le texte doit être changé.

lpString
Pointe vers une chaîne se terminant par 00 (null-terminated) qui va être utilisée comme nouveau texte pour la champ, ou titre pour la barre.

Bonne Journée

Christal