分享
 
 
 

iczelion pe tut7

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

Tutorial 7: Export Table

We have learned about one part of the dynamic linking, namely the import table, in the previous tutorial. Now we will learn about the other side of the coin, the export table.

Theory:

When the PE loader runs a program, it loads the associated DLLs into the process address space. It then extracts information about the import functions from the main program. It uses the information to search the DLLs for the addresses of the functions to be patched into the main program. The place in the DLLs where the PE loader looks for the addresses of the functions is the export table.

When a DLL/EXE exports a function to be used by other DLL/EXE, it can do so in two ways: it can export the function by name or by ordinal only. Say if there is a function named "GetSysConfig" in a DLL, it can choose to tell the other DLLs/EXEs that if they want to call the function, they must specify it by its name, ie. GetSysConfig. The other way is to export by ordinal. What's an ordinal? An ordinal is a 16-bit number that uniquely identifies a function in a particular DLL. This number is unique only within the DLL it refers to. For example, in the above example, the DLL can choose to export the function by ordinal, say, 16. Then the other DLLs/EXEs which want to call this function must specify this number in GetProcAddress. This is called export by ordinal only.

Export by ordinal only is strongly discouraged because it can cause a maintenance problem for the DLL. If the DLL is upgraded/updated, the programmer of that DLL cannot alter the ordinals of the functions else other programs that depend on the DLL will break.

Now we can examine the export structure. As with import table, you can find where the export table is from looking at the data directory. In this case, the export table is the first member of the data directory. The export structure is called IMAGE_EXPORT_DIRECTORY. There are 11 members in the structure but only some of them are really used.

Field Name

Meaning

nName

The actual name of the module. This field is necessary because the name of the file can be changed. If it's the case, the PE loader will use this internal name.

nBase

A number that you must bias against the ordinals to get the indexes into the address-of-function array.

NumberOfFunctions

Total number of functions/symbols that are exported by this module.

NumberOfNames

Number of functions/symbols that are exported by name. This value is not the number of ALL functions/symbols in the module. For that number, you need to check NumberOfFunctions. This value can be 0. In that case, the module may export by ordinal only. If there is no function/symbol to be exported in the first case, the RVA of the export table in the data directory will be 0.

AddressOfFunctions

An RVA that points to an array of RVAs of the functions/symbols in the module. In short, RVAs to all functions in the module are kept in an array and this field points to the head of that array.

AddressOfNames

An RVA that points to an array of RVAs of the names of functions in the module.

AddressOfNameOrdinals

An RVA that points to a 16-bit array that contains the ordinals associated with the function names in the AddressOfNames array above.

Just reading the above table may not give you the real picture of the export table. The simplified explanation below will clarify the concept.

The export table exists for use by the PE loader. First of all, the module must keep the addresses of all exported functions somewhere so the PE loader can look them up. It keeps them in an array that is pointed to by the field AddressOfFunctions. The number of elements in the array is kept in NumberOfFunctions. Thus if the module exports 40 functions, it must have 40 members in the array pointed to by AddressOfFunctions and NumberOfFunctions must contain a value 40. Now if some functions are exported by names, the module must keep the names in the file. It keeps the RVAs to the names in an array so the PE loader can look them up. That array is pointed to by AddressOfNames and the number of names in NumberOfNames. Think about the job of the PE loader, it knows the names of the functions, it must somehow obtain the addresses of those functions. Up to now, the module has two arrays: the names and the addresses but there is no linkage between them. Thus we need something that relates the names of the functions to their addresses. The PE specification uses indexes into the address array as that essential linkage. Thus if the PE loader finds the name it looks for in the name array, it can obtain the index into the address table for that name too. The indexes are kept in another array (the last one) pointed to by the field AddressOfNameOrdinals. Since this array exists as the linkage between the names and the addresses, it must have exactly the same number of elements as the name array, ie. each name can have one and only one associated address. The reverse is not true: an address may have several names associated with it. Thus we can have "aliases" that refer to the same address. To make the linkage works, both name and index arrays must run in parallel, ie. the first element in the index array must hold the index for the first name and so on.

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

An example or two is in order. If we have the name of an export function and we need to get its address in the module, we can do like this:

Go to the PE header

Read the virtual address of the export table in the data directory

Go to the export table and obtain the number of names (NumberOfNames)

Walk the arrays pointed to by AddressOfNames and AddressOfNameOrdinals in parallel, searching for the matching name. If the name is found in the AddressOfNames array, you must extract the value in the associated element in the AddressOfNameOrdinals array. For example, if you find the RVA of the matching name in 77th element of the AddressOfNames array, you must extract the value stored in the 77th element of the AddressOfNameOrdinals array. If you walk the array until NumberOfNames elements are examined, you know that the name is not in this module.

Use the value from the AddressOfNameOrdinals array as the index into the AddressOfFunctions array. Say, if the value is 5, you must extract the value in the 5th element of the AddressOfFunctions array. That value is the RVA of the function.

Now we can turn our attention to the nBase member of the IMAGE_EXPORT_DIRECTORY structure. You already know that the AddressOfFunctions array contains the addresses of all export symbols in a module. And the PE loader uses the indexes into this array to find the addresses of the functions. Let's imagine the scenario where we use the indexes into this array as the ordinals. Since the programmers can specify the starting ordinal number in .def file, like 200, it means that there must be at least 200 elements in the AddressOfFunctions array. Furthermore the first 200 elements are not used but they must exist so that the PE loader can use the indexes to find the correct addresses. This is not good at all. The nBase member exists to solve this problem. If the programmer specifies the starting ordinal of 200, the value in nBase would be 200. When the PE loader reads the value in nBase, it knows that the first 200 elements do not exist and that it should subtract the ordinal by the value in nBase to obtain the true index into the AddressOfFunctions array. With the use of nBase, there is no need to provide 200 empty elements.

Note that nBase doesn't affect the values in the AddressOfNameOrdinals array. Despite the name "AddressOfNameOrdinals", this array contains the true indexes into the AddressOfFunctions array, not the ordinals.

With the discussion of nBase out of the way, we can continue to the next example.

Suppose that we have an ordinal of a function and we need to obtain the address of that function, we can do it like this:

Go to the PE header

Obtain the RVA of the export table from the data directory

Go to the export table and obtain the value of nBase.

Subtract the ordinal by the value in nBase and you have the index into the AddressOfFunctions array.

Compare the index with the value in NumberOfFunctions. If the index is larger or equal to the value in NumberOfFunctions, the ordinal is invalid.

Use the index to obtain the RVA of the function in the AddressOfFunctions array.

Note that obtaining the address of a function from an ordinal is much easier and faster than using the name of the function. There is no need to walk the AddressOfNames and AddressOfNameOrdinals arrays. The performance gain, however, must be balanced against the difficulty in the maintaining the module.

In conclusion, if you want to obtain the address of a function from its name, you need to walk both AddressOfNames and AddressOfNameOrdinals arrays to obtain the index into the AddressOfFunctions array. If you have the ordinal of the function, you can go directly to the AddressOfFunctions array after the ordinal is biased by nBase.

If a function is exported by name, you can use either its name or its ordinal in GetProcAddress. But what if the function is exported by ordinal only? We come to that now.

"A function is exported by ordinal only" means the function doesn't have entries in both AddressOfNames and AddressOfNameOrdinals arrays. Remember the two fields, NumberOfFunctions and NumberOfNames. The existence of these two fields is the evidence that some functions may not have names. The number of functions must be at least equal to the number of names. The functions that don't have names are exported by their ordinals only. For example, if there are 70 functions but only 40 entries in the AddressOfNames array, it means there are 30 functions in the module that are exported by their ordinals only. Now how can we find out which functions are exported by ordinals only? It's not easy. You must find that out by exclusion, ie. the entries in the AddressOfFunctions array that are not referenced by the AddressOfNameOrdinals array contain the RVAs of the functions that are exported by ordinals only.

Example:

This example is similar to the one in the previous tutorial. However, it displays the values of some members of IMAGE_EXPORT_DIRECTORY structure and also lists the RVAs, ordinals, and names of the exported functions. Note that this example doesn't list the functions that are exported by ordinals only.

.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

Analysis:

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

After the program verifies that the file is a valid PE, it goes to the data directory and obtains the virtual address of the export table. If the virtual address is zero, the file doesn't have any exported symbol.

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

We display the important information in the IMAGE_EXPORT_DIRECTORY structure in the edit control.

push [edi].NumberOfNames

pop NumberOfNames

push [edi].nBase

pop Base

Since we want to enumerate all function names, we need to know how many names there are in the export table. nBase is used when we want to convert the indexes into the AddressOfFunctions array into ordinals.

invoke RVAToFileMap,pMapping,[edi].AddressOfNames

mov esi,eax

invoke RVAToFileMap,pMapping,[edi].AddressOfNameOrdinals

mov ebx,eax

invoke RVAToFileMap,pMapping,[edi].AddressOfFunctions

mov edi,eax

The addresses of the three arrays are stored in esi, ebx, and edi, ready to be accessed.

.while NumberOfNames>0

Continue until all names are processed.

invoke RVAToFileMap,pMapping,dword ptr [esi]

Since esi points to an array of RVAs of the exported names, dereference it will give the RVA of the current name. We convert it to the virtual address, to be used in wsprintf later.

mov dx,[ebx]

movzx edx,dx

mov ecx,edx

add ecx,Base

ebx points to the array of ordinals. Its array elements are word-size. Thus we need to convert the value into a dword first. edx and ecx contain the index into the AddressOfFunctions array. We will use edx as the pointer into the AddressOfFunctions array. We add the value of nBase to ecx to obtain the ordinal number of the function.

shl edx,2

add edx,edi

We multiply the index by 4 (each element in the AddressOfFunctions array is 4 bytes in size) and then add the address of the AddressOfFunctions array to it. Thus edx points to the RVA of the function.

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

invoke AppendText,hDlg,addr temp

We display the RVA, ordinal, and the name of the function in the edit control.

dec NumberOfNames

add esi,4

add ebx,2

.endw

Update the counter and the addresses of the current elements in AddressOfNames and AddressOfNameOrdinals arrays. Continue until all names are processed.

[Iczelion's Win32 Assembly Homepage]

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
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- 王朝網路 版權所有