第4章介绍了PE文件的导入表,导入表描述的是其所在PE文件中的指令调用了其他动态链接库函数的情况。本章重点介绍与导入表刚好相反的导出表,它描述了导出表所在PE文件向其他程序提供的可供调用的函数的情况。
一般情况下,PE中的导出表存在于动态链接库文件里。导出表的主要作用是将PE中存在的函数引出到外部,以便其他人可以使用这些函数,实现代码的重用。本章将和大家一起制作一个含有导出表的DLL文件,并通过该DLL文件分析导出表结构,讨论导出表的利用技术。
5.1 导出表的作用
代码重用机制提供了重用代码的动态链接库,它会向调用者说明库里的哪些函数是可以被别人使用的,这些用来说明的信息便组成了导出表。
通常情况下,导出表存在于动态链接库文件里。但我们不能简单地认为EXE中没有导出表,例如WinWord.exe文件里就有;也不能简单地认为所有的DLL中都有导出表,例如一些专门存放资源文件的DLL里就没有导出表。不过可以这样理解:EXE文件中很少有导出表,大部分的DLL文件中都有导出表。所以一提到导出表,大家总是首先想到动态链接库。
导出表的存在可以让程序的开发者很容易清楚PE中到底有多少可以使用的函数,但如果没有函数使用说明,开发者也只能通过名称、反汇编代码或者运行结果对函数的调用方式、函数的功能等进行猜测。如同Win32 API函数一样,每位开发者可以通过MSDN网站获取对每个API函数的声明、功能介绍、参数介绍,甚至调用实例。
提示 如果你想开发一个类库供自己或他人使用,不仅要通过导出机制声明每个库文件的公用函数,还要详细编写每个函数的说明文档。
Windows装载器在进行PE装载时,会将导入表中登记的所有DLL一并装入,然后根据DLL的导出表中对导入函数的描述修正导入表的IAT值。通过导出表,DLL文件向调用它的程序或系统提供导出函数的名称、序号,以及入口地址等信息。
综上所述,可以得出结论,导出表的作用有两个:
一是可以通过导出表分析不认识的动态链接库文件所能提供的功能。
二是向调用者提供输出函数指令在模块中的起始地址。
下面分别进行讨论。
5.1.1 分析动态链接库功能
很多时候,我们都得不到某些动态链接库中输出函数的完整说明,这时候只能通过猜测,猜测的依据就是导出表中输出的函数的名字。以下是从一个安装程序中获取到的某动态链接库文件的导出表:
导出序号 虚拟地址 导出函数名称
----------------------------------------------------------------
00000001 00241976 (按照序号导出)
00000002 00466d1a PSA_CheckFeaturesGrantedByLicense
00000003 00466cad PSA_DisableFeaturesGrantedByLicense
00000004 00477c1c PSA_DummyFunction
00000005 00466c40 PSA_GetFeaturesGrantedByLicense
00000006 00466e5b PSA_GetLicenseCreationDateTime
00000007 00467233 PSA_GetLicenseExecutionTimeLimit
00000008 00466fa5 PSA_GetLicenseExpirationDateTime
00000009 00466bcd PSA_GetLicenseInformation
0000000a 00467012 PSA_GetLicenseLifeTimeLimit
0000000b 004670ec PSA_GetLicenseNumberOfRunsLimit
0000000c 0046738f PSA_GetLicenseStoragePath
0000000d 0046730d PSA_GetNumberOfConnections
0000000e 00467159 PSA_GetRemainingExecutionTime
0000000f 004671c6 PSA_GetRemainingExecutionTimeAtStart
根据输出函数的英文提示不难看出,该动态链接库提供了程序的License验证功能(通过系列函数PSA_GetLicenseXXXXXX可以看出),而且程序具有限制并发连接数功能(通过函数PSA_GetNumberOfConnections可以看出)。对于猜测出功能的函数,可以尝试通过程序调用来测试函数的参数以及调用方法,从而为自己所用。这样不仅避免了重复开发,还大大提高了开发效率。
5.1.2 获得导出函数地址
对一个动态链接库里导出的函数的调用,既可以通过函数名称来进行,也可以通过函数在导出表的索引来进行。Windows加载器将与进程相关的DLL加载到虚拟地址空间以后,会根据导入表中登记的与该动态链接库相关的由INT指向的名称或编号来遍历DLL所在虚拟地址空间,通过函数名或编号查找导出表结构,从而确定该导出函数在虚拟地址空间中的起始地址VA,并将该VA覆盖导入表的IAT相关项。
在覆盖IAT的过程中,导出表起到了参照和指引的作用。如果一个动态链接库没有定义导出表,其内部包含的所有函数都无法被其他程序透明地调用。这里所说的透明,是指公开调用。当然,只要你掌握了动态链接库的内部编码,即使没有导出表,你也可以随意地引用里面的函数,哪怕这些函数是私有的。5.5.2节将对私有函数的导出方法进行阐述。
5.2 构造含导出表的PE文件
本节将编写并创建一个动态链接库文件,该文件输出的函数可以被其他的基于Windows GUI的程序调用,用来增加程序窗口显示或退出时的动态效果。本节的例子是本书第一个以DLL为扩展名的PE文件,它提供了两种窗口显示或退出时可以使用的特效:
❑ 逐级缩放
❑ 渐入渐出
使用一个DLL文件需要经过以下四步:
步骤1 编写DLL文件的源代码。
步骤2 编写函数导出声明文件(扩展名为def)。
步骤3 使用特殊参数编译链接生成最终的DLL文件。
步骤4 编写包含文件(扩展名为inc)。
在其他源代码中,可以通过静态引用和动态加载两种技术使用新编写的DLL文件中声明的导出函数。下面分别介绍。
5.2.1 DLL源代码
编写DLL源代码和编写其他程序唯一不同之处是,在源代码内部必须定义DLL的入口函数。该入口函数必须符合一定的格式。详细代码见代码清单5-1。
代码清单5-1 显示窗口的特殊效果(chapter5\winResult.asm)
;winResult.asm DLL动态链接库功能:提供了几个窗口效果
;在XP系统环境下运行,使用ml命令进行编译和链接:
;ml /c /coff winResult.asm
;link /dll /subsystem:windows /Def:winResult.def winResult.obj
.386
.model flat,stdcall
option casemap:none ;区分大小写
;include文件定义
include C:/masm32/include/windows.inc
include C:/masm32/include/user32.inc
includelib C:/masm32/lib/user32.lib
include C:/masm32/include/kernel32.inc
includelib C:/masm32/lib/kernel32.lib
MAX_XYSTEPS equ 50
DELAY_VALUE equ 50 ;动画效果使用的步长
X_STEP_SIZE equ 10
Y_STEP_SIZE equ 9
X_START_SIZE equ 20
Y_START_SIZE equ 10
LMA_ALPHA equ 2
LMA_COLORKEY equ 1
WS_EX_LAYERED equ 80000h
;数据段
.data
dwCount dd ?
Value dd ?
Xsize dd ?
Ysize dd ?
sWth dd ?
sHth dd ?
Xplace dd ?
Yplace dd ?
counts dd ?
pSLWA dd ?
User32 db 'user32.dll',0
SLWA db 'SetLayeredWindowAttributes',0
;代码段
.code
;------------------
; DLL入口
;------------------
DllEntry proc _hInstance, _dwReason, _dwReserved
mov eax, TRUE
ret
DllEntry endp
;-------------------------------
; 私有函数
;-------------------------------
TopXY proc wDim:DWORD, sDim:DWORD
shr sDim, 1
shr wDim, 1
mov eax, wDim
sub sDim, eax
mov eax, sDim
ret
TopXY endp
;-----------------------------------------------------------
; 窗口抖动进入效果
;-----------------------------------------------------------
AnimateOpen proc hWin:DWORD
LOCAL Rct:RECT
invoke GetWindowRect, hWin, addr Rct
mov Xsize, X_START_SIZE
mov Ysize, Y_START_SIZE
invoke GetSystemMetrics, SM_CXSCREEN
mov sWth, eax
invoke TopXY, Xsize, eax
mov Xplace, eax
invoke GetSystemMetrics, SM_CYSCREEN
mov sHth, eax
invoke TopXY, Ysize, eax
mov Yplace, eax
mov counts, MAX_XYSTEPS
aniloop:
invoke MoveWindow, hWin, Xplace, Yplace, Xsize, Ysize, FALSE
invoke ShowWindow, hWin, SW_SHOWNA
invoke Sleep, DELAY_VALUE
invoke ShowWindow, hWin, SW_HIDE
add Xsize, X_STEP_SIZE
add Ysize, Y_STEP_SIZE
invoke TopXY, Xsize, sWth
mov Xplace, eax
invoke TopXY, Ysize, sHth
mov Yplace, eax
dec counts
jnz aniloop ;不等0跳转
mov eax, Rct.left
mov ecx, Rct.right
sub ecx, eax
mov Xsize, ecx
mov eax, Rct.top
mov ecx, Rct.bottom
sub ecx, eax
mov Ysize, ecx
invoke TopXY, Xsize, sWth
mov Xplace, eax
invoke TopXY, Ysize, sHth
mov Yplace, eax
invoke MoveWindow, hWin, Xplace, Yplace, Xsize, Ysize, TRUE
invoke ShowWindow, hWin, SW_SHOW
ret
AnimateOpen endp
;-------------------------
; 窗口抖动退出效果
;-------------------------
AnimateClose proc hWin:DWORD
LOCAL Rct:RECT
invoke ShowWindow, hWin, SW_HIDE
invoke GetWindowRect, hWin, addr Rct
mov eax, Rct.left
mov ecx, Rct.right
sub ecx, eax
mov Xsize, ecx
mov eax, Rct.top
mov ecx, Rct.bottom
sub ecx, eax
mov Ysize, ecx
invoke GetSystemMetrics, SM_CXSCREEN
mov sWth, eax
invoke TopXY, Xsize, eax
mov Xplace, eax
invoke GetSystemMetrics, SM_CYSCREEN
mov sHth, eax
invoke TopXY, Ysize, eax
mov Yplace, eax
mov counts, MAX_XYSTEPS
aniloop:
invoke MoveWindow, hWin, Xplace, Yplace, Xsize, Ysize, FALSE
invoke ShowWindow, hWin, SW_SHOWNA
invoke Sleep, DELAY_VALUE
invoke ShowWindow, hWin, SW_HIDE
sub Xsize, X_STEP_SIZE
sub Ysize, Y_STEP_SIZE
invoke TopXY, Xsize, sWth
mov Xplace, eax
invoke TopXY, Ysize, sHth
mov Yplace, eax
dec counts
jnz aniloop
ret
AnimateClose endp
;--------------------------------------------
; 窗口淡入效果,仅运行在2000/XP以上操作系统
;--------------------------------------------
FadeInOpen proc hWin:DWORD
invoke GetWindowLongA, hWin, GWL_EXSTYLE
or eax, WS_EX_LAYERED
invoke SetWindowLongA, hWin, GWL_EXSTYLE, eax
invoke GetModuleHandleA, addr User32
invoke GetProcAddress, eax, addr SLWA
mov pSLWA, eax
push LMA_ALPHA
push 0
push 0
push hWin
call pSLWA
mov Value, 90
invoke ShowWindow, hWin, SW_SHOWNA
doloop:
push LMA_COLORKEY + LMA_ALPHA
push Value
push Value
push hWin
call pSLWA
invoke Sleep, DELAY_VALUE
add Value, 15
cmp Value, 255
jne doloop ;不相等跳转
push LMA_ALPHA
push 255
push 0
push hWin
call pSLWA
ret
FadeInOpen endp
;--------------------------------------------
; 窗口淡出效果,仅运行在2000/XP以上操作系统
;--------------------------------------------
FadeOutClose proc hWin:DWORD
invoke GetWindowLongA, hWin, GWL_EXSTYLE
or eax, WS_EX_LAYERED
invoke SetWindowLongA, hWin, GWL_EXSTYLE, eax
invoke GetModuleHandleA, addr User32
invoke GetProcAddress, eax, addr SLWA
mov pSLWA, eax
push LMA_ALPHA
push 255
push 0
push hWin
call pSLWA
mov Value, 255
doloop:
push LMA_COLORKEY + LMA_ALPHA
push Value
push Value
push hWin
call pSLWA
invoke Sleep, DELAY_VALUE
sub Value, 15
cmp Value, 0
jne doloop
ret
FadeOutClose endp
end DllEntry
以上代码中一共定义了6个函数,其中有4个是可以被导出的公有函数,有一个私有函数,还有一个动态链接库必须具备的入口函数。4个公有函数分别代表了4种显示窗口的动态效果:
❑ AnimateOpen(窗口放大进入)
❑ AnimateClose(窗口缩小退出)
❑ FadeInOpen (窗口淡入)
❑ FadeOutClose(窗口淡出)
入口函数名为DllEntry,由于我们没有初始化代码,所以只是简单地返回了TRUE。入口函数负责处理动态链接库的生命周期中发生的各种消息,如库被装载、卸载、新线程创建和新线程结束等操作。入口函数的名字可以随意命名,但格式必须遵循一定规范(这些规范包括入口参数的个数和返回值的类型)。
5.2.2 编写def文件
为了能让系统识别哪些是私有函数,哪些是导出函数,还需要在源代码外另外附加一个文件,在文件中列出要导出的函数的名字。这个文件就是def文件。链接器会根据这个def文件的内容在导出表中加入由EXPORTS指定的函数名。以下是编写def文件的过程:在记事本中输入如下内容,并保存为文件“winResult.def”。
EXPORTS AnimateOpen
AnimateClose
FadeInOpen
FadeOutClose
5.2.3 编译和链接
编译的方法和前面其他章节的编译方法没有区别,链接时需要额外增加两个链接参数,命令如下:
D:\masm32\source\chapter6>ml -c -coff winResult.asm
D:\masm32\source\chapter6>link -DLL -subsystem:windows
-Def:winResult.def winResult.obj
新增加的两个链接参数分别是“-DLL”和“-Def”。前者表示生成的最终文件是一个动态链接库,扩展名为dll;后者表示在生成的链接库的导出表中,加入该参数指定的def文件中的函数。
链接后生成以下两个相关文件:
❑ winResult.dll是动态链接库文件。该文件可以共享给使用其他高级语言如VC++、Delphi、VB等的开发者使用。
❑ winResult.lib是汇编语言环境下的库文件。使用winResult.dll的汇编程序必须使用该库文件和下面要介绍的inc包含文件。
5.2.4 编写头文件
包含文件扩展名为“.inc”,该文件类似于C语言的“.h”头文件,所以又称为头文件。该文件中包含了动态链接库中导出函数的声明。将如下内容输入记事本程序,并保存为“winResult.inc”。
AnimateOpen proto :dword
AnimateClose proto :dword
FadeInOpen proto :dword
FadeOutClose proto :dword
有了动态链接库、相关的lib文件和头文件,就可以在任何一个程序的代码中使用这个DLL里导出的函数了。
5.2.5 使用导出函数
接下来使用刚生成的三个文件:winResult.dll、winResult.lib和winResult.inc来编写一个渐入式窗口显示程序,详细代码见清单5-2。
代码清单5-2 具备动态显示效果的窗口程序(chapter5\FirstWindow.asm)
;FirstWindow.asm 简单窗口程序
;具有窗口的大部分基本特性,其中显示和退出使用了渐入和渐出效果
;该程序主要演示自己制作的dll的函数调用
;在XP系统环境下运行,使用ml命令进行编译和链接:
;ml /c /coff FirstWindow.asm
;link /subsystem:windows FirstWindow.obj
.386
.model flat,stdcall
option casemap:none ;区分大小写
;include文件定义
include C:/masm32/include/windows.inc
include C:/masm32/include/gdi32.inc
includelib C:/masm32/lib/gdi32.lib
include C:/masm32/include/user32.inc
includelib C:/masm32/lib/user32.lib
include C:/masm32/include/kernel32.inc
includelib C:/masm32/lib/kernel32.lib
include winResult.inc ;与其他动态链接库的引用方式一样
includelib winResult.lib
;数据段
.data?
hInstance dd ?
hWinMain dd ?
;常量定义
.const
szClassName db 'MyClass', 0
szCaptionMain db '窗口特效演示', 0
szText db '你好,认识我吗?^_^', 0
;代码段
.code
;------------------
; 窗口消息处理子程序
;------------------
_ProcWinMain proc uses ebx edi esi, hWnd, uMsg, wParam, lParam
local @stPs:PAINTSTRUCT
local @stRect:RECT
local @hDc
mov eax, uMsg
.if eax==WM_PAINT
invoke BeginPaint, hWnd, addr @stPs
mov @hDc, eax
invoke GetClientRect, hWnd, addr @stRect
invoke DrawText, @hDc, addr szText, -1, \
addr @stRect, \
DT_SINGLELINE or DT_CENTER or DT_VCENTER
invoke EndPaint, hWnd, addr @stPs
.elseif eax== WM_CLOSE ;关闭窗口
invoke FadeOutClose, hWinMain ;调用自已写的动态库
invoke DestroyWindow, hWinMain
invoke PostQuitMessage, NULL
.else
invoke DefWindowProc, hWnd, uMsg, wParam, lParam
ret
.endif
xor eax, eax
ret
_ProcWinMain endp
;----------------------
; 主窗口程序
;----------------------
_WinMain proc
local @stWndClass:WNDCLASSEX
local @stMsg:MSG
invoke GetModuleHandle, NULL
mov hInstance, eax
invoke RtlZeroMemory, addr @stWndClass, sizeof @stWndClass
;注册窗口类
invoke LoadCursor, 0, IDC_ARROW
mov @stWndClass.hCursor, eax
push hInstance
pop @stWndClass.hInstance
mov @stWndClass.cbSize, sizeof WNDCLASSEX
mov @stWndClass.style, CS_HREDRAW or CS_VREDRAW
mov @stWndClass.lpfnWndProc, offset _ProcWinMain
mov @stWndClass.hbrBackground, COLOR_WINDOW+1
mov @stWndClass.lpszClassName, offset szClassName
invoke RegisterClassEx, addr @stWndClass
;建立并显示窗口
invoke CreateWindowEx, WS_EX_CLIENTEDGE, \
offset szClassName, offset szCaptionMain, \
WS_OVERLAPPEDWINDOW, \
100, 100, 600, 400, \
NULL, NULL, hInstance, NULL
mov hWinMain, eax
invoke FadeInOpen, hWinMain ;调用自已写的动态库
;invoke ShowWindow,hWinMain,SW_SHOWNORMAL
invoke UpdateWindow, hWinMain ;更新客户区,即发送WM_PAINT消息
;消息循环
.while TRUE
invoke GetMessage, addr @stMsg, NULL, 0, 0
.break .if eax==0
invoke TranslateMessage, addr @stMsg
invoke DispatchMessage, addr @stMsg
.endw
ret
_WinMain endp
start:
call _WinMain
invoke ExitProcess, NULL
end start
与引入其他动态链接库的方法一样,在代码行19引入winResult.lib,在行20引入头文件winResult.inc。创建窗口以后,显示时不再使用如下所示的正常的显示代码:
invoke ShowWindow,hWinMain,SW_SHOWNORMAL
而是使用了具有动画特效的代码(如下所示),该代码由动态链接库winResult.dll输出。
invoke FadeInOpen,hWinMain
可以看出,调用公用函数的方法和调用其他动态链接库的方法(使用常规方法编译链接程序,生成FirstWindow.exe,然后运行它以便查看窗口显示的动画效果)是完全一样的。
接下来简单分析到目前为止接触的两种不同类型的PE文件:
❑ FirstWindow.exe(EXE可执行文件)
❑ winResult.dll(DLL动态链接库)
使用PEInfo小工具查看两者结构,通过比较可以看出,两种不同类型的PE文件在内容上存在很大差异,如头文件中的装载基地址、入口地址、文件属性均不相同。EXE文件的PE格式中不存在重定位表项,也没有导出表;而DLL文件的PE格式中这两项都存在。
5.3 导出表数据结构
本节将描述PE文件中的导出表及其数据组织方式。内容包括:
❑ 在PE文件里定位导出表
❑ 导出表的数据组织结构
❑ 导出表实例
下面分别来介绍。
5.3.1 导出表定位
导出表数据为数据目录中注册的数据类型之一,其描述信息处于数据目录的第1个目录项中。使用PEDump小工具获取chapter5\winResult.dll的数据目录内容如下:

