导出表位于数据目录表的第一个位置
导出表结构
typedef struct _IMAGE_EXPORT_DIRECTORY {
+0x0 DWORD Characteristics;
+0x4 DWORD TimeDateStamp;
+0x8 WORD MajorVersion;
+0xa WORD MinorVersion;
+0xc DWORD Name; //指向该导出表文件名 (RVA)
+0x10 DWORD Base;//导出表的起始序号 (序号表的最小序号)
+0x14 DWORD NumberOfFunctions; // 所有导出函数的个数(序号的最大减去最小+1)
+0x18 DWORD NumberOfNames; //以函数名导出的函数的个数 序号表的个数
+0x1c DWORD AddressOfFunctions; //导出函数的地址表偏移(RVA)
+0x20 DWORD AddressOfNames; //函数名称表偏移(RVA)
+0x24 DWORD AddressOfNameOrdinals; //函数序号表偏移(RVA)
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
测试DLL导出函数序号和名字
LIBRARY test
EXPORTS
add @15
sub @11
mul @31
div1 @17
VOID PrintExportTable(PVOID pImageBuffer)
{
DWORD dwAddressOfExportTable = 0;
DWORD dwSizeOfExportTable = 0;
//获取image_export_table 结构的数据
PIMAGE_NT_HEADERS NtHeaders = (PIMAGE_NT_HEADERS)(((PIMAGE_DOS_HEADER)pImageBuffer)->e_lfanew + (ULONG_PTR)pImageBuffer);
if (NtHeaders->OptionalHeader.Magic == 0x10b)
{
dwAddressOfExportTable = ((PIMAGE_OPTIONAL_HEADER32)(&NtHeaders->OptionalHeader))->DataDirectory[0].VirtualAddress;
dwSizeOfExportTable = ((PIMAGE_OPTIONAL_HEADER32)(&NtHeaders->OptionalHeader))->DataDirectory[0].Size;
}
else
{
dwAddressOfExportTable = ((PIMAGE_OPTIONAL_HEADER64)(&NtHeaders->OptionalHeader))->DataDirectory[0].VirtualAddress;
dwSizeOfExportTable = ((PIMAGE_OPTIONAL_HEADER64)(&NtHeaders->OptionalHeader))->DataDirectory[0].Size;
}
if (0 == dwAddressOfExportTable)
{
return;
}
PIMAGE_EXPORT_DIRECTORY pied = (PIMAGE_EXPORT_DIRECTORY)((char*)pImageBuffer + dwAddressOfExportTable);
PULONG AddressOfFunctions = (PULONG)((char*)pImageBuffer + pied->AddressOfFunctions);
PULONG AddressOfNames = (PULONG)((char*)pImageBuffer + pied->AddressOfNames);
PUSHORT AddressOfNameOrdinals = (PUSHORT)((char*)pImageBuffer + pied->AddressOfNameOrdinals);
printf("DLL名字%s 导出Base:%d\n", (char*)(pied->Name +(ULONG64)pImageBuffer), pied->Base);
/*
在内存中的排列
导出地址表
导出名字表
导出序号表
这三个表紧挨着 因此 遍历导出地址表可以AddressOfFunctions < AddressOfNames来判断边界
*/
//导出地址表
int iIndex = 0;
while (AddressOfFunctions < AddressOfNames)
{
if (*AddressOfFunctions)
{
printf("索引:%d 函数地址:0x%x\n", iIndex, *AddressOfFunctions);
}
AddressOfFunctions++;
iIndex++;
}
printf("\n\n");
//导出名字表 函数名字表是按照字母顺序排列的(可以二分法查找)
for (size_t i = 0; i < pied->NumberOfNames; i++)
{
printf("导出函数名:%s\n", (char*)pImageBuffer + AddressOfNames[i]);
}
printf("\n\n");
//导出序号表紧挨着的是 pied->Name 然后是函数名
while (AddressOfNameOrdinals < pied->Name + (ULONG64)pImageBuffer)
{
//实际的序号值是减去了Base
printf("序号表中序号:%d 真正的导出序号:%d\n", *AddressOfNameOrdinals ,*AddressOfNameOrdinals + pied->Base);
AddressOfNameOrdinals++;
}
}
打印的3张表如下
根据名字查找函数地址:
例如查找mul函数:
1:在名字表中对比,mul函数在名字表中的索引是2 (从0开始)
2:在序号表索引2找出函数索引,在这里是20
3:在地址表索引20的位置得到函数地址0xc7a0
根据序号找函数地址
例如查找序号15
1.序号减去base 得到4
2.对比序号表,第0项就是序号4
3.函数名就是名字表第0项 add
4.在地址表索引4找到函数地址0xc7c0