理论:
首先,您得了解什么是引入函数。一个引入函数是被某模块调用的但又不在调用者模块中的函数,因而命名为"import(引入)"。引入函数实际位于一个或者更多的DLL里。调用者模块里只保留一些函数信息,包括函数名及其驻留的DLL名。现在,我们怎样才能找到PE文件中保存的信息呢?转到data
directory寻求答案吧。再回顾一把,下面就是PE
header:
IMAGE_NT_HEADERS STRUCT Signature dd
? FileHeader IMAGE_FILE_HEADER <> OptionalHeader
IMAGE_OPTIONAL_HEADER <>IMAGE_NT_HEADERS ENDS
optional header最后一个成员就是data
directory(数据目录):
IMAGE_OPTIONAL_HEADER32 STRUCT .... LoaderFlags dd ? NumberOfRvaAndSizes dd ? DataDirectory IMAGE_DATA_DIRECTORY 16
dup(<>)IMAGE_OPTIONAL_HEADER32 ENDS
data directory是一个IMAGE_DATA_DIRECTORY结构数组,共有16个成员。如果您还记得节表可以看作是PE文件各节的根目录的话,也可以认为data directory是存储在这些节里的逻辑元素的根目录。明确点,data
directory包含了PE文件中各重要数据结构的位置和尺寸信息。每个成员包含了一个重要数据结构的信息。
Member
Info
inside
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
上面那些金色显示的是我熟悉的。了解data
directory包含域后,我们可以仔细研究它们了。data directory的每个成员都是IMAGE_DATA_DIRECTORY结构类型的,其定义如下所示:
IMAGE_DATA_DIRECTORY STRUCT VirtualAddress dd ? isize dd ?IMAGE_DATA_DIRECTORY ENDS
VirtualAddress实际上是数据结构的相对虚拟地址(RVA)。比如,如果该结构是关于import symbols的,该域就包含指向IMAGE_IMPORT_DESCRIPTOR数组的RVA。isize含有VirtualAddress所指向数据结构的字节数。
下面就是如何找寻PE文件中重要数据结构的一般方法:
从DOS header定位到PE
header
从optional header读取data directory的地址。
IMAGE_DATA_DIRECTORY结构尺寸乘上找寻结构的索引号:比如您要找寻import
symbols的位置信息,必须用IMAGE_DATA_DIRECTORY结构尺寸(8 bytes)乘上1(import symbols在data directory中的索引号)。
将上面的结果加上data
directory地址,我们就得到包含所查询数据结构信息的IMAGE_DATA_DIRECTORY结构项。
现在我们开始真正讨论引入表了。data
directory数组第二项的VirtualAddress包含引入表地址。引入表实际上是一个IMAGE_IMPORT_DESCRIPTOR结构数组。每个结构包含PE文件引入函数的一个相关DLL的信息。比如,如果该PE文件从10个不同的DLL中引入函数,那么这个数组就有10个成员。该数组以一个全0的成员结尾。下面详细研究结构组成:
IMAGE_IMPORT_DESCRIPTOR STRUCT union
Characteristics dd ? OriginalFirstThunk dd ? ends
TimeDateStamp dd ? ForwarderChain dd ? Name1 dd ? FirstThunk
dd ?IMAGE_IMPORT_DESCRIPTOR ENDS
结构第一项是一个union子结构。事实上,这个union子结构只是给OriginalFirstThunk增添了个别名,您也可以称其为"Characteristics"。 该成员项含有指向一个IMAGE_THUNK_DATA结构数组的RVA。什么是IMAGE_THUNK_DATA?这是一个dword类型的集合。通常我们将其解释为指向一个IMAGE_IMPORT_BY_NAME结构的指针。注意IMAGE_THUNK_DATA包含了指向一个IMAGE_IMPORT_BY_NAME结构的指针:而不是结构本身。请看这里:现有几个IMAGE_IMPORT_BY_NAME结构,我们收集起这些结构的RVA (IMAGE_THUNK_DATAs)组成一个数组,并以0结尾,然后再将数组的RVA放入OriginalFirstThunk。此IMAGE_IMPORT_BY_NAME结构存有一个引入函数的相关信息。再来研究IMAGE_IMPORT_BY_NAME结构到底是什么样子的呢:
IMAGE_IMPORT_BY_NAME STRUCT Hint dw ? Name1
db ?IMAGE_IMPORT_BY_NAME ENDS
Hint指示本函数在其所驻留DLL的引出表中的索引号。该域被PE装载器用来在DLL的引出表里快速查询函数。该值不是必须的,一些连接器将此值设为0。Name1含有引入函数的函数名。函数名是一个ASCIIZ字符串。注意这里虽然将Name1的大小定义成字节,其实它是可变尺寸域,只不过我们没有更好方法来表示结构中的可变尺寸域。The structure is provided so that you can refer to the data
structure with descriptive names.
TimeDateStamp和ForwarderChain可是高级东东:让我们精通其他成员后再来讨论它们吧。
Name1含有指向DLL名字的RVA,即指向DLL名字的指针,也是一个ASCIIZ字符串。
FirstThunk与OriginalFirstThunk非常相似,它也包含指向一个IMAGE_THUNK_DATA结构数组的RVA(当然这是另外一个IMAGE_THUNK_DATA结构数组)。好了,如果您还在犯糊涂,就朝这边看过来:现在有几个IMAGE_IMPORT_BY_NAME结构,同时您又创建了两个结构数组,并同样寸入指向那些IMAGE_IMPORT_BY_NAME结构的RVAs,这样两个数组就包含相同数值了(可谓相当精确的复制啊)。最后您决定将第一个数组的RVA赋给OriginalFirstThunk,第二个数组的RVA赋给FirstThunk,这样一切都很清楚了。
OriginalFirstThunk
IMAGE_IMPORT_BY_NAME
FirstThunk
|
|
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
...
IMAGE_THUNK_DATA
--->
--->
--->
--->
--->
--->
Function
1
Function
2
Function
3
Function 4
...
Function
n
<---
<---
<---
<---
<---
<---
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
...
IMAGE_THUNK_DATA
现在您应该明白我的意思。不要被IMAGE_THUNK_DATA这个名字弄糊涂:它仅是指向IMAGE_IMPORT_BY_NAME结构的RVA。 如果将IMAGE_THUNK_DATA字眼想象成RVA,就更容易明白了。OriginalFirstThunk和FirstThunk所指向的这两个数组大小取决于PE文件从DLL中引入函数的数目。比如,如果PE文件从kernel32.dll中引入10个函数,那么IMAGE_IMPORT_DESCRIPTOR结构的Name1域包含指向字符串"kernel32.dll"的RVA,同时每个IMAGE_THUNK_DATA数组有10个元素。
下一个问题是:为什么我们需要两个完全相同的数组?为了回答该问题,我们需要了解当PE文件被装载到内存时,PE装载器将查找IMAGE_THUNK_DATA和IMAGE_IMPORT_BY_NAME这些结构数组,以此决定引入函数的地址。然后用引入函数真实地址来替代由FirstThunk指向的IMAGE_THUNK_DATA数组里的元素值。因此当PE文件准备执行时,上图已转换成:
OriginalFirstThunk
IMAGE_IMPORT_BY_NAME
FirstThunk
|
|
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
...
IMAGE_THUNK_DATA
--->
--->
--->
--->
--->
--->
Function
1
Function
2
Function
3
Function 4
...
Function
n
Address of
Function 1
Address of
Function 2
Address of
Function 3
Address of
Function 4
...
Address of
Function n
由OriginalFirstThunk指向的RVA数组始终不会改变,所以若还反过头来查找引入函数名,PE装载器还能找寻到。当然再简单的事物都有其复杂的一面。有些情况下一些函数仅由序数引出,也就是说您不能用函数名来调用它们:您只能用它们的位置来调用。此时,调用者模块中就不存在该函数的IMAGE_IMPORT_BY_NAME结构。不同的,对应该函数的IMAGE_THUNK_DATA值的低位字指示函数序数,而最高二进位(MSB)设为1。例如,如果一个函数只由序数引出且其序数是1234h,那么对应该函数的IMAGE_THUNK_DATA值是80001234h。Microsoft提供了一个方便的常量来测试dword值的MSB位,就是IMAGE_ORDINAL_FLAG32,其值为80000000h。假设我们要列出某个PE文件的所有引入函数,可以照着下面步骤走:
校验文件是否是有效的PE。
从DOS header定位到PE
header。
获取位于OptionalHeader数据目录地址。
转至数据目录的第二个成员提取其VirtualAddress值。
利用上值定位第一个IMAGE_IMPORT_DESCRIPTOR结构。
检查OriginalFirstThunk值。若不为0,顺着OriginalFirstThunk里的RVA值转入那个RVA数组。若OriginalFirstThunk为0,就改用FirstThunk值。有些连接器生成PE文件时会置OriginalFirstThunk值为0,这应该算是个bug。不过为了安全起见,我们还是检查OriginalFirstThunk值先。
对于每个数组元素,我们比对元素值是否等于IMAGE_ORDINAL_FLAG32。如果该元素值的最高二进位为1, 那么函数是由序数引入的,可以从该值的低字节提取序数。
如果元素值的最高二进位为0,就可将该值作为RVA转入IMAGE_IMPORT_BY_NAME数组,跳过Hint就是函数名字了。
再跳至下一个数组元素提取函数名一直到数组底部(它以null结尾)。现在我们已遍历完一个DLL的引入函数,接下去处理下一个DLL。
即跳转到下一个IMAGE_IMPORT_DESCRIPTOR并处理之,如此这般循环直到数组见底。(IMAGE_IMPORT_DESCRIPTOR数组以一个全0域元素结尾)。
示例:
本例程打开一PE文件,将所有引入函数名读入一编辑控件,同时显示IMAGE_IMPORT_DESCRIPTOR结构各域值。
.386.model flat,stdcalloption casemap:noneinclude masm32includewindows.incinclude masm32includekernel32.incinclude masm32includecomdlg32.incinclude masm32includeuser32.incincludelib masm32libuser32.libincludelib masm32libkernel32.libincludelib masm32libcomdlg32.libIDD_MAINDLG equ 101IDC_EDIT
equ 1000IDM_OPEN equ 40001IDM_EXIT equ 40003DlgProc proto
:DWORD,:DWORD,:DWORD,:DWORDShowImportFunctions proto :DWORDShowTheFunctions proto :DWORD,:DWORDAppendText proto :DWORD,:DWORDSEH structPrevLink dd ? ; the address of the previous seh structureCurrentHandler dd ? ; the address of the new exception handlerSafeOffset dd ? ; The offset where it's safe to continue executionPrevEsp dd ? ; the old value in espPrevEbp dd ? ; The old value in ebpSEH ends.dataAppName db "PE tutorial no.6",0ofn
OPENFILENAME <>FilterString db "Executable Files (*.exe,
*.dll)",0,"*.exe;*.dll",0 db "All Files",0,"*.*",0,0FileOpenError db "Cannot open the file for reading",0FileOpenMappingError db "Cannot open the file for memory mapping",0FileMappingError db "Cannot map the file into memory",0NotValidPE db
"This file is not a valid PE",0CRLF db 0Dh,0Ah,0ImportDescriptor db
0Dh,0Ah,"================[ IMAGE_IMPORT_DESCRIPTOR ]=============",0IDTemplate db "OriginalFirstThunk = %lX",0Dh,0Ah db
"TimeDateStamp = %lX",0Dh,0Ah db "ForwarderChain = %lX",0Dh,0Ah db "Name = %s",0Dh,0Ah db "FirstThunk = %lX",0NameHeader db 0Dh,0Ah,"Hint Function",0Dh,0Ah db
"-----------------------------------------",0NameTemplate db "%u %s",0OrdinalTemplate db "%u (ord.)",0.data?buffer db 512 dup(?)hFile dd ?hMapping dd ?pMapping dd ?ValidPE dd ?.codestart:invoke GetModuleHandle,NULLinvoke
DialogBoxParam, eax, IDD_MAINDLG,NULL,addr DlgProc, 0invoke ExitProcess, 0DlgProc 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.endifmov eax,TRUEretDlgProc endpSEHHandler proc uses edx 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 retSEHHandler endpShowImportFunctions 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
.endifFinalExit: 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 retShowImportFunctions endpAppendText 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 retAppendText endpRVAToOffset 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
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 retRVAToOffset endpShowTheFunctions 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 ShowTheTextImportByOrdinal: mov edx,dword ptr
[esi] and edx,0FFFFh invoke wsprintf,addr temp,addr
OrdinalTemplate,edxShowTheText: invoke AppendText,hDlg,addr temp add esi,4 .endw add edi,sizeof
IMAGE_IMPORT_DESCRIPTOR .endw retShowTheFunctions endpend
start
分析:
本例中,用户点击打开菜单显示文件打开对话框,检验文件的PE有效性后调用ShowTheFunctions。
ShowTheFunctions proc uses esi ecx ebx hDlg:DWORD,
pNTHdr:DWORD LOCAL temp[512]:BYTE
保留512字节堆栈空间用于字符串操作。
invoke SetDlgItemText,hDlg,IDC_EDIT,0
清除编辑控件内容。
invoke AppendText,hDlg,addr buffer
将PE文件名插入编辑控件。AppendText通过传递一个EM_REPLACESEL消息以通知向编辑控件添加文本。然后它又向编辑控件发送一个设置了wParam=-1和lParam=0的EM_SETSEL消息,使光标定位到文本末。
mov edi,pNTHdr assume edi:ptr IMAGE_NT_HEADERS mov edi, [edi].OptionalHeader.DataDirectory[sizeof
IMAGE_DATA_DIRECTORY].VirtualAddress
获取import
symbols的RVA。edi起初指向PE header,以此我们可以定位到数据目录数组的第二个数组元素来得到虚拟地址值。
invoke RVAToOffset,pMapping,edi mov edi,eax add edi,pMapping
这儿对PE编程初学者来说可能有点困难。在PE文件中大多数地址多是RVAs 而RVAs只有当PE文件被PE装载器装入内存后才有意义。本例中,我们直接将文件映射到内存而不是通过PE装载器载入,因此我们不能直接使用那些RVAs。必须先将那些RVAs转换成文件偏移量,RVAToOffset函数就起到这个作用。
这里不准备详细分析。指出的是,它还将给定的RVA和PE文件所有节的始末RVA作比较(检验RVA的有效性),然后通过IMAGE_SECTION_HEADER结构中的PointerToRawData域(当然是所在节的那个PointerToRawData域啦)将RVA转换成文件偏移量。函数使用需要传递两个参数:
内存映射文件指针和所要转换的RVA。eax里返回文件偏移量。上面代码中,我们必须将文件偏移量加上内存映射文件指针以转换成虚拟地址。是不是有点复杂?
:)
assume edi:ptr IMAGE_IMPORT_DESCRIPTOR .while
!([edi].OriginalFirstThunk==0 && [edi].TimeDateStamp==0 &&
[edi].ForwarderChain==0 && [edi].Name1==0 &&
[edi].FirstThunk==0)
edi现在指向第一个IMAGE_IMPORT_DESCRIPTOR结构。接下来我们遍历整个结构数组直到遇上一个全0结构,这就是数组末尾了。
invoke AppendText,hDlg,addr ImportDescriptor
invoke RVAToOffset,pMapping, [edi].Name1 mov edx,eax add
edx,pMapping
我们要显示当前IMAGE_IMPORT_DESCRIPTOR结构的值。Name1不同于其他结构成员,它含有指向相关dll名的RVA。因此必须先将其转换成虚拟地址。
invoke wsprintf, addr temp, addr IDTemplate,
[edi].OriginalFirstThunk,[edi].TimeDateStamp,[edi].ForwarderChain,edx,[edi].FirstThunk
invoke AppendText,hDlg,addr temp
显示当前IMAGE_IMPORT_DESCRIPTOR结构的值。
.if [edi].OriginalFirstThunk==0 mov
esi,[edi].FirstThunk .else mov esi,[edi].OriginalFirstThunk .endif
接下来准备遍历IMAGE_THUNK_DATA数组。通常我们会选择OriginalFirstThunk指向的那个数组,不过,如果某些连接器错误地将OriginalFirstThunk置0,这可以通过检查OriginalFirstThunk值是否为0判断。这样的话,只要选择FirstThunk指向的数组了。
invoke RVAToOffset,pMapping,esi add
eax,pMapping mov esi,eax
同样的,OriginalFirstThunk/FirstThunk值是一个RVA。必须将其转换为虚拟地址。
invoke AppendText,hDlg,addr NameHeader
.while dword ptr [esi]!=0
现在我们准备遍历IMAGE_THUNK_DATAs数组以查找该DLL引入的函数名,直到遇上全0项。
test dword ptr [esi],IMAGE_ORDINAL_FLAG32 jnz ImportByOrdinal
第一件事是校验IMAGE_THUNK_DATA是否含有IMAGE_ORDINAL_FLAG32标记。检查IMAGE_THUNK_DATA的MSB是否为1,如果是1,则函数是通过序数引出的,所以不需要更进一步处理了。直接从IMAGE_THUNK_DATA提取低字节获得序数,然后是下一个IMAGE_THUNK_DATA双字。
invoke RVAToOffset,pMapping,dword ptr [esi] mov edx,eax add edx,pMapping assume edx:ptr
IMAGE_IMPORT_BY_NAME
如果IMAGE_THUNK_DATA的MSB是0,那么它包含了IMAGE_IMPORT_BY_NAME结构的RVA。需要先转换为虚拟地址。
mov cx, [edx].Hint movzx ecx,cx invoke wsprintf,addr temp,addr NameTemplate,ecx,addr [edx].Name1 jmp ShowTheText
Hint是字类型,所以先转换为双字后再传递给wsprintf,然后我们将hint和函数名都显示到编辑控件中。
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
在编辑控件中插入相应的函数名/序数后,跳转到下个IMAGE_THUNK_DATA。
.endw add edi,sizeof IMAGE_IMPORT_DESCRIPTOR
处理完当前IMAGE_THUNK_DATA数组里的所有双字,跳转到下个IMAGE_IMPORT_DESCRIPTOR开始处理其他DLLs的引入函数了。
附录:
让我们再来讨论一下bound
import。当PE装载器装入PE文件时,检查引入表并将相关DLLs映射到进程地址空间。然后象我们这样遍历IMAGE_THUNK_DATA数组并用引入函数的真实地址替换IMAGE_THUNK_DATAs值。这一步需要很多时间。如果程序员能事先正确预测函数地址,PE装载器就不用每次装入PE文件时都去修正IMAGE_THUNK_DATAs值了。Bound import就是这种思想的产物。为了方便实现,Microsoft出品的类似Visual Studio的编译器多提供了bind.exe这样的工具,由它检查PE文件的引入表并用引入函数的真实地址替换IMAGE_THUNK_DATA值。当文件装入时,PE装载器必定检查地址的有效性,如果DLL版本不同于PE文件存放的相关信息,或则DLLs需要重定位,那么装载器认为原先计算的地址是无效的,它必定遍历OriginalFirstThunk指向的数组以获取引入函数新地址。Bound
import在本课中并非很重要,我们确省就是用到了OriginalFirstThunk。要了解更多信息可参见LUEVELSMEYER's pe.txt