00000130 40 21 00 00 8F 00 00 00 ........@!.....
00000140 2C 20 00 00 3C 00 00 00 00 00 00 00 00 00 00 00 , ..<...........
00000150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000160 00 40 00 00 B4 00 00 00 00 00 00 00 00 00 00 00 .@.............
00000170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000190 00 00 00 00 00 00 00 00 00 20 00 00 2C 00 00 00 ......... ..,...
000001A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000001B0 00 00 00 00 00 00 00 00 ........
加黑部分为导出表数据目录项信息。通过以上字节码可以获得与导出表有关的两条信息:
❑ 导出表所在地址RVA=0x000002140
❑ 导出表数据大小=0000008fh
以下是使用小工具PEInfo获取的该文件所有的节信息:
节名称 未对齐前真实长度 内存中的偏移(对齐后的) 文件中对齐后的长度 文件的偏移 节的属性
------------------------------------------------------------------------------
.text 000003de 00001000 00000400 00000400 60000020
.rdata 000001cf 00002000 00000200 00000800 40000040
.data 0000004e 00003000 00000200 00000a00 c0000040
.reloc 000000ca 00004000 00000200 00000c00 42000040
根据RVA与FOA的换算关系,可以得到:offset=2140h-2000h=140h , FOA = 0800h+offset = 940h
导出表数据所在文件的偏移地址为:0x00000940。
5.3.2 导出目录IMAGE_EXPORT_DIRECTORY
导出数据的第一个结构是IMAGE_EXPORT_DIRECTORY。该结构详细定义如下:(40个字节)
IMAGE_EXPORT_DIRECTORY STRUCT
Characteristics DWORD ? ; 0000h - 标志,未用
TimeDateStamp DWORD ? ; 0004h - 时间戳
MajorVersion WORD ? ; 0008h - 未用
MinorVersion WORD ? ; 000ah - 未用
nName DWORD ? ; 000ch - 指向该导出表的文件名字符串
nBase DWORD ? ; 0010h - 导出函数的起始序号
NumberOfFunctions DWORD ? ; 0014h - 所有的导出函数个数
NumberOfNames DWORD ? ; 0018h - 以函数名导出的函数个数
AddressOfFunctions DWORD ? ; 001ch - 导出函数地址表RVA
AddressOfNames DWORD ? ; 0020h - 函数名称地址表RVA
AddressOfNameOrdinals DWORD ? ; 0024h - 函数序号地址表
IMAGE_EXPORT_DIRECTORY ENDS
导入表的IMAGE_IMPORT_DESCRIPTOR个数与调用的动态链接库个数相等,而导出表的IMAGE_EXPORT_DIRECTORY只有一个。各字段解释如下:
64. IMAGE_EXPORT_DIRECTORY.nName
+000ch,双字。该字段指示的地址指向了一个以“\0”结尾的字符串,字符串记录了导出表所在的文件的最初文件名。
65. IMAGE_EXPORT_DIRECTORY.NumberOfFunctions
+0014h,双字。该字段定义了文件中导出函数的总个数。
66. IMAGE_EXPORT_DIRECTORY.NumberOfNames
+0018h,双字。在导出表中,有些函数是定义名字的,有些是没有定义名字的。该字段记录了所有定义名字函数的个数。如果这个值是0,则表示所有的函数都没有定义名字。NumberOfNames和NumberOfFunctions的关系是前者小于等于后者。
67. IMAGE_EXPORT_DIRECTORY.AddressOfFunctions
+001ch,双字。该指针指向了全部导出函数的入口地址的起始。从入口地址开始为双字数组,数组的个数由字段IMAGE_EXPORT_DIRECTORY.NumberOfFunctions决定。导出函数的每一个地址按函数的编号顺序依次往后排开。在内存中,我们可以通过函数编号来定位某个函数的地址。大致代码如下:
mov eax,[esi].AddressOfFunctions ;esi指向导出表结构
IMAGE_EXPORT_DIRECTORY的起始地址
mov ebx,num ;假设ebx为函数编号
sub ebx,[esi].nBase
add eax,[num*4]
mov eax,[eax] ;到这里已获取函数的虚拟地址RVA,加上模块实际装入地址
;就是在虚拟地址空间里真实的地址VA
68. IMAGE_EXPORT_DIRECTORY.nBase
+0010h,双字。导出函数编号的起始值。DLL中的第一个导出函数并不是从0开始的,某导出函数的编号等于从AddressOfFunctions开始的顺序号加上这个值,大致示意图如图5-1所示。

