显示关于 IMAGE_EXPORT_DIRECTORY 的信息
Show info about IMAGE_EXPORT_DIRECTORY
我想打印关于 IMAGE_EXPORT_DIRECTORY
的信息,格式如下:
<Name1>,<Ordinal1>,<FileAddress1>
我知道这个图像由 3 个数组组成:
- AddressOfFunctions -(导出地址table,每个元素是一个RVA)
- AddressOfNames -(导出名称指针 table,每个元素也是一个 RVA - 有序?)
- AddressOfNameOrdinals(其中数组的元素 - Base 表示 EAT 中的序数)
但是我如何访问那些 table 以便在一行中打印每个元素的信息?另外,当 NumberOfNames < NumberOfFunctions 时会发生什么?
我已经知道怎么去 IMAGE_EXPORT_DIRECTORY
PIMAGE_DATA_DIRECTORY pFirstDir = &(pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]);
if (pFirstDir->Size > 0)
{
PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)((BYTE *)pDosHeader + ConvertRvaToOffset(pFirstDir->VirtualAddress, pNTHeaders));
}
如果有人能解释这些数组的实际工作原理或如何访问它们的算法,我们将不胜感激。
您可以使用以下 pseudo-C-code:
UINT32 *export_addr_table = (UINT32 *) MAP(pExportDir->AddressOfFunctions);
UINT32 *export_nameptr_table = (UINT32 *) MAP(pExportDir->AddressOfNames);
UINT16 *export_ordinal_table = (UINT16 *) MAP(pExportDir->AddressOfNameOrdinals);
for (SIZE_T i = 0; i < pExportDir->NumberOfFunctions; i++)
{
UINT32 ordinal = pExportDir->Base + i;
UINT32 export_rva = export_addr_table[i];
if (is_forwarder_rva(export_rva))
{
// TODO: special care must be taken here - we cannot resolve directly to a VA unless target module is memory mapped
}
else
{
BOOL found_symname = FALSE;
char symname[MAX_SYMNAME_LEN];
// Loop through all exported names
for (SIZE_T j = 0; j < pExportDir->NumberOfNames; j++)
{
if (export_ordinal_table[j] == i)
{
UINT32 export_symname_rva = export_nameptr_table[j];
const char *export_symname = (const char *) MAP(export_symname_rva);
found_symname = TRUE;
// Copy export_symname into symname (i.e. using strncat or similar)
}
}
if (!found_symname)
{
snprintf(symname, MAX_SYMNAME_LEN, "#%"PRIu32, ordinal);
}
// Print symname, ordinal, address
}
}
在这里,我使用 MAP()
来表示将 RVA 解析为可解引用虚拟地址的操作(类似于您使用 ConvertRvaToOffset
所做的操作)。
解释:
导出的所有函数都存在于导出地址 Table 中,但正如您已经指出的,只有命名函数存在于导出名称指针 Table 和导出序号 Table(平行表)。
导出名称指针 Table 中的条目按词法排序,以便于从导出函数的符号名称快速解析为其 "unbiased" 序数(即索引到导出地址 Table).因此,换一种方式,找到导出函数的导出名称(如果有的话),唯一的解决方案实际上是遍历 每个 导出名称并尝试匹配 RVA。
但是,导出地址 Table 中的一些条目代表所谓的 "forwarder RVA" 条目,它们将导出重定向到另一个 DLL 模块中的符号(参见 PE/COFF 6.3.2 ).这些不能真正以您想要的格式打印,所以我只是在上面的 pseudo-code 中添加了一个 // TODO
用于这种情况。
此外,请注意对于每个导出的函数(按名称和仅按序号导出),此函数的有效符号名称(w.r.t 解析导入查找 Table 条目)是 #OrdinalNumber
,其中 OrdinalNumber
被替换为实际序号(请参阅 PE/COFF 6.3.2 中对 Forwarder RVA 的描述),因此此 可以 将未按名称导出的导出函数的名称列为有意义。
我想打印关于 IMAGE_EXPORT_DIRECTORY
的信息,格式如下:
<Name1>,<Ordinal1>,<FileAddress1>
我知道这个图像由 3 个数组组成:
- AddressOfFunctions -(导出地址table,每个元素是一个RVA)
- AddressOfNames -(导出名称指针 table,每个元素也是一个 RVA - 有序?)
- AddressOfNameOrdinals(其中数组的元素 - Base 表示 EAT 中的序数)
但是我如何访问那些 table 以便在一行中打印每个元素的信息?另外,当 NumberOfNames < NumberOfFunctions 时会发生什么?
我已经知道怎么去 IMAGE_EXPORT_DIRECTORY
PIMAGE_DATA_DIRECTORY pFirstDir = &(pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]);
if (pFirstDir->Size > 0)
{
PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)((BYTE *)pDosHeader + ConvertRvaToOffset(pFirstDir->VirtualAddress, pNTHeaders));
}
如果有人能解释这些数组的实际工作原理或如何访问它们的算法,我们将不胜感激。
您可以使用以下 pseudo-C-code:
UINT32 *export_addr_table = (UINT32 *) MAP(pExportDir->AddressOfFunctions);
UINT32 *export_nameptr_table = (UINT32 *) MAP(pExportDir->AddressOfNames);
UINT16 *export_ordinal_table = (UINT16 *) MAP(pExportDir->AddressOfNameOrdinals);
for (SIZE_T i = 0; i < pExportDir->NumberOfFunctions; i++)
{
UINT32 ordinal = pExportDir->Base + i;
UINT32 export_rva = export_addr_table[i];
if (is_forwarder_rva(export_rva))
{
// TODO: special care must be taken here - we cannot resolve directly to a VA unless target module is memory mapped
}
else
{
BOOL found_symname = FALSE;
char symname[MAX_SYMNAME_LEN];
// Loop through all exported names
for (SIZE_T j = 0; j < pExportDir->NumberOfNames; j++)
{
if (export_ordinal_table[j] == i)
{
UINT32 export_symname_rva = export_nameptr_table[j];
const char *export_symname = (const char *) MAP(export_symname_rva);
found_symname = TRUE;
// Copy export_symname into symname (i.e. using strncat or similar)
}
}
if (!found_symname)
{
snprintf(symname, MAX_SYMNAME_LEN, "#%"PRIu32, ordinal);
}
// Print symname, ordinal, address
}
}
在这里,我使用 MAP()
来表示将 RVA 解析为可解引用虚拟地址的操作(类似于您使用 ConvertRvaToOffset
所做的操作)。
解释:
导出的所有函数都存在于导出地址 Table 中,但正如您已经指出的,只有命名函数存在于导出名称指针 Table 和导出序号 Table(平行表)。
导出名称指针 Table 中的条目按词法排序,以便于从导出函数的符号名称快速解析为其 "unbiased" 序数(即索引到导出地址 Table).因此,换一种方式,找到导出函数的导出名称(如果有的话),唯一的解决方案实际上是遍历 每个 导出名称并尝试匹配 RVA。
但是,导出地址 Table 中的一些条目代表所谓的 "forwarder RVA" 条目,它们将导出重定向到另一个 DLL 模块中的符号(参见 PE/COFF 6.3.2 ).这些不能真正以您想要的格式打印,所以我只是在上面的 pseudo-code 中添加了一个 // TODO
用于这种情况。
此外,请注意对于每个导出的函数(按名称和仅按序号导出),此函数的有效符号名称(w.r.t 解析导入查找 Table 条目)是 #OrdinalNumber
,其中 OrdinalNumber
被替换为实际序号(请参阅 PE/COFF 6.3.2 中对 Forwarder RVA 的描述),因此此 可以 将未按名称导出的导出函数的名称列为有意义。