今天遇到了parsingWindows Portable Executable文件结构的一个非常奇怪的问题。 具体在导出表中。
当试图parsingDLL中的导出函数的函数地址时,我发现自己得到了堆栈溢出(所以这看起来像是最合适的QA板)。
我写了我自己的GetProcAddress
版本,它手动parsing,而不是调用现有的GetProcAddress
方法。 请不要只是告诉我使用现有的GetProcAddress
方法,它不适合我目前的情况,我想从中学到一些东西。
对于我遇到的大多数情况,我的版本工作令人钦佩,并没有遇到任何问题。 然而,该函数是针对名为API-MS-Win-Core-ProcessThreads-L1-1-0.dll
(作为Kernel32.dll
的recursion分析的一部分)的DLL进行testing的,这是在发生StackOverflow时发生的。
我已经缩小到从API-MS-Win-Core-ProcessThreads-L1-1-0.dll
导出的以下函数:
CreateRemoteThreadEx
现在,这个导出的函数实际上是一个转发的导出。 通常这是不用担心的; 我写了我的function,以便它应该处理转发的出口。 然而这个function被转发到
api-ms-win-core-processthreads-l1-1-0.CreateRemoteThreadEx
有人在这里看到这个问题? 通过代码,我的GetProcAddress
函数然后调用api-ms-win-core-processthreads-l1-1-0
,然后尝试recursion查找CreateRemoteThreadEx
。 然而,在接下来的迭代中, CreateRemoteThreadEx
函数再次被转发…到
api-ms-win-core-processthreads-l1-1-0.CreateRemoteThreadEx
这样就开始了StackOverflow。 经过多一点调查,我发现调用的结果
LoadLibraryA("api-ms-win-core-processthreads-l1-1-0");
返回与之相同的结果
LoadLibraryA("kernel32.dll");
我很难过
这是我现在的代码:
#include <Windows.h> #define MKPTR(p1,p2) ((DWORD_PTR)(p1) + (DWORD_PTR)(p2)) INT LookupExport(IMAGE_DOS_HEADER* pDosHd, DWORD* pNames, DWORD nNames, LPCSTR lpProcName) { // Do a binary search on the name pointer table INT start = 0, index = -1, middle = -1, end = nNames - 1, cmp = 0; CHAR *pName; while (start <= end && index == -1) { middle = (start + end) >> 1; pName = (CHAR*)MKPTR(pDosHd, pNames[middle]); if ((cmp = strcmp(pName, lpProcName)) == 0) index = middle; // found else if (cmp < 0) start = middle + 1; else end = middle; } return index; } FARPROC InternalGetProcAddress(HMODULE hModule, LPCSTR lpProcName) { BOOL ordinalSearch = HIWORD(lpProcName) == 0; WORD ordinal = 0; IMAGE_DOS_HEADER *pDosHd = (IMAGE_DOS_HEADER*)hModule; if (pDosHd->e_magic != IMAGE_DOS_SIGNATURE) return NULL; IMAGE_NT_HEADERS *pNtHd = (IMAGE_NT_HEADERS*)MKPTR(pDosHd, pDosHd->e_lfanew); if (pNtHd->Signature != IMAGE_NT_SIGNATURE) return NULL; IMAGE_DATA_DIRECTORY directory = pNtHd->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; if (directory.Size == 0 || directory.VirtualAddress == 0) return NULL; IMAGE_EXPORT_DIRECTORY *pExports = (IMAGE_EXPORT_DIRECTORY*)MKPTR(pDosHd, directory.VirtualAddress); if (!ordinalSearch) { INT index = LookupExport(pDosHd, (DWORD*)MKPTR(pDosHd, pExports->AddressOfNames), pExports->NumberOfNames, lpProcName); if (index == -1) return NULL; ordinal = ((WORD*)MKPTR(pDosHd, pExports->AddressOfNameOrdinals))[index]; } else { ordinal = LOWORD(lpProcName); } INT delta = pExports->Base - 1; DWORD dwAddress = ((DWORD*)MKPTR(pDosHd, pExports->AddressOfFunctions))[ordinal - delta]; // Check whether forwarded: if (dwAddress >= directory.VirtualAddress && dwAddress < (directory.VirtualAddress + directory.Size)) { CHAR pForward[256]; strcpy(pForward, (CHAR*)MKPTR(pDosHd, dwAddress)); CHAR *pFunction = strchr(pForward, '.'); if (pFunction == NULL) return NULL; // break into seperate parts and recurse *pFunction++ = 0; return InternalGetProcAddress(LoadLibraryA(pForward), pFunction); } return (FARPROC)MKPTR(hModule, dwAddress); }
任何有识之士将不胜感激。
好的,在遵循@ sergmat的建议之后,我看了一下API Set文档(在这里可以找到任何感兴趣的人)。 我现在修改了我的GetProcAddress代码来对Api Set表做一个简单的查询。
#include <Windows.h> #define MKPTR(p1,p2) ((DWORD_PTR)(p1) + (DWORD_PTR)(p2)) typedef struct _stripped_peb32 { BYTE unused1[0x038]; PVOID ApiSet; BYTE unused2[0x1AC]; } PEB32; typedef struct _stripped_peb64 { BYTE unused1[0x068]; PVOID ApiSet; BYTE unused2[0x23C]; } PEB64; typedef struct _PROCESS_BASIC_INFORMATION { PVOID Reserved1; LPVOID PebBaseAddress; PVOID Reserved2[2]; ULONG_PTR UniqueProcessId; PVOID Reserved3; } PROCESS_BASIC_INFORMATION; typedef struct _api_set_host { DWORD ImportmoduleeName; WORD ImportmoduleeNameLength; DWORD HostmoduleeName; WORD HostmoduleeNameLength; } API_SET_HOST; typedef struct _api_set_host_descriptor { DWORD NumberOfHosts; API_SET_HOST Hosts[1]; } API_SET_HOST_DESCRIPTOR; typedef struct _api_set_entry { DWORD Name; WORD NameLength; DWORD HostDescriptor; } API_SET_ENTRY; typedef struct _api_set_header { DWORD unknown1; DWORD NumberOfEntries; API_SET_ENTRY Entries[1]; } API_SET_HEADER; typedef NTSTATUS (__stdcall *fnNtQueryInformationProcess)(HANDLE ProcessHandle, DWORD ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength); API_SET_HEADER *GetApiSetHeader() { fnNtQueryInformationProcess NtQueryInformationProcess = (fnNtQueryInformationProcess)GetProcAddress(LoadLibraryW(L"ntdll.dll"), "NtQueryInformationProcess"); if (!NtQueryInformationProcess) return NULL; PROCESS_BASIC_INFORMATION info; if (NtQueryInformationProcess(GetCurrentProcess(), 0, &info, sizeof(info), NULL) != S_OK) return NULL; #if defined(_WIN32) return (API_SET_HEADER*)(((PEB32*)info.PebBaseAddress)->ApiSet); #elif defined(_WIN64) return (API_SET_HEADER*)(((PEB64*)info.PebBaseAddress)->ApiSet); #else return NULL; // unsupported architecture #endif } HMODULE ResolveImportMap(LPCSTR lpmoduleeName) { API_SET_HEADER *pHeader = GetApiSetHeader(); if (pHeader == NULL) return NULL; API_SET_ENTRY *pEntry = pHeader->Entries; API_SET_HOST_DESCRIPTOR* pDescriptor; wchar_t module[128]; // First, normalize the LPCSTR, the API Set table doesn't have the API- prefix if (strnicmp("api-", lpmoduleeName, 4) == 0) lpmoduleeName += 4; // Next convert the LPCSTR to a unicode string for comparison and remove the extension (if found) mbstowcs(module, lpmoduleeName, sizeof(module) / sizeof(wchar_t)); wchar_t *dot = wcsrchr(module, L'.'); if (dot) *dot = L'\0'; // Begin the lookup: // todo: implement a case-insensitive binary search, not much to be gained for the effort IMO as there's // only 35 entries in the current version of Windows 7, but the option is there for performance nuts. for(unsigned long i = 0; i < pHeader->NumberOfEntries; ++i, ++pEntry) { // Check the top-level host map if (wcsnicmp(module, (const wchar_t*)MKPTR(pHeader, pEntry->Name), pEntry->NameLength) == 0) { pDescriptor = (API_SET_HOST_DESCRIPTOR*)MKPTR(pHeader, pEntry->HostDescriptor); // iterate backwards through the hosts to find the most important one (I think this is naive) for(unsigned long j = pDescriptor->NumberOfHosts; j > 0; --j) { if (pDescriptor->Hosts[j - 1].HostmoduleeNameLength) { memcpy(module, (const void*)MKPTR(pHeader, pDescriptor->Hosts[j - 1].HostmoduleeName), pDescriptor->Hosts[j - 1].HostmoduleeNameLength); module[pDescriptor->Hosts[j - 1].HostmoduleeNameLength / sizeof(wchar_t)] = L'\0'; return GetmoduleeHandleW(module); // All the modules should already be loaded, so use GetmoduleeHandle rather than LoadLibrary } } } } return NULL; } INT LookupExport(IMAGE_DOS_HEADER* pDosHd, DWORD* pNames, DWORD nNames, LPCSTR lpProcName) { // Do a binary search on the name pointer table INT start = 0, index = -1, middle = -1, end = nNames - 1, cmp = 0; CHAR *pName; while (start <= end && index == -1) { middle = (start + end) >> 1; pName = (CHAR*)MKPTR(pDosHd, pNames[middle]); if ((cmp = strcmp(pName, lpProcName)) == 0) index = middle; else if (cmp < 0) start = middle + 1; else end = middle; } return index; } FARPROC InternalGetProcAddress(HMODULE hmodulee, LPCSTR lpProcName) { if (hmodulee == NULL) return NULL; BOOL ordinalSearch = HIWORD(lpProcName) == 0; WORD ordinal = 0; IMAGE_DOS_HEADER *pDosHd = (IMAGE_DOS_HEADER*)hmodulee; if (pDosHd->e_magic != IMAGE_DOS_SIGNATURE) return NULL; IMAGE_NT_HEADERS *pNtHd = (IMAGE_NT_HEADERS*)MKPTR(pDosHd, pDosHd->e_lfanew); if (pNtHd->Signature != IMAGE_NT_SIGNATURE) return NULL; IMAGE_DATA_DIRECTORY directory = pNtHd->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; if (directory.Size == 0 || directory.VirtualAddress == 0) return NULL; IMAGE_EXPORT_DIRECTORY *pExports = (IMAGE_EXPORT_DIRECTORY*)MKPTR(pDosHd, directory.VirtualAddress); if (!ordinalSearch) { INT index = LookupExport(pDosHd, (DWORD*)MKPTR(pDosHd, pExports->AddressOfNames), pExports->NumberOfNames, lpProcName); if (index == -1) return NULL; ordinal = ((WORD*)MKPTR(pDosHd, pExports->AddressOfNameOrdinals))[index]; } else { ordinal = LOWORD(lpProcName); } INT ordbase = pExports->Base - 1; DWORD dwAddress = ((DWORD*)MKPTR(pDosHd, pExports->AddressOfFunctions))[ordinal - ordbase]; // Check whether forwarded: if (dwAddress >= directory.VirtualAddress && dwAddress < (directory.VirtualAddress + directory.Size)) { CHAR pForward[256]; strcpy(pForward, (CHAR*)MKPTR(pDosHd, dwAddress)); CHAR *pFunction = strchr(pForward, '.'); if (pFunction == NULL) return NULL; // break into seperate parts and recurse *pFunction++ = 0; // check if ordinal-forwarded if (*pFunction == '#') pFunction = (PSTR)(unsigned short)(atoi(++pFunction)); HMODULE hDestination = LoadLibraryA(pForward); // detect an infinite loop, the forward declaration requests the same module handle with // the same function lookup, this could be an Api Set (Windows7+) if (hDestination == hmodulee && (ordinalSearch ? LOWORD(pFunction) == LOWORD(lpProcName) : strcmp(pFunction, lpProcName) == 0)) hDestination = ResolveImportMap(pForward); // ResolveImportMap will return NULL if not an API Set and so avoid infinite recursion return InternalGetProcAddress(hDestination, pFunction); } return (FARPROC)MKPTR(hmodulee, dwAddress); }