背景

在网上搜索了很多病毒木马的分析报告,看了一段时间后,发现还是有很多病毒木马都能够模拟PE加载器,把DLL或者是EXE等PE文件,直接从内存中直接加载到自己的内存中执行,不需要通过API函数去操作,以此躲过一些杀软的检测。

程序实现原理(以exe 为例,dll 同理)

  • 首先,在EXE文件中,根据PE结构格式获取其加载映像的大小SizeOfImage,并根据SizeOfImage在自己的程序中申请一块可读、可写、可执行的内存,那么这块内存的首地址就是EXE程序的加载基址
  • 然后,根据EXE中的PE结构格式获取其映像对齐大小SectionAlignment,然后把EXE文件数据按照SectionAlignment对齐大小拷贝到上述申请的可读、可写、可执行的内存中
  • 接着,根据PE结构的重定位表,重新对重定位表进行修正
  • 接着,根据PE结构的导入表,加载所需的DLL,并获取导入表导入函数的地址并写入导入表中
  • 接着,修改EXE的加载基址ImageBase
  • 最后,根据PE结构获取EXE的入口地址AddressOfEntryPoint,然后跳转到入口地址处继续执行

这样,EXE就成功加载到程序中并运行起来了。要注意的一个问题就是,并不是所有的EXE都有重定位表,对于没有重定位表的EXE程序,那就不适用于本文介绍的方法。

编码实现(exe)

// 模拟PE加载器加载内存EXE文件到进程中// lpData: 内存EXE文件数据的基址// dwSize: 内存EXE文件的内存大小// 返回值: 内存EXE加载到进程的加载基址LPVOID MmRunExe(LPVOID lpData, DWORD dwSize){LPVOID lpBaseAddress = NULL;// 获取镜像大小DWORD dwSizeOfImage = GetSizeOfImage(lpData);// 在进程中开辟一个可读、可写、可执行的内存块lpBaseAddress = ::VirtualAlloc(NULL, dwSizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);if (NULL == lpBaseAddress){ShowError("VirtualAlloc");return NULL;}::RtlZeroMemory(lpBaseAddress, dwSizeOfImage);// 将内存PE数据按SectionAlignment大小对齐映射到进程内存中if (FALSE == MmMapFile(lpData, lpBaseAddress)){ShowError("MmMapFile");return NULL;}// 修改PE文件重定位表信息if (FALSE == DoRelocationTable(lpBaseAddress)){ShowError("DoRelocationTable");return NULL;}// 填写PE文件导入表信息if (FALSE == DoImportTable(lpBaseAddress)){ShowError("DoImportTable");return NULL;}//修改页属性。应该根据每个页的属性单独设置其对应内存页的属性。//统一设置成一个属性PAGE_EXECUTE_READWRITEDWORD dwOldProtect = 0;if (FALSE == ::VirtualProtect(lpBaseAddress, dwSizeOfImage, PAGE_EXECUTE_READWRITE, &dwOldProtect)){ShowError("VirtualProtect");return NULL;}// 修改PE文件加载基址IMAGE_NT_HEADERS.OptionalHeader.ImageBaseif (FALSE == SetImageBase(lpBaseAddress)){ShowError("SetImageBase");return NULL;}// 跳转到PE的入口点处执行, 函数地址即为PE文件的入口点IMAGE_NT_HEADERS.OptionalHeader.AddressOfEntryPointif (FALSE == CallExeEntry(lpBaseAddress)){ShowError("CallExeEntry");return NULL;}return lpBaseAddress;}int _tmain(int argc, _TCHAR* argv[]){char szFileName[] = "KuaiZip_Setup_2.8.28.8.exe";// 打开EXE文件并获取EXE文件大小HANDLE hFile = CreateFile(szFileName, GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE, NULL);if (INVALID_HANDLE_VALUE == hFile){ShowError("CreateFile");return 1;}DWORD dwFileSize = GetFileSize(hFile, NULL);// 申请动态内存并读取DLL到内存中BYTE *pData = new BYTE[dwFileSize];if (NULL == pData){ShowError("new");return 2;}DWORD dwRet = 0;ReadFile(hFile, pData, dwFileSize, &dwRet, NULL);CloseHandle(hFile);// 判断有无重定位表if (FALSE == IsExistRelocationTable(pData)){printf("[FALSE] IsExistRelocationTable\n");system("pause");return 0;}// 将内存DLL加载到程序中LPVOID lpBaseAddress = MmRunExe(pData, dwFileSize);if (NULL == lpBaseAddress){ShowError("MmRunExe");return 3;}system("pause");return 0;}

效果图


编码实现(dll)

// 模拟LoadLibrary加载内存DLL文件到进程中// lpData: 内存DLL文件数据的基址// dwSize: 内存DLL文件的内存大小// 返回值: 内存DLL加载到进程的加载基址LPVOID MmLoadLibrary(LPVOID lpData, DWORD dwSize){LPVOID lpBaseAddress = NULL;// 获取镜像大小DWORD dwSizeOfImage = GetSizeOfImage(lpData);// 在进程中开辟一个可读、可写、可执行的内存块lpBaseAddress = ::VirtualAlloc(NULL, dwSizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);if (NULL == lpBaseAddress){ShowError("VirtualAlloc");return NULL;}::RtlZeroMemory(lpBaseAddress, dwSizeOfImage);// 将内存DLL数据按SectionAlignment大小对齐映射到进程内存中if (FALSE == MmMapFile(lpData, lpBaseAddress)){ShowError("MmMapFile");return NULL;}// 修改PE文件重定位表信息if(FALSE == DoRelocationTable(lpBaseAddress)){ShowError("DoRelocationTable");return NULL;}// 填写PE文件导入表信息if (FALSE == DoImportTable(lpBaseAddress)){ShowError("DoImportTable");return NULL;}//修改页属性。应该根据每个页的属性单独设置其对应内存页的属性。//统一设置成一个属性PAGE_EXECUTE_READWRITEDWORD dwOldProtect = 0;if (FALSE == ::VirtualProtect(lpBaseAddress, dwSizeOfImage, PAGE_EXECUTE_READWRITE, &dwOldProtect)){ShowError("VirtualProtect");return NULL;}// 修改PE文件加载基址IMAGE_NT_HEADERS.OptionalHeader.ImageBaseif (FALSE == SetImageBase(lpBaseAddress)){ShowError("SetImageBase");return NULL;}// 调用DLL的入口函数DllMain,函数地址即为PE文件的入口点IMAGE_NT_HEADERS.OptionalHeader.AddressOfEntryPointif (FALSE == CallDllMain(lpBaseAddress)){ShowError("CallDllMain");return NULL;}return lpBaseAddress;}// 模拟GetProcAddress获取内存DLL的导出函数// lpBaseAddress: 内存DLL文件加载到进程中的加载基址// lpszFuncName: 导出函数的名字// 返回值: 返回导出函数的的地址LPVOID MmGetProcAddress(LPVOID lpBaseAddress, PCHAR lpszFuncName){LPVOID lpFunc = NULL;// 获取导出表PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((ULONG32)pDosHeader + pDosHeader->e_lfanew);PIMAGE_EXPORT_DIRECTORY pExportTable = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pDosHeader + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);// 获取导出表的数据PDWORD lpAddressOfNamesArray = (PDWORD)((DWORD)pDosHeader + pExportTable->AddressOfNames);PCHAR lpFuncName = NULL;PWORD lpAddressOfNameOrdinalsArray = (PWORD)((DWORD)pDosHeader + pExportTable->AddressOfNameOrdinals);WORD wHint = 0;PDWORD lpAddressOfFunctionsArray = (PDWORD)((DWORD)pDosHeader + pExportTable->AddressOfFunctions);DWORD dwNumberOfNames = pExportTable->NumberOfNames;DWORD i = 0;// 遍历导出表的导出函数的名称, 并进行匹配for (i = 0; i < dwNumberOfNames; i++){lpFuncName = (PCHAR)((DWORD)pDosHeader + lpAddressOfNamesArray[i]);if (0 == ::lstrcmpi(lpFuncName, lpszFuncName)){// 获取导出函数地址wHint = lpAddressOfNameOrdinalsArray[i];lpFunc = (LPVOID)((DWORD)pDosHeader + lpAddressOfFunctionsArray[wHint]);break;}}return lpFunc;}// 释放从内存加载的DLL到进程内存的空间// lpBaseAddress: 内存DLL数据按SectionAlignment大小对齐映射到进程内存中的内存基址// 返回值: 成功返回TRUE,否则返回FALSEBOOL MmFreeLibrary(LPVOID lpBaseAddress){BOOL bRet = FALSE;if (NULL == lpBaseAddress){return bRet;}bRet = ::VirtualFree(lpBaseAddress, 0, MEM_RELEASE);lpBaseAddress = NULL;return bRet;}

我们编写一个用来测试的DLL程序TestDll,导出函数的代码如下所示:

BOOLShowMessage(char *lpszText, char *lpszCaption){::MessageBox(NULL, lpszText, lpszCaption, MB_OK);return TRUE;}

在 main 函数中,调用上述封装好的函数接口进行测试。main 函数为:

int _tmain(int argc, _TCHAR* argv[]){char szFileName[MAX_PATH] = "TestDll.dll";// 打开DLL文件并获取DLL文件大小HANDLE hFile = ::CreateFile(szFileName, GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE, NULL);if (INVALID_HANDLE_VALUE == hFile){ShowError("CreateFile");return 1;}DWORD dwFileSize = ::GetFileSize(hFile, NULL);// 申请动态内存并读取DLL到内存中BYTE *lpData = new BYTE[dwFileSize];if (NULL == lpData){ShowError("new");return 2;}DWORD dwRet = 0;::ReadFile(hFile, lpData, dwFileSize, &dwRet, NULL);// 将内存DLL加载到程序中LPVOID lpBaseAddress = MmLoadLibrary(lpData, dwFileSize);if (NULL == lpBaseAddress){ShowError("MmLoadLibrary");return 3;}printf("DLL加载成功\n");// 获取DLL导出函数并调用typedef BOOL(*typedef_ShowMessage)(char *lpszText, char *lpszCaption);typedef_ShowMessage ShowMessage = (typedef_ShowMessage)MmGetProcAddress(lpBaseAddress, "ShowMessage");if (NULL == ShowMessage){ShowError("MmGetProcAddress");return 4;}ShowMessage("CDIY - www.coderdiy.com - 专注计算机技术交流分享\n", "CDIY");// 释放从内存加载的DLLBOOL bRet = MmFreeLibrary(lpBaseAddress);if (FALSE == bRet){ShowError("MmFreeLirbary");}// 释放delete[] lpData;lpData = NULL;::CloseHandle(hFile);system("pause");return 0;}

效果图

c++ 在内存中加载 exe/dll (不使用CreateProcess、LoadLibrary 等 API)相关推荐

  1. 从内存中加载并启动一个exe

    从内存中加载并启动一个exe 文章作者:Idle_ (阿呆) 信息来源:[url]http://cnxhacker.net/article/show/2821.html[/url] windows似乎 ...

  2. 从内存中加载并运行exe(两种方法)

    windows似乎只提供了一种启动进程的方法:即必须从一个可执行文件中加载并启动.      而下面这段代码就是提供一种可以直接从内存中启动一个exe的变通办法.      用途嘛,     也许可以 ...

  3. 从内存中加载并运行exe

    {配合anskya的AnyWhereFileToPas效果不错} { ******************************************************* } { *     ...

  4. 从内存中加载DLL Delphi版(转)

    源:从内存中加载DLL DELPHI版 原文 : http://www.2ccc.com/article.asp?articleid=5784 MemLibrary.pas //从内存中加载DLL D ...

  5. 从内存中加载DLL DELPHI版

    //从内存中加载DLL DELPHI版 unit MemLibrary; interface uses Windows;function memLoadLibrary(pLib: Pointer): ...

  6. Twebbrowser从内存中加载页面

    //从内存中加载页面(比加载htm文件速度快)uses ActiveX; procedure WBLoadHTML(WebBrowser: TWebBrowser; HTMLCode: tstring ...

  7. Win32编程之从内存中加载位图,并显示到hdc上

    近期在项目中遇到一个问题,如何在实现从内存中加载RBG帧数据,然后提交到hdc上显示,这里假设大家对win32程序已经很熟了,有了相关的框架,如果不熟的小伙伴可以看我的老师编写的一本书<游戏程序 ...

  8. Windows编程 内存中加载图片并显示 Direct离屏表面的实现

    版本:VS2015 语言:C++ 前段时间去白空轨了,感觉快燃尽了.没有看Windows的书,所以博客也没更,不过请组织放心,从现在开始,即使是节假日,我也会仔细钻研DirectX的. 今天是第七章的 ...

  9. Linux内存中加载二进制,linux – 程序退出后二进制文件会留在内存中吗?

    我知道程序首次启动时,由于代码不在内存中,因此在开始时会出现大量页面错误,因此需要从磁盘加载代码. 程序退出后会发生什么?二进制文件是否留在内存中?该程序的后续调用是否会发现代码已经在内存中,因此没有 ...

  10. cbitmap 从内存中加载jpg_[转载]windows照片查看器无法显示图片内存不足

    问题描述 最近在使用Windows照片查看器打开一个jpg文件的时候异常 Windows照片查看器无法显示此图片,因为计算机上的可用内存可能不足.请关闭一些目前没有使用的程序或者释放部分硬盘空间(如果 ...

最新文章

  1. python实现三种以上判断条件_Python小课笔记--Python控制流:if逻辑判断
  2. stun 协议 NAT穿透方式 简介
  3. Oracle 通用查询:表、字段、表说明、所有用户、库版本
  4. Cobbler全自动安装CentOS(整理)
  5. python作用域-Python之函数作用域
  6. 先知模型 facebook_使用Facebook先知进行犯罪率预测
  7. java php js_【javascript/PHP】当一个JavaScripter初次进入PHP的世界,他将看到这样的风景...
  8. 树莓派3代刷ubuntu mate在命令行下配置wifi不能连接的一个诡异的bug的解决
  9. swoole服务器主动推消息,实现websocket-主动消息推送laravelswoole
  10. mysql的rowscn_Oracle ORA_ROWSCN 伪列 说明
  11. 用友与中国互联网协会签署战略合作 共推企业互联网转型
  12. CRM 客户管理系统(SpringBoot+MyBatis)
  13. Electron 安装报错 'Electron failed to install correctly'
  14. 华为笔记本怎么激活windows_HUAWEI MateBook笔记本怎么激活Office?
  15. 6个让你10T硬盘立马爆掉的资源网站,再也不需要去百度上找资源了
  16. 151202storyboard中, 设置子控件和父控件的高宽比
  17. element-ui表格表头内容 限制不换行
  18. WIFI 国家码和信道划分
  19. android dy 完结篇(0x3)
  20. cad高程测绘图lisp_已知CAD中的高程测绘图,很多点,如何求出所有高程的平均值呢?难道只能用计算器一个一个的相加来算吗?...

热门文章

  1. STM32F103 驱动DS18B20
  2. excel常用快捷键汇总
  3. Java的身份证号码工具类
  4. PyQt5,资源文件 .qrc 的使用
  5. 【Android容器组件—AdapterView】
  6. 微型计算机与接口技术总结,微机原理与接口技术课程总结
  7. 完稿—单片机原理与接口技术
  8. “爱装X”开源组织:“教科书级”AI知识树究竟长什么样?
  9. linux 虚拟ip 漂移,keepalived 虚拟ip切换
  10. 凤凰系统 android 分辨率,凤凰系统如何修改屏幕分辨率[多图]