《Windows PE权威指南》学习之第5章 导出表(1)

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

第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.........

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值