PE文件和COFF文件格式分析——导出表

December 17, 2023
测试
测试
测试
测试
23 分钟阅读

      在之前的《PE可选文件头》相关博文中我们介绍了可选文件头中很多重要的属性,而其中一个非常重要的属性是(转载请指明来源于breaksoftware的CSDN博客)

IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; 

        该数组保存了如下节(不一定全包括,要以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

        我们在之后会介绍各个节的结构和相关应用,本文我将介绍该数组中第一个元素(DataDirectory[0])的信息——导出表信息。         首先我介绍下导出表。我们做程序时,新手一般喜欢做的是copy+paste。这个方法在代码结构不是很复杂的时候还能过的去。如果像微软这样的系统也是这么写,我想我们的黑客和漏洞挖掘者会非常高兴了——因为这样必定会产生更多的漏洞和bug。因为这样写的代码非常难维护。打个比方,我们有个函数实现了对XML的解析,有ABCDE这么多业务方去copy了这段代码。若干年后某天XML规则发生了改变,我们要修正XML解析算法,这个时候可能由于原来引入该段代码的员工离职了或者时间久远等原因,ABCDE各方都不知道自己的逻辑中用了XML,更不知道要去修正为新的算法。于是如何解决呢?ABCDE方应该让XML解析算法编写者提供一个.h和.cpp文件,里面包含了我们可能会调用的XML算法,然后在各自的代码中include这个XML算法编写者维护的目录下的这个.h文件,并调用.h中的方法。这样,以后XML算法即使改了,各业务方也可以保证我们使用的算法是最新的。但是还别高兴的太早,还有个问题放在我们面前。如果我们的程序是一个独立的Exe发布的话,在后续升级时会带来些麻烦。比如我们发布的Exe文件是1G,可是发布后我们发现一行代码写错了,于是我改了这行代码,却要让用户升级一个1G的文件!!流量啊!怎么办?为了便于升级,我们还是把1G文件合理分割成若干个文件,并保证它们可以协同工作。DLL就是这样被拆分出来的文件中一个非常重要的组成部分,它里面的导出函数就如同供其他方调用的XML解析的各种方法。导出表就是用于保存这些方法的名称和地址等信息的地方。

        现在我们来说下导出表节的结构。在DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]中保存了导出表节的相对虚拟偏移RVA和大小,在之后的章节中我们会发现除了DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]保存的是RA(相对文件头的偏移),其他都是RVA。通过该RVA,我们算出RA,从而得到一个描述导出表头的结构体信息,该结构体是

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。

        TimeDataStamp保存的生成导出信息的时间。

        MajorVersion和MinorVersion分别是主版本号和此版本号。这些信息是我们可以决定的。

        Name字段保存的该导出文件的名称的偏移。这儿要注意一点,这个地址是系统不关心的,我们可以将其指向的地址设置为违法的地址,这样会干扰部分PE分析工具的分析结果。

        Base是导出函数的起始序数值,该值一般为1。如我们用View dependencies打开一个文件,红色部分就是Base字段相关的

        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!其结构是以下结构体的一个集合。

// 导出表信息
typedef struct _IMAGE_Export_Address_Table_
{
    union {
        DWORD dwExportRVA;
        DWORD dwForwarderRVA;
    };
}IMAGE_Export_Address_Table, *pIMAGE_Export_Address_Table;

        如果它保存的是导出函数入口地址,那没什么好说的。我们说下它保存的是指向一个字符串的偏移的情况。在我的XP系统下Kernel32.dll中AddVectoredExceptionHandler函数的导出函数地址指向的字符串是NTDLL.RtlAddVectoredExceptionHandler。看到这样的名字组合,我想你大概能猜出个眉目。AddVectoredExceptionHandler函数,在Kernel32.dll文件内部是没有实现的。但是如果有程序需要加载Kernel32.dll并需要调用这个函数,则这样的写法会告诉加载器在加载Kernel32.dll时,要将AddVectoredExceptionHandler函数的地址直接改成Ntdll.dll中的RtlAddVectoredExceptionHandler函数地址(即自动加载Ntdll.dll)。这个特性非常有趣吧!我想做加壳的朋友应该对这个场景很熟悉。我之后会介绍利用这个特性去隐性自动加载DLL。最后说一下,我们如何辨别这个字段保存的是函数的入口地址的RVA还是字符串呢?只要判断该偏移不在导出表节中即可:指向的地址在节中就是字符串的RVA;在节外是函数入口的RVA。

导出名称表。计算机做出来是给人用的,如果给人一堆010101这样的数据,我想没谁会有太多兴趣去看的。于是出于人性化考虑,人们发明了别名,比如发明了汇编映射二进制指令,从而帮助理解程序逻辑。导出名称表就是出于这样的考虑而设计的。其结构是以下结构体的一个集合。

typedef struct _IMAGE_Export_Name_Pointer_Table_ {
    DWORD dwPointer;
}IMAGE_Export_Name_Pointer_Table,*pIMAGE_Export_Name_Pointer_Table;

        它是指向字符串的RVA,该字符串是以\0结尾的。

        说到这儿,我觉得我们可以停下思考一个问题,是不是只要有这两个表就够了?如果对于我们自己编写的且非常标准的DLL,只要有这两个表的确是够了。你想,当我们调用GetProcAddress时,我们在导入名称表中找到该名称对应的index,然后再返回导出函数地址表中该index的数据即可。

lpFunc = ExportAddressTable[ExportNameTable.find(FuncName)]

        但是,PE文件设计的远没有这么简单。如果如此简单,那很多事都好办了。举一个特殊的例子来推翻这种简单的场景: 函数入口地址和函数名之间的关系是1对N(0~n)。我们程序运行起来后,很多时候是要调用其他逻辑,即函数入口。可以说一个函数入口可以唯一标注一个逻辑。而我们经常说的某某API,其实只是某个函数过程的一个名字。比如我们一个实现XML解析的函数,我们可以叫做ParseXML,也可以叫XMLParse。不管是叫哪个名字,该函数的功能是不变的,它的入口地址是不变的。如果入口地址变了,那就是另外一个函数了。这就是为什么说函数入口地址和函数名之间是1对N的关系。

        针对以上问题,可能有人会想到,有多少个导出函数名(以导出函数名的数量为标准)就设置多少个导出地址,导出地址表中数据可以重复,比如上图中ParseXML和XMLParse函数名对应的导出地址都设置成0xXXXXXXXX就行了嘛。如

但是还有个场景:windows平台可以通过序数导入一个函数地址(GerProcAddress的第二个参数传序数),那么这就意味着函数可以没有函数名!!因为序数也可以看成一个函数的编号嘛,虽然这样非常不友好,但是仍然是一种可行的方法。那么如果在这种场景下,我们还能以导出函数名的数量为标准么?不可以了吧,因为函数名表元素数量可能是0!其实这类文件挺多,如mfc40u.dll,见下图

        通过以上分析,我们可以得出,我们还是要一个能在导出函数地址表和导出函数名称表建立纽带的结构体。这个我们期待的辅助结构体就是我们下面介绍的导出序数表。

  导出序数表。该表保存的是导出地址表的序数偏移!切记这个重要的概念。那这个偏移是相对什么偏移的呢?是针对IMAGE_EXPORT_DIRECTORY::Base属性的。即这个表中保存的值加上Base,就是导出地址表的序数。其结构是以下结构体的一个集合。

typedef struct _IMAGE_Export_Ordinal_Table_ {
    WORD dwOrdinal;
}IMAGE_Export_Ordinal_Table,*pIMAGE_Export_Ordinal_Table;

        从这个表的命名(AddressOfNameOrdinals )看,应该可以发现这个表应该和导出名称表存在一定的关系!是的,它的元素的数量和导出名称表的元素数量是一样的。可能有人会疑问,什么这个表元素的个数不是和导出地址表元素个数一致呢?因为如上面所说,一个函数过程可以对应多个函数名,如果导出序数表元素个数和导出函数地址表元素个数一样,则无法让地址与函数名对应上。比如我们导出地址表有1个函数入口,而我们有2个函数名都指向这个地址,那么导出序数表个数如果是1,则如何表示这两个名称与函数入口的对应呢?如果导出序数表格式是2个,则我们可以让这两个元素都“指向”同一个导出函数入口即可。OK,这儿我就解答了上面我们Mark过的那个问题:导出序数表个数和导出名称表个数一致。

       那么这三个表之间具体什么关系呢?我首先以一个简单的、常规的文件为例,这个文件是上面提到的deskmon.dll。我们看一下View Dependencies的分析结果:

        我们再把它的PE文件拿出来看下

        我们把各个信息提取出来看下:

Characteristics;        0x00000000
TimeDateStamp;          0x3B7D74B7
MajorVersion;           0x0000
MinorVersion;           0x0000
Name;                   0x00002E6C
Base;                   0x00000001
NumberOfFunctions;      0x00000002 
NumberOfNames;          0x00000002
AddressOfFunctions;     0x00002E58
AddressOfNames;         0x00002E60
AddressOfNameOrdinals;  0x00002E68

        可以看到这个Dll的导出地址表有2个元素,导出名称表和导出序数表也是有2个元素的。用之前《PE文件和COFF文件格式分析——RVA和RA相互计算》介绍的算法,我们可以得出

        导出地址表RVA(0x00002E58)对应的RA是0x00002258。两个元素分别为{ {0,0x0002218},{1,0x00002534}}。和View Dependencis分析结果对比发现,这组数据是一致的。

        导出名称表RVA(0x00002E60)对应的RA是0x00002260,其数据是{0,0x00002E78}和{1,0x00002E88}。0x00002E78是函数名的RVA,其对应的RA是0x00002278,即字符串“DllCanUnloadNow”;0x00002E88也是函数名称的RVA,其RA是0x00002288,即字符串“DllGetClassObject”。于是可以把导出函数名表看成{{0,DllCanUnloadNow},{1,DllGetClassObject}}。这个数据和View Dependencies中信息一致。

        导出序数表RVA(0x00002E68)对应的RA是0x00002268,其数据是{{0,0x0000},{1,0x0001}}。但是这并不是最终数据,刚才我在介绍导出序数表时,说过这个表保存的是相对Base的偏移,该文件的Base是1,于是真实的数据是{{0,0x0001},{1,0x0002}}。

       我们用图来说一下这三者的关系。

       比如我们试图得到DllGetClassObject的函数地址。我们现在名称表中找到它的index是1。然后在序数表中找到index是1的元素的值0x00000002,。0x00000002要减去Base的值1得到值1。最后在地址表中找到index为1的元素的值,这个值就是DllGetClassObject函数的入口地址。表达式是

i = Search_ExportNamePointerTable (ExportName);
ordinal = ExportOrdinalTable [i];
SymbolRVA = ExportAddressTable [ordinal - OrdinalBase];

        看了上面的逻辑, 我们在序数表中“加上”Base,然后要通过名称去找函数入口时又要从序数表中“减去”Base,是不是这儿Base是多余的?如果单从通过名称获取函数地址来看,Base的确是多余的。那么如果通过序数来获取函数地址呢?我构造了一个DLL——DllTestIndex.dll

LIBRARY	"DllTestIndex"
EXPORTS
	Ret1 @2.
	Ret2 @4
	Ret3 @8
	Ret4 @6

        发现通过序数去得到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的值。于是

      当我们如此调用时

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;
}

        此时GetProcAddress的第二个参数就是上图中间一列的信息,即View Dependencies的Ordinals信息。这儿要特别注意这个Ordinals和导出序数表(AddressOfNameOrdinals指向的表)不是一个东西。这样我就解答了上面我们Mark的那个问题——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;
}

继续阅读

更多来自我们博客的帖子

如何安装 BuddyPress
由 测试 December 17, 2023
经过差不多一年的开发,BuddyPress 这个基于 WordPress Mu 的 SNS 插件正式版终于发布了。BuddyPress...
阅读更多
Filter如何工作
由 测试 December 17, 2023
在 web.xml...
阅读更多
如何理解CGAffineTransform
由 测试 December 17, 2023
CGAffineTransform A structure for holding an affine transformation matrix. ...
阅读更多