像加载DLL一样加载EXE
介紹
你可能已經被警告過,不要用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?sizeDWORD?ulsize?=?0;PIMAGE_IMPORT_DESCRIPTOR?pImportDesc?=?(PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(h,TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT,&ulsize);if?(!pImportDesc)return;//?Loop?namesfor?(;?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?functionsPIMAGE_THUNK_DATA?pThunk?=?(PIMAGE_THUNK_DATA)((PBYTE)h?+?pImportDesc->FirstThunk);//?Replace?current?function?address?with?new?function?addressfor?(;?pThunk->u1.Function;?pThunk++){FARPROC?pfnNew?=?0;size_t?rva?=?0; #ifdef?_WIN64if?(pThunk->u1.Ordinal?&?IMAGE_ORDINAL_FLAG64) #elseif?(pThunk->u1.Ordinal?&?IMAGE_ORDINAL_FLAG32) #endif{//?Ordinal #ifdef?_WIN64size_t?ord?=?IMAGE_ORDINAL64(pThunk->u1.Ordinal); #elsesize_t?ord?=?IMAGE_ORDINAL32(pThunk->u1.Ordinal); #endifPROC*?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?addressPROC*?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:?
但這么做又會影響到你的初始化,因此你還得這么修改:?
?但是你其實還得要知道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.exe14017B578?Import?Address?Table14017BC18?Import?Name?Table0?time?date?stamp0?Index?of?first?forwarder?reference0?e01?e1源代碼下載:
http://download.csdn.net/download/liujiayu2/9972121
總結
以上是生活随笔為你收集整理的像加载DLL一样加载EXE的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 异步socket优雅的关闭-Cancel
- 下一篇: 直接运行内存中的代码