showy的文章

  • 根据PE文件格式从导出表中获取指定导出函数的地址

    背景了解 PE 文件格式,对于做一些数据分析都是比较重要的基础。在 PE 文件格式中,理解导入表以及导出表的工作原理,又是重中之重。理解了 PE 格式的导入表,就可以修改 PE 格式进行 DLL 注入,也可以修改导入表实现 API HOOK 等。理解了 PE 格式的导出表,可以不需要 WIN32 API 函数就可以根据 DLL 加载基址定位出导出函数的名称和导出函数的地址,这在 SHELLCODE 的编写中,比较重要。
    本文主要介绍导出表。给出一个 DLL 的加载基址和导出函数的名称,获取 DLL 中导出函数对应的导出地址。现在我把程序的实现过程整理成文档,分享给大家。
    实现原理我们根据PE文件格式结构中的导出表工作原理来进行实现,实现原理如下所示:

    首先,我们根据 DLL 加载基址,也就是 PE 文件结构的起始地址,从 DOS 头获取 NT 头,并根据 NT 头中的 OptionalHeader.DataDirectory 的导出表选项,获取导出表的偏移位置以及数据大小
    我们来到导出表的数据偏移处,获取导出函数名称的数量以及导出函数名称的地址列表偏移
    接着,我们开始根据导出表中导出名称的地址偏移列表遍历每一个导出函数的名称,与要寻找的导出函数名称进行匹配。若没有找到,则继续匹配下一个函数名称。若找到,则获取对应偏移的 AddressOfNameOrdinals 的值,这个值就是表示该导出函数在导出函数列表中的位置。这样,我们就可以在导出表 AddressOfFunctions 中获取相应的导出函数地址,并结束遍历,执行返回

    其中,在导出表中 AddressOfName 与 AddressOfNameOrdinals 的值是一一对应的,而 AddressOfName 与 AddressOfFunctions 的值并不是一一对应的。因为导出函数中,并不是所有的导出函数都会有名称,有的导出函数只有导出索引而已。所以,PE 结构便创建 AddressOfNameOrdinals 字段来保存有导出函数名称的函数在 AddressOfFunctions 导出函数地址列表中的位置。
    编码实现// 获取DLL的导出函数// lpBaseAddress: 内存DLL文件加载到进程中的加载基址// lpszFuncName: 导出函数的名字// 返回值: 返回导出函数的的地址LPVOID PEGetProcAddress(LPVOID lpBaseAddress, PCHAR lpszFuncName){ LPVOID lpFunc = NULL; // 获取导出表 PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress; PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((BYTE *)pDosHeader + pDosHeader->e_lfanew); PIMAGE_EXPORT_DIRECTORY pExportTable = (PIMAGE_EXPORT_DIRECTORY)((BYTE *)pDosHeader + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); // 获取导出表的数据 PDWORD lpAddressOfNamesArray = (PDWORD)((BYTE *)pDosHeader + pExportTable->AddressOfNames); PCHAR lpFuncName = NULL; PWORD lpAddressOfNameOrdinalsArray = (PWORD)((BYTE *)pDosHeader + pExportTable->AddressOfNameOrdinals); WORD wHint = 0; PDWORD lpAddressOfFunctionsArray = (PDWORD)((BYTE *)pDosHeader + pExportTable->AddressOfFunctions); DWORD dwNumberOfNames = pExportTable->NumberOfNames; DWORD i = 0; // 遍历导出表的导出函数的名称, 并进行匹配 for (i = 0; i < dwNumberOfNames; i++) { lpFuncName = (PCHAR)((BYTE *)pDosHeader + lpAddressOfNamesArray[i]); if (0 == ::lstrcmpi(lpFuncName, lpszFuncName)) { // 获取导出函数地址 wHint = lpAddressOfNameOrdinalsArray[i]; lpFunc = (LPVOID)((BYTE *)pDosHeader + lpAddressOfFunctionsArray[wHint]); break; } } return lpFunc;}
    程序测试我们直接运行程序,获取程序中的 GetModuleHandleA 函数的导出地址,并与 API 函数 GetProcAddress 获取的地址进行比较,结果相同。

    总结这个程序不是很复杂,但是关键是要理解 PE 文件结构的导出表,要理解清楚导出表的工作原理。
    参考参考自《Windows黑客编程技术详解》一书
    1  留言 2018-12-23 17:23:48

发送私信

如果你错过了爱,便错过了生活

11
文章数
11
评论数
eject