PE文件和COFF文件格式分析——导出表
在之前的《PE可选文件头》相关博文中我们介绍了可选文件头中很多重要的属性,而其中一个非常重要的属性是(转载请指明来源于breaksoftware的CSDN博客)
NumberOfFunctions标志导出函数的函数地址数。该数据是非常重要的,我们要知道该文件导出了多少个函数就是要依据这个信息。我们之后会详细说的。
NumberOfNames标志导出函数的函数名数量。
AddressOfFunctions标志导出函数的函数地址表的RVA。
AddressOfNames标志导出函数的函数名表的RVA。
AddressOfNameOrdinals标志导出函数的导出序数表的RVA。
以我电脑上desktmon.dll为例,我们看一下该文件中该结构的布局
我们再用一个图来描述一下PE导出表在View dependencies中显示的相关关系
初次研究这个结构的同学可能会注意一个问题,该结构中有三个表的RVA(AddressOfFunctions,AddressOfNames,AddressOfNameOrdinals),而只给出了其中前两个表的元素个数(NumberOfFunctions,NumberOfNames)。那第三个表——导出序数表的个数是多少?是按导出函数地址表(AddressOfFunctions)中元素个数(NumberOfFunctions)还是按导出函数名称表(AddressOfNames)中元素个数(NumberOfNames)?还有个问题:为什么要设置Base属性?这些问题我们先Mark下。我们先来详细介绍这三个表。
导出地址表。顾名思义,该表中保存了函数入口RVA。但是如果仅仅是如此简单就好了,这个地方保存的还可能是一个指向字符串的RVA!其结构是以下结构体的一个集合。
针对以上问题,可能有人会想到,有多少个导出函数名(以导出函数名的数量为标准)就设置多少个导出地址,导出地址表中数据可以重复,比如上图中ParseXML和XMLParse函数名对应的导出地址都设置成0xXXXXXXXX就行了嘛。如
但是还有个场景:windows平台可以通过序数导入一个函数地址(GerProcAddress的第二个参数传序数),那么这就意味着函数可以没有函数名!!因为序数也可以看成一个函数的编号嘛,虽然这样非常不友好,但是仍然是一种可行的方法。那么如果在这种场景下,我们还能以导出函数名的数量为标准么?不可以了吧,因为函数名表元素数量可能是0!其实这类文件挺多,如mfc40u.dll,见下图
通过以上分析,我们可以得出,我们还是要一个能在导出函数地址表和导出函数名称表建立纽带的结构体。这个我们期待的辅助结构体就是我们下面介绍的导出序数表。
导出序数表。该表保存的是导出地址表的序数偏移!切记这个重要的概念。那这个偏移是相对什么偏移的呢?是针对IMAGE_EXPORT_DIRECTORY::Base属性的。即这个表中保存的值加上Base,就是导出地址表的序数。其结构是以下结构体的一个集合。
我们再把它的PE文件拿出来看下
我们把各个信息提取出来看下:
比如我们试图得到DllGetClassObject的函数地址。我们现在名称表中找到它的index是1。然后在序数表中找到index是1的元素的值0x00000002,。0x00000002要减去Base的值1得到值1。最后在地址表中找到index为1的元素的值,这个值就是DllGetClassObject函数的入口地址。表达式是
发现通过序数去得到Ordinal为3的函数地址时会出错。这儿有两种可能:
A GetProcAddress看到函数地址是0x00000000就认为获取出错
B GetProcAddress是发现序数3不在序数表中(该文件导出序数表为{2,4,6,8},于是返回出错。
那么到底是那种呢?我将这个文件修改成如下,即将Ordinal为3的函数地址修改成一个有效的函数地址,得到一个文件DllTestIndex_Modify
如果是B原因,则此时我们去获取Ordinal为3的函数地址还是会失败。可是结果呢?GetProcAddress成功了,并正确返回了0x00011069这个函数入口地址。这个实验证明A原因是对的。这从而证明Base这个字段,对通过函数名寻找函数入口地址的算法的确是多余的信息。如果真要找个原因,可能从文件大小的说起。PE文件序数表的元素是WORD为单位的,而Base是DWORD。那么就是说,我们最多可以有0x10000(0x0000~0xFFFF)个导出函数。假如这些函数都在导出序数表中有对应元素,且导出序数表每个元素用DWORD描述,则需要sizeof(DWORD)*0x10000的空间。如果采用Base+WORD的方法,则只需要sizeof(WORD)*0x10000+sizeof(DWORD)的空间。采用后者最多可以节省0x3FFF8(0x40000-8)byte空间。其实这个空间很小的,可以忽略不计的。
除了之上那个非常强求的原因,Base就没用了么?不是!我们继续看上面那个例子,从我们DEF文件中看出,我们希望导出的4个函数的序号分别是2、4、8、6。我们看下PE文件中的布局
我们看到信息如下:
Base是0x00000002;NumberOfFunctions是0x00000007;导出函数地址分别为0x00011069、0x00000000、0x000110F5、0x00000000、0x000110A5、0x00000000、0x000110C3;导出名称是按我们在DEF申明的顺序是一致的,分别是:Ret1、Ret2、Ret3、Ret4。导出序数表是0x0000、0x0002、0x0006、0x0004。
注意View Dependencies的Ordinal列,该列的信息是函数地址的Index加上Base的值。于是
当我们如此调用时
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);}// 应该按地址数量来算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;}