图5-1 nBase字段决定导出函数编号
如图所示,Fun1的函数编号为nBase+0=200h,Fun2的函数编号为nBase+1=201h,以此类推。
69. IMAGE_EXPORT_DIRECTORY.AddressOfNames
+0020h,双字。该值为一个指针。该指针指向的位置是一连串的双字值,这些双字值均指向了对应的定义了函数名的函数的字符串地址。这一连串的双字个数为NumberOf Names。
70. IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals
+0024h,双字。该值也是一个指针,与AddressOfNames是一一对应关系(注意,是一一对应),所不同的是,AddressOfNames指向的是字符串的指针数组,而AddressOfNameOrdinals则指向了该函数在AddressOfFunctions中的索引值。
注意 索引值是一个字,而非双字。该值与函数编号是两个不同的概念,两者之间的关系为:
索引值=编号-nBase
以上所述,字段之间的关系可以用图5-2表示。

图5-2 PE导出表结构
如图所示,AddressOfNames中的函数是从Function2开始的,也就是说,这里假设Function1只提供编号访问;其nBase为200h,所以对应的AddressOfNameOrdinals是0001h,但最终函数Function1的编号为:索引值+nBase的值,即0201h。
提示 在图4-5中最后指向的结构 “Hint/Name”中的Hint值是AddressOfFunctions的索引值,并非函数编号。
5.3.3 导出表实例分析
下面以动态链接库winResult.dll为例,分析该PE文件的导出表结构及其数据组织。
使用小工具PEDump获取winResult.dll的导出表字节码内容如下(从文件地址偏移0x00000940开始):

