介绍 你可能已经被警告过,不要用LoadLibrary()加载可执行文件,你可能尝试这么做过,然后程序就崩溃了,所以你可能会认为这是不可能的。 但实际上这是可行的,本文就将介绍具体的方法。 
声明 这好像跟微软说的有点不一样。实际上,微软没说不要加载,他们只是说“不要用LoadLibrary()加载可执行文件,应该用CreateProcess() ”。不过除非你很清楚你在做什么,否则不要把这用在产品代码中,我已经警告过你了| 准备可执行文件 首先要做的是把可执行文件标记为可重定位的文件,能够从任何的基地址(任何DLL)加载。你可以用/FIXED:NO来实现,如果想要提高安全性,还可以使用/DYNAMICBASE(默认就是开启的)。EXE文件可能设置了/FIXED:YES,那样的话exe就只能在它的首选基地址加载了,如果没有用/BASE设置过的话这个地址就是0×400000。 下一步的准备工作就是要我们需要从另外的exe文件调用的函数,这跟调DLL很类似 extern "C" void __stdcall some_func()
{
...
}
#ifdef _WIN64
#pragma comment(linker, "/EXPORT:some_func=some_func")
#else
#pragma comment(linker, "/EXPORT:some_func=_some_func@0")
#endif使用LoadLibrary加载可执行文件 在使用LoadLibraryEx()加载可执行文件时候,不要指定LOAD_LIBRARY_AS_DATAFILE或者LOAD_LIBRARY_AS_IMAGE_RESOURCE,如果这么做的话,exe中的导出的函数就不能成功导出,而执行GetProcAddress()时就会失败。 调用LoadLibrary()后,我们就可以得到一个有效的HINSTANCE handle。但是当我们用LoadLibrary()加载exe文件时,以下两件关键的事没有发生: 1. CRT运行库没有初始化,包括所有全局变量
2. 导入地址表没有正确配置,这就意味着所有对导入函数的调用就会导致崩溃 更新导入表 首先我们得要更新可执行文件的导入表。下面的程序片段展示了其过程: void ParseIAT(HINSTANCE h)
{
// Find the IAT size
DWORD ulsize = 0;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(h,TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT,&ulsize);
if (!pImportDesc)
return;
// Loop names
for (; pImportDesc->Name; pImportDesc++)
{
PSTR pszModName = (PSTR)((PBYTE)h + pImportDesc->Name);
if (!pszModName)
break;
HINSTANCE hImportDLL = LoadLibraryA(pszModName);
if (!hImportDLL)
{
// ... (error)
}
// Get caller's import address table (IAT) for the callee's functions
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)
((PBYTE)h + pImportDesc->FirstThunk);
// Replace current function address with new function address
for (; pThunk->u1.Function; pThunk++)
{
FARPROC pfnNew = 0;
size_t rva = 0;
#ifdef _WIN64
if (pThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG64)
#else
if (pThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG32)
#endif
{
// Ordinal
#ifdef _WIN64
size_t ord = IMAGE_ORDINAL64(pThunk->u1.Ordinal);
#else
size_t ord = IMAGE_ORDINAL32(pThunk->u1.Ordinal);
#endif
PROC* ppfn = (PROC*)&pThunk->u1.Function;
if (!ppfn)
{
// ... (error)
}
rva = (size_t)pThunk;
char fe[100] = {0};
sprintf_s(fe,100,"#%u",ord);
pfnNew = GetProcAddress(hImportDLL,(LPCSTR)ord);
if (!pfnNew)
{
// ... (error)
}
}
else
{
// Get the address of the function address
PROC* ppfn = (PROC*)&pThunk->u1.Function;
if (!ppfn)
{
// ... (error)
}
rva = (size_t)pThunk;
PSTR fName = (PSTR)h;
fName += pThunk->u1.Function;
fName += 2;
if (!fName)
break;
pfnNew = GetProcAddress(hImportDLL,fName);
if (!pfnNew)
{
// ... (error)
}
}
// Patch it now...
auto hp = GetCurrentProcess();
if (!WriteProcessMemory(hp,(LPVOID*)rva,&pfnNew,sizeof(pfnNew),NULL) && (ERROR_NOACCESS == GetLastError()))
{
DWORD dwOldProtect;
if (VirtualProtect((LPVOID)rva,sizeof(pfnNew),PAGE_WRITECOPY,&dwOldProtect))
{
if (!WriteProcessMemory(GetCurrentProcess(),(LPVOID*)rva,&pfnNew,sizeof(pfnNew),NULL))
{
// ... (error)
}
if (!VirtualProtect((LPVOID)rva,sizeof(pfnNew),dwOldProtect,&dwOldProtect))
{
// ... (error)
}
}
}
}
}
} 这个函数在整个IAT导入表中循环,将对导入函数的无效引用替换成我们自己的IAT表中的正确引用(来自LoadLibrary()和GetProcAddress())。 初始化CRT 可执行文件的入口点不是WinMain而是WinMainCRTStartup()。这个函数会初始化CRT,建立异常处理器,加载argc和argv,并且调用WinMain。当WinMain返回时,WinMainCRTStartup则会调用exit()。 因此你得要从你的exe中导出调用WinMainCRTStartup的函数:
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|