PE文件结构详解(三)PE导出表
上篇文章 PE文件结构详解(二)可执行文件头 的结尾出现了一个大数组,这个数组中的每一项都是一个特定的结构,通过函数获取数组中的项可以用RtlImageDirectoryEntryToData函数,DataDirectory中的每一项都可以用这个函数获取,函数原型如下:
PVOID NTAPI RtlImageDirectoryEntryToData(PVOID Base, BOOLEAN MappedAsImage, USHORT Directory, PULONG Size);
Base:模块基地址。
MappedAsImage:是否映射为映象。
Directory:数据目录项的索引。
上图中红框位置显示的就是模块的导出函数,有时候显示的导出函数名字中有一些符号,像 ??0CP2PDownloadUIInterface@@QAE@ABV0@@Z,这种是导出了C++的函数名,编译器将名字进行了修饰。
下面看一下导出表的定义吧:
在上图中,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;}