分享
 
 
 

iczelion pe tutcn7

王朝other·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

PE教程7: Export Table(引出表)

上一课我们已经学习了动态联接中关于引入表那部分知识,现在继续另外一部分,那就是引出表。

理论:

当PE装载器执行一个程序,它将相关DLLs都装入该进程的地址空间。然后根据主程序的引入函数信息,查找相关DLLs中的真实函数地址来修正主程序。PE装载器搜寻的是DLLs中的引出函数。

DLL/EXE要引出一个函数给其他DLL/EXE使用,有两种实现方法: 通过函数名引出或者仅仅通过序数引出。比如某个DLL要引出名为"GetSysConfig"的函数,如果它以函数名引出,那么其他DLLs/EXEs若要调用这个函数,必须通过函数名,就是GetSysConfig。另外一个办法就是通过序数引出。什么是序数呢? 序数是唯一指定DLL中某个函数的16位数字,在所指向的DLL里是独一无二的。例如在上例中,DLL可以选择通过序数引出,假设是16,那么其他DLLs/EXEs若要调用这个函数必须以该值作为GetProcAddress调用参数。这就是所谓的仅仅靠序数引出。

我们不提倡仅仅通过序数引出函数这种方法,这会带来DLL维护上的问题。一旦DLL升级/修改,程序员无法改变函数的序数,否则调用该DLL的其他程序都将无法工作。

现在我们开始学习引出结构。象引出表一样,可以通过数据目录找到引出表的位置。这儿,引出表是数据目录的第一个成员,又可称为IMAGE_EXPORT_DIRECTORY。该结构中共有11 个成员,常用的列于下表。

Field Name

Meaning

nName

模块的真实名称。本域是必须的,因为文件名可能会改变。这种情况下,PE装载器将使用这个内部名字。

nBase

基数,加上序数就是函数地址数组的索引值了。

NumberOfFunctions

模块引出的函数/符号总数。

NumberOfNames

通过名字引出的函数/符号数目。该值不是模块引出的函数/符号总数,这是由上面的NumberOfFunctions给出。本域可以为0,表示模块可能仅仅通过序数引出。如果模块根本不引出任何函数/符号,那么数据目录中引出表的RVA为0。

AddressOfFunctions

模块中有一个指向所有函数/符号的RVAs数组,本域就是指向该RVAs数组的RVA。简言之,模块中所有函数的RVAs都保存在一个数组里,本域就指向这个数组的首地址。

AddressOfNames

类似上个域,模块中有一个指向所有函数名的RVAs数组,本域就是指向该RVAs数组的RVA。

AddressOfNameOrdinals

RVA,指向包含上述 AddressOfNames数组中相关函数之序数的16位数组。

上面也许无法让您完全理解引出表,下面的简述将助您一臂之力。

引出表的设计是为了方便PE装载器工作。首先,模块必须保存所有引出函数的地址以供PE装载器查询。模块将这些信息保存在AddressOfFunctions域指向的数组中,而数组元素数目存放在NumberOfFunctions域中。 因此,如果模块引出40个函数,则AddressOfFunctions指向的数组必定有40个元素,而NumberOfFunctions值为40。现在如果有一些函数是通过名字引出的,那么模块必定也在文件中保留了这些信息。这些 名字的RVAs存放在一数组中以供PE装载器查询。该数组由AddressOfNames指向,NumberOfNames包含名字数目。考虑一下PE装载器的工作机制,它知道函数名,并想以此获取这些函数的地址。至今为止,模块已有两个模块: 名字数组和地址数组,但两者之间还没有联系的纽带。因此我们还需要一些联系函数名及其地址的东东。PE参考指出使用到地址数组的索引作为联接,因此PE装载器在名字数组中找到匹配名字的同时,它也获取了 指向地址表中对应元素的索引。 而这些索引保存在由AddressOfNameOrdinals域指向的另一个数组(最后一个)中。由于该数组是起了联系名字和地址的作用,所以其元素数目必定和名字数组相同,比如,每个名字有且仅有一个相关地址,反过来则不一定: 每个地址可以有好几个名字来对应。因此我们给同一个地址取"别名"。为了起到连接作用,名字数组和索引数组必须并行地成对使用,譬如,索引数组的第一个元素必定含有第一个名字的索引,以此类推。

AddressOfNames

AddressOfNameOrdinals

|

|

RVA of Name 1

RVA of Name 2

RVA of Name 3

RVA of Name 4

...

RVA of Name N

<-->

<-->

<-->

<-->

...

<-->

Index of Name 1

Index of Name 2

Index of Name 3

Index of Name 4

...

Index of Name N

下面举一两个例子说明问题。如果我们有了引出函数名并想以此获取地址,可以这么做:

定位到PE header。

从数据目录读取引出表的虚拟地址。

定位引出表获取名字数目(NumberOfNames)。

并行遍历AddressOfNames和AddressOfNameOrdinals指向的数组匹配名字。如果在AddressOfNames 指向的数组中找到匹配名字,从AddressOfNameOrdinals 指向的数组中提取索引值。例如,若发现匹配名字的RVA存放在AddressOfNames 数组的第77个元素,那就提取AddressOfNameOrdinals数组的第77个元素作为索引值。如果遍历完NumberOfNames 个元素,说明当前模块没有所要的名字。

从AddressOfNameOrdinals 数组提取的数值作为AddressOfFunctions 数组的索引。也就是说,如果值是5,就必须读取AddressOfFunctions 数组的第5个元素,此值就是所要函数的RVA。

现在我们在把注意力转向IMAGE_EXPORT_DIRECTORY 结构的nBase成员。您已经知道AddressOfFunctions 数组包含了模块中所有引出符号的地址。当PE装载器索引该数组查询函数地址时,让我们设想这样一种情况,如果程序员在.def文件中设定起始序数号为200,这意味着AddressOfFunctions 数组至少有200个元素,甚至这前面200个元素并没使用,但它们必须存在,因为PE装载器这样才能索引到正确的地址。这种方法很不好,所以又设计了nBase 域解决这个问题。如果程序员指定起始序数号为200,nBase 值也就是200。当PE装载器读取nBase域时,它知道开始200个元素并不存在,这样减掉一个nBase值后就可以正确地索引AddressOfFunctions 数组了。有了nBase,就节约了200个空元素。

注意nBase并不影响AddressOfNameOrdinals数组的值。尽管取名"AddressOfNameOrdinals",该数组实际包含的是指向AddressOfFunctions 数组的索引,而不是什么序数啦。

讨论完nBase的作用,我们继续下一个例子。

假设我们只有函数的序数,那么怎样获取函数地址呢,可以这么做:

定位到PE header。

从数据目录读取引出表的虚拟地址。

定位引出表获取nBase值。

减掉nBase值得到指向AddressOfFunctions 数组的索引。

将该值与NumberOfFunctions作比较,大于等于后者则序数无效。

通过上面的索引就可以获取AddressOfFunctions 数组中的RVA了。

可以看出,从序数获取函数地址比函数名快捷容易。不需要遍历AddressOfNames 和 AddressOfNameOrdinals 这两个数组。然而,综合性能必须与模块维护的简易程度作一平衡。

总之,如果想通过名字获取函数地址,需要遍历AddressOfNames 和 AddressOfNameOrdinals 这两个数组。如果使用函数序数,减掉nBase值后就可直接索引AddressOfFunctions 数组。

如果一函数通过名字引出,那在GetProcAddress中可以使用名字或序数。但函数仅由序数引出情况又怎样呢? 现在就来看看。

"一个函数仅由序数引出"意味着函数在AddressOfNames 和 AddressOfNameOrdinals 数组中不存在相关项。记住两个域,NumberOfFunctions 和 NumberOfNames。这两个域可以清楚地显示有时某些函数没有名字的。函数数目至少等同于名字数目,没有名字的函数通过序数引出。比如,如果存在70个函数但AddressOfNames数组中只有40项,这就意味着模块中有30个函数是仅通过序数引出的。现在我们怎样找出那些仅通过序数引出的函数呢?这不容易,必须通过排除法,比如,AddressOfFunctions 的数组项在AddressOfNameOrdinals 数组中不存在相关指向,这就说明该函数RVA只通过序数引出。

示例:

本例类似上课的范例。然而,在显示IMAGE_EXPORT_DIRECTORY 结构一些成员信息的同时,也列出了引出函数的RVAs,序数和名字。注意本例没有列出仅由序数引出的函数。

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

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

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

分析:

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

程序检验PE有效性后,定位到数据目录获取引出表的虚拟地址。若该虚拟地址为0,则文件不含引出符号。

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

在编辑控件中显示IMAGE_EXPORT_DIRECTORY 结构的一些重要信息。

push [edi].NumberOfNames

pop NumberOfNames

push [edi].nBase

pop Base

由于我们要枚举所有函数名,就要知道引出表里的名字数目。nBase 在将AddressOfFunctions 数组索引转换成序数时派到用场。

invoke RVAToFileMap,pMapping,[edi].AddressOfNames

mov esi,eax

invoke RVAToFileMap,pMapping,[edi].AddressOfNameOrdinals

mov ebx,eax

invoke RVAToFileMap,pMapping,[edi].AddressOfFunctions

mov edi,eax

将三个数组的地址相应存放到esi,,ebx,edi中。准备开始访问。

.while NumberOfNames>0

直到所有名字都被处理完毕。

invoke RVAToFileMap,pMapping,dword ptr [esi]

由于esi指向包含名字字符串RVAs的数组,所以[esi]含有当前名字的RVA,需要将它转换成虚拟地址,后面wsprintf要用的。

mov dx,[ebx]

movzx edx,dx

mov ecx,edx

add ecx,Base

ebx指向序数数组,值是字类型的。因此我们先要将其转换成双字,此时edx和ecx含有指向AddressOfFunctions 数组的索引。我们用edx作为索引值,而将ecx加上nBase得到函数的序数值。=

shl edx,2

add edx,edi

索引乘以4 (AddressOfFunctions 数组中每个元素都是4字节大小) 然后加上数组首地址,这样edx指向的就是所要函数的RVA了。

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

invoke AppendText,hDlg,addr temp

在编辑控件中显示函数的RVA, 序数, 和名字。

dec NumberOfNames

add esi,4

add ebx,2

.endw

修正计数器,AddressOfNames 和 AddressOfNameOrdinals 两数组的当前指针,继续遍历直到所有名字全都处理完毕。

翻译:iamgufeng [Iczelion's Win32 Assembly Homepage][LuoYunBin's Win32 ASM Page]

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有