导出表(Export Table)是Windows可执行文件中的一个结构,记录了可执行文件中某些函数或变量的名称和地址,这些名称和地址可以供其他程序调用或使用。当PE文件执行时Windows装载器将文件装入内存并将导入表中登记的DLL文件一并装入,再根据DLL文件中函数的导出信息对可执行文件的导入表(IAT)进行修正。
导出表中包含了三种信息:
- 函数名称:记录了可执行文件中导出函数的名称,在其他程序中调用时需要用到这个名称。
- 函数地址:记录了可执行文件中导出函数的地址,使用时需要调用该函数的地址。
- 函数序号:记录了每个导出函数的序号,可以通过序号直接调用函数。
导出函数的DLL文件中,导出信息被保存在导出表,导出表就是记载着动态链接库的一些导出信息。通过导出表,DLL文件可以向系统提供导出函数的名称、序号和入口地址等信息,以便Windows装载器能够通过这些信息来完成动态链接的整个过程。
导出函数存储在PE文件的导出表里,导出表的位置存放在PE文件头中的数据目录表中,与导出表对应的项目是数据目录中的首个IMAGE_DATA_DIRECTORY
结构,从这个结构的VirtualAddress
字段得到的就是导出表的RVA值,导出表同样可以使用函数名或序号这两种方法导出函数。
导出表的起始位置有一个IMAGE_EXPORT_DIRECTORY
结构与导入表中有多个IMAGE_IMPORT_DESCRIPTOR
结构不同,导出表只有一个IMAGE_EXPORT_DIRECTORY
结构,该结构定义如下:
typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; DWORD Name; DWORD Base; DWORD NumberOfFunctions; DWORD NumberOfNames; DWORD AddressOfFunctions; DWORD AddressOfNames; DWORD AddressOfNameOrdinals; } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
|
上面的_IMAGE_EXPORT_DIRECTORY
结构如果总结成一张图,如下所示:
在上图中最左侧AddressOfNames
结构成员指向了一个数组,数组里保存着一组RVA,每个RVA指向一个字符串即导出的函数名,与这个函数名对应的是AddressOfNameOrdinals
中的结构成员,该对应项存储的正是函数的唯一编号并与AddressOfFunctions
结构成员相关联,形成了一个导出链式结构体。
获取导出函数地址时,先在AddressOfNames
中找到对应的名字MyFunc1
,该函数在AddressOfNames
中是第1项,然后从AddressOfNameOrdinals
中取出第1项的值这里是1,然后就可以通过导出函数的序号AddressOfFunctions[1]
取出函数的入口RVA,然后通过RVA加上模块基址便是第一个导出函数的地址,向后每次相加导出函数偏移即可依次遍历出所有的导出函数地址,代码如下所示:
int main(int argc, char * argv[]) { BOOL PE = IsPeFile(OpenPeFile("c://pe/lyshark.dll"), 0);
if (PE == TRUE) { DWORD ImageBase = NtHeader->OptionalHeader.ImageBase;
DWORD rav = NtHeader->OptionalHeader.DataDirectory[0].VirtualAddress;
auto ExportTable = (PIMAGE_EXPORT_DIRECTORY)(RVAtoFOA(rav) + GlobalFileBase);
DWORD NameCount = ExportTable->NumberOfNames; DWORD FunctionCount = ExportTable->NumberOfFunctions;
DWORD* Addr_Table = (DWORD*)(RVAtoFOA(ExportTable->AddressOfFunctions) + GlobalFileBase); DWORD* Name_Table = (DWORD*)(RVAtoFOA(ExportTable->AddressOfNames) + GlobalFileBase); WORD* Id_Table = (WORD*)(RVAtoFOA(ExportTable->AddressOfNameOrdinals) + GlobalFileBase);
printf("序号 \t 导出RVA地址 \t 导出VA地址 \t 导出FOA地址 \t 导出函数 \t \n");
for (DWORD i = 0; i < FunctionCount; ++i) { bool HaveName = FALSE;
for (DWORD j = 0; j < NameCount; ++j) { if (i == Id_Table[j]) { HaveName = TRUE; CHAR* Name = (CHAR*)(RVAtoFOA(Name_Table[j]) + GlobalFileBase); printf("%5d \t %10p \t 0x%08X \t 0x%08X \t %-35s \n", i + ExportTable->Base, Addr_Table[i], ImageBase + Addr_Table[i], RVAtoFOA(Addr_Table[i]), Name); break; } } if (HaveName == FALSE) { printf("%5d \t %10p \t 0x%08X \t 0x%08X \t None \n", i + ExportTable->Base, Addr_Table[i], ImageBase + Addr_Table[i], RVAtoFOA(Addr_Table[i])); } } } else { printf("非标准程序 \n"); }
system("pause"); return 0; }
|
运行如上程序片段,则会输出lyshark.dll
动态链接库里面所有的导出函数,其输出效果如下图所示;