主要步骤:

1.将要加载的文件读取到内存中(简称为文内),检查文件格式无误后,根据可选PE头(简称op头)的SizeOfImage,申请出一块空间用于存储该文件加载到内存后展开的数据(简称为内内)。记得先全部初始化为0,免去后续拷贝中对齐补0的步骤。

2.将文件数据拷贝到申请出来内存空间中(模仿PE加载器将文件装载到虚拟内存中),先根据op头的SizeOfHeaders,将文件的各种头数据先拷贝过来(因为各种头数据是线性存储的,在静态动态都是相同的存放顺序),随后复制节表数据,遍历每个节表,如果当前节表在文内长度时为0,则说明该节在内存中仅做对齐用,并没有实际数据,遍历下一节,将数据从文起复制文长的数据到内起中,由于前面申请空间时已初始化,所以无需在填0对齐。

3.进行重定位,如果当前加载到内存当中的基址与op的IB一样,即在理想基址中展开了,或重定位表data[5]的长度为0,则无需要重定位。否则获取到重定位表的块数据后,根据他的(块长度-8)/2得到该块的地址数量,前8字节存放着该块的偏移和大小,每个占4字节,一个重定位地址占2字节,通过块地址+8+(2*i)取出需要重定位的地址,与0x3000进行异或,如果首位为3,则后12位为地址偏移,则重定位地址=后12位(块中偏移)+块的起始位置+内存起始位置 重定位则为*重定位地址=重定位地址+(理想基址和实际基址的偏移) 即*重定位地址+=(实际基址-理想基址)。如果首位为0,则说明该偏移为对齐使用,遍历下一个(当前块基址+当前块长度)。将全部块遍历重定位完后,将op的IB也替换成当前加载到内存的基址。

4.构建导入表:通过偏移+内存基址,获取导入表第一个dll的数据,按照导入的dll逐个遍历,直到当前导入表的OriginalFirstThunk为0,即遍历完毕。 先通过GetModuleHandleA函数获取当前DLL的句柄,如果返回为NULL,则当前进程还未加载该DLL,loadlibary进来,获得句柄。通过内存基址+OriginalFirstThunk获得INT(输入名字表),内存基址+FirstThunk获得IAT(输入地址表)。检查INT的Ordinal是否为0,为0则遍历完当前DLL,如果不为0,检查第32位是否为1(&0x80000000),为1则为序号导入,Ordinal的后16位为序号取出(&0xFFFF),GetProcAddress得到函数地址,并赋值给IAT表的Function。如果第32位为0,则说明按名称导入,此时先通过内存基址+AddressOfData获得函数名,再用GetProcAddress得到函数地址,并赋值给IAT表的Function,循环遍历各个DLL即可。

导出表在PE文件加载到内存时并不会使用到

5.通过VirtualProtect修改整个内存内容的保护属性,修改为PAGE_EXECUTE_READWRITE(执行读写)。

6.定义一个指向DLL加载函数类型的函数指针,typedef BOOL(WINAPI *DllProcEntry)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved); 随后声明该函数指针的实例,并将(当前内存基址+op的AddressOfEntryPoint)的函数地址赋值给它,随后调用该入口函数。

DllProcEntry ProcAddr = (DllProcEntry)(g_pFileBuffer + pNtHead->OptionalHeader.AddressOfEntryPoint);//定义函数入口地址

bool bRetval = (*ProcAddr)((HINSTANCE)g_pFileBuffer, DLL_PROCESS_ATTACH, 0);//调用入口函数

附上代码:

  1. #include<iostream>
  2. #include<Windows.h>
  3. #include <winnt.h>
  4. using namespace std;
  5.  
  6. char *g_pFileSrc = NULL;//文件内容
  7. char *g_pFileBuffer = NULL;//虚拟内存空间
  8. int g_iFileBufferLen = 0;//虚拟内存空间大小
  9.  
  10. //定义一个函数指针 指向DLL加载的入口函数类型的函数
  11. typedef BOOL(WINAPI *DllProcEntry)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved);
  12.  
  13. DWORD RVAtoFA(DWORD dwRVA) //rva转文件地址
  14. {
  15. PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)g_pFileSrc; //dos头
  16. PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew); //NT头
  17. PIMAGE_FILE_HEADER pFileHead = (PIMAGE_FILE_HEADER)&pNtHead->FileHeader; //PE头
  18. PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHead); //节表
  19. int dwSectionCount = pFileHead->NumberOfSections;//获得节表数量
  20. for (int iNum = 0; iNum < dwSectionCount; iNum++)
  21. {
  22. if (dwRVA >= pSection->VirtualAddress && dwRVA < (pSection->VirtualAddress + pSection->Misc.VirtualSize))//如果RVA的值落在当前节点的范围内
  23. {
  24. return (DWORD)g_pFileSrc + ((dwRVA - pSection->VirtualAddress) + pSection->PointerToRawData);
  25. /*则文件地址=映射基址 + 文件偏移地址( RVA- VirtualAddress + RawAddress)
  26. = 映射基址 + RVA - VirtualAddress + RawAddress*/
  27. }
  28. pSection++;//指向下一个节表
  29. }
  30. return 0;
  31. }
  32. bool LoadFile(char *pFileName) //读取文件
  33. {
  34. //读取文件内容
  35. FILE* fp = fopen(pFileName, "rb");
  36. if (!fp)
  37. {
  38. cout << "打开文件失败" << endl;
  39. return false;
  40. }
  41. fseek(fp, 0, SEEK_END);
  42. int iFileSize = ftell(fp);
  43. g_pFileSrc = (char*)malloc(iFileSize);
  44. //DWORD dwBufferSize = *(int*)((DWORD)g_pFileSrc - 16);//这种方法可以取出这段空间的长度
  45. if (!g_pFileSrc)
  46. {
  47. cout << "分配内存失败" << endl;
  48. return false;
  49. }
  50. memset(g_pFileSrc, 0, iFileSize);
  51. rewind(fp);
  52. fread(g_pFileSrc, 1, iFileSize, fp);
  53.  
  54. //检查文件格式
  55. PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)g_pFileSrc;
  56. if (pDosHead->e_magic != 0x5A4D)
  57. {
  58. cout << "该文件不是可执行文件" << endl;
  59. return false;
  60. }
  61. PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew);
  62. if (pNtHead->Signature != 0x4550)
  63. {
  64. cout << "该文件不是PE文件" << endl;
  65. return false;
  66. }
  67.  
  68. PIMAGE_OPTIONAL_HEADER pOptionalHead = (PIMAGE_OPTIONAL_HEADER)&pNtHead->OptionalHeader;
  69.  
  70. g_pFileBuffer = (char*)malloc(pOptionalHead->SizeOfImage);
  71. if (!g_pFileBuffer)
  72. {
  73. cout << "分配模虚拟内存失败" << endl;
  74. return false;
  75. }
  76. memset(g_pFileBuffer, 0, pOptionalHead->SizeOfImage);
  77. cout << "读取文件成功,by:阿怪 2020.7.9" << endl;
  78. return true;
  79. }
  80.  
  81. bool CopyContent()//拷贝数据
  82. {
  83.  
  84. PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)g_pFileSrc; //dos头
  85. PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew); //NT头
  86. PIMAGE_FILE_HEADER pFileHead = (PIMAGE_FILE_HEADER)&pNtHead->FileHeader; //PE头
  87. PIMAGE_OPTIONAL_HEADER pOptionalHead = (PIMAGE_OPTIONAL_HEADER)&pNtHead->OptionalHeader; //可选PE头
  88. PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHead); //节表
  89. int iSection = pFileHead->NumberOfSections;//节数量
  90.  
  91. memcpy(g_pFileBuffer, g_pFileSrc, pOptionalHead->SizeOfHeaders);//复制各种头数据
  92. // //pSection->PointerToRawData;//文件中节的起始地址 pSection->SizeOfRawData;//文件中节的长度
  93. // //pSection->VirtualAddress;//虚拟内存中节的起始地址 pSection->Misc.VirtualSize;//虚拟内存中节的长度
  94. for (int num = 0; num < iSection; num++)
  95. {
  96. if (pSection->SizeOfRawData == 0) //如果在文件中这个节的长度为0,证明该节为未被初始化的静态内存区
  97. {
  98. pSection++;
  99. continue;
  100. }
  101. memcpy(g_pFileBuffer + pSection->VirtualAddress,
  102. g_pFileSrc + pSection->PointerToRawData,
  103. pSection->SizeOfRawData
  104. );
  105. pSection++;
  106. }
  107. cout << "从文件拷贝数据到内存完毕,by:阿怪 2020.7.9" << endl;
  108. return true;
  109. }
  110.  
  111. bool Relocation() //进行重定位并修改基址
  112. {
  113. PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)g_pFileBuffer; //dos头
  114. PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew); //NT头
  115. PIMAGE_OPTIONAL_HEADER pOptional = (PIMAGE_OPTIONAL_HEADER)&(pNtHead->OptionalHeader);
  116. DWORD dwRelocationRVA = pOptional->DataDirectory[5].VirtualAddress;//重定位表RVA
  117. int iRelocationSize = pOptional->DataDirectory[5].Size;//重定位表长度
  118. DWORD dwImageBaseGap = (DWORD)g_pFileBuffer - pOptional->ImageBase; //计算加载后的基址与原先预想的基址的距离
  119. if ((dwImageBaseGap == 0) || (iRelocationSize == 0))
  120. {
  121. cout << "该程序无需重定位" << endl;
  122. return false;
  123. }
  124. PIMAGE_BASE_RELOCATION pBaseRelocation = (PIMAGE_BASE_RELOCATION)RVAtoFA(dwRelocationRVA);//重定位表当前块
  125. while ((pBaseRelocation->VirtualAddress != 0) && (pBaseRelocation->SizeOfBlock != 0)) //遍历到重定位表末尾为止
  126. {
  127. for (int i = 0; i < ((pBaseRelocation->SizeOfBlock - 8) / 2); i++) //块首地址-8(前4为块偏移,后4为块长度)/2=块中需重定位地址数量
  128. {
  129. WORD pRelocationAddr = *(WORD*)((DWORD)pBaseRelocation + 8 + (2 * i));//在块中,每个重定位地址占2字节(WORD类型)
  130. if (pRelocationAddr != 0) //为0时,说明该位置数据为对齐而填充
  131. {
  132. DWORD dwRVA = (pRelocationAddr ^ 0x3000) + pBaseRelocation->VirtualAddress;//需要重定位的偏移
  133. PDWORD dwFileAddr = (DWORD*)(dwRVA + g_pFileBuffer);//重定位地址=当前程序基址+当前块基址+当前目标偏移
  134. *dwFileAddr += dwImageBaseGap;
  135. }
  136. }
  137. pBaseRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)pBaseRelocation + pBaseRelocation->SizeOfBlock); //下个块文件地址=当前块文件地址+当前块长度
  138. }
  139. pOptional->ImageBase = (DWORD)g_pFileBuffer; //将当前文件的基址改为加载到内存后的基址
  140.  
  141. cout << "程序重定位并修改基址成功,by:阿怪 2020.7.9" << endl;
  142. return true;
  143.  
  144. }
  145.  
  146. bool ImportList() //导入表
  147. {
  148. PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)g_pFileBuffer; //dos头
  149. PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew); //NT头
  150. PIMAGE_OPTIONAL_HEADER pOptional = (PIMAGE_OPTIONAL_HEADER)&(pNtHead->OptionalHeader);
  151. DWORD dwImportListRVA = pOptional->DataDirectory[1].VirtualAddress;//导入表RVA
  152. int dwImportListSize = pOptional->DataDirectory[1].Size;//导入表长度
  153. PIMAGE_IMPORT_DESCRIPTOR pImpotrList = (PIMAGE_IMPORT_DESCRIPTOR)(dwImportListRVA + g_pFileBuffer); //获取导入表第一个导入的dll
  154.  
  155. while (pImpotrList->OriginalFirstThunk != 0) //只有该结构中有一个成员内容为0,即遍历完了导入表了
  156. {
  157. char* szDllName = (char*)(g_pFileBuffer + pImpotrList->Name);//获取当前DLL的名字
  158. HMODULE hDllImageBase = LoadLibrary(szDllName);//先将当前dll加载到程序中
  159. if (!hDllImageBase)
  160. {
  161. int iError = GetLastError();
  162. cout << "加载当前dll失败,错误代码:" << iError << endl;
  163. return false;
  164. }
  165.  
  166. PIMAGE_THUNK_DATA pDllINT = (PIMAGE_THUNK_DATA)(g_pFileBuffer + pImpotrList->OriginalFirstThunk);//获取当前DLL导入的函数的输入名称表(INT)
  167. PIMAGE_THUNK_DATA pDllIAT = (PIMAGE_THUNK_DATA)(g_pFileBuffer + pImpotrList->FirstThunk);//获取当前DLL导入的函数的输入地址表(IAT) 当前IAT和INT指向同一内容
  168.  
  169. for (int i = 0; pDllINT->u1.Ordinal; i++) //当当前DLL导入的函数Ordinal为0时,即遍历完当前DLL所有函数
  170. {
  171. if (pDllINT->u1.Ordinal & 0x80000000) //如果当前结构信息的序数Ordinal的第32位为1 则当前dll的函数由序号导入
  172. {
  173. DWORD dwFuncNum = pDllINT->u1.AddressOfData & 0xFFFF;//后16位为导入序号
  174. //dwDllIAT->u1.Function = (DWORD)hDllImageBase + dwFuncNum;
  175. pDllIAT->u1.Function = (DWORD)GetProcAddress(hDllImageBase, (LPCSTR)dwFuncNum);
  176. }
  177. else //不为1则由函数名字导入
  178. {
  179. PIMAGE_IMPORT_BY_NAME szFuncName = (PIMAGE_IMPORT_BY_NAME)(g_pFileBuffer +pDllINT->u1.AddressOfData); //获得函数名
  180. pDllIAT->u1.Function = (DWORD)GetProcAddress(hDllImageBase, szFuncName->Name); //通过dll和函数名 获得当前函数在内存中的地址
  181. }
  182. pDllINT++;
  183. pDllIAT++;
  184. }
  185. pImpotrList++;
  186. }
  187. cout << "构建IAT成功,by:阿怪 2020.7.9" << endl;
  188. return true;
  189. }
  190.  
  191. bool DynamicLoad(char *pDllName)
  192. {
  193. if (!LoadFile(pDllName)) //获取文件内容、检查文件格式、分配2块内存(存放文件数据和虚拟内存空间)
  194. {
  195. return false;
  196. }
  197. if (!CopyContent()) //将文件数据装载到虚拟内容中
  198. {
  199. return false;
  200. }
  201.  
  202. Relocation(); //重定位并修改基址(并非所有PE结构都需要重定位)
  203.  
  204. if (!ImportList()) //通过导入表构建IAT
  205. {
  206. return false;
  207. }
  208.  
  209. PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)g_pFileBuffer;
  210. PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew);
  211.  
  212. DWORD dwOldProtect = 0;
  213. if (FALSE == VirtualProtect(g_pFileBuffer, pNtHead->OptionalHeader.SizeOfImage, PAGE_EXECUTE_READWRITE, &dwOldProtect))
  214. {
  215. printf("设置页属性失败\n");
  216. return NULL;
  217. }
  218.  
  219. DllProcEntry ProcAddr = (DllProcEntry)(g_pFileBuffer + pNtHead->OptionalHeader.AddressOfEntryPoint);//定义函数入口地址
  220. MessageBoxA(0, 0, 0, 0);
  221. bool bRetval = (*ProcAddr)((HINSTANCE)g_pFileBuffer, DLL_PROCESS_ATTACH, 0);//调用入口函数
  222.  
  223. cout << "加载完成"<<endl<<"by:阿怪 2020.7.9" << endl;
  224.  
  225. return true;
  226. }

运行结果:

在需要调试的位置调用messagebox 随后可在调试器(OD,MDbug)中定位到关键点,方便调试。

踩坑点:

1.节数据赋值时的对齐,如果不按照上面的步骤,也可通过对齐值,求得当前节在虚拟内存中的对齐后的大小,用该值-当前节在文件中的大小,可得用0补齐的长度。以及如何去复制各种头文件(各种头文件为线性的,可直接复制),当第一个节在文件大小为0时该怎么处理。

2.重定位的重定位地址为当前内存基址+当前重定位块基址+当前重定位偏移,重定位后的值应该赋值给*重定位,即*重定位地址=重定位地址+(预先理想的基址与当前内存基址的距离)

3.在静态文件到动态加载到内存时,导入表是通过INT表,把所导入的函数的地址装载到IAT表,全部加载完后,INT表就没用了,通过IAT表就可以找到相应的函数地址。且在取相应的地址时,应该是由导入表的成员的RVA+内存基址,而不是通过RVAtoFA去取值。

导入表相关文章: https://www.sohu.com/a/278971010_653604

导出表相关文章:https://www.write-bug.com/article/1926.html https://www.cnblogs.com/Madridspark/p/WinPEFile.html

模拟PE解析器工作原理:https://www.cnblogs.com/onetrainee/p/12938085.html

感谢以上资料作者带来的启发与借鉴,在这也是分享自己在做这个动态加载时的感受,如有不足之处也希望大家不吝赐教,指点出来。谢谢。

有什么问题或看法欢迎评论

PE文件动态加载执行过程的更多相关文章

  1. 动态加载JS过程中如何判断JS加载完成

    在正常的加载过程中,js文件的加载是同步的,也就是说在js加载的过程中,浏览器会阻塞接下来的内容的解析.这时候,动态加载便显得尤为重要了,由于它是异步加载,因此,它可以在后台自动下载,并不会妨碍其它内 ...

  2. C#遍历XML文件动态加载菜单

    通过遍历XML文件动态加载菜单,顺便利用WebBrowser控件实现一个简单的桌面浏览器 效果如下: 代码如下: XMLFile1.xml <?xml version="1.0&quo ...

  3. Java之——Web项目中DLL文件动态加载方法

    本文转自:https://blog.csdn.net/l1028386804/article/details/53903557 在Java Web项目中,我们经常会用到通过JNI调用dll动态库文件来 ...

  4. android sax解析xml 文件 动态加载标题

    要解决一个问题 : 问题描述为 把标题动态的加载到 listView子布局中 我们首先通过 java程序写一个把标题写到xml文件的程序.这个程序会在以后讲解. 现在截图 已经写好的xm文件格式如下 ...

  5. so文件动态加载注意事项

    动态加载是指将so文件存放于服务器,在需要用的时候,通过服务器下载到本地,然后加载. 需要注意的: 手机cpu架构,不同的架构运行不同的so 解决方法: 1,欺骗性: 如果so架构不全,就在apk打包 ...

  6. AngularJS的加载执行过程

    1. HTML页面的加载,这会触发加载页面包含的所有JS (包括 AngularJS) 2. AngularJS启动,搜寻所有的指令(directive) 3. 找到ng-app,搜寻其指定的模块(M ...

  7. html 文件动态加载.PDI 流程图

    1 //javascript脚本 <script> window.onload = function () { var aid = document.getElementById(&quo ...

  8. 180807-Quick-Task 动态脚本支持框架之Groovy脚本加载执行

    Quick-Task 动态脚本支持框架之Groovy脚本加载执行 上一篇简答说了如何判断有任务动态添加.删除或更新,归于一点就是监听文件的变化,判断目录下的Groovy文件是否有新增删除和改变,从而判 ...

  9. [转载] Android动态加载Dex机制解析

    本文转载自: http://blog.csdn.net/wy353208214/article/details/50859422 1.什么是类加载器? 类加载器(class loader)是 Java ...

随机推荐

  1. layui导出表格的两种方法

    一.不熟悉layui小白使用方法 1.引入如下js文件: 2.编写如下函数: 3.表格ID要与函数取值保持一致即可,再就是自定义一个按钮触发事件 二.引入插件使用方法 1.layui官网下载插件包: ...

  2. Docker+Selenium+TestNG+Maven+Jenkins环境搭建

    一.Selenium环境准备 standalone-chrome Docker容器启动: docker pull selenium/standalone-chrome version: '3' ser ...

  3. VMWare的三种网络连接方式

    VMWare和主机的三种网络连接方式 桥接 这种模式下,虚拟机通过主机的网卡与主机通信,如果主机能够上网,则虚拟机也能联网. 在虚拟机中,需要将虚拟机的IP配置为与主机处于同一网段. 虚拟机也可以与同 ...

  4. 青蛙的约会 (ax+by=c求最小整数解)【拓展欧几里得】

                                                  青蛙的约会(点击跳转) 两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面.它们很高兴地发现它们住 ...

  5. 色彩空间转换 rgb转ycbcr422/ycbcr422转rgb

    在图像处理过程中通常需要会对图像类型进行互相转换,在此给出两种转换的工程代码. 1.在将ycbCr422转rgb时,通常先将ycbcr422转换成ycbcr444再讲ycbcr444转成rgb 1.1 ...

  6. 如何获取Apollo上项目下的所有namespace?

    背景 项目配置迁移到Apollo之后,通过统一的配置管理及配置监听使得项目配置修改的成本大大降低. 但是,在使用Apollo的过程中,强哥也遇到一个问题:如果我们要获取Apollo下的namespac ...

  7. @font-face规则指定字体

    兼容性写法: @font-face { font-family: '字体名'; src: url('字体名.eot'); /* IE9 兼容模式 */ src: url('字体名.eot?#iefix ...

  8. 电商安全无小事,如何有效抵御 CSRF 攻击?

    现在,我们绝大多数人都会在网上购物买东西.但是很多人都不清楚的是,很多电商网站会存在安全漏洞.乌云就通报过,国内很多家公司的网站都存在 CSRF 漏洞.如果某个网站存在这种安全漏洞的话,那么我们在购物 ...

  9. Maven发展历史

    1.1 Maven是什么 Maven是一个项目管理和综合工具. Maven提供了开发人员构建一个完整的生命周期框架.开发者团队可以自动完成项目的基础工具建设, Maven使用标准的目录结构和默认构建生 ...

  10. 解决 React Native Android:app:validateSigningRelease FAILED 错误

    RN 运行的时候报这个错这咋办: