上篇文章 PE文件結構詳解(二)可執行文件頭 的結尾出現了一個大數組,這個數組中的每一項都是一個特定的結構,通過函數獲取數組中的項可以用RtlImageDirectoryEntryToData函數,DataDirectory中的每一項都可以用這個函數獲取,函數原型如下:
PVOID NTAPI RtlImageDirectoryEntryToData(PVOID Base, BOOLEAN MappedAsImage, USHORT Directory, PULONG Size);
Base:模塊基地址。
MappedAsImage:是否映射爲映象。
Directory:數據目錄項的索引。
#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
Size:對應數據目錄項的大小,比如Directory爲0,則表示導出表的大小。
返回值表示數據目錄項的起始地址。
這次來看看第一項:導出表。導出表是用來描述模塊中的導出函數的結構,如果一個模塊導出了函數,那麼這個函數會被記錄在導出表中,這樣通過GetProcAddress函數就能動態獲取到函數的地址。函數導出的方式有兩種,一種是按名字導出,一種是按序號導出。這兩種導出方式在導出表中的描述方式也不相同。模塊的導出函數可以通過Dependency walker工具來查看:
上圖中紅框位置顯示的就是模塊的導出函數,有時候顯示的導出函數名字中有一些符號,像 ??0CP2PDownloadUIInterface@@QAE@ABV0@@Z,這種是導出了C++的函數名,編譯器將名字進行了修飾。
下面看一下導出表的定義吧:
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 image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
結構還算比較簡單,具體每一項的含義如下:
Characteristics:現在沒有用到,一般爲0。
TimeDateStamp:導出表生成的時間戳,由連接器生成。
MajorVersion,MinorVersion:看名字是版本,實際貌似沒有用,都是0。
Name:模塊的名字。
Base:序號的基數,按序號導出函數的序號值從Base開始遞增。
NumberOfFunctions:所有導出函數的數量。
NumberOfNames:按名字導出函數的數量。
AddressOfFunctions:一個RVA,指向一個DWORD數組,數組中的每一項是一個導出函數的RVA,順序與導出序號相同。
AddressOfNames:一個RVA,依然指向一個DWORD數組,數組中的每一項仍然是一個RVA,指向一個表示函數名字。
AddressOfNameOrdinals:一個RVA,還是指向一個WORD數組,數組中的每一項與AddressOfNames中的每一項對應,表示該名字的函數在AddressOfFunctions中的序號。
第一次接觸這個結構的童鞋被後面的5項搞暈了吧,理解這個結構比結構本身看上去要複雜一些,文字描述不管怎麼說都顯得晦澀,所謂一圖勝千言,無圖無真相,直接上圖:
在上圖中,AddressOfNames指向一個數組,數組裏保存着一組RVA,每個RVA指向一個字符串,這個字符串即導出的函數名,與這個函數名對應的是AddressOfNameOrdinals中的對應項。獲取導出函數地址時,先在AddressOfNames中找到對應的名字,比如Func2,他在AddressOfNames中是第二項,然後從AddressOfNameOrdinals中取出第二項的值,這裏是2,表示函數入口保存在AddressOfFunctions這個數組中下標爲2的項裏,即第三項,取出其中的值,加上模塊基地址便是導出函數的地址。如果函數是以序號導出的,那麼查找的時候直接用序號減去Base,得到的值就是函數在AddressOfFunctions中的下標。
用代碼實現如下:
DWORD* CEAT::SearchEAT( const char* szName)
{
if (IS_VALID_PTR(m_pTable))
{
bool bByOrdinal = HIWORD(szName) == 0;
DWORD* pProcs = (DWORD*)((char*)RVA2VA(m_pTable->AddressOfFunctions));
if (bByOrdinal)
{
DWORD dwOrdinal = (DWORD)szName;
if (dwOrdinal < m_pTable->NumberOfFunctions && dwOrdinal >= m_pTable->Base)
{
return &pProcs[dwOrdinal-m_pTable->Base];
}
}
else
{
WORD* pOrdinals = (WORD*)((char*)RVA2VA(m_pTable->AddressOfNameOrdinals));
DWORD* pNames = (DWORD*)((char*)RVA2VA(m_pTable->AddressOfNames));
for (unsigned int i=0; i<m_pTable->NumberOfNames; ++i)
{
char* pNameVA = (char*)RVA2VA(pNames[i]);
if (strcmp(szName, pNameVA) != 0)
{
continue;
}
return &pProcs[pOrdinals[i]];
}
}
}
return NULL;
}
by evil.eagle 轉載請註明出處。