Tutorial 7: La Table des Exportations
(Export Table)

Nous avons vus la première partie des liaisons dynamiques, à savoir la table des importations, ceci dans le Tutorial précédent. Maintenant nous allons nous attaquer à l'autre partie, la table des exportations.

Downloadez l'exemple.

Théorie:

Quand le PE loader lance un programme, il charge également les DLLs associées à ce process dans son espace d'adresses. Il extrait alors les informations sur les fonctions d'importation à partir du programme principal. Il se sert de ces informations pour rechercher dans les DLLs les adresses des fonctions que le programme principal va appeler. L'endroit à l'intérieur des DLLs où le PE loader recherche ces adresses (celles des fonctions) est la table des exportations.

Quand une DLL/EXE exporte une fonction pour qu'elle soit utilisée par d'autres DLLs/EXEs, elle peut le faire de deux façons : elle peut exporter la fonction à partir de son nom ou à partir de son classement (par ordre). Dire qu'il existe une fonction du nom de "GetSysConfig" dans une DLL, signifie que si d'autres DLLs/EXEs souhaitent appeler cette fonction, elles doivent le spécifier à partir de son nom, c'est-à-dire : GetSysConfig. L'autre méthode c'est d'exporter la fonction d'après sa position (son n° d'ordre dans la DLL). Qu'est ce qu'un ordre ? Un ordre est un numéro sur 16 bits qui identifie une unique fonction dans une DLL particulière. Ce numéro est unique seulement dans la DLL à laquelle le programme se réfère. Par exemple, dans le susdit exemple, la DLL peut vouloir exporter la fonction par ordre, disons le n°16 (la 16e fonction à l'intérieur de la DLL). Alors les autres DLLs/EXEs qui souhaitent appeler cette fonction doit spécifier ce numéro dans GetProcAddress. Ceci une exportation appelée par ordre (unique).

L'exportation par ordre est fortement déconseillée car elle peut provoquer des problèmes de maintenance pour la DLL. Si la DLL est mise à jour (nouvelle version), alors le programmeur de la DLL n'a pas d'autre choix que de garder l'ordre des fonctions sinon d'autres programmes qui dépendent eux aussi de cette DLL se planteront.

Maintenant nous pouvons examiner la structure d'exportation. De la même façon qu'avec la table des importations, vous pouvez trouver où est située la table des exportations en regardant le 'Répertoire de Données'. Dans ce cas, la table des exportations est le premier membre du Répertoire de Données. La structure d'exportation est appelée IMAGE_EXPORT_DIRECTORY. Elle possède 11 membres, mais seulement quelques-uns sont réellement utiles.

Nom des paramètres Signification
nName Le nom réel du module qui contient les fonctions (nom de la DLL). C'est un paramètre nécessaire parce que le nom du fichier peut être modifié. Si c'est le cas, le PE loader emploiera ce nom intérieurement.
nBase Un nombre que vous devez appliquer sur le n° d'ordre des fonctions pour obtenir leurs index dans le tableau d'adresses des fonctions.
NumberOfFunctions Le nombre total de fonctions/symboles qui sont exportés par ce module (par la DLL).
NumberOfNames Le nombre de fonctions/symboles qui sont exportés grâce à leur nom. Cette valeur n'est pas le nombre TOTAL de fonctions/symboles dans le module. Pour le nombre total, vous avez besoin de regarder le paramètre NumberOfFunctions. Cette valeur peut être nulle. Dans ce cas, le module ne peut exporter que par ordre. S'il n'y a aucune fonction/symbole qui puissent être exportée par son nom, le RVA de la table des exportations à l'intérieur du Répertoire des Données sera 0.
AddressOfFunctions Est un RVA qui pointe sur un tableau contenant les RVAs des fonctions/symboles du module. Bref, les RVAs de toutes les fonctions du module sont regroupés dans un tableau et ce paramètre pointe sur le début de ce tableau.
AddressOfNames Un RVA qui pointe sur un tableau de RVAs contenant les noms des fonctions de la DLL (du module).
AddressOfNameOrdinals Un RVA qui pointe sur un tableau (16 bits) qui contient les n° d'ordre associés aux noms des fonctions dans le tableau AddressOfNames ci-dessus.

Cette simple lecture de la table ne peut pas vous donner une image réelle de la table des exportations. Voici une explication simplifiée qui éclairera son concept.

La table des exportations existe pour que le PE loader puisse s'en servir. Tout d'abord, le module (la DLL) doit garder les adresses de chacune des fonctions exportées quelque part, ainsi le PE loader peut les retrouver. Il les référence dans un tableau qui est pointé par le paramètre AddressOfFunctions. Le nombre d'éléments dans le tableau est contenu dans NumberOfFunctions. Ainsi si le module (la DLL) exporte 40 fonctions, il doit y avoir 40 membres (paramètres) dans le tableau pointé par AddressOfFunctions, et NumberOfFunctions doit contenir la valeur 40. Maintenant si certaines fonctions sont exportées par leurs noms, le module doit contenir les noms. Il conserve les RVAs des noms dans un tableau, ainsi le PE loader peut les retrouver. Ce tableau est pointé par AddressOfNames et le nombre de noms est dans NumberOfNames. Pensez au travail du PE loader, il connaît les noms des fonctions, il doit d'une façon ou d'une autre obtenir les adresses de ces fonctions. Jusqu'ici, le module (la DLL) a deux tableaux à sa disposition : les noms et les adresses mais il n'y a aucun lien entre eux. C'est pourquoi nous avons besoin de quelque chose qui qui puisse relier ces noms de fonctions à leurs adresses. La spécification du PE c'est d'employer des index à l'intérieur d'un tableau d'adresses pour remplacer ce lien essentiel. Ainsi si le PE loader trouve le nom, alors il cherche dans le tableau des noms, il peut récupérer l'index dans la table d'adresses pour ce nom précis. Les index sont regroupés dans un autre tableau (le dernier) pointés par le paramètre AddressOfNameOrdinals. Puisque ce tableau existe en tant que lien entre les noms et les adresses, il doit posséder exactement le même nombre d'éléments que le tableau des noms. C'est-à-dire, chaque nom peut avoir une et uniquement une adresse associée. L'inverse n'est pas vrai : une seule adresse peut posséder plusieurs noms qui lui soient associés. Ainsi nous pouvons avoir des "alias" qui se réfèrent à une même adresse. Pour faire le travail qui consiste à élaborer une relation entre les noms et les index dans le tableau, on doit se déplacer en parallèle. C'est-à-dire, que le premier élément (1er index) du tableau doit contenir l'index associé au premier nom, et ainsi de suite...

AddressOfNames AddressOfNameOrdinals
|
|
RVA du Nom 1
RVA du Nom 2
RVA du Nom 3
RVA du Nom 4
...
RVA du Nom N
<-->
<-->
<-->
<-->
...
<-->
Index du Nom 1
Index du Nom 2
Index du Nom 3
Index du Nom 4
...
Index du Nom N

Un exemple ou deux suivant l'appel par ordre. Si nous avons le nom d'une fonction d'exportation et que nous avons besoin d'obtenir son adresse dans le module (la DLL), nous pouvons nous débrouiller comme ça:

  1. On va jusqu'au PE header
  2. On lit l'adresse virtuelle de la table des exportations dans le Répertoire des données
  3. On va à la table des exportations et on récupère le nombre de noms qui est dans (NumberOfNames)
  4. On parcours les tableaux pointés par AddressOfNames et AddressOfNameOrdinals en parallèle, en recherchant le nom (de la fonction) correspondant. Si le nom est trouvé dans le tableau AddressOfNames, vous devez extraire la valeur dans l'élément associé du tableau AddressOfNameOrdinals. Par exemple, si vous trouvez le RVA du nom correspondant, dans le 77h élément du tableau AddressOfNames, vous devez extraire la valeur stockée dans le 77h élément du tableau AddressOfNameOrdinals. Si vous parcourez le tableau jusqu'au dernier élément du tableau NumberOfNames sans succès, alors vous savez que le nom n'est pas dans ce module. (Votre programme principal fait appelle à une fonction dans une DLL mais cette fonction ne s'y trouve pas en fait.)
  5. On utilise la valeur du tableau AddressOfNameOrdinals en tant qu'index pour le tableau AddressOfFunctions. C'est à dire, si la valeur est 5, alors vous devez extraire la valeur contenue dans le 5ème élément du tableau AddressOfFunctions. Cette valeur est le RVA de la fonction.

