导出表的结构
xxxxxxxxxxtypedef 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头部代码不变
xxxxxxxxxxpDosHeader = (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地址
xxxxxxxxxxprintf("导出表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的偏移xxxxxxxxxxfor (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后即可获得其中的三张表:
xxxxxxxxxxDWORD FoaAddressOfNames = RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfNames);DWORD FoaAddressOfNameOrdinals = RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfNameOrdinals);DWORD FoaAddressOfFunctions = RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfFunctions);根据函数序号表索引和函数地址表对应的规律,遍历两个表进行搜索
xxxxxxxxxxDWORD ordIndex = -1;for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++) { // ...}得到当前索引i在函数名称表中的偏移
xxxxxxxxxxPDWORD nameOff = (PDWORD)FoaAddressOfNames + i;拿到当前索引在函数名称表中具体的值
xxxxxxxxxxDWORD nameOffset = *(PDWORD)((DWORD)pFileBuffer + (DWORD)nameOff);保存的值是一个RVA字符串地址,转为FOA
xxxxxxxxxxLPSTR nameAddr = (LPSTR)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, nameOffset));如果与我们需要的字符串相等,跳出
xxxxxxxxxxif (!strcmp(nameAddr, pFunctionOfName)){ ordIndex = i; break;}对于隐藏函数来说,是找不到函数名序号的
xxxxxxxxxxif (ordIndex == -1){ printf("导出表中没有这个函数名称\n"); return 0;}
参考图片,此时的索引i在序号表中对应的值,是函数地址表的索引
拿到序号表中保存的值(注意序号表宽度是WORD)
xxxxxxxxxxWORD ord = *(PWORD)((DWORD)pFileBuffer + (DWORD)((PWORD)FoaAddressOfNameOrdinals + ordIndex));通过序号拿到函数地址RVA
xxxxxxxxxxDWORD addr = *(PDWORD)((DWORD)pFileBuffer + (DWORD)((PDWORD)FoaAddressOfFunctions + ord));
到此为止代码就完成了
xxxxxxxxxxDWORD 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
于是记录在序号表中的内容为
xxxxxxxxxxDWORD Sequence = targetOrd - pExportDirectory->Base;拿到函数地址表FOA
xxxxxxxxxxDWORD FoaAddressOfFunctions = RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfFunctions);PDWORD pFoaAddressOfFunctions = (PDWORD)((DWORD)pFileBuffer + FoaAddressOfFunctions);函数地址表第Sequence个是输入序号对应的函数地址
xxxxxxxxxxpFoaAddressOfFunctions += Sequence;return *pFoaAddressOfFunctions;
完整代码
xxxxxxxxxxDWORD 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;}