00000940 00 00 00 00 EE 0E 51 4D 00 00 00 00 90 21 00 00 .....QM....!..
00000950 01 00 00 00 04 00 00 00 04 00 00 00 68 21 00 00 ............h!..
00000960 78 21 00 00 88 21 00 00 83 11 00 00 22 10 00 00 x!..!....."...
00000970 82 12 00 00 23 13 00 00 9E 21 00 00 AB 21 00 00 ...#...!..!..
00000980 B7 21 00 00 C2 21 00 00 00 00 01 00 02 00 03 00 !..!..........
00000990 77 69 6E 72 65 73 75 6C 74 2E 64 6C 6C 00 41 6E winresult.dll.An
000009A0 69 6D 61 74 65 43 6C 6F 73 65 00 41 6E 69 6D 61 imateClose.Anima
000009B0 74 65 4F 70 65 6E 00 46 61 64 65 49 6E 4F 70 65 teOpen.FadeInOpe
000009C0 6E 00 46 61 64 65 4F 75 74 43 6C 6F 73 65 00 00 n.FadeOutClose..
其中,主要字段所对应的字节码解释如下:
>>90 21 00 00
对应IMAGE_EXPORT_DIRECTORY.nName字段,指向文件偏移0x00000990。该处的值为字符串“winResult.dll”,是动态链接库的最初的名字。
>>01 00 00 00
对应IMAGE_EXPORT_DIRECTORY.nBase字段,表示起始编号为1。
>>04 00 00 00
对应IMAGE_EXPORT_DIRECTORY.NumberOfFunctions字段,表示共有4个导出函数。
>>04 00 00 00
对应IMAGE_EXPORT_DIRECTORY.NumberOfNames字段,表示4个导出函数均为按照名称导出。
>>68 21 00 00
对应IMAGE_EXPORT_DIRECTORY.AddressOfFunctions字段。从该位置取出连续4个地址(个数由IMAGE_EXPORT_DIRECTORY.NumberOfFunctions字段决定),这些地址分别对应4个函数的RVA。AddressOfFunctions的值见图5-3。

图5-3 AddressOfFunctions数组
>>78 21 00 00
对应IMAGE_EXPORT_DIRECTORY.AddressOfNames字段。从该位置取出的连续4个地址依次为:

>>88 21 00 00
对应IMAGE_EXPORT_DIRECTORY. AddressOfNameOrdinals字段。从该位置取出的连续4个单字索引依次为:

这些索引的值存在于字段IMAGE_EXPORT_DIRECTORY.AddressOfFunctions所指向的函数地址列表中。最终4个函数的编号将分别是此处的索引值加上nBase的值,即0001、0002、0003和0004。函数名对应的索引值可以在调用了该动态链接库的程序FirstWindow. exe的导入表数据中查找到(以下加黑部分):
00000850 72 6E 65 6C 33 32 2E 64 6C 6C 00 00 02 00 46 61 rnel32.dll....Fa
00000860 64 65 49 6E 4F 70 65 6E 00 00 03 00 46 61 64 65 deInOpen....Fade
00000870 4F 75 74 43 6C 6F 73 65 00 00 77 69 6E 52 65 73 OutClose..winRes
00000880 75 6C 74 2E 64 6C 6C 00 00 00 00 00 00 00 00 00 ult.dll.........

本文详细介绍了Windows操作系统提供的六个API函数,用于读写配置设置文件。包括如何使用这些函数进行配置文件的读取与写入操作,以及如何动态搜索配置文件的位置,确保程序在路径变化时仍能正常工作。

1万+

被折叠的 条评论
为什么被折叠?



