像加载DLL一样加载EXE
来源:互联网 发布:扎克拉文数据 编辑:程序博客网 时间:2024/05/21 17:20
介绍
你可能已经被警告过,不要用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的函数:
extern "C" void WinMainCRTStartup();extern "C" void __stdcall InitCRT() { WinMainCRTStartup(); }
问题是,这样的话你的WinMain会被调用。所以你得要放一个global flag。
extern "C" void WinMainCRTStartup();bool DontDoAnything = false;extern "C" void __stdcall InitCRT() { DontDoAnything = true; WinMainCRTStartup(); } int __stdcall WinMain(...) { if (DontDoAnything) return 0; // ... }
现在又有另外的问题了,当WinMain return的时候,WinMainCRTStartup会调用exit(),但你并不希望那样。因此,你不希望WinMain return:
int __stdcall WinMain(...) { if (DontDoAnything) { for(;;) { Sleep(60000); } } // ... }
但这么做又会影响到你的初始化,因此你还得这么修改:
std::thread t([] () { InitCRT(); } );t.detach();
但是你其实还得要知道CRT什么时候完成初始化,所以最终的解决方案应该是使用事件:
HANDLE hEv = CreateEvent(0,0,0,0);void(__stdcall * InitCRT)(HANDLE) = (void(__stdcall*)(HANDLE)) GetProcAddress(hL,"InitCRT");if (!InitCRT) return 0;std::thread t([&] (HANDLE h) { InitCRT(h); } ,hEv);t.detach();WaitForSingleObject(hEv,INFINITE);
其他的代码:
extern "C" void WinMainCRTStartup();HANDLE hEV = 0;extern "C" void __stdcall InitCRT(HANDLE hE) { hEV = hE; WinMainCRTStartup(); } int __stdcall WinMain(...) { if (hEV) { SetEvent(hEV); for(;;) { Sleep(60000); } } }
不用LoadLibrary/GetProcAddress链接EXE
幸运的是,LINK.EXE会为我们的DLLEXE.EXE生成一个.lib,因此我们可以用它从我们的exe中链接另外的exe,就好像链接DLL一样:
#pragma comment(lib,"..\\dllexe\\dllexe.lib")extern "C" void __stdcall e0(HANDLE);extern "C" void __stdcall e1();
我们还是得要修改IAT,然后调用CRT初始化,但我们不再需要对函数进行GetProcAddress()了:
dllexe.exe 14017B578 Import Address Table 14017BC18 Import Name Table 0 time date stamp 0 Index of first forwarder reference 0 e0 1 e1
源代码下载:
http://download.csdn.net/download/liujiayu2/9972121
- 像加载DLL一样加载EXE
- rundll32.exe加载dll
- EXE加载DLL文件
- exe的dll加载过程
- .net DLL(exe)加载时间
- 调试dll和exe加载失败
- EXE加载DLL(两个默认堆问题)
- 微信小程序如何像webview一样加载html5网页
- dll加载
- 加载DLL
- 加载dll
- 加载DLL
- 加载DLL
- 加载dll
- 加载DLL
- 加载dll
- 例说Exe程序作为DLL进行加载
- 系统会优先在exe当前目录加载dll
- HDFS启动过程了解
- 欢迎使用CSDN-markdown编辑器
- UVA 11825 状态压缩DP
- MacOS 开发
- 从零开始的"E"世界(J2SE)
- 像加载DLL一样加载EXE
- Connections in Galaxy War
- linux系统管理命令
- 机器学习开源算法库
- <c:set value="${pageContext.request.contextPath}" var="blog"/>是什么意思
- Idea Android项目无法预览布局文件
- JAVA第一次作业(3)
- CSS3盒模型
- java并发编程学习7--同步--ReentrantLock