Maintenant nous pouvons attirer notre attention sur le membre nBase de la structure IMAGE_EXPORT_DIRECTORY. Vous savez déjà que le tableau AddressOfFunctions contient les adresses de tous les symboles d'exportation d'une DLL. Et le PE loader utilise des index dans ce tableau pour trouver les adresses des fonctions. On va imaginer le scénario où nous nous servons des index de ce tableau en tant que n° d'ordre. Puisque les programmeurs peuvent spécifier le nombre initial de n° d'ordre dans le fichier .def, comme 200, ça signifie qu'il doit y avoir au moins 200 éléments dans le tableau AddressOfFunctions. Par ailleurs imaginons que les 200 premiers éléments ne sont pas utilisés, mais de toute façon ils doivent exister pour que le PE loader puisse se servir des index pour retrouver leurs adresses correctes. Ce n'est pas bon du tout. Le membre nBase existe pour résoudre ce problème. Si le programmeur indique que le n° d'ordre initial est de 200, alors la valeur dans nBase doit être de 200. Lorsque le PE loader lit la valeur dans nBase, il sait que les 200 premiers éléments n'existent pas et qu'il doit soustraire le n° d'ordre à la valeur de nBase pour obtenir le vrai index dans le tableau AddressOfFunctions. Index(Fonction)='n° d'ordre'-nBase. Grâce à l'utilisation du paramètre nBase, c'est pas la peine de créer 200 éléments vides.

