Delphi 在内存中运行EXE程序,从资源文件中加载

  1. unit MemRun;
  2. interface
  3. uses windows;
  4. function MemExecute(const ABuffer; Len: Integer; CmdParam: stringvar ProcessId: Cardinal): Cardinal;
  5. implementation
  6. //{$R ExeShell.res} // 外壳程序模板(98下使用)
  7. type
  8. TImageSectionHeaders = array [0..0of TImageSectionHeader;
  9. PImageSectionHeaders = ^TImageSectionHeaders;
  10. { 计算对齐后的大小 }
  11. function GetAlignedSize(Origin, Alignment: Cardinal): Cardinal;
  12. begin
  13. result := (Origin + Alignment - 1div Alignment * Alignment;
  14. end;
  15. { 计算加载pe并对齐需要占用多少内存,未直接使用OptionalHeader.SizeOfImage作为结果是因为据说有的编译器生成的exe这个值会填0 }
  16. function CalcTotalImageSize(MzH: PImageDosHeader; FileLen: Cardinal; peH: PImageNtHeaders;
  17. peSecH: PImageSectionHeaders): Cardinal;
  18. var
  19. i: Integer;
  20. begin
  21. {计算pe头的大小}
  22. result := GetAlignedSize(PeH.OptionalHeader.SizeOfHeaders, PeH.OptionalHeader.SectionAlignment);
  23. {计算所有节的大小}
  24. for i := 0 to peH.FileHeader.NumberOfSections - 1 do
  25. if peSecH[i].PointerToRawData + peSecH[i].SizeOfRawData > FileLen then // 超出文件范围
  26. begin
  27. result := 0;
  28. exit;
  29. end
  30. else if peSecH[i].VirtualAddress <> 0 then //计算对齐后某节的大小
  31. if peSecH[i].Misc.VirtualSize <> 0 then
  32. result := GetAlignedSize(peSecH[i].VirtualAddress + peSecH[i].Misc.VirtualSize, PeH.OptionalHeader.SectionAlignment)
  33. else
  34. result := GetAlignedSize(peSecH[i].VirtualAddress + peSecH[i].SizeOfRawData, PeH.OptionalHeader.SectionAlignment)
  35. else if peSecH[i].Misc.VirtualSize < peSecH[i].SizeOfRawData then
  36. result := result + GetAlignedSize(peSecH[i].SizeOfRawData, peH.OptionalHeader.SectionAlignment)
  37. else
  38. result := result + GetAlignedSize(peSecH[i].Misc.VirtualSize, PeH.OptionalHeader.SectionAlignment);
  39. end;
  40. { 加载pe到内存并对齐所有节 }
  41. function AlignPEToMem(const Buf; Len: Integer; var PeH: PImageNtHeaders;
  42. var PeSecH: PImageSectionHeaders; var Mem: Pointer; var ImageSize: Cardinal): Boolean;
  43. var
  44. SrcMz: PImageDosHeader; // DOS头
  45. SrcPeH: PImageNtHeaders; // PE头
  46. SrcPeSecH: PImageSectionHeaders; // 节表
  47. i: Integer;
  48. l: Cardinal;
  49. Pt: Pointer;
  50. begin
  51. result := false;
  52. SrcMz := @Buf;
  53. if Len < sizeof(TImageDosHeader) then exit;
  54. if SrcMz.e_magic <> IMAGE_DOS_SIGNATURE then exit;
  55. if Len < SrcMz._lfanew+Sizeof(TImageNtHeaders) then exit;
  56. SrcPeH := pointer(Integer(SrcMz)+SrcMz._lfanew);
  57. if (SrcPeH.Signature <> IMAGE_NT_SIGNATURE) then exit;
  58. if (SrcPeH.FileHeader.Characteristics and IMAGE_FILE_DLL <> 0or
  59. (SrcPeH.FileHeader.Characteristics and IMAGE_FILE_EXECUTABLE_IMAGE = 0)
  60. or (SrcPeH.FileHeader.SizeOfOptionalHeader <> SizeOf(TImageOptionalHeader)) then exit;
  61. SrcPeSecH := Pointer(Integer(SrcPeH)+SizeOf(TImageNtHeaders));
  62. ImageSize := CalcTotalImageSize(SrcMz, Len, SrcPeH, SrcPeSecH);
  63. if ImageSize = 0 then
  64. exit;
  65. Mem := VirtualAlloc(nil, ImageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); // 分配内存
  66. if Mem <> nil then
  67. begin
  68. // 计算需要复制的PE头字节数
  69. l := SrcPeH.OptionalHeader.SizeOfHeaders;
  70. for i := 0 to SrcPeH.FileHeader.NumberOfSections - 1 do
  71. if (SrcPeSecH[i].PointerToRawData <> 0and (SrcPeSecH[i].PointerToRawData < l) then
  72. l := SrcPeSecH[i].PointerToRawData;
  73. Move(SrcMz^, Mem^, l);
  74. PeH := Pointer(Integer(Mem) + PImageDosHeader(Mem)._lfanew);
  75. PeSecH := Pointer(Integer(PeH) + sizeof(TImageNtHeaders));
  76. Pt := Pointer(Cardinal(Mem) + GetAlignedSize(PeH.OptionalHeader.SizeOfHeaders, PeH.OptionalHeader.SectionAlignment));
  77. for i := 0 to PeH.FileHeader.NumberOfSections - 1 do
  78. begin
  79. // 定位该节在内存中的位置
  80. if PeSecH[i].VirtualAddress <> 0 then
  81. Pt := Pointer(Cardinal(Mem) + PeSecH[i].VirtualAddress);
  82. if PeSecH[i].SizeOfRawData <> 0 then
  83. begin
  84. // 复制数据到内存
  85. Move(Pointer(Cardinal(SrcMz) + PeSecH[i].PointerToRawData)^, pt^, PeSecH[i].SizeOfRawData);
  86. if peSecH[i].Misc.VirtualSize < peSecH[i].SizeOfRawData then
  87. pt := pointer(Cardinal(pt) + GetAlignedSize(PeSecH[i].SizeOfRawData, PeH.OptionalHeader.SectionAlignment))
  88. else
  89. pt := pointer(Cardinal(pt) + GetAlignedSize(peSecH[i].Misc.VirtualSize, peH.OptionalHeader.SectionAlignment));
  90. // pt 定位到下一节开始位置
  91. end
  92. else
  93. pt := pointer(Cardinal(pt) + GetAlignedSize(PeSecH[i].Misc.VirtualSize, PeH.OptionalHeader.SectionAlignment));
  94. end;
  95. result := True;
  96. end;
  97. end;
  98. type
  99. TVirtualAllocEx = function (hProcess: THandle; lpAddress: Pointer;
  100. dwSize, flAllocationType: DWORD; flProtect: DWORD): Pointer; stdcall;
  101. var
  102. MyVirtualAllocEx: TVirtualAllocEx = nil;
  103. function IsNT: Boolean;
  104. begin
  105. result := Assigned(MyVirtualAllocEx);
  106. end;
  107. { 生成外壳程序命令行 }
  108. function PrepareShellExe(CmdParam: string; BaseAddr, ImageSize: Cardinal): string;
  109. var
  110. r, h, sz: Cardinal;
  111. p: Pointer;
  112. fid, l: Integer;
  113. buf: Pointer;
  114. peH: PImageNtHeaders;
  115. peSecH: PImageSectionHeaders;
  116. begin
  117. if IsNT then
  118. { NT 系统下直接使用自身程序作为外壳进程 }
  119. result := ParamStr(0) + CmdParam
  120. else begin
  121. // 由于98系统下无法重新分配外壳进程占用内存,所以必须保证运行的外壳程序能容纳目标进程并且加载地址一致
  122. // 此处使用的方法是从资源中释放出一个事先建立好的外壳程序,然后通过修改其PE头使其运行时能加载到指定地址并至少能容纳目标进程
  123. r := FindResource(HInstance, 'SHELL_EXE', RT_RCDATA);
  124. h := LoadResource(HInstance, r);
  125. p := LockResource(h);
  126. l := SizeOfResource(HInstance, r);
  127. GetMem(Buf, l);
  128. Move(p^, Buf^, l); // 读到内存
  129. FreeResource(h);
  130. peH := Pointer(Integer(Buf) + PImageDosHeader(Buf)._lfanew);
  131. peSecH := Pointer(Integer(peH) + sizeof(TImageNtHeaders));
  132. peH.OptionalHeader.ImageBase := BaseAddr; // 修改PE头重的加载基址
  133. if peH.OptionalHeader.SizeOfImage < ImageSize then // 目标比外壳大,修改外壳程序运行时占用的内存
  134. begin
  135. sz := Imagesize - peH.OptionalHeader.SizeOfImage;
  136. Inc(peH.OptionalHeader.SizeOfImage, sz); // 调整总占用内存数
  137. Inc(peSecH[peH.FileHeader.NumberOfSections-1].Misc.VirtualSize, sz); // 调整最后一节占用内存数
  138. end;
  139. // 生成外壳程序文件名, 为本程序改后缀名得到的
  140. // 由于不想 uses SysUtils (一旦 use 了程序将增大80K左右), 而且偷懒,所以只支持最多运行11个进程,后缀名为.dat, .da0~.da9
  141. result := ParamStr(0);
  142. result := copy(result, 1, length(result) - 4) + '.dat';
  143. r := 0;
  144. while r < 10 do
  145. begin
  146. fid := CreateFile(pchar(result), GENERIC_READ or GENERIC_WRITE, 0nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
  147. if fid < 0 then
  148. begin
  149. result := copy(result, 1, length(result)-3)+'da'+Char(r+Byte('0'));
  150. inc(r);
  151. end
  152. else begin
  153. //SetFilePointer(fid, Imagesize, nil, 0);
  154. //SetEndOfFile(fid);
  155. //SetFilePointer(fid, 0, nil, 0);
  156. WriteFile(fid, Buf^, l, h, nil); // 写入文件
  157. CloseHandle(fid);
  158. break;
  159. end;
  160. end;
  161. result := result + CmdParam; // 生成命令行
  162. FreeMem(Buf);
  163. end;
  164. end;
  165. { 是否包含可重定向列表 }
  166. function HasRelocationTable(peH: PImageNtHeaders): Boolean;
  167. begin
  168. result := (peH.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress <> 0)
  169. and (peH.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size <> 0);
  170. end;
  171. type
  172. PImageBaseRelocation= ^TImageBaseRelocation;
  173. TImageBaseRelocation = packed record
  174. VirtualAddress: cardinal;
  175. SizeOfBlock: cardinal;
  176. end;
  177. { 重定向PE用到的地址 }
  178. procedure DoRelocation(peH: PImageNtHeaders; OldBase, NewBase: Pointer);
  179. var
  180. Delta: Cardinal;
  181. p: PImageBaseRelocation;
  182. pw: PWord;
  183. i: Integer;
  184. begin
  185. Delta := Cardinal(NewBase) - peH.OptionalHeader.ImageBase;
  186. p := pointer(cardinal(OldBase) + peH.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
  187. while (p.VirtualAddress + p.SizeOfBlock <> 0do
  188. begin
  189. pw := pointer(Integer(p) + Sizeof(p^));
  190. for i := 1 to (p.SizeOfBlock - Sizeof(p^)) div 2 do
  191. begin
  192. if pw^ and $F000 = $3000 then
  193. Inc(PCardinal(Cardinal(OldBase) + p.VirtualAddress + (pw^ and $0FFF))^, Delta);
  194. inc(pw);
  195. end;
  196. p := Pointer(pw);
  197. end;
  198. end;
  199. type
  200. TZwUnmapViewOfSection = function (Handle, BaseAdr: Cardinal): Cardinal; stdcall;
  201. { 卸载原外壳占用内存 }
  202. function UnloadShell(ProcHnd, BaseAddr: Cardinal): Boolean;
  203. var
  204. M: HModule;
  205. ZwUnmapViewOfSection: TZwUnmapViewOfSection;
  206. begin
  207. result := False;
  208. m := LoadLibrary('ntdll.dll');
  209. if m <> 0 then
  210. begin
  211. ZwUnmapViewOfSection := GetProcAddress(m, 'ZwUnmapViewOfSection');
  212. if assigned(ZwUnmapViewOfSection) then
  213. result := (ZwUnmapViewOfSection(ProcHnd, BaseAddr) = 0);
  214. FreeLibrary(m);
  215. end;
  216. end;
  217. { 创建外壳进程并获取其基址、大小和当前运行状态 }
  218. function CreateChild(Cmd: stringvar Ctx: TContext; var ProcHnd, ThrdHnd, ProcId, BaseAddr, ImageSize: Cardinal): Boolean;
  219. var
  220. si: TStartUpInfo;
  221. pi: TProcessInformation;
  222. Old: Cardinal;
  223. MemInfo: TMemoryBasicInformation;
  224. p: Pointer;
  225. begin
  226. FillChar(si, Sizeof(si), 0);
  227. FillChar(pi, SizeOf(pi), 0);
  228. si.cb := sizeof(si);
  229. result := CreateProcess(nil, PChar(Cmd), nilnil, False, CREATE_SUSPENDED, nilnil, si, pi); // 以挂起方式运行进程
  230. if result then
  231. begin
  232. ProcHnd := pi.hProcess;
  233. ThrdHnd := pi.hThread;
  234. ProcId := pi.dwProcessId;
  235. { 获取外壳进程运行状态,[ctx.Ebx+8]内存处存的是外壳进程的加载基址,ctx.Eax存放有外壳进程的入口地址 }
  236. ctx.ContextFlags := CONTEXT_FULL;
  237. GetThreadContext(ThrdHnd, ctx);
  238. ReadProcessMemory(ProcHnd, Pointer(ctx.Ebx+8), @BaseAddr, SizeOf(Cardinal), Old); // 读取加载基址
  239. p := Pointer(BaseAddr);
  240. { 计算外壳进程占有的内存 }
  241. while VirtualQueryEx(ProcHnd, p, MemInfo, Sizeof(MemInfo)) <> 0 do
  242. begin
  243. if MemInfo.State = MEM_FREE then
  244. break;
  245. p := Pointer(Cardinal(p) + MemInfo.RegionSize);
  246. end;
  247. ImageSize := Cardinal(p) - Cardinal(BaseAddr);
  248. end;
  249. end;
  250. { 创建外壳进程并用目标进程替换它然后执行 }
  251. function AttachPE(CmdParam: string; peH: PImageNtHeaders; peSecH: PImageSectionHeaders;
  252. Ptr: Pointer; ImageSize: Cardinal; var ProcId: Cardinal): Cardinal;
  253. var
  254. s: string;
  255. Addr, Size: Cardinal;
  256. ctx: TContext;
  257. Old: Cardinal;
  258. p: Pointer;
  259. Thrd: Cardinal;
  260. begin
  261. result := INVALID_HANDLE_VALUE;
  262. s := PrepareShellExe(CmdParam, peH.OptionalHeader.ImageBase, ImageSize);
  263. if CreateChild(s, ctx, result, Thrd, ProcId, Addr, Size) then
  264. begin
  265. p := nil;
  266. if (peH.OptionalHeader.ImageBase = Addr) and (Size >= ImageSize) then // 外壳进程可以容纳目标进程并且加载地址一致
  267. begin
  268. p := Pointer(Addr);
  269. VirtualProtectEx(result, p, Size, PAGE_EXECUTE_READWRITE, Old);
  270. end
  271. else if IsNT then // 98 下失败
  272. begin
  273. if UnloadShell(result, Addr) then // 卸载外壳进程占有内存
  274. // 重新按目标进程加载基址和大小分配内存
  275. p := MyVirtualAllocEx(Result, Pointer(peH.OptionalHeader.ImageBase), ImageSize, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  276. if (p = niland hasRelocationTable(peH) then // 分配内存失败并且目标进程支持重定向
  277. begin
  278. // 按任意基址分配内存
  279. p := MyVirtualAllocEx(result, nil, ImageSize, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  280. if p <> nil then
  281. DoRelocation(peH, Ptr, p); // 重定向
  282. end;
  283. end;
  284. if p <> nil then
  285. begin
  286. WriteProcessMemory(Result, Pointer(ctx.Ebx+8), @p, Sizeof(DWORD), Old); // 重置目标进程运行环境中的基址
  287. peH.OptionalHeader.ImageBase := Cardinal(p);
  288. if WriteProcessMemory(Result, p, Ptr, ImageSize, Old) then // 复制PE数据到目标进程
  289. begin
  290. ctx.ContextFlags := CONTEXT_FULL;
  291. if Cardinal(p) = Addr then
  292. ctx.Eax := peH.OptionalHeader.ImageBase + peH.OptionalHeader.AddressOfEntryPoint // 重置运行环境中的入口地址
  293. else
  294. ctx.Eax := Cardinal(p) + peH.OptionalHeader.AddressOfEntryPoint;
  295. SetThreadContext(Thrd, ctx); // 更新运行环境
  296. ResumeThread(Thrd); // 执行
  297. CloseHandle(Thrd);
  298. end
  299. else begin // 加载失败,杀掉外壳进程
  300. TerminateProcess(Result, 0);
  301. CloseHandle(Thrd);
  302. CloseHandle(Result);
  303. Result := INVALID_HANDLE_VALUE;
  304. end;
  305. end
  306. else begin // 加载失败,杀掉外壳进程
  307. TerminateProcess(Result, 0);
  308. CloseHandle(Thrd);
  309. CloseHandle(Result);
  310. Result := INVALID_HANDLE_VALUE;
  311. end;
  312. end;
  313. end;
  314. function MemExecute(const ABuffer; Len: Integer; CmdParam: stringvar ProcessId: Cardinal): Cardinal;
  315. var
  316. peH: PImageNtHeaders;
  317. peSecH: PImageSectionHeaders;
  318. Ptr: Pointer;
  319. peSz: Cardinal;
  320. begin
  321. result := INVALID_HANDLE_VALUE;
  322. if alignPEToMem(ABuffer, Len, peH, peSecH, Ptr, peSz) then
  323. begin
  324. result := AttachPE(CmdParam, peH, peSecH, Ptr, peSz, ProcessId);
  325. VirtualFree(Ptr, peSz, MEM_DECOMMIT);
  326. //VirtualFree(Ptr, 0, MEM_RELEASE);
  327. end;
  328. end;
  329. initialization
  330. MyVirtualAllocEx := GetProcAddress(GetModuleHandle('Kernel32.dll'), 'VirtualAllocEx');
  331. end.

你可能感兴趣的:(DELPHI编程)