像加载DLL一样加载EXE
介紹
你可能已經(jīng)被警告過,不要用LoadLibrary()加載可執(zhí)行文件,你可能嘗試這么做過,然后程序就崩潰了,所以你可能會(huì)認(rèn)為這是不可能的。
但實(shí)際上這是可行的,本文就將介紹具體的方法。
聲明
這好像跟微軟說的有點(diǎn)不一樣。實(shí)際上,微軟沒說不要加載,他們只是說“不要用LoadLibrary()加載可執(zhí)行文件,應(yīng)該用CreateProcess() ”。不過除非你很清楚你在做什么,否則不要把這用在產(chǎn)品代碼中,我已經(jīng)警告過你了|?
準(zhǔn)備可執(zhí)行文件
首先要做的是把可執(zhí)行文件標(biāo)記為可重定位的文件,能夠從任何的基地址(任何DLL)加載。你可以用/FIXED:NO來實(shí)現(xiàn),如果想要提高安全性,還可以使用/DYNAMICBASE(默認(rèn)就是開啟的)。EXE文件可能設(shè)置了/FIXED:YES,那樣的話exe就只能在它的首選基地址加載了,如果沒有用/BASE設(shè)置過的話這個(gè)地址就是0×400000。
下一步的準(zhǔn)備工作就是要我們需要從另外的exe文件調(diào)用的函數(shù),這跟調(diào)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加載可執(zhí)行文件
在使用LoadLibraryEx()加載可執(zhí)行文件時(shí)候,不要指定LOAD_LIBRARY_AS_DATAFILE或者LOAD_LIBRARY_AS_IMAGE_RESOURCE,如果這么做的話,exe中的導(dǎo)出的函數(shù)就不能成功導(dǎo)出,而執(zhí)行GetProcAddress()時(shí)就會(huì)失敗。
調(diào)用LoadLibrary()后,我們就可以得到一個(gè)有效的HINSTANCE handle。但是當(dāng)我們用LoadLibrary()加載exe文件時(shí),以下兩件關(guān)鍵的事沒有發(fā)生:
1.?CRT運(yùn)行庫沒有初始化,包括所有全局變量 2.?導(dǎo)入地址表沒有正確配置,這就意味著所有對導(dǎo)入函數(shù)的調(diào)用就會(huì)導(dǎo)致崩潰更新導(dǎo)入表
首先我們得要更新可執(zhí)行文件的導(dǎo)入表。下面的程序片段展示了其過程:
?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)}}}}}}?
這個(gè)函數(shù)在整個(gè)IAT導(dǎo)入表中循環(huán),將對導(dǎo)入函數(shù)的無效引用替換成我們自己的IAT表中的正確引用(來自LoadLibrary()和GetProcAddress())。
初始化CRT
可執(zhí)行文件的入口點(diǎn)不是WinMain而是WinMainCRTStartup()。這個(gè)函數(shù)會(huì)初始化CRT,建立異常處理器,加載argc和argv,并且調(diào)用WinMain。當(dāng)WinMain返回時(shí),WinMainCRTStartup則會(huì)調(diào)用exit()。
因此你得要從你的exe中導(dǎo)出調(diào)用WinMainCRTStartup的函數(shù):
extern?"C"?void?WinMainCRTStartup(); extern?"C"?void?__stdcall?InitCRT(){WinMainCRTStartup();}問題是,這樣的話你的WinMain會(huì)被調(diào)用。所以你得要放一個(gè)global flag。
extern?"C"?void?WinMainCRTStartup(); bool?DontDoAnything?=?false; extern?"C"?void?__stdcall?InitCRT(){DontDoAnything?=?true;WinMainCRTStartup();}int?__stdcall?WinMain(...){if?(DontDoAnything)return?0;//?...} 現(xiàn)在又有另外的問題了,當(dāng)WinMain return的時(shí)候,WinMainCRTStartup會(huì)調(diào)用exit(),但你并不希望那樣。因此,你不希望WinMain return:?
但這么做又會(huì)影響到你的初始化,因此你還得這么修改:?
?但是你其實(shí)還得要知道CRT什么時(shí)候完成初始化,所以最終的解決方案應(yīng)該是使用事件:
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
幸運(yùn)的是,LINK.EXE會(huì)為我們的DLLEXE.EXE生成一個(gè).lib,因此我們可以用它從我們的exe中鏈接另外的exe,就好像鏈接DLL一樣:
#pragma?comment(lib,"..\\dllexe\\dllexe.lib") extern?"C"????void?__stdcall?e0(HANDLE); extern?"C"????void?__stdcall?e1();我們還是得要修改IAT,然后調(diào)用CRT初始化,但我們不再需要對函數(shù)進(jìn)行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
總結(jié)
以上是生活随笔為你收集整理的像加载DLL一样加载EXE的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 异步socket优雅的关闭-Cancel
- 下一篇: 直接运行内存中的代码