Dans ce Tutorial nous allons voir la table des importations. Laissez-moi vous avertir d'abord, ce Tutorial est long et difficile pour ceux qui ne connaissent rien à la table des importations. Il y a des chances que vous deviez relire ce Tutorial plusieurs fois et deviez même examiner les structures relatives aux debuggers.
Downloadez l'exemple.
Tout d'abord, vous devez savoir ce qu'est une fonction d'importation. Une fonction d'importation est une fonction qui n'est pas directement dans le programme, mais est appelée par le programme, d'où le nom "import". Les fonctions d'importation résident en réalité dans une ou plusieurs DLLs. Seules les informations à propos de ces fonctions sont contenue dans le programme qui fait appel à la DLL dans laquelle est contenue le fonction d'importation. Ces informations sont entre autres le nom de chaque fonction et le nom de la (ou les) DLL(s) dans lesquels elles ont été placées.
Maintenant comment pouvons-nous découvrir à quel endroit dans le PE file (le programme) se situent ces informations ? Nous devons nous tourner vers le Répertoire des Données pour trouver la réponse. Je vous rafraîchi un peu la mémoire. Ci-dessous le PE header :
IMAGE_NT_HEADERS STRUCT
Signature dd ?
FileHeader IMAGE_FILE_HEADER <>
OptionalHeader IMAGE_OPTIONAL_HEADER <>
IMAGE_NT_HEADERS ENDS
Le dernier membre de l'Optional header est le répertoire des données:
IMAGE_OPTIONAL_HEADER32 STRUCT
....
LoaderFlags dd ?
NumberOfRvaAndSizes dd ?
DataDirectory IMAGE_DATA_DIRECTORY 16 dup(<>)
IMAGE_OPTIONAL_HEADER32 ENDS
Le 'répertoire des données' est un tableau, ce tableau est en faite une structure du nom d'IMAGE_DATA_DIRECTORY. Elle possède 16 membres (ou paramètres). Si vous vous rappelez de la table des sections qu'on avait comparés à un répertoire racine pour les sections du PE file, et bien vous pouvez de la même manière comparer le 'répertoire des données' à un répertoire racine des composants logiques stockés à l'intérieur de ces sections. Pour être plus précis, le 'répertoire des données' contient les emplacements et les tailles des structures de données importantes à l'intérieur du PE file. Chaque membre contient des informations sur une structure de données importante.
Membre
|
Info qu'il contient |
---|---|
0
|
Export symbols |
1
|
Import symbols |
2
|
Resources |
3
|
Exception |
4
|
Security |
5
|
Base relocation |
6
|
Debug |
7
|
Copyright string |
8
|
Unknown |
9
|
Thread local storage (TLS) |
10
|
Load configuration |
11
|
Bound Import |
12
|
Import Address Table |
13
|
Delay Import |
14
|
COM descriptor |
Je n'ai des connaissances qu'uniquement sur les membres surlignés. Maintenant que vous connaissez ce que contient chaque membre du 'répertoire des données', nous pouvons regarder en détail le membre. Chaque membre du 'répertoire des données' est une structure appelée IMAGE_DATA_DIRECTORY laquelle est défie comme suit :
IMAGE_DATA_DIRECTORY STRUCT
VirtualAddress dd ?
isize dd ?
IMAGE_DATA_DIRECTORY ENDS
VirtualAddress est en réalité l'adresse relative virtuelle (RVA) de la structure de données (Data Structure). Par exemple, si cette structure est destinée aux Symboles d'Importation, alors ce membre contient le RVA du tableau IMAGE_IMPORT_DESCRIPTOR.
isize contient la taille en octets de la structure de données mentionnée par VirtualAddress
.
Voici l'arrangement général des différentes structures de données importantes dans un PE file:
Maintenant on arrête de jouer, on va pouvoir vraiment discuter de la table des importations. L'adresse de la table des importations est contenue dans le membre (paramètre) VirtualAddress du deuxième membre du Répertoire des Données. La table des importations est en réalité un tableau contenant plusieures structures IMAGE_IMPORT_DESCRIPTOR. Chaque structure contient des informations sur une DLL dont le PE File importe ses symboles. Par exemple, si le PE File importe 10 fonctions appartenant à 10 DLLs différentes, alors nous retrouverons 10 membres dans ce tableau. Le dernier membre de ce tableau qui sert à terminé le tableau ne contient que des zéros. Maintenant nous pouvons examiner la structure en détail :
IMAGE_IMPORT_DESCRIPTOR STRUCT
union
Characteristics dd ?
OriginalFirstThunk dd ?
ends
TimeDateStamp dd ?
ForwarderChain dd ?
Name1 dd ?
FirstThunk dd ?
IMAGE_IMPORT_DESCRIPTOR ENDS
Le premier membre de cette structure est une union. En réalité, l'union fournit seulement le pseudonyme d'OriginalFirstThunk, de cette façon vous pouvez appeler "Characteristics". Ce membre contient le RVA d'un tableau contenant des structures IMAGE_THUNK_DATA.
Qu'est-ce que IMAGE_THUNK_DATA ? C'est une union de taille dword. D'habitude, nous l'interprétons comme le pointer portant sur une structure IMAGE_IMPORT_BY_NAME. Remarquez qu'IMAGE_THUNK_DATA contient le pointer qui est sur une structure IMAGE_IMPORT_BY_NAME : et non pas sur la structure elle-même.
Regardez sa logique : Il y a plusieurs structures IMAGE_IMPORT_BY_NAME. Nous rassemblons dans un tableau le RVA de ces structures (IMAGE_THUNK_DATAs), et le terminons avec un 0. Ensuite nous mettons le RVA du tableau (lui-même) dans OriginalFirstThunk.
La structure IMAGE_IMPORT_BY_NAME contient les informations à propos d'une fonction d'importation. Maintenant regardons à quoi ressemble la structure IMAGE_IMPORT_BY_NAME:
IMAGE_IMPORT_BY_NAME STRUCT
Hint dw ?
Name1 db ?
IMAGE_IMPORT_BY_NAME ENDS
Hint contient l'index de la fonction, cet index réside dans 'la table d'exportations de la DLL'. Ce membre sert uniquement à l'utilisation du PE Loader. Ainsi le PE Loader peut rapidement retrouver la fonction à partir de la table d'exportation de la DLL. Ce n'est pas une valeur essentielle et il se peut même que certains linkers mette la valeur de ce paramètre à 0.
Name1 contient le nom de la fonction d'importation. Le nom est une chaîne de caractères ASCIIZ. Notez que la taille de Name1 est par défaut d'un octet mais en réalité c'est un paramètre de taille variable. C'est juste qu'il n'y a aucune façon de représenter un paramètre de taille variable dans une structure. Cette structure nous est gracieusement fournit, ainsi nous pouvons nous référer à la 'Structure de Données' avec des noms en guise de descriptifs.
TimeDateStamp et ForwarderChain sont des machins avancés : Nous parlerons d'eux une foie que vous aurez bien pris la main avec les autres membres.
Name1 contient le RVA du nom de la DLL, bref, le pointer qui est sur le nom de la DLL. C'est une chaîne de caractères ASCIIZ.
FirstThunk est très semblable à OriginalFirstThunk, c'est-à-dire. Il contient le RVA d'un tableau contenant des structures IMAGE_THUNK_DATA (bien qu'en fait c'est un tableau différent).
Ok, si vous êtes encore embrouillés, regardez ça : Il y a plusieurs structures IMAGE_IMPORT_BY_NAME. Vous créez deux tableaux, les remplissez ensuite avec les RVAs de ces structures IMAGE_IMPORT_BY_NAME, donc les deux tableaux contiennent exactement les mêmes valeurs (c'est-à-dire des duplicatas exactes). Maintenant vous assignez le RVA du premier tableau à OriginalFirstThunk et le RVA du deuxième tableau à FirstThunk
.
OriginalFirstThunk | IMAGE_IMPORT_BY_NAME | FirstThunk | ||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| |
|
|
|||||||||||||||||||||||||||||||||
|
|
|
|
|
Maintenant vous devez être capables de comprendre ce que je veux dire. Ne soyez pas intimidé par le nom IMAGE_THUNK_DATA : c'est seulement un RVA dans la structure IMAGE_IMPORT_BY_NAME. Si vous remplacez le mot IMAGE_THUNK_DATA par RVA dans votre esprit, ça vous semblera peut-être plus clairement.
Le nombre d'éléments dans les tableaux OriginalFirstThunk et FirstThunk dépend des Fonctions que le PE file importe de la DLL.
Par exemple, si le PE file importe 10 Fonctions de kernel32.DLL, alors Name1 appartenant à la structure IMAGE_IMPORT_DESCRIPTOR contiendra le RVA de la chaîne de caractères "kernel32.DLL" et il y aura 10 IMAGE_THUNK_DATAs dans chaque tableau.
La question suivante c'est : pourquoi avons-nous besoin de deux tableaux qui sont identiques trait pour trait ? En réponse à cette question, une fois que le PE file est chargé en mémoire nous avons besoin de savoir ce qu'il regardera dans les IMAGE_THUNK_DATAs et IMAGE_IMPORT_BY_NAMEs, il déterminera les adresses des Fonctions d'importation.
Ainsi il remplace les IMAGE_THUNK_DATAs dans le tableau indiqué par FirstThunk avec les adresses réelles des Fonctions. Ainsi lorsque le PE file est prêt à être exécuté, la susdite image est déjà modifiée :
OriginalFirstThunk | IMAGE_IMPORT_BY_NAME | FirstThunk | ||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| |
|
|
|||||||||||||||||||||||||||||||||
|
|
|
|
|
Le tableau des RVAs indiqué par OriginalFirstThunk reste inchangé pour que si le besoin s'en fasse sentir il soit possible de retrouver les noms des fonctions importées, le PE loader peut toujours les retrouver.
Il y a une petite entorse à ce schéma *simple et directe*. Certaines fonctions sont uniquement exportées suivant un ordre. Ça signifie que vous n'appelez pas les fonctions par leurs noms : vous les appelez par leurs positions. Dans ce cas, il n'y aura aucune structure IMAGE_IMPORT_BY_NAME pour ces fonctions dans le 'module Caller' (le programme principale qui fait appelle à une DLL pour se servir de ses fonctions (en les important)). Au lieu de ça, l'IMAGE_THUNK_DATA pour cette fonction contiendra le n° de position de la fonction dans le WORD de poids faible et le 'bit de poids fort' (MSB ou the most significant bit) d'IMAGE_THUNK_DATA sera mis à 1. Par exemple, si une fonction est exportée uniquement par ordre et que son n° est 1234h, l'IMAGE_THUNK_DATA pour cette fonction sera 80001234h. Microsoft fournit une constante très pratique pour tester l'état du MSB d'un dword, IMAGE_ORDINAL_FLAG32. Sa valeur est de 80000000h. (=poids fort à 1)
Supposons que nous voulons inscrire les noms de TOUTES les fonctions d'importation du PE file, nous avons besoin de suivre les étapes ci-dessous :
Cet exemple ouvre un PE file et lit puis affiche les noms de toutes les fonctions d'importation de ce fichier dans un contrôle d'édition. Il affiche aussi les valeurs dans les structures IMAGE_IMPORT_DESCRIPTOR.
.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
ShowImportFunctions proto :DWORD
ShowTheFunctions proto :DWORD,:DWORD
AppendText proto :DWORD,:DWORD
SEH struct
PrevLink dd ? ; adresse de la structure SEH ci-dessus
CurrentHandler dd ? ; adresse du nouvel 'exception handler'
SafeOffset dd ? ; endroit où est sauvegardé l'offset pour continuer l'exécution
PrevEsp dd ? ; l'ancienne valeur dans 'esp'
PrevEbp dd ? ; l'ancienne valeur dans 'ebp'
SEH ends
.data
AppName db "PE tutorial no.6",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
CRLF db 0Dh,0Ah,0
ImportDescriptor db 0Dh,0Ah,"================[ IMAGE_IMPORT_DESCRIPTOR ]=============",0
IDTemplate db "OriginalFirstThunk = %lX",0Dh,0Ah
db "TimeDateStamp
= %lX",0Dh,0Ah
db "ForwarderChain
= %lX",0Dh,0Ah
db "Name = %s",0Dh,0Ah
db "FirstThunk
= %lX",0
NameHeader db 0Dh,0Ah,"Hint Function",0Dh,0Ah
db "-----------------------------------------",0
NameTemplate db "%u %s",0
OrdinalTemplate db "%u (ord.)",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 ShowImportFunctions,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
ShowImportFunctions 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
ShowImportFunctions 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
RVAToOffset 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 ; check all sections
.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
mov eax,[edx].PointerToRawData
add eax,edi ; eax == file offset
ret
.endif
.endif
add edx,sizeof IMAGE_SECTION_HEADER
dec ecx
.endw
assume edx:nothing
assume esi:nothing
mov eax,edi
ret
RVAToOffset endp
ShowTheFunctions proc uses esi ecx ebx hDlg:DWORD, pNTHdr:DWORD
LOCAL temp[512]:BYTE
invoke SetDlgItemText,hDlg,IDC_EDIT,0
invoke AppendText,hDlg,addr buffer
mov edi,pNTHdr
assume edi:ptr IMAGE_NT_HEADERS
mov edi, [edi].OptionalHeader.DataDirectory[sizeof IMAGE_DATA_DIRECTORY].VirtualAddress
invoke RVAToOffset,pMapping,edi
mov edi,eax
add edi,pMapping
assume edi:ptr IMAGE_IMPORT_DESCRIPTOR
.while !([edi].OriginalFirstThunk==0 && [edi].TimeDateStamp==0
&& [edi].ForwarderChain==0 && [edi].Name1==0 && [edi].FirstThunk==0)
invoke AppendText,hDlg,addr ImportDescriptor
invoke RVAToOffset,pMapping, [edi].Name1
mov edx,eax
add edx,pMapping
invoke wsprintf, addr temp, addr IDTemplate, [edi].OriginalFirstThunk,[edi].TimeDateStamp,[edi].ForwarderChain,edx,[edi].FirstThunk
invoke AppendText,hDlg,addr temp
.if [edi].OriginalFirstThunk==0
mov esi,[edi].FirstThunk
.else
mov esi,[edi].OriginalFirstThunk
.endif
invoke RVAToOffset,pMapping,esi
add eax,pMapping
mov esi,eax
invoke AppendText,hDlg,addr NameHeader
.while dword ptr [esi]!=0
test dword ptr [esi],IMAGE_ORDINAL_FLAG32
jnz ImportByOrdinal
invoke RVAToOffset,pMapping,dword ptr [esi]
mov edx,eax
add edx,pMapping
assume edx:ptr IMAGE_IMPORT_BY_NAME
mov cx, [edx].Hint
movzx ecx,cx
invoke wsprintf,addr temp,addr NameTemplate,ecx,addr
[edx].Name1
jmp ShowTheText
ImportByOrdinal:
mov edx,dword ptr [esi]
and edx,0FFFFh
invoke wsprintf,addr temp,addr OrdinalTemplate,edx
ShowTheText:
invoke AppendText,hDlg,addr temp
add esi,4
.endw
add edi,sizeof IMAGE_IMPORT_DESCRIPTOR
.endw
ret
ShowTheFunctions endp
end start
Le programme affiche une DialogBox de type OPENFILE (laquelle sert à sélectionner et ouvrir un fichier quand l'utilisateur clique dessus). Il vérifie que le fichier est un PE valide et appelle ensuite ShowTheFunctions.
ShowTheFunctions proc uses esi ecx ebx hDlg:DWORD, pNTHdr:DWORD
LOCAL temp[512]:BYTE
Réservez un espace de 512 octets sur la pile pour des opérations sur des chaînes de caractères.
invoke SetDlgItemText,hDlg,IDC_EDIT,0
Efface le texte dans le contrôle d'édition
invoke AppendText,hDlg,addr buffer
Insère le nom du PE file dans le contrôle d'édition.AppendText envoie juste des messages EM_REPLACESEL pour ajouter du texte dans le contrôle d'édition. Notez qu'il envoie EM_SETSEL avec wParam=-1 et lParam=0 au contrôle d'édition pour déplacer le curseur en fin de texte.
mov edi,pNTHdr
assume edi:ptr IMAGE_NT_HEADERS
mov edi, [edi].OptionalHeader.DataDirectory[sizeof IMAGE_DATA_DIRECTORY].VirtualAddress
Sert à récupérer le RVA des symboles d'importation. Au départ edi pointe sur le PE header. On s'en sert pour aller jusqu'au 2ème membre du tableau du Répertoire des Données et on récupère la valeur du membre VirtualAddress.
invoke RVAToOffset,pMapping,edi
mov edi,eax
add edi,pMapping
Voici ici un des pièges pour les nouveaux venus à la programmation des PE. La plupart des adresses dans un PE file sont des RVAs et les RVAs n'ont de sens que lorsque le PE file est chargé en mémoire par le PE loader. Dans notre cas, nous mappons le fichier en mémoire, mais pas comme le fait le PE loader. Ainsi nous ne pouvons pas employer directement ces RVAs. D'une façon ou d'une autre nous devons convertir ces RVAs offsets pour le fichier. J'ai écris la fonction RVAToOffset dans ce but. Je ne l'analyserai pas en détail ici. Il est suffisant de dire qu'il compare le RVA qu'on lui a suggéré avec les début et fins des RVAs de toutes les sections du PE file, et utilise la valeur du paramètre PointerToRawData de la structure IMAGE_SECTION_HEADER pour convertir le RVA en un offset pour le fichier.
Pour utiliser cette fonction, vous devez lui passer deux paramètres : le pointer sur le fichier a Mapper en mémoire et le RVA que vous voulez convertir. Elle retourne l'offset(de fichier) dans eax. Dans ce susdit petit bout, nous devons additionner le 'pointer sur le fichier a Mapper en mémoire' avec l'offset (de fichier)' pour le convertir en une adresse virtuelle. Ça vous semble compliqué, huh ? :)
assume edi:ptr IMAGE_IMPORT_DESCRIPTOR
.while !([edi].OriginalFirstThunk==0 && [edi].TimeDateStamp==0
&& [edi].ForwarderChain==0 && [edi].Name1==0 && [edi].FirstThunk==0)
edi pointe maintenant sur la première structure IMAGE_IMPORT_DESCRIPTOR. Nous lisons le tableau jusqu'à ce que nous trouvions la structure qui ne comporte que des membres nuls, elle sert à marquer la fin du tableau.
invoke AppendText,hDlg,addr
ImportDescriptor
invoke RVAToOffset,pMapping,
[edi].Name1
mov edx,eax
add edx,pMapping
Nous souhaitons afficher les valeurs de la structure IMAGE_IMPORT_DESCRIPTOR courante dans le contrôle d'édition. Name1 diffère des autres membres puisque il contient le RVA du nom de la dll. C'est pourquoi nous devons d'abord le convertir en une adresse virtuelle.
invoke wsprintf, addr temp, addr IDTemplate, [edi].OriginalFirstThunk,[edi].TimeDateStamp,[edi].ForwarderChain,edx,[edi].FirstThunk invoke AppendText,hDlg,addr temp
Affiche les valeurs de l'IMAGE_IMPORT_DESCRIPTOR courante.
.if [edi].OriginalFirstThunk==0
mov esi,[edi].FirstThunk
.else
mov esi,[edi].OriginalFirstThunk
.endif
Ensuite nous nous préparons à parcourir le tableau IMAGE_THUNK_DATA. Normalement nous aimerions pouvoir utiliser le tableau pointé par OriginalFirstThunk. Cependant, certains mauvais linkers mettent 0 dans OriginalFirstThunk, c'est pourquoi nous devons d'abord vérifier si la valeur d'OriginalFirstThunk est zéro ou non. S'il est à 0, nous employons le tableau pointé par FirstThunk instead.
invoke RVAToOffset,pMapping,esi
add eax,pMapping
mov esi,eax
De nouveau, la valeur dans OriginalFirstThunk/FirstThunk est un RVA. Nous devons la convertir en une adresse virtuelle.
invoke AppendText,hDlg,addr NameHeader
.while dword ptr [esi]!=0
Maintenant nous sommes prêts nous promener dans le tableau IMAGE_THUNK_DATAs pour chercher les noms des fonctions importées dans cette DLL. Nous lisons le tableau jusqu'à ce que nous trouvions une entrée qui contient 0.
test dword ptr [esi],IMAGE_ORDINAL_FLAG32
jnz ImportByOrdinal
La première chose que nous faisons avec l'IMAGE_THUNK_DATA c'est de le comparer à IMAGE_ORDINAL_FLAG32. Ce teste est validé si le bit le plus significatif (MSB) d'IMAGE_THUNK_DATA est 1. S'il l'est, la fonction est exportée par ordre donc nous n'avons aucun besoin de la traiter plus loin. Nous pouvons extraire son n° d'ordre à partir du WORD de poids faible d'IMAGE_THUNK_DATA puis passer au DWORD IMAGE_THUNK_DATA suivant.
invoke RVAToOffset,pMapping,dword
ptr [esi]
mov edx,eax
add edx,pMapping
assume edx:ptr IMAGE_IMPORT_BY_NAME
Si le MSB d'IMAGE_THUNK_DATA est 0, il contient le RVA de la structure IMAGE_IMPORT_BY_NAME. Nous avons d'abord besoin de le convertir en une adresse virtuelle.
mov cx, [edx].Hint
movzx ecx,cx
invoke wsprintf,addr temp,addr NameTemplate,ecx,addr
[edx].Name1
jmp ShowTheText
Hint est un paramètre de taille WORD. Nous devons le convertir en une valeur de taille DWORD avant de l'envoyer à l'API wsprintf. Et nous affichons ensemble dans le contrôle d'édition 'Hint' et le nom de la fonction
ImportByOrdinal:
mov edx,dword ptr [esi]
and edx,0FFFFh
invoke wsprintf,addr temp,addr OrdinalTemplate,edx
Au cas où la fonction est uniquement exportée par ordre, nous mettons à zéro le WORD de poids fort et plaçons le n° d'ordre.
ShowTheText:
invoke AppendText,hDlg,addr temp
add esi,4
Après avoir inséré le nom/numéro de la fonction dans le contrôle d'édition, nous passons à l'IMAGE_THUNK_DATA suivante.
.endw
add edi,sizeof IMAGE_IMPORT_DESCRIPTOR
Quand tous les dwords IMAGE_THUNK_DATA du tableau ont été traités, nous sautons au IMAGE_IMPORT_DESCRIPTOR suivant pour traiter les fonctions d'importation des éventuelles autres DLLs.
Ce serait incomplet si je ne mentionnais rien sur les importations attachées (Bound Import). Pour expliquer ce que c'est, j'ai besoin de faire un petit aparté. Quand le PE loader charge le PE file (un programme) en mémoire, il examine sa table des importations et charge les DLLs qui lui serviront dans l'espace d'adresses qui lui est réservée. Ensuite il parcoure le tableau IMAGE_THUNK_DATA de long en large comme nous l'avons fait et remplace les IMAGE_THUNK_DATAs par les adresses réelles des fonctions d'importation, ça ne prend pas longtemps. Si d'une façon ou d'une autre le programmeur peut prévoir les adresses des fonctions correctement, alors le PE loader n'a pas besoin de fixer les IMAGE_THUNK_DATAs à chaque fois que le PE file est exécuté. L'importation attachée est le produit de cette idée.
Pour l'expliquée en termes simples, il existe un utilitaire nommé bind.exe qui est fournit avec les compilateurs de Microsoft tel que Visual Studio qui examine la table des importations du PE file et remplace les dwords IMAGE_THUNK_DATA par les adresses des fonctions d'importation. Lorsque le fichier est chargé, le PE loader doit vérifier si les adresses sont valides. Si les versions des DLL ne correspondent pas avec celles utilisées par le PE File ou bien si les DLLs ont besoin d'être transférées, alors le PE loader sait que les adresses de precomputed ne sont pas valides c'est pourquoi il doit parcourir le tableau indiqué par OriginalFirstThunk pour recalculer les nouvelles adresses des fonctions d'importation.
L'importation attachée n'a pas beaucoup de signification pour notre exemple parce que nous utilisons OriginalFirstThunk par défaut. Pour plus d'informations sur les importations attachées, je vous recommande de lire la doc LUEVELSMEYER's pe.txt.
[Iczelion's Win32 Assembly Homepage]