深入理解PE结构中导出表Image_Export_Table(EAT)

一个PE文件生成之后,里面不仅仅有我们自己编写的代码,还有编译器往节区里面填充一些信息,这些数据包括:

这个PE引用了外部的哪些dll

我们这个exe提供一些函数供外部使用

………………等等内容

如果编译器只是胡乱的把这些重要信息放在PE中,那么程序可以运行不起来。

因此就就使用数据目录表 记录了编译器把16种不同的信息存在哪里。

这样程序在使用的时候就能找到。

因此数据目录表,代表了16种不同类型的数据信息。

 导出表:这个程序提供了多少函数供外部使用,这些函数地址在哪里。

以函数名称查找函数地址:

    当以函数名称查找函数地址的时候,首先遍历AddressOfNames这个表,这个表里存的是导出函数名称的RVA,需要先转成FOA,然后在磁盘文件中以FOA偏移找到函数名称,

    然后再以我们要查找的函数名称与之比较,依次遍历直至找到,找到之后,要记住这个函数名称在AddressOfNames表中的序号m,然后在AddressOfNameOrdinals这个表中,相同序号的位置取出真正的序号值,

             AddressOfNameOrdinals[m]  。这个值再当做AddressOfFunctions表的序号,里面存的才是我们要找的函数的地址RVA(加上ImageBase才是函数真正的地址)。

             AddressOfNameOrdinals表和AddressOfNames表大小是一样的,其实这个AddressOfNameOrdinals表相当于一个中介,主要是供AddressOfNames表使用的。

            而以序号查函数地址也是直接使用AddressOfFunctions,与AddressOfNameOrdinals表没什么关系。

以函数序号查找函数地址: 

            当以函数序号查找的时候,知己把输入的序号值减去Base得到一个值作为索引,这个索引作为AddressOfFunctions表中的索引,直接取出来就是我们要找的函数的地址RVA。

我们之前编程写导出程序的时候:

      当定义函数名称导出的时候,定义几个以函数名称导出的函数,AddressOfNames表就会有几个值,AddressOfNames这个表是连续的。

           AddressOfNameOrdinals这个表里面存的值不是真正的序号,里面存的值,是我们在源代码中定义的序号值减去Base(以上图为例2-2=0,0x1e-2=0x1C),这个值加上Base就是我们源码中定义的序号。

            AddressOfFunctions 这个表里的个数是我们在源代码中定义的序号值最大值-最小值+1(0x1E - 0x2 + 1 =0x1D )

这3张表设计的很巧妙。

理解这个函数查找地址的过程:就明白EAT表的结构:

unsigned int getProcAddress(void* FileBuffer,char* FunctionName)
{
unsigned int* NTOffset = (unsigned int*)((char*)FileBuffer + 60);

PE_ntHeader* lpNtHeader=(PE_ntHeader*)((char*)FileBuffer + *NTOffset);

int SearchSign = 0;

if(lpNtHeader->SizeofOptionalHeader == 0xE0)
{
PE_OptionalHeader32* lpOptionHeader =(PE_OptionalHeader32*)((char*)FileBuffer + *NTOffset + 24);

//找到Export Struct的RVA,转成FOA,
unsigned int Export_Table_Offset = RvaToFoa(FileBuffer,lpOptionHeader->DataDirectory[0].RVA);
//在磁盘文件中根据FOA找到Export Struct
Image_Ex_Directory* lpExportTable = (Image_Ex_Directory*)((char*)FileBuffer + Export_Table_Offset);
//然后开始找3张表

printf("PE文件名:%s\n",(char*)FileBuffer+RvaToFoa(FileBuffer,lpExportTable->Name));
//Names表在文件中的偏移
unsigned int* NamesTable= (unsigned int*)((char*)FileBuffer + RvaToFoa(FileBuffer,lpExportTable->AddressOfNames));
unsigned short* OrdinalTable = (unsigned short*)((char*)FileBuffer + RvaToFoa(FileBuffer,lpExportTable->AddressOfNameOrdinals));
unsigned int* AddressFunctionTable = (unsigned int*)((char*)FileBuffer + RvaToFoa(FileBuffer,lpExportTable->AddressOfFunctions));

int dwName = (int)(FunctionName);
//因为如果是序号,那么char* FunctionName =0x0019
//如果是字符串,那么char* FunctionName=0x12345678
if((dwName & 0xFFFF0000)==0)
{
goto Ordinals;
}

for(int i=0;i<lpExportTable->NumberOfNames;i++)
{
//printf("NamesTable:%08x\n",NamesTable[i]);
if(strcmp(FunctionName,(char*)FileBuffer+RvaToFoa(FileBuffer,NamesTable[i])) == 0)
{
SearchSign = 1;
printf("函数的RVA地址为:%08x\n,实际地址为ImageBase+RVA:%08x",AddressFunctionTable[OrdinalTable[i]],lpOptionHeader->ImageBase+AddressFunctionTable[OrdinalTable[i]]);
return AddressFunctionTable[OrdinalTable[i]];
//break;
}else
continue;
}

return 0;

Ordinals:

if(dwName<lpExportTable->Base || dwName>lpExportTable->Base+lpExportTable->NumberOfFunctions-1)
{
return 0;
}else
{
printf("以序号找到的地址:%08x\n",AddressFunctionTable[dwName-lpExportTable->Base]);
return AddressFunctionTable[dwName-lpExportTable->Base];
}
}

posted @ 2023-07-26 10:11  一日学一日功  阅读(176)  评论(0)    收藏  举报