介绍

你可能已经被警告过,不要用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:

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.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相关推荐

  1. 查看php 加载.dll,无法加载PHP_OCI8.DLL的解决

    在服务器上装了Windows 2003 R2操作系统,打算用来学习Oracle 10g. 想用PHP来连接Oracle,还想图方便,于是装了Zend Server,没想到折腾了两天. 软件清单: 1. ...

  2. PE文件和COFF文件格式分析——导出表的应用——通过导出表隐性加载DLL

    通过导出表隐性加载DLL?导出表?加载DLL?还隐性?是的.如果觉得不可思议,可以先看<PE文件和COFF文件格式分析--导出表>中关于"导出地址表"的详细介绍.(转载 ...

  3. C#导入Excel2010出现加载DLL失败或者类库未注册的问题,0x8002801D或者0x80029C4A .

    最近在客户机器上出现了无法导出报表的问题,错误提示为: System.InvalidCastException: Unable to cast COM object of type 'Microsof ...

  4. 无法加载 DLL“oramts.dll”: 找不到指定的模块。 (异常来自 HRESULT:0x8007007E)

    最近在做一个小项目,语言VS2005,C#,数据库Oracle9i,但是为了兼容以前数据库SqlServer2005,以便于数据库版本的切换,事务中,仍然使用 System.Transaction.T ...

  5. c# winform程序调用托管dll(c#的dll),使用添加引用和动态加载dll

    1. dll要强名. 2. winform程序要添加引用dll(自动获得dll的函数调用定义),"复制本地"属性设false,因为下一步会动态加载dll,所以"复制本地& ...

  6. 静态链接库(LIB)和动态链接库(DLL),DLL的静态加载和动态加载,两种LIB文件。

    静态链接库(LIB)和动态链接库(DLL),DLL的静态加载和动态加载,两种LIB文件. 一. 静态链接库(LIB,也简称"静态库")与动态链接库(DLL,也简称"动态库 ...

  7. LoadLibrary无法加载.dll解决思路

    动态载入DLL所需要的三个函数详解(LOADLIBRARY,GETPROCADDRESS,FREELIBRARY) dll, lib, h的区别 dll是动态库, 将其链接进工程, 一般用LoadLi ...

  8. 使用c#封装海康SDK出现无法加载 DLL“..\bin\HCNetSDK.dll”: 找不到指定的模块

    最近在研究网络摄像头的二次开发,测试了一款海康威视的网络摄像头,程序调试的时候,出现如题的报错. 调试随机自带的demo时,程序运行正常,但当把该程序引入到我自己的程序中时,就开始报错.根据开发软件包 ...

  9. 模块xxxx.dll已加载,但对DllRegisterServer的调用失败,错误代码为 XXXXXXXXX

    WIN7.WIN8  注册 卸载dll  报错: 模块"xxxx.dll"已加载,但对DllRegisterServer的调用失败,错误代码为 XXXXXXXXX 解决方法: 若为 ...

最新文章

  1. 云计算将成为金融服务业的主流技术
  2. lzg_ad:使用OPENROWSET函数连接并访问远程数据库数据
  3. 关键词优化一定要从正规渠道入手
  4. 登陆sqlserver及修改端口号
  5. PHPCMS v9 二次开发_验证码结合Session开发
  6. Spring Cloud构建微服务架构:服务注册与发现(Eureka、Consul)【Dalston版】
  7. Win32 SDK 编写截图小工具
  8. 家用电脑虚拟机做服务器_家用电脑能当服务器吗
  9. css值变量吗,CSS变量初体验
  10. [HNOI2008]GT考试
  11. FreeMarker缓存处理
  12. ESXi主机管理内存资源的方式
  13. 【kafka】查看消费组报错 Executing consumer group command failed due to Request METADATA failed on brokers Lis
  14. python中索引和下标_Series下标索引、标签索引、切片索引、布尔索引
  15. linux python pymssql,如何在UbuntuLinux上将pymssql安装到Python3.4而不是2.7?
  16. 操作系统概念:系统引导过程、引导程序、固件
  17. 项目范围管理:范围定义
  18. 《C++ primer》学习笔记(第二章)——变量和基本类型
  19. 二期开发立项申请书,已经提交,等待批准!
  20. 数字证书包含哪些内容

热门文章

  1. RocketMQ各角色介绍
  2. sqoop增量导入hdfs和导出
  3. flume案例-网络数据采集-启动flume
  4. 角色管理与今日内容介绍
  5. 在jsx中绑定js表达式以及jsx注释
  6. hello-world
  7. Calendar类的常用成员方法
  8. Redisson分布式锁实战-1:构建分布式锁
  9. SpringBoot(配置druid数据源、配置MyBatis、事务控制、druid 监控)
  10. Shell变量作用域