Remarquez que nBase n'affecte pas les valeurs du tableau AddressOfNameOrdinals. Malgré le nom "AddressOfNameOrdinals", ce tableau contient les vrais index du tableau AddressOfFunctions, et non pas les n° de classement des fonctions (les n° d'ordre).

Continuons à parler de nBase.
Supposons que nous avons le n° de classement (n° d'ordre) d'une fonction et que nous avons besoin d'obtenir l'adresse de cette fonction, nous pouvons le faire comme ça :

  1. On va jusqu'au PE header
  2. On récupère le RVA de la table des exportations contenue dans le Répertoire des données
  3. On va à la table des exportations et on récupère la valeur de nBase .
  4. On soustrait la valeur dans nBase au n° d'ordre et ainsi on obtient l'index de la fonction qui se trouve dans le tableau AddressOfFunctions.
  5. On compare l'index avec la valeur dans NumberOfFunctions. Si l'index est plus grand ou égal à la valeur dans NumberOfFunctions, alors le n° d'ordre est invalide.
  6. On utilise l'index pour récupérer le RVA de la fonction à l'intérieur du tableau AddressOfFunctions

Remarquez que l'obtention de l'adresse d'une fonction (par ordre) est beaucoup plus facile et plus rapide que d'utiliser le nom de cette fonction. Il n'y a aucun besoin de parcourir les tableaux AddressOfNames et AddressOfNameOrdinals.

Pour conclure, si vous souhaitez obtenir l'adresse d'une fonction à partir de son nom, vous avez besoin de parcourir les tableaux AddressOfNames et AddressOfNameOrdinals pour récupérer son index qui se trouve dans le AddressOfFunctions. Si vous avez le n° d'ordre (de classement) de la fonction, vous pouvez directement fouiner dans le tableau AddressOfFunctions (après avoir soustrait nBase à son n° d'ordre, bien entendu).

Si une fonction est exportée à partir de son nom, vous pouvez employer soit son nom soit son n° d'ordre dans GetProcAddress. Mais si la fonction est uniquement exportée à partir de son n° d'ordre? Nous y voici maintenant.
"La fonction est exportée par n° d'ordre uniquement" signifie que la fonction n'a pas d'entrées dans les tableaux AddressOfNames et AddressOfNameOrdinals. Rappelez-vous des deux paramètres, NumberOfFunctions et NumberOfNames. L'existence de ces deux membres est évidemment que certaines fonctions ne peuvent pas avoir de noms. Le nombre de fonctions doit être au moins (au minimum) égal au nombre de noms. Les fonctions qui n'ont pas de noms sont exportées grâce à leur n° d'ordre (unique). Par exemple, s'il y a 70 fonctions, et qu'il n'y a que 40 entrées dans le tableau AddressOfNames, ça signifie qu'il y a 30 fonctions dans la DLL qui sont exportées d'après leur n° d'ordre. Maintenant comment pouvons-nous découvrir quelles fonctions sont exportées à partir de leur n° d'ordre ? Ce n'est pas facile. On ne peut les trouver que par exclusion. C'est-à-dire, les entrées dans le tableau AddressOfFunctions qui ne fait pas référence au tableau AddressOfNameOrdinals contiennent les RVAs des fonctions qui sont exportées par n° d'ordre.

Exemple:

Cet exemple est semblable à celui du Tutorial précédent. Cependant, il affiche les valeurs de certains membres de la structure IMAGE_EXPORT_DIRECTORY et inscrit aussi les RVAs, les n° d'ordre et les noms des fonctions exportées. Notez que cet exemple n'inscrit pas les fonctions qui sont exportées uniquement par n° d'ordre.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib

IDD_MAINDLG equ 101
IDC_EDIT equ 1000
IDM_OPEN equ 40001
IDM_EXIT equ 40003

DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD
ShowExportFunctions proto :DWORD
ShowTheFunctions proto :DWORD,:DWORD
AppendText proto :DWORD,:DWORD


SEH struct
   PrevLink dd ?
   CurrentHandler dd ?
   SafeOffset dd ?
   PrevEsp dd ?
   PrevEbp dd ?
SEH ends

.data
AppName db "PE tutorial no.7",0
ofn OPENFILENAME <>
FilterString db "Executable Files (*.exe, *.dll)",0,"*.exe;*.dll",0
             db "All Files",0,"*.*",0,0
FileOpenError db "Cannot open the file for reading",0
FileOpenMappingError db "Cannot open the file for memory mapping",0
FileMappingError db "Cannot map the file into memory",0
NotValidPE db "This file is not a valid PE",0
NoExportTable db "No export information in this file",0
CRLF db 0Dh,0Ah,0
ExportTable db 0Dh,0Ah,"======[ IMAGE_EXPORT_DIRECTORY ]======",0Dh,0Ah
            db "Name of the module: %s",0Dh,0Ah
            db "nBase: %lu",0Dh,0Ah
            db "NumberOfFunctions: %lu",0Dh,0Ah
            db "NumberOfNames: %lu",0Dh,0Ah
            db "AddressOfFunctions: %lX",0Dh,0Ah
            db "AddressOfNames: %lX",0Dh,0Ah
            db "AddressOfNameOrdinals: %lX",0Dh,0Ah,0
Header db "RVA Ord. Name",0Dh,0Ah
       db "----------------------------------------------",0
template db "%lX %u %s",0

.data?
buffer db 512 dup(?)
hFile dd ?
hMapping dd ?
pMapping dd ?
ValidPE dd ?

.code
start:
invoke GetModuleHandle,NULL
invoke DialogBoxParam, eax, IDD_MAINDLG,NULL,addr DlgProc, 0
invoke ExitProcess, 0

DlgProc proc hDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.if uMsg==WM_INITDIALOG
   invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_SETLIMITTEXT,0,0
.elseif uMsg==WM_CLOSE
   invoke EndDialog,hDlg,0
.elseif uMsg==WM_COMMAND
   .if lParam==0
     mov eax,wParam
     .if ax==IDM_OPEN
       invoke ShowExportFunctions,hDlg
     .else ; IDM_EXIT
       invoke SendMessage,hDlg,WM_CLOSE,0,0
     .endif
   .endif
.else
   mov eax,FALSE
   ret
.endif
mov eax,TRUE
ret
DlgProc endp

SEHHandler proc C pExcept:DWORD, pFrame:DWORD, pContext:DWORD, pDispatch:DWORD
mov edx,pFrame
assume edx:ptr SEH
mov eax,pContext
assume eax:ptr CONTEXT
push [edx].SafeOffset
pop [eax].regEip
push [edx].PrevEsp
pop [eax].regEsp
push [edx].PrevEbp
pop [eax].regEbp
mov ValidPE, FALSE
mov eax,ExceptionContinueExecution
ret
SEHHandler endp

ShowExportFunctions proc uses edi hDlg:DWORD
LOCAL seh:SEH
mov ofn.lStructSize,SIZEOF ofn
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,512
mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
   invoke CreateFile, addr buffer, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
   .if eax!=INVALID_HANDLE_VALUE
     mov hFile, eax
     invoke CreateFileMapping, hFile, NULL, PAGE_READONLY,0,0,0
     .if eax!=NULL
       mov hMapping, eax
       invoke MapViewOfFile,hMapping,FILE_MAP_READ,0,0,0
       .if eax!=NULL
         mov pMapping,eax
         assume fs:nothing
         push fs:[0]
         pop seh.PrevLink
         mov seh.CurrentHandler,offset SEHHandler
         mov seh.SafeOffset,offset FinalExit
         lea eax,seh
         mov fs:[0], eax
         mov seh.PrevEsp,esp
         mov seh.PrevEbp,ebp
         mov edi, pMapping
         assume edi:ptr IMAGE_DOS_HEADER
         .if [edi].e_magic==IMAGE_DOS_SIGNATURE
           add edi, [edi].e_lfanew
           assume edi:ptr IMAGE_NT_HEADERS
           .if [edi].Signature==IMAGE_NT_SIGNATURE
             mov ValidPE, TRUE
           .else
             mov ValidPE, FALSE
           .endif
         .else
           mov ValidPE,FALSE
         .endif
FinalExit:
         push seh.PrevLink
         pop fs:[0]
         .if ValidPE==TRUE
           invoke ShowTheFunctions, hDlg, edi
         .else
           invoke MessageBox,0, addr NotValidPE, addr AppName, MB_OK+MB_ICONERROR
        
.endif
         invoke UnmapViewOfFile, pMapping
       .else
         invoke MessageBox, 0, addr FileMappingError, addr AppName, MB_OK+MB_ICONERROR
       .endif
       invoke CloseHandle,hMapping
     .else
       invoke MessageBox, 0, addr FileOpenMappingError, addr AppName, MB_OK+MB_ICONERROR
     .endif
     invoke CloseHandle, hFile
   .else
     invoke MessageBox, 0, addr FileOpenError, addr AppName, MB_OK+MB_ICONERROR
   .endif
.endif
ret
ShowExportFunctions endp

AppendText proc hDlg:DWORD,pText:DWORD
invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_REPLACESEL,0,pText
invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_REPLACESEL,0,addr CRLF
invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_SETSEL,-1,0
ret
AppendText endp

RVAToFileMap PROC uses edi esi edx ecx pFileMap:DWORD,RVA:DWORD
mov esi,pFileMap
assume esi:ptr IMAGE_DOS_HEADER
add esi,[esi].e_lfanew
assume esi:ptr IMAGE_NT_HEADERS
mov edi,RVA ; edi == RVA
mov edx,esi
add edx,sizeof IMAGE_NT_HEADERS
mov cx,[esi].FileHeader.NumberOfSections
movzx ecx,cx
assume edx:ptr IMAGE_SECTION_HEADER
.while ecx>0
   .if edi>=[edx].VirtualAddress
     mov eax,[edx].VirtualAddress
     add eax,[edx].SizeOfRawData
     .if edi<eax ; The address is in this section mov eax,[edx].VirtualAddress sub edi,eax edi == difference between specified RVA and's eax,[edx].PointerToRawData add eax,edi eax == file offset eax,pFileMap ret .endif edx,sizeof IMAGE_SECTION_HEADER dec ecx .endw assume edx:nothing esi:nothing RVAToFileMap endp ShowTheFunctions proc uses esi ebx hDlg:DWORD, pNTHdr:DWORD LOCAL temp[512]:BYTE NumberOfNames:DWORD Base:DWORD AddressOfFunctions:DWORD AddressOfNameOrdinals:DWORD edi,pNTHdr edi:ptr IMAGE_NT_HEADERS edi, [edi].OptionalHeader.DataDirectory.VirtualAddress .if invoke MessageBox,0, addr NoExportTable,addr AppName,MB_OK+MB_ICONERROR SetDlgItemText,hDlg,IDC_EDIT,0 AppendText,hDlg,addr buffer RVAToFileMap,pMapping,edi IMAGE_EXPORT_DIRECTORY eax,[edi].NumberOfFunctions RVAToFileMap, pMapping,[edi].nName wsprintf, temp,addr ExportTable,eax,[edi].nBase,[edi].NumberOfFunctions,[edi].NumberOfNames,[edi].AddressOfFunctions,[edi].AddressOfNames,[edi].AddressOfNameOrdinals temp push [edi].NumberOfNames pop NumberOfNames [edi].nBase Base [edi].AddressOfFunctions AddressOfFunctions RVAToFileMap,pMapping,AddressOfFunctions AddressOfFunctions,eax esi,[edi].AddressOfNames RVAToFileMap,pMapping,esi esi,eax ebx,[edi].AddressOfNameOrdinals RVAToFileMap,pMapping,ebx ebx,eax AddressOfNameOrdinals,ebx Header edi,AddressOfFunctions .while><eax
       mov eax,[edx].VirtualAddress
       sub edi,eax
       mov eax,[edx].PointerToRawData
       add eax,edi
       add eax,pFileMap
       ret
     .endif
   .endif
   add edx,sizeof IMAGE_SECTION_HEADER
   dec ecx
.endw
assume edx:nothing
assume esi:nothing
mov eax,edi
ret
RVAToFileMap endp

ShowTheFunctions proc uses esi ecx ebx hDlg:DWORD, pNTHdr:DWORD
LOCAL temp[512]:BYTE
LOCAL NumberOfNames:DWORD
LOCAL Base:DWORD

mov edi,pNTHdr
assume edi:ptr IMAGE_NT_HEADERS
mov edi, [edi].OptionalHeader.DataDirectory.VirtualAddress
.if edi==0
  invoke MessageBox,0, addr NoExportTable,addr AppName,MB_OK+MB_ICONERROR
  ret
.endif
invoke SetDlgItemText,hDlg,IDC_EDIT,0
invoke AppendText,hDlg,addr buffer
invoke RVAToFileMap,pMapping,edi
mov edi,eax
assume edi:ptr IMAGE_EXPORT_DIRECTORY
mov eax,[edi].NumberOfFunctions
invoke RVAToFileMap, pMapping,[edi].nName
invoke wsprintf, addr temp,addr ExportTable, eax, [edi].nBase, [edi].NumberOfFunctions, [edi].NumberOfNames, [edi].AddressOfFunctions, [edi].AddressOfNames, [edi].AddressOfNameOrdinals
invoke AppendText,hDlg,addr temp
invoke AppendText,hDlg,addr Header
push [edi].NumberOfNames
pop NumberOfNames

push [edi].nBase
pop Base
invoke RVAToFileMap,pMapping,[edi].AddressOfNames
mov esi,eax
invoke RVAToFileMap,pMapping,[edi].AddressOfNameOrdinals
mov ebx,eax
invoke RVAToFileMap,pMapping,[edi].AddressOfFunctions
mov edi,eax
.while NumberOfNames>0
  
invoke RVAToFileMap,pMapping,dword ptr [esi]
   mov dx,[ebx]
   movzx edx,dx
   mov ecx,edx
   shl edx,2
   add edx,edi
   add ecx,Base
   invoke wsprintf, addr temp,addr template,dword ptr [edx],ecx,eax
   invoke AppendText,hDlg,addr temp
   dec NumberOfNames
   add esi,4
   add ebx,2
.endw
ret
ShowTheFunctions endp
end start

Analyse:

mov edi,pNTHdr
assume edi:ptr IMAGE_NT_HEADERS
mov edi, [edi].OptionalHeader.DataDirectory.VirtualAddress
.if edi==0
  invoke MessageBox,0, addr NoExportTable,addr AppName,MB_OK+MB_ICONERROR
  ret
.endif

Après que le programme ait vérifié que le fichier était un PE File valide, il va au répertoire des données et récupère l'adresse virtuelle de la table des exportations. Si l'adresse virtuelle est nulle, le fichier n'a aucun symbole exporté.

mov eax,[edi].NumberOfFunctions
invoke RVAToFileMap, pMapping,[edi].nName
invoke wsprintf, addr temp,addr ExportTable, eax, [edi].nBase, [edi].NumberOfFunctions, [edi].NumberOfNames, [edi].AddressOfFunctions, [edi].AddressOfNames, [edi].AddressOfNameOrdinals
invoke AppendText,hDlg,addr temp

Nous affichons les informations importantes de la structure IMAGE_EXPORT_DIRECTORY dans le contrôle d'édition.

push [edi].NumberOfNames
pop NumberOfNames
push [edi].nBase
pop Base

Puisque nous souhaitons énumérer tous les noms des fonctions, nous avons besoin de savoir combien de noms il y a dans la table des exportations. nBase est employé lorsque nous voulons convertir les index dans le tableau AddressOfFunctions en n° d'ordre.

invoke RVAToFileMap,pMapping,[edi].AddressOfNames
mov esi,eax
invoke RVAToFileMap,pMapping,[edi].AddressOfNameOrdinals
mov ebx,eax
invoke RVAToFileMap,pMapping,[edi].AddressOfFunctions
mov edi,eax

Les adresses des trois tableaux sont stockées dans esi, ebx et edi, prêt pour qu'on puisse y accéder.

.while NumberOfNames>0

Continue tant que tous les noms n'aient pas été traités.

   invoke RVAToFileMap,pMapping,dword ptr [esi]

Puisque esi pointe sur un tableau contenant les RVAs des noms exportés, ça nous donne le RVA du nom actuel. On le convertit en une adresse virtuelle, pour pouvoir l'utiliser dans l'API wsprintf plus tard.

   mov dx,[ebx]
   movzx edx,dx
   mov ecx,edx
   add ecx,Base

ebx pointe sur le tableau des n° d'ordre. Les éléments de ce tableau sont de taille WORD. C'est pourquoi nous avons besoin de convertir ces valeur en dword d'abord. edx et ecx contiennent les index dans le tableau AddressOfFunctions. Nous emploierons edx en tant que pointer dans le tableau AddressOfFunctions. Et nous ajoutons la valeur de nBase à ecx pour obtenir le numéro d'ordre de la fonction.

   shl edx,2
   add edx,edi

On multiplie l'index par 4 (car chaque élément du tableau AddressOfFunctions est d'une taille de 4 octets) et on y ajoute ensuite l'adresse du tableau AddressOfFunctions. Comme ça edx représente le RVA de la fonction.

   invoke wsprintf, addr temp,addr template,dword ptr [edx],ecx,eax
   invoke AppendText,hDlg,addr temp

On affiche le RVA, le n° d'ordre et le nom de la fonction dans le contrôle d'édition.

   dec NumberOfNames
   add esi,4
   add ebx,2
.endw

On rafraîchit le compteur ainsi que les adresses des éléments actuels dans les tableaux AddressOfNames et AddressOfNameOrdinals. On continue tant que tous les noms n'aient été traités.


[Iczelion's Win32 Assembly Homepage]


Traduit par Morgatte