PE文件和COFF文件格式分析——导出表
? ? ? ? 在之前的《PE可選文件頭》相關(guān)博文中我們介紹了可選文件頭中很多重要的屬性,而其中一個(gè)非常重要的屬性是(轉(zhuǎn)載請指明來源于breaksoftware的CSDN博客)
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
? ? ? ? 該數(shù)組保存了如下節(jié)(不一定全包括,要以IMAGE_OPTIONAL_HEADER32(64)::NumberOfRvaAndSizes來確定)的信息
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory
// IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor
? ? ? ? 我們在之后會(huì)介紹各個(gè)節(jié)的結(jié)構(gòu)和相關(guān)應(yīng)用,本文我將介紹該數(shù)組中第一個(gè)元素(DataDirectory[0])的信息——導(dǎo)出表信息。
? ? ? ? 首先我介紹下導(dǎo)出表。我們做程序時(shí),新手一般喜歡做的是copy+paste。這個(gè)方法在代碼結(jié)構(gòu)不是很復(fù)雜的時(shí)候還能過的去。如果像微軟這樣的系統(tǒng)也是這么寫,我想我們的黑客和漏洞挖掘者會(huì)非常高興了——因?yàn)檫@樣必定會(huì)產(chǎn)生更多的漏洞和bug。因?yàn)檫@樣寫的代碼非常難維護(hù)。打個(gè)比方,我們有個(gè)函數(shù)實(shí)現(xiàn)了對XML的解析,有ABCDE這么多業(yè)務(wù)方去copy了這段代碼。若干年后某天XML規(guī)則發(fā)生了改變,我們要修正XML解析算法,這個(gè)時(shí)候可能由于原來引入該段代碼的員工離職了或者時(shí)間久遠(yuǎn)等原因,ABCDE各方都不知道自己的邏輯中用了XML,更不知道要去修正為新的算法。于是如何解決呢?ABCDE方應(yīng)該讓XML解析算法編寫者提供一個(gè).h和.cpp文件,里面包含了我們可能會(huì)調(diào)用的XML算法,然后在各自的代碼中include這個(gè)XML算法編寫者維護(hù)的目錄下的這個(gè).h文件,并調(diào)用.h中的方法。這樣,以后XML算法即使改了,各業(yè)務(wù)方也可以保證我們使用的算法是最新的。但是還別高興的太早,還有個(gè)問題放在我們面前。如果我們的程序是一個(gè)獨(dú)立的Exe發(fā)布的話,在后續(xù)升級(jí)時(shí)會(huì)帶來些麻煩。比如我們發(fā)布的Exe文件是1G,可是發(fā)布后我們發(fā)現(xiàn)一行代碼寫錯(cuò)了,于是我改了這行代碼,卻要讓用戶升級(jí)一個(gè)1G的文件!!流量啊!怎么辦?為了便于升級(jí),我們還是把1G文件合理分割成若干個(gè)文件,并保證它們可以協(xié)同工作。DLL就是這樣被拆分出來的文件中一個(gè)非常重要的組成部分,它里面的導(dǎo)出函數(shù)就如同供其他方調(diào)用的XML解析的各種方法。導(dǎo)出表就是用于保存這些方法的名稱和地址等信息的地方。
? ? ? ? 現(xiàn)在我們來說下導(dǎo)出表節(jié)的結(jié)構(gòu)。在DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]中保存了導(dǎo)出表節(jié)的相對虛擬偏移RVA和大小,在之后的章節(jié)中我們會(huì)發(fā)現(xiàn)除了DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]保存的是RA(相對文件頭的偏移),其他都是RVA。通過該RVA,我們算出RA,從而得到一個(gè)描述導(dǎo)出表頭的結(jié)構(gòu)體信息,該結(jié)構(gòu)體是
typedef struct _IMAGE_EXPORT_DIRECTORY {DWORD Characteristics;DWORD TimeDateStamp;WORD MajorVersion;WORD MinorVersion;DWORD Name;DWORD Base;DWORD NumberOfFunctions;DWORD NumberOfNames;DWORD AddressOfFunctions; // RVA from base of imageDWORD AddressOfNames; // RVA from base of imageDWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
? ? ? ? Characteristics是保留字段,要求為0。
? ? ? ? TimeDataStamp保存的生成導(dǎo)出信息的時(shí)間。
? ? ? ? MajorVersion和MinorVersion分別是主版本號(hào)和此版本號(hào)。這些信息是我們可以決定的。
? ? ? ? Name字段保存的該導(dǎo)出文件的名稱的偏移。這兒要注意一點(diǎn),這個(gè)地址是系統(tǒng)不關(guān)心的,我們可以將其指向的地址設(shè)置為違法的地址,這樣會(huì)干擾部分PE分析工具的分析結(jié)果。
? ? ? ??Base是導(dǎo)出函數(shù)的起始序數(shù)值,該值一般為1。如我們用View dependencies打開一個(gè)文件,紅色部分就是Base字段相關(guān)的
? ? ? ??NumberOfFunctions標(biāo)志導(dǎo)出函數(shù)的函數(shù)地址數(shù)。該數(shù)據(jù)是非常重要的,我們要知道該文件導(dǎo)出了多少個(gè)函數(shù)就是要依據(jù)這個(gè)信息。我們之后會(huì)詳細(xì)說的。
? ? ? ??NumberOfNames標(biāo)志導(dǎo)出函數(shù)的函數(shù)名數(shù)量。
? ? ? ??AddressOfFunctions標(biāo)志導(dǎo)出函數(shù)的函數(shù)地址表的RVA。
? ? ? ??AddressOfNames標(biāo)志導(dǎo)出函數(shù)的函數(shù)名表的RVA。
? ? ? ??AddressOfNameOrdinals標(biāo)志導(dǎo)出函數(shù)的導(dǎo)出序數(shù)表的RVA。
? ? ? ? 以我電腦上desktmon.dll為例,我們看一下該文件中該結(jié)構(gòu)的布局
? ? ? ? 我們再用一個(gè)圖來描述一下PE導(dǎo)出表在View dependencies中顯示的相關(guān)關(guān)系
? ? ? ? 初次研究這個(gè)結(jié)構(gòu)的同學(xué)可能會(huì)注意一個(gè)問題,該結(jié)構(gòu)中有三個(gè)表的RVA(AddressOfFunctions,AddressOfNames,AddressOfNameOrdinals),而只給出了其中前兩個(gè)表的元素個(gè)數(shù)(NumberOfFunctions,NumberOfNames)。那第三個(gè)表——導(dǎo)出序數(shù)表的個(gè)數(shù)是多少?是按導(dǎo)出函數(shù)地址表(AddressOfFunctions)中元素個(gè)數(shù)(NumberOfFunctions)還是按導(dǎo)出函數(shù)名稱表(AddressOfNames)中元素個(gè)數(shù)(NumberOfNames)?還有個(gè)問題:為什么要設(shè)置Base屬性?這些問題我們先Mark下。我們先來詳細(xì)介紹這三個(gè)表。
? ? ? ? 導(dǎo)出地址表。顧名思義,該表中保存了函數(shù)入口RVA。但是如果僅僅是如此簡單就好了,這個(gè)地方保存的還可能是一個(gè)指向字符串的RVA!其結(jié)構(gòu)是以下結(jié)構(gòu)體的一個(gè)集合。
// 導(dǎo)出表信息
typedef struct _IMAGE_Export_Address_Table_
{union {DWORD dwExportRVA;DWORD dwForwarderRVA;};
}IMAGE_Export_Address_Table, *pIMAGE_Export_Address_Table;
? ? ? ? 如果它保存的是導(dǎo)出函數(shù)入口地址,那沒什么好說的。我們說下它保存的是指向一個(gè)字符串的偏移的情況。在我的XP系統(tǒng)下Kernel32.dll中AddVectoredExceptionHandler函數(shù)的導(dǎo)出函數(shù)地址指向的字符串是NTDLL.RtlAddVectoredExceptionHandler。看到這樣的名字組合,我想你大概能猜出個(gè)眉目。AddVectoredExceptionHandler函數(shù),在Kernel32.dll文件內(nèi)部是沒有實(shí)現(xiàn)的。但是如果有程序需要加載Kernel32.dll并需要調(diào)用這個(gè)函數(shù),則這樣的寫法會(huì)告訴加載器在加載Kernel32.dll時(shí),要將AddVectoredExceptionHandler函數(shù)的地址直接改成Ntdll.dll中的RtlAddVectoredExceptionHandler函數(shù)地址(即自動(dòng)加載Ntdll.dll)。這個(gè)特性非常有趣吧!我想做加殼的朋友應(yīng)該對這個(gè)場景很熟悉。我之后會(huì)介紹利用這個(gè)特性去隱性自動(dòng)加載DLL。最后說一下,我們?nèi)绾伪鎰e這個(gè)字段保存的是函數(shù)的入口地址的RVA還是字符串呢?只要判斷該偏移不在導(dǎo)出表節(jié)中即可:指向的地址在節(jié)中就是字符串的RVA;在節(jié)外是函數(shù)入口的RVA。
? ? ? ? 導(dǎo)出名稱表。計(jì)算機(jī)做出來是給人用的,如果給人一堆010101這樣的數(shù)據(jù),我想沒誰會(huì)有太多興趣去看的。于是出于人性化考慮,人們發(fā)明了別名,比如發(fā)明了匯編映射二進(jìn)制指令,從而幫助理解程序邏輯。導(dǎo)出名稱表就是出于這樣的考慮而設(shè)計(jì)的。其結(jié)構(gòu)是以下結(jié)構(gòu)體的一個(gè)集合。
typedef struct _IMAGE_Export_Name_Pointer_Table_ {DWORD dwPointer;
}IMAGE_Export_Name_Pointer_Table,*pIMAGE_Export_Name_Pointer_Table;
? ? ? ? 它是指向字符串的RVA,該字符串是以\0結(jié)尾的。
? ? ? ? 說到這兒,我覺得我們可以停下思考一個(gè)問題,是不是只要有這兩個(gè)表就夠了?如果對于我們自己編寫的且非常標(biāo)準(zhǔn)的DLL,只要有這兩個(gè)表的確是夠了。你想,當(dāng)我們調(diào)用GetProcAddress時(shí),我們在導(dǎo)入名稱表中找到該名稱對應(yīng)的index,然后再返回導(dǎo)出函數(shù)地址表中該index的數(shù)據(jù)即可。
lpFunc = ExportAddressTable[ExportNameTable.find(FuncName)]
? ? ? ? 但是,PE文件設(shè)計(jì)的遠(yuǎn)沒有這么簡單。如果如此簡單,那很多事都好辦了。舉一個(gè)特殊的例子來推翻這種簡單的場景: 函數(shù)入口地址和函數(shù)名之間的關(guān)系是1對N(0~n)。我們程序運(yùn)行起來后,很多時(shí)候是要調(diào)用其他邏輯,即函數(shù)入口。可以說一個(gè)函數(shù)入口可以唯一標(biāo)注一個(gè)邏輯。而我們經(jīng)常說的某某API,其實(shí)只是某個(gè)函數(shù)過程的一個(gè)名字。比如我們一個(gè)實(shí)現(xiàn)XML解析的函數(shù),我們可以叫做ParseXML,也可以叫XMLParse。不管是叫哪個(gè)名字,該函數(shù)的功能是不變的,它的入口地址是不變的。如果入口地址變了,那就是另外一個(gè)函數(shù)了。這就是為什么說函數(shù)入口地址和函數(shù)名之間是1對N的關(guān)系。
?
? ? ? ??
? ? ? ? 針對以上問題,可能有人會(huì)想到,有多少個(gè)導(dǎo)出函數(shù)名(以導(dǎo)出函數(shù)名的數(shù)量為標(biāo)準(zhǔn))就設(shè)置多少個(gè)導(dǎo)出地址,導(dǎo)出地址表中數(shù)據(jù)可以重復(fù),比如上圖中ParseXML和XMLParse函數(shù)名對應(yīng)的導(dǎo)出地址都設(shè)置成0xXXXXXXXX就行了嘛。如
但是還有個(gè)場景:windows平臺(tái)可以通過序數(shù)導(dǎo)入一個(gè)函數(shù)地址(GerProcAddress的第二個(gè)參數(shù)傳序數(shù)),那么這就意味著函數(shù)可以沒有函數(shù)名!!因?yàn)樾驍?shù)也可以看成一個(gè)函數(shù)的編號(hào)嘛,雖然這樣非常不友好,但是仍然是一種可行的方法。那么如果在這種場景下,我們還能以導(dǎo)出函數(shù)名的數(shù)量為標(biāo)準(zhǔn)么?不可以了吧,因?yàn)楹瘮?shù)名表元素?cái)?shù)量可能是0!其實(shí)這類文件挺多,如mfc40u.dll,見下圖
? ? ? ? 通過以上分析,我們可以得出,我們還是要一個(gè)能在導(dǎo)出函數(shù)地址表和導(dǎo)出函數(shù)名稱表建立紐帶的結(jié)構(gòu)體。這個(gè)我們期待的輔助結(jié)構(gòu)體就是我們下面介紹的導(dǎo)出序數(shù)表。
? ? ? ? 導(dǎo)出序數(shù)表。該表保存的是導(dǎo)出地址表的序數(shù)偏移!切記這個(gè)重要的概念。那這個(gè)偏移是相對什么偏移的呢?是針對IMAGE_EXPORT_DIRECTORY::Base屬性的。即這個(gè)表中保存的值加上Base,就是導(dǎo)出地址表的序數(shù)。其結(jié)構(gòu)是以下結(jié)構(gòu)體的一個(gè)集合。
typedef struct _IMAGE_Export_Ordinal_Table_ {WORD dwOrdinal;
}IMAGE_Export_Ordinal_Table,*pIMAGE_Export_Ordinal_Table;
? ? ? ? 從這個(gè)表的命名(AddressOfNameOrdinals )看,應(yīng)該可以發(fā)現(xiàn)這個(gè)表應(yīng)該和導(dǎo)出名稱表存在一定的關(guān)系!是的,它的元素的數(shù)量和導(dǎo)出名稱表的元素?cái)?shù)量是一樣的。可能有人會(huì)疑問,什么這個(gè)表元素的個(gè)數(shù)不是和導(dǎo)出地址表元素個(gè)數(shù)一致呢?因?yàn)槿缟厦嫠f,一個(gè)函數(shù)過程可以對應(yīng)多個(gè)函數(shù)名,如果導(dǎo)出序數(shù)表元素個(gè)數(shù)和導(dǎo)出函數(shù)地址表元素個(gè)數(shù)一樣,則無法讓地址與函數(shù)名對應(yīng)上。比如我們導(dǎo)出地址表有1個(gè)函數(shù)入口,而我們有2個(gè)函數(shù)名都指向這個(gè)地址,那么導(dǎo)出序數(shù)表個(gè)數(shù)如果是1,則如何表示這兩個(gè)名稱與函數(shù)入口的對應(yīng)呢?如果導(dǎo)出序數(shù)表格式是2個(gè),則我們可以讓這兩個(gè)元素都“指向”同一個(gè)導(dǎo)出函數(shù)入口即可。OK,這兒我就解答了上面我們Mark過的那個(gè)問題:導(dǎo)出序數(shù)表個(gè)數(shù)和導(dǎo)出名稱表個(gè)數(shù)一致。
? ? ? ?那么這三個(gè)表之間具體什么關(guān)系呢?我首先以一個(gè)簡單的、常規(guī)的文件為例,這個(gè)文件是上面提到的deskmon.dll。我們看一下View Dependencies的分析結(jié)果:
? ? ? ? 我們再把它的PE文件拿出來看下
? ? ? ? 我們把各個(gè)信息提取出來看下:
Characteristics; 0x00000000
TimeDateStamp; 0x3B7D74B7
MajorVersion; 0x0000
MinorVersion; 0x0000
Name; 0x00002E6C
Base; 0x00000001
NumberOfFunctions; 0x00000002
NumberOfNames; 0x00000002
AddressOfFunctions; 0x00002E58
AddressOfNames; 0x00002E60
AddressOfNameOrdinals; 0x00002E68
? ? ? ? 可以看到這個(gè)Dll的導(dǎo)出地址表有2個(gè)元素,導(dǎo)出名稱表和導(dǎo)出序數(shù)表也是有2個(gè)元素的。用之前《PE文件和COFF文件格式分析——RVA和RA相互計(jì)算》介紹的算法,我們可以得出
?? ? ? ?導(dǎo)出地址表RVA(0x00002E58)對應(yīng)的RA是0x00002258。兩個(gè)元素分別為{ {0,0x0002218},{1,0x00002534}}。和View Dependencis分析結(jié)果對比發(fā)現(xiàn),這組數(shù)據(jù)是一致的。
? ? ? ? 導(dǎo)出名稱表RVA(0x00002E60)對應(yīng)的RA是0x00002260,其數(shù)據(jù)是{0,0x00002E78}和{1,0x00002E88}。0x00002E78是函數(shù)名的RVA,其對應(yīng)的RA是0x00002278,即字符串“DllCanUnloadNow”;0x00002E88也是函數(shù)名稱的RVA,其RA是0x00002288,即字符串“DllGetClassObject”。于是可以把導(dǎo)出函數(shù)名表看成{{0,DllCanUnloadNow},{1,DllGetClassObject}}。這個(gè)數(shù)據(jù)和View Dependencies中信息一致。
? ? ? ? 導(dǎo)出序數(shù)表RVA(0x00002E68)對應(yīng)的RA是0x00002268,其數(shù)據(jù)是{{0,0x0000},{1,0x0001}}。但是這并不是最終數(shù)據(jù),剛才我在介紹導(dǎo)出序數(shù)表時(shí),說過這個(gè)表保存的是相對Base的偏移,該文件的Base是1,于是真實(shí)的數(shù)據(jù)是{{0,0x0001},{1,0x0002}}。
? ? ? ?我們用圖來說一下這三者的關(guān)系。
? ? ? ?比如我們試圖得到DllGetClassObject的函數(shù)地址。我們現(xiàn)在名稱表中找到它的index是1。然后在序數(shù)表中找到index是1的元素的值0x00000002,。0x00000002要減去Base的值1得到值1。最后在地址表中找到index為1的元素的值,這個(gè)值就是DllGetClassObject函數(shù)的入口地址。表達(dá)式是
i = Search_ExportNamePointerTable (ExportName);
ordinal = ExportOrdinalTable [i];
SymbolRVA = ExportAddressTable [ordinal - OrdinalBase];
? ? ? ? 看了上面的邏輯, 我們在序數(shù)表中“加上”Base,然后要通過名稱去找函數(shù)入口時(shí)又要從序數(shù)表中“減去”Base,是不是這兒Base是多余的?如果單從通過名稱獲取函數(shù)地址來看,Base的確是多余的。那么如果通過序數(shù)來獲取函數(shù)地址呢?我構(gòu)造了一個(gè)DLL——DllTestIndex.dll(工程地址)
LIBRARY "DllTestIndex"
EXPORTSRet1 @2.Ret2 @4Ret3 @8Ret4 @6
?
? ? ? ? 發(fā)現(xiàn)通過序數(shù)去得到Ordinal為3的函數(shù)地址時(shí)會(huì)出錯(cuò)。這兒有兩種可能:
? ? ? ? A GetProcAddress看到函數(shù)地址是0x00000000就認(rèn)為獲取出錯(cuò)
? ? ? ? B GetProcAddress是發(fā)現(xiàn)序數(shù)3不在序數(shù)表中(該文件導(dǎo)出序數(shù)表為{2,4,6,8},于是返回出錯(cuò)。
? ? ? ? 那么到底是那種呢?我將這個(gè)文件修改成如下,即將Ordinal為3的函數(shù)地址修改成一個(gè)有效的函數(shù)地址,得到一個(gè)文件DllTestIndex_Modify
? ? ? ? 如果是B原因,則此時(shí)我們?nèi)カ@取Ordinal為3的函數(shù)地址還是會(huì)失敗。可是結(jié)果呢?GetProcAddress成功了,并正確返回了0x00011069這個(gè)函數(shù)入口地址。這個(gè)實(shí)驗(yàn)證明A原因是對的。這從而證明Base這個(gè)字段,對通過函數(shù)名尋找函數(shù)入口地址的算法的確是多余的信息。如果真要找個(gè)原因,可能從文件大小的說起。PE文件序數(shù)表的元素是WORD為單位的,而Base是DWORD。那么就是說,我們最多可以有0x10000(0x0000~0xFFFF)個(gè)導(dǎo)出函數(shù)。假如這些函數(shù)都在導(dǎo)出序數(shù)表中有對應(yīng)元素,且導(dǎo)出序數(shù)表每個(gè)元素用DWORD描述,則需要sizeof(DWORD)*0x10000的空間。如果采用Base+WORD的方法,則只需要sizeof(WORD)*0x10000+sizeof(DWORD)的空間。采用后者最多可以節(jié)省0x3FFF8(0x40000-8)byte空間。其實(shí)這個(gè)空間很小的,可以忽略不計(jì)的。
? ? ? ?除了之上那個(gè)非常強(qiáng)求的原因,Base就沒用了么?不是!我們繼續(xù)看上面那個(gè)例子,從我們DEF文件中看出,我們希望導(dǎo)出的4個(gè)函數(shù)的序號(hào)分別是2、4、8、6。我們看下PE文件中的布局
? ? ? ? 我們看到信息如下:
? ? ? ? Base是0x00000002;NumberOfFunctions是0x00000007;導(dǎo)出函數(shù)地址分別為0x00011069、0x00000000、0x000110F5、0x00000000、0x000110A5、0x00000000、0x000110C3;導(dǎo)出名稱是按我們在DEF申明的順序是一致的,分別是:Ret1、Ret2、Ret3、Ret4。導(dǎo)出序數(shù)表是0x0000、0x0002、0x0006、0x0004。
? ? ? ? 注意View Dependencies的Ordinal列,該列的信息是函數(shù)地址的Index加上Base的值。于是
? ? ? 當(dāng)我們?nèi)绱苏{(diào)用時(shí)
typedef int(WINAPI* PRetN)();void ExprotFunc(LPSTR lpFileName) {HMODULE hDll = NULL;do {printf("%s\n", lpFileName);hDll = LoadLibraryA(lpFileName);if ( NULL == hDll ) {break;}for ( int nIndex = 0; nIndex < 10; nIndex ++ ) {PRetN pRetn = (PRetN)GetProcAddress( hDll, (LPCSTR)(LPVOID)(nIndex) );if ( NULL != pRetn ) {printf("nIndex is %d: Value is %d\n",nIndex,pRetn());}}FreeLibrary(hDll);} while (0);
}int _tmain(int argc, _TCHAR* argv[])
{ExprotFunc("DllTestIndex.dll");ExprotFunc("DllTestIndex_Modify.dll");system("pause");return 0;
}
? ? ? ? 此時(shí)GetProcAddress的第二個(gè)參數(shù)就是上圖中間一列的信息,即View Dependencies的Ordinals信息。這兒要特別注意這個(gè)Ordinals和導(dǎo)出序數(shù)表(AddressOfNameOrdinals指向的表)不是一個(gè)東西。這樣我就解答了上面我們Mark的那個(gè)問題——Base到底是為什么設(shè)計(jì)的?是為了通過序數(shù)導(dǎo)出函數(shù)而設(shè)計(jì)的。
? ? ? ? 之后我將會(huì)介紹幾個(gè)對導(dǎo)出表好玩的應(yīng)用。
? ? ? ? 最后貼一段導(dǎo)出表解析的代碼
BOOL CGetPEInfo::GetExportInfo()
{BOOL bSuc = ( 0 == m_ExpDir.Characteristics ) ? TRUE : FALSE;if ( FALSE == bSuc ) {_ASSERT(FALSE);}m_ExpFullInfo.ExpDir = m_ExpDir;std::string wszTime;if ( FALSE == GetTime( m_ExpDir.TimeDateStamp, wszTime ) ) {_ASSERT(FALSE);}m_ExpFullInfo.wszTime = wszTime;std::string wszDllName;DWORD dwNameRA = 0;if ( FALSE == GetRAByRVA( m_ExpDir.Name, dwNameRA ) ) {_ASSERT(FALSE);}else {LPBYTE lpFileName = m_lpFileStart + dwNameRA;if ( lpFileName < m_lpFileStart || lpFileName > m_lpFileEnd ) {wszDllName.clear();}else {wszDllName = (LPSTR)lpFileName;}}m_ExpFullInfo.szImgName = wszDllName;MapIMAGE_Export_Address_Table MapImageExpAddrTable;DWORD dwFunAddrTableRA = 0;if ( FALSE == GetRAByRVA( m_ExpDir.AddressOfFunctions, dwFunAddrTableRA ) ) {if ( 0 != m_ExpDir.AddressOfFunctions ) {//_ASSERT(FALSE);}else {}}else {LPBYTE lpStart = m_lpFileStart + dwFunAddrTableRA;for ( WORD i = 0; i < m_ExpDir.NumberOfFunctions; i++ ) {IMAGE_Export_Address_Table ImgExpAddrTable;if ( FALSE == SafeCopy( &ImgExpAddrTable, lpStart, sizeof(IMAGE_Export_Address_Table)) ) {break;}MapImageExpAddrTable[i] = ImgExpAddrTable;lpStart += sizeof(IMAGE_Export_Address_Table);}}MapIMAGE_Export_Name_Pointer_Table MapImageExpNamePointerTable;DWORD dwFunNameTableRA = 0;if ( FALSE == GetRAByRVA( m_ExpDir.AddressOfNames, dwFunNameTableRA ) ) {if ( 0 != m_ExpDir.AddressOfNames ) {_ASSERT(FALSE);}else {}}else {LPBYTE lpStart = m_lpFileStart + dwFunNameTableRA;for ( DWORD i = 0; i < m_ExpDir.NumberOfNames; i++ ) {IMAGE_Export_Name_Pointer_Table ImgExpNamePointer;if ( FALSE == SafeCopy( &ImgExpNamePointer, lpStart , sizeof(IMAGE_Export_Name_Pointer_Table) ) ) {break;}MapImageExpNamePointerTable[i] = ImgExpNamePointer;lpStart += sizeof(IMAGE_Export_Name_Pointer_Table);}}MapIMAGE_Export_Ordinal_Table MapImageExpOrdinalTable;DWORD dwOrdinalTableRA = 0;if ( FALSE == GetRAByRVA( m_ExpDir.AddressOfNameOrdinals, dwOrdinalTableRA ) ) {if ( 0 != m_ExpDir.AddressOfNameOrdinals ) {_ASSERT(FALSE);}else {//C:\Config.Msi\1ecac1a.rbf}}else {LPBYTE lpStart = m_lpFileStart + dwOrdinalTableRA;for ( WORD i = 0; i < m_ExpDir.NumberOfNames; i++ ) {IMAGE_Export_Ordinal_Table ImgExpOrdinalTable;if ( FALSE == SafeCopy( &ImgExpOrdinalTable, lpStart, sizeof(IMAGE_Export_Ordinal_Table) ) ) {break;}MapImageExpOrdinalTable[i] = ImgExpOrdinalTable;lpStart += sizeof(IMAGE_Export_Ordinal_Table);}}EXPORT_TABLE_FULL_INFO ExpTableFullInfo;if ( 0 != m_ExpDir.Base && 1 != m_ExpDir.Base ) {_ASSERT(FALSE);}if ( m_ExpDir.NumberOfNames != m_ExpDir.NumberOfFunctions ) {// _ASSERT(FALSE);}// 應(yīng)該按地址數(shù)量來算for ( MapIMAGE_Export_Address_TableIter ImgExpAdIter = MapImageExpAddrTable.begin();ImgExpAdIter != MapImageExpAddrTable.end();ImgExpAdIter++ ){std::string strName;strName.empty();ExpTableFullInfo.wHint = 0xFFFF;ExpTableFullInfo.wOrdinal = ImgExpAdIter->first + (WORD)m_ExpDir.Base;DWORD dwRVA = ImgExpAdIter->second.dwExportRVA;if ( FALSE == IsRVAinSection( dwRVA, IMAGE_DIRECTORY_ENTRY_EXPORT )&& 0 != dwRVA ) {ExpTableFullInfo.bForwarderRVA = FALSE;ExpTableFullInfo.dwRVA.dwExportRVA = dwRVA;}else{ExpTableFullInfo.dwRVA.dwForwarderRVA = dwRVA;ExpTableFullInfo.bForwarderRVA = TRUE;DWORD dwForwarderRA;if ( FALSE == GetRAByRVA( ExpTableFullInfo.dwRVA.dwForwarderRVA, dwForwarderRA ) ) {if ( 0 != ExpTableFullInfo.dwRVA.dwForwarderRVA ) {_ASSERT(FALSE);}else {// C:\4f1b3cac6fdc7b2cb9092b46e7c0fc71\Mobile Partner-dial\Mobile Partner-dial\mfc40u.dll// _ASSERT(FALSE);}//continue;}else {ExpTableFullInfo.strForwarder = (LPSTR)(m_lpFileStart + dwForwarderRA);}}MapIMAGE_Export_Ordinal_TableIter ImgExpOrdIter = MapImageExpOrdinalTable.begin();for ( ;ImgExpOrdIter != MapImageExpOrdinalTable.end();ImgExpOrdIter++ ){if ( ImgExpAdIter->first != ImgExpOrdIter->second.dwOrdinal ) {continue;}ExpTableFullInfo.wHint = ImgExpOrdIter->first;for ( MapIMAGE_Export_Name_Pointer_TableIter ImgExpNamePointerIter = MapImageExpNamePointerTable.begin();ImgExpNamePointerIter != MapImageExpNamePointerTable.end();ImgExpNamePointerIter++ ){if ( ImgExpNamePointerIter->first != ExpTableFullInfo.wHint ) {continue;}DWORD dwNamePointerRA = 0;if ( FALSE == GetRAByRVA( ImgExpNamePointerIter->second.dwPointer, dwNamePointerRA ) ) {continue;}else {strName =(LPSTR)(m_lpFileStart + dwNamePointerRA);}break;}break;}ExpTableFullInfo.strFuncName = strName;m_ExpFullInfo.vecExpTable.push_back( ExpTableFullInfo );}return bSuc;
}
?
總結(jié)
以上是生活随笔為你收集整理的PE文件和COFF文件格式分析——导出表的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PE文件和COFF文件格式分析——RVA
- 下一篇: PE文件和COFF文件格式分析——导出表