导出的函数已转发到自身
Exported function forwarded to itself?
我今天在解析Windows可移植可执行文件结构时遇到了一个非常奇怪的问题。具体在"导出"表中。
当我试图解析DLL中导出函数的函数地址时,我发现自己遇到了堆栈溢出(所以这似乎是最合适的QA板)。
我已经编写了自己版本的GetProcAddress
,它手动进行解析,而不是调用现有的GetProcAddress
方法。请不要只是告诉我使用现有的GetProcAddress
方法,它不适合我目前的情况,我想从中学习一些东西。
对于我遇到的大多数情况,我的版本都运行得很好,没有遇到任何问题。然而,该函数是针对名为API-MS-Win-Core-ProcessThreads-L1-1-0.dll
的DLL进行测试的(作为Kernel32.dll
递归解析的一部分),此时发生了StackOverflow。
我已经将其缩小到以下从API-MS-Win-Core-ProcessThreads-L1-1-0.dll
导出的函数:
CreateRemoteThreadEx
现在,这个导出的函数实际上是一个转发导出。通常情况下,这并不令人担忧;我已经编写了我的函数,以便它能够处理转发的导出。但是,此功能被转发到
api-ms-win-core-processthreads-l1-1-0.CreateRemoteThreadEx
有人看到这里的问题吗?通过代码,我的GetProcAddress
函数在api-ms-win-core-processthreads-l1-1-0
上调用LoadLibrary
,然后尝试递归查找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);
}
如有任何见解,我们将不胜感激。
好吧,在遵循@sercgmat的建议后,我看了一看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 ImportModuleName;
WORD ImportModuleNameLength;
DWORD HostModuleName;
WORD HostModuleNameLength;
} 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 lpModuleName)
{
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-", lpModuleName, 4) == 0)
lpModuleName += 4;
// Next convert the LPCSTR to a unicode string for comparison and remove the extension (if found)
mbstowcs(module, lpModuleName, sizeof(module) / sizeof(wchar_t));
wchar_t *dot = wcsrchr(module, L'.');
if (dot) *dot = L' ';
// 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].HostModuleNameLength)
{
memcpy(module, (const void*)MKPTR(pHeader, pDescriptor->Hosts[j - 1].HostModuleName), pDescriptor->Hosts[j - 1].HostModuleNameLength);
module[pDescriptor->Hosts[j - 1].HostModuleNameLength / sizeof(wchar_t)] = L' ';
return GetModuleHandleW(module); // All the modules should already be loaded, so use GetModuleHandle 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 hModule, LPCSTR lpProcName)
{
if (hModule == NULL)
return NULL;
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 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 == hModule && (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(hModule, dwAddress);
}
- 将函数参数完美转发到函数指针:按值传递呢?
- 在按值调用 (c++) 中转发构造函数参数
- 使用函数指针转发声明作为 lamba 声明
- 完美的转发和构造函数
- 我可以在构造函数的主体中转发构造吗?
- 通过类型别名从构造函数转发模板推导
- 在可变参数函数中转发特定范围的参数
- 为什么添加析构函数(甚至是空的)会破坏我的结构,该结构使用 ref 转发和折叠来保存 ref 或值的副本?
- 如何在程序集函数中将元素数组作为参数传递时转发 ARM 寄存器的地址指针
- 如何通过可变参数模板将多个构造函数参数转发到数组初始值设定项列表?
- 类似函数的化简函数中的转发和返回类型
- 在编写包装现有函数并检查错误的模板函数时,如何使用完美转发?
- 在完美转发函数中公开参数类型,避免代码重复
- 从转发函数类型推断的模板化返回类型
- 完美转发函数以构建函数列表类
- 转发函数指针
- 如何在此转发函数中自动推导T
- 如何在 C++11 中使用可变参数模板正确创建显式参数转发函数
- 可以 QSignalMapper 转发函数参数
- 何时应该转发函数调用