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

看雪資料發表於2015-11-15

 

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 NameMeaning
nName模組的真實名稱。本域是必須的,因為檔名可能會改變。這種情況下,PE裝載器將使用這個內部名字。
nBase基數,加上序數就是函式地址陣列的索引值了。
NumberOfFunctions模組引出的函式/符號總數。
NumberOfNames透過名字引出的函式/符號數目。該值不是模組引出的函式/符號總數,這是由上面的NumberOfFunctions給出。本域可以為0,表示模組可能僅僅透過序數引出。如果模組根本不引出任何函式/符號,那麼資料目錄中引出表的RVA為0。
AddressOfFunctions模組中有一個指向所有函式/符號的RVAs陣列,本域就是指向該RVAs陣列的RVA。簡言之,模組中所有函式的RVAs都儲存在一個陣列裡,本域就指向這個陣列的首地址。
AddressOfNames類似上個域,模組中有一個指向所有函式名的RVAs陣列,本域就是指向該RVAs陣列的RVA。
AddressOfNameOrdinalsRVA,指向包含上述 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

下面舉一兩個例子說明問題。如果我們有了引出函式名並想以此獲取地址,可以這麼做:

  1. 定位到PE header。
  2. 從資料目錄讀取引出表的虛擬地址。
  3. 定位引出表獲取名字數目(NumberOfNames)。
  4. 並行遍歷AddressOfNamesAddressOfNameOrdinals指向的陣列匹配名字。如果在AddressOfNames 指向的陣列中找到匹配名字,從AddressOfNameOrdinals 指向的陣列中提取索引值。例如,若發現匹配名字的RVA存放在AddressOfNames 陣列的第77個元素,那就提取AddressOfNameOrdinals陣列的第77個元素作為索引值。如果遍歷完NumberOfNames 個元素,說明當前模組沒有所要的名字。
  5. 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的作用,我們繼續下一個例子。
假設我們只有函式的序數,那麼怎樣獲取函式地址呢,可以這麼做:

  1. 定位到PE header。
  2. 從資料目錄讀取引出表的虛擬地址。
  3. 定位引出表獲取nBase值。
  4. 減掉nBase值得到指向AddressOfFunctions 陣列的索引。
  5. 將該值與NumberOfFunctions作比較,大於等於後者則序數無效。
  6. 透過上面的索引就可以獲取AddressOfFunctions 陣列中的RVA了。

可以看出,從序數獲取函式地址比函式名快捷容易。不需要遍歷AddressOfNamesAddressOfNameOrdinals 這兩個陣列。然而,綜合效能必須與模組維護的簡易程度作一平衡。

總之,如果想透過名字獲取函式地址,需要遍歷AddressOfNamesAddressOfNameOrdinals 這兩個陣列。如果使用函式序數,減掉nBase值後就可直接索引AddressOfFunctions 陣列。

如果一函式透過名字引出,那在GetProcAddress中可以使用名字或序數。但函式僅由序數引出情況又怎樣呢? 現在就來看看。
"一個函式僅由序數引出"意味著函式在AddressOfNames AddressOfNameOrdinals 陣列中不存在相關項。記住兩個域,NumberOfFunctionsNumberOfNames。這兩個域可以清楚地顯示有時某些函式沒有名字的。函式數目至少等同於名字數目,沒有名字的函式透過序數引出。比如,如果存在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

相關文章