导出表的结构
xxxxxxxxxx
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name; // 指向该导出表文件名字符串
DWORD Base; // 导出函数起始序号
DWORD NumberOfFunctions; // 所有导出函数的个数
DWORD NumberOfNames; // 以函数名字导出的函数个数
DWORD AddressOfFunctions; // 导出函数序号表RVA
DWORD AddressOfNames; // 导出函数名称表RVA
DWORD AddressOfNameOrdinals; // 导出函数序号表RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
该表项中的值是RVA
(加上ImageBase
才是函数真正的地址)
以上部分字段对应的关系
对于AddressOfNameOrdinals
表来说:存储的内容 + Base = 函数的导出序号
函数的真正的名字在文件中位置是不确定的,在AddressOfNames
中记录了函数名地址,是有序的
如何根据函数名字获得函数的地址
AddressOfNames
和AddressOfNameOrdinals
是相互对应的。搜索的函数名在AddressOfNames
中的位置是第i
个,那么该名称对应的AddressOfNameOrdinals
中的位置也是第i
个,即两个数组下标是对应相同的
例如图中的红色高亮,索引为2
表示第3个元素,根据记下的下标,找该函数的索引值,也就是寻址到AddressOfNameOrdinals
的第3个元素
将记下的值4
作为AddressOfFunctions
数组的下标,找出该函数的RVA
再进行转换得到VA
解析PE头部代码不变
xxxxxxxxxx
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 0x04);
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
pDataDirectory = (PIMAGE_DATA_DIRECTORY)pOptionHeader->DataDirectory;
通过数据目录记录的导出表RVA,找到对应的FOA地址
xxxxxxxxxx
printf("导出表RVA地址: %#010X\n", pDataDirectory->VirtualAddress);
DWORD FoaExportTable = RvaToFileOffset(pFileBuffer, pDataDirectory->VirtualAddress);
printf("导出表FOA地址: %#010X\n", FoaExportTable);
pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pFileBuffer + FoaExportTable);
RVA转FOA核心代码如下:
PointerToRawData
的偏移xxxxxxxxxx
for (DWORD n = 0; n < numberOfSection; n++)
{
if (RVA < pTempSectionHeader->VirtualAddress)
{
return 0;
}
if (RVA < pTempSectionHeader->VirtualAddress + pTempSectionHeader->Misc.VirtualSize)
{
FOAValue = RVA - pTempSectionHeader->VirtualAddress + pTempSectionHeader->PointerToRawData;
break;
}
pTempSectionHeader++;
}
参考上图,拿到导出表FOA后即可获得其中的三张表:
xxxxxxxxxx
DWORD FoaAddressOfNames = RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfNames);
DWORD FoaAddressOfNameOrdinals = RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfNameOrdinals);
DWORD FoaAddressOfFunctions = RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfFunctions);
根据函数序号表索引和函数地址表对应的规律,遍历两个表进行搜索
xxxxxxxxxx
DWORD ordIndex = -1;
for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++)
{
// ...
}
得到当前索引i
在函数名称表中的偏移
xxxxxxxxxx
PDWORD nameOff = (PDWORD)FoaAddressOfNames + i;
拿到当前索引在函数名称表中具体的值
xxxxxxxxxx
DWORD nameOffset = *(PDWORD)((DWORD)pFileBuffer + (DWORD)nameOff);
保存的值是一个RVA字符串地址,转为FOA
xxxxxxxxxx
LPSTR nameAddr = (LPSTR)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, nameOffset));
如果与我们需要的字符串相等,跳出
xxxxxxxxxx
if (!strcmp(nameAddr, pFunctionOfName))
{
ordIndex = i;
break;
}
对于隐藏函数来说,是找不到函数名序号的
xxxxxxxxxx
if (ordIndex == -1)
{
printf("导出表中没有这个函数名称\n");
return 0;
}
参考图片,此时的索引i
在序号表中对应的值,是函数地址表的索引
拿到序号表中保存的值(注意序号表宽度是WORD
)
xxxxxxxxxx
WORD ord = *(PWORD)((DWORD)pFileBuffer + (DWORD)((PWORD)FoaAddressOfNameOrdinals + ordIndex));
通过序号拿到函数地址RVA
xxxxxxxxxx
DWORD addr = *(PDWORD)((DWORD)pFileBuffer + (DWORD)((PDWORD)FoaAddressOfFunctions + ord));
到此为止代码就完成了
xxxxxxxxxx
DWORD GetFunctionAddrByName(IN PVOID pFileBuffer, IN LPSTR pFunctionOfName)
{
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;
PIMAGE_EXPORT_DIRECTORY pExportDirectory = NULL;
if (!pFileBuffer)
{
return 0;
}
if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
{
return 0;
}
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
if (*((PWORD)((DWORD)pFileBuffer + pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
{
return 0;
}
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 0x04);
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
pDataDirectory = (PIMAGE_DATA_DIRECTORY)pOptionHeader->DataDirectory;
if (!pDataDirectory->VirtualAddress)
{
return 0;
}
printf("导出表RVA地址: %#010X\n", pDataDirectory->VirtualAddress);
DWORD FoaExportTable = RvaToFileOffset(pFileBuffer, pDataDirectory->VirtualAddress);
printf("导出表FOA地址: %#010X\n", FoaExportTable);
pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pFileBuffer + FoaExportTable);
DWORD FoaAddressOfNames = RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfNames);
DWORD FoaAddressOfNameOrdinals = RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfNameOrdinals);
DWORD FoaAddressOfFunctions = RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfFunctions);
DWORD ordIndex = -1;
for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++)
{
PDWORD nameOff = (PDWORD)FoaAddressOfNames + i;
DWORD nameOffset = *(PDWORD)((DWORD)pFileBuffer + (DWORD)nameOff);
LPSTR nameAddr = (LPSTR)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, nameOffset));
if (!strcmp(nameAddr, pFunctionOfName))
{
ordIndex = i;
break;
}
}
if (ordIndex == -1)
{
printf("导出表中没有这个函数名称\n");
return 0;
}
WORD ord = *(PWORD)((DWORD)pFileBuffer + (DWORD)((PWORD)FoaAddressOfNameOrdinals + ordIndex));
DWORD addr = *(PDWORD)((DWORD)pFileBuffer + (DWORD)((PDWORD)FoaAddressOfFunctions + ord));
return addr;
}
运行测试
xxxxxxxxxx
int main()
{
LPVOID pFileBuffer = NULL;
ReadPEFile((CHAR*)TEST_DLL, &pFileBuffer);
DWORD FuncAddr = GetFunctionAddrByName(pFileBuffer, (LPSTR)"curl_easy_getinfo");
printf("%#010X\n", FuncAddr);
return 0;
}
如何通过函数的导出序号获取函数的地址
假设导出序号是10,导出表的Base
是5,那么函数地址数组下标应该是10-5
用下标5去AddressOfFunctions
找到RVA
再进行转换得到VA
于是记录在序号表中的内容为
xxxxxxxxxx
DWORD Sequence = targetOrd - pExportDirectory->Base;
拿到函数地址表FOA
xxxxxxxxxx
DWORD FoaAddressOfFunctions = RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfFunctions);
PDWORD pFoaAddressOfFunctions = (PDWORD)((DWORD)pFileBuffer + FoaAddressOfFunctions);
函数地址表第Sequence
个是输入序号对应的函数地址
xxxxxxxxxx
pFoaAddressOfFunctions += Sequence;
return *pFoaAddressOfFunctions;
完整代码
xxxxxxxxxx
DWORD GetFunctionAddrByOrdinals(PVOID pFileBuffer, DWORD pFunctionOfOrdinal)
{
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;
PIMAGE_EXPORT_DIRECTORY pExportDirectory = NULL;
if (!pFileBuffer)
{
return 0;
}
if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
{
return 0;
}
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
if (*((PWORD)((DWORD)pFileBuffer + pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
{
return 0;
}
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 0x04);
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
pDataDirectory = (PIMAGE_DATA_DIRECTORY)pOptionHeader->DataDirectory;
if (!pDataDirectory->VirtualAddress)
{
return 0;
}
DWORD FoaExportTable = RvaToFileOffset(pFileBuffer, pDataDirectory->VirtualAddress);
pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pFileBuffer + FoaExportTable);
DWORD Sequence = pFunctionOfOrdinal - pExportDirectory->Base;
DWORD FoaAddressOfFunctions = RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfFunctions);
PDWORD pFoaAddressOfFunctions = (PDWORD)((DWORD)pFileBuffer + FoaAddressOfFunctions);
pFoaAddressOfFunctions += Sequence;
return *pFoaAddressOfFunctions;
}