[轉貼] 從內存中加載 啟動一個EXE 源碼

  1. //PEUnit.pas
  2. { ******************************************************* }
  3. { * 從內存中加載並運行exe * }
  4. { ******************************************************* }
  5. { * 參數: }
  6. { * Buffer: 內存中的exe地址 }
  7. { * Len: 內存中exe佔用長度 }
  8. { * CmdParam: 命令行參數(不包含exe文件名的剩餘命令行參數)}
  9. { * ProcessId: 返回的進程Id }
  10. { * 返回值: 如果成功則返回進程的Handle(ProcessHandle), }
  11. { 如果失敗則返回INVALID_HANDLE_VALUE }
  12. { ******************************************************* }

  13. unit PEUnit;

  14. interface

  15. uses windows;

  16. function MemExecute(const ABuffer; Len: Integer; CmdParam: string; var ProcessId: Cardinal): Cardinal;

  17. implementation

  18. //{$R ExeShell.res} // 外殼程序模板(98下使用)

  19. type
  20. TImageSectionHeaders = array [0..0] of TImageSectionHeader;
  21. PImageSectionHeaders = ^TImageSectionHeaders;

  22. { 計算對齊後的大小 }
  23. function GetAlignedSize(Origin, Alignment: Cardinal): Cardinal;
  24. begin
  25. result := (Origin + Alignment - 1) div Alignment * Alignment;
  26. end;

  27. { 計算加載pe並對齊需要佔用多少內存,未直接使用OptionalHeader.SizeOfImage作為結果是因為據說有的編譯器生成的exe這個值會填0 }
  28. function CalcTotalImageSize(MzH: PImageDosHeader; FileLen: Cardinal; peH: PImageNtHeaders;
  29. peSecH: PImageSectionHeaders): Cardinal;
  30. var
  31. i: Integer;
  32. begin
  33. {計算pe頭的大小}
  34. result := GetAlignedSize(PeH.OptionalHeader.SizeOfHeaders, PeH.OptionalHeader.SectionAlignment);

  35. {計算所有節的大小}
  36. for i := 0 to peH.FileHeader.NumberOfSections - 1 do
  37. if peSecH[i].PointerToRawData + peSecH[i].SizeOfRawData > FileLen then // 超出文件範圍
  38. begin
  39. result := 0;
  40. exit;
  41. end
  42. else if peSecH[i].VirtualAddress <> 0 then //計算對齊後某節的大小
  43. if peSecH[i].Misc.VirtualSize <> 0 then
  44. result := GetAlignedSize(peSecH[i].VirtualAddress + peSecH[i].Misc.VirtualSize, PeH.OptionalHeader.SectionAlignment)
  45. else
  46. result := GetAlignedSize(peSecH[i].VirtualAddress + peSecH[i].SizeOfRawData, PeH.OptionalHeader.SectionAlignment)
  47. else if peSecH[i].Misc.VirtualSize < peSecH[i].SizeOfRawData then
  48. result := result + GetAlignedSize(peSecH[i].SizeOfRawData, peH.OptionalHeader.SectionAlignment)
  49. else
  50. result := result + GetAlignedSize(peSecH[i].Misc.VirtualSize, PeH.OptionalHeader.SectionAlignment);

  51. end;


  52. { 加載pe到內存並對齊所有節 }
  53. function AlignPEToMem(const Buf; Len: Integer; var PeH: PImageNtHeaders;
  54. var PeSecH: PImageSectionHeaders; var Mem: Pointer; var ImageSize: Cardinal): Boolean;
  55. var
  56. SrcMz: PImageDosHeader; // DOS頭
  57. SrcPeH: PImageNtHeaders; // PE頭
  58. SrcPeSecH: PImageSectionHeaders; // 節表
  59. i: Integer;
  60. l: Cardinal;
  61. Pt: Pointer;
  62. begin
  63. result := false;
  64. SrcMz := @Buf;
  65. if Len < sizeof(TImageDosHeader) then exit;
  66. if SrcMz.e_magic <> IMAGE_DOS_SIGNATURE then exit;
  67. if Len < SrcMz._lfanew+Sizeof(TImageNtHeaders) then exit;
  68. SrcPeH := pointer(Integer(SrcMz)+SrcMz._lfanew);
  69. if (SrcPeH.Signature <> IMAGE_NT_SIGNATURE) then exit;
  70. if (SrcPeH.FileHeader.Characteristics and IMAGE_FILE_DLL <> 0) or
  71. (SrcPeH.FileHeader.Characteristics and IMAGE_FILE_EXECUTABLE_IMAGE = 0)
  72. or (SrcPeH.FileHeader.SizeOfOptionalHeader <> SizeOf(TImageOptionalHeader)) then exit;
  73. SrcPeSecH := Pointer(Integer(SrcPeH)+SizeOf(TImageNtHeaders));
  74. ImageSize := CalcTotalImageSize(SrcMz, Len, SrcPeH, SrcPeSecH);
  75. if ImageSize = 0 then
  76. exit;
  77. Mem := VirtualAlloc(nil, ImageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); // 分配內存
  78. if Mem <> nil then
  79. begin
  80. // 計算需要復制的PE頭字節數
  81. l := SrcPeH.OptionalHeader.SizeOfHeaders;
  82. for i := 0 to SrcPeH.FileHeader.NumberOfSections - 1 do
  83. if (SrcPeSecH[i].PointerToRawData <> 0) and (SrcPeSecH[i].PointerToRawData < l) then
  84. l := SrcPeSecH[i].PointerToRawData;
  85. Move(SrcMz^, Mem^, l);
  86. PeH := Pointer(Integer(Mem) + PImageDosHeader(Mem)._lfanew);
  87. PeSecH := Pointer(Integer(PeH) + sizeof(TImageNtHeaders));

  88. Pt := Pointer(Cardinal(Mem) + GetAlignedSize(PeH.OptionalHeader.SizeOfHeaders, PeH.OptionalHeader.SectionAlignment));
  89. for i := 0 to PeH.FileHeader.NumberOfSections - 1 do
  90. begin
  91. // 定位該節在內存中的位置
  92. if PeSecH[i].VirtualAddress <> 0 then
  93. Pt := Pointer(Cardinal(Mem) + PeSecH[i].VirtualAddress);

  94. if PeSecH[i].SizeOfRawData <> 0 then
  95. begin
  96. // 復制數據到內存
  97. Move(Pointer(Cardinal(SrcMz) + PeSecH[i].PointerToRawData)^, pt^, PeSecH[i].SizeOfRawData);
  98. if peSecH[i].Misc.VirtualSize < peSecH[i].SizeOfRawData then
  99. pt := pointer(Cardinal(pt) + GetAlignedSize(PeSecH[i].SizeOfRawData, PeH.OptionalHeader.SectionAlignment))
  100. else
  101. pt := pointer(Cardinal(pt) + GetAlignedSize(peSecH[i].Misc.VirtualSize, peH.OptionalHeader.SectionAlignment));
  102. // pt 定位到下一節開始位置
  103. end
  104. else
  105. pt := pointer(Cardinal(pt) + GetAlignedSize(PeSecH[i].Misc.VirtualSize, PeH.OptionalHeader.SectionAlignment));
  106. end;
  107. result := True;
  108. end;
  109. end;

  110. type
  111. TVirtualAllocEx = function (hProcess: THandle; lpAddress: Pointer;
  112. dwSize, flAllocationType: DWORD; flProtect: DWORD): Pointer; stdcall;

  113. var
  114. MyVirtualAllocEx: TVirtualAllocEx = nil;

  115. function IsNT: Boolean;
  116. begin
  117. result := Assigned(MyVirtualAllocEx);
  118. end;

  119. { 生成外殼程序命令行 }
  120. function PrepareShellExe(CmdParam: string; BaseAddr, ImageSize: Cardinal): string;
  121. var
  122. r, h, sz: Cardinal;
  123. p: Pointer;
  124. fid, l: Integer;
  125. buf: Pointer;
  126. peH: PImageNtHeaders;
  127. peSecH: PImageSectionHeaders;
  128. begin
  129. if IsNT then
  130. { NT 系統下直接使用自身程序作為外殼進程 }
  131. result := ParamStr(0)+CmdParam
  132. else begin
  133. // 由于98系統下無法重新分配外殼進程佔用內存,所以必須保證運行的外殼程序能容納目標進程並且加載地址一致
  134. // 此處使用的方法是從資源中釋放出一個事先建立好的外殼程序,然後通過修改其PE頭使其運行時能加載到指定地址並至少能容納目標進程
  135. r := FindResource(HInstance, 'SHELL_EXE', RT_RCDATA);
  136. h := LoadResource(HInstance, r);
  137. p := LockResource(h);
  138. l := SizeOfResource(HInstance, r);
  139. GetMem(Buf, l);
  140. Move(p^, Buf^, l); // 讀到內存
  141. FreeResource(h);
  142. peH := Pointer(Integer(Buf) + PImageDosHeader(Buf)._lfanew);
  143. peSecH := Pointer(Integer(peH) + sizeof(TImageNtHeaders));
  144. peH.OptionalHeader.ImageBase := BaseAddr; // 修改PE頭重的加載基址
  145. if peH.OptionalHeader.SizeOfImage < ImageSize then // 目標比外殼大,修改外殼程序運行時佔用的內存
  146. begin
  147. sz := Imagesize - peH.OptionalHeader.SizeOfImage;
  148. Inc(peH.OptionalHeader.SizeOfImage, sz); // 調整總佔用內存數
  149. Inc(peSecH[peH.FileHeader.NumberOfSections-1].Misc.VirtualSize, sz); // 調整最後一節佔用內存數
  150. end;

  151. // 生成外殼程序文件名, 為本程序改後綴名得到的
  152. // 由于不想 uses SysUtils (一旦 use 了程序將增大80K左右), 而且偷懶,所以只支持最多運行11個進程,後綴名為.dat, .da0~.da9
  153. result := ParamStr(0);
  154. result := copy(result, 1, length(result) - 4) + '.dat';
  155. r := 0;
  156. while r < 10 do
  157. begin
  158. fid := CreateFile(pchar(result), GENERIC_READ or GENERIC_WRITE, 0, nil, Create_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
  159. if fid < 0 then
  160. begin
  161. result := copy(result, 1, length(result)-3)+'da'+Char(r+Byte('0'));
  162. inc(r);
  163. end
  164. else begin
  165. //SetFilePointer(fid, Imagesize, nil, 0);
  166. //SetEndOfFile(fid);
  167. //SetFilePointer(fid, 0, nil, 0);
  168. WriteFile(fid, Buf^, l, h, nil); // 寫入文件
  169. CloseHandle(fid);
  170. break;
  171. end;
  172. end;
  173. result := result + CmdParam; // 生成命令行
  174. FreeMem(Buf);
  175. end;
  176. end;
  177. { 是否包含可重定向列表 }
  178. function HasRelocationTable(peH: PImageNtHeaders): Boolean;
  179. begin
  180. result := (peH.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress <> 0)
  181. and (peH.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size <> 0);
  182. end;

  183. type
  184. PImageBaseRelocation= ^TImageBaseRelocation;
  185. TImageBaseRelocation = packed record
  186. VirtualAddress: cardinal;
  187. SizeOfBlock: cardinal;
  188. end;

  189. { 重定向PE用到的地址 }
  190. procedure DoRelocation(peH: PImageNtHeaders; OldBase, NewBase: Pointer);
  191. var
  192. Delta: Cardinal;
  193. p: PImageBaseRelocation;
  194. pw: PWord;
  195. i: Integer;
  196. begin
  197. Delta := Cardinal(NewBase) - peH.OptionalHeader.ImageBase;
  198. p := pointer(cardinal(OldBase) + peH.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
  199. while (p.VirtualAddress + p.SizeOfBlock <> 0) do
  200. begin
  201. pw := pointer(Integer(p) + Sizeof(p^));
  202. for i := 1 to (p.SizeOfBlock - Sizeof(p^)) div 2 do
  203. begin
  204. if pw^ and $F000 = $3000 then
  205. Inc(PCardinal(Cardinal(OldBase) + p.VirtualAddress + (pw^ and $0FFF))^, Delta);
  206. inc(pw);
  207. end;
  208. p := Pointer(pw);
  209. end;
  210. end;

  211. type
  212. TZwUnmapViewOfSection = function (Handle, BaseAdr: Cardinal): Cardinal; stdcall;

  213. { 卸載原外殼佔用內存 }
  214. function UnloadShell(ProcHnd, BaseAddr: Cardinal): Boolean;
  215. var
  216. M: HModule;
  217. ZwUnmapViewOfSection: TZwUnmapViewOfSection;
  218. begin
  219. result := False;
  220. m := LoadLibrary('ntdll.dll');
  221. if m <> 0 then
  222. begin
  223. ZwUnmapViewOfSection := GetProcAddress(m, 'ZwUnmapViewOfSection');
  224. if assigned(ZwUnmapViewOfSection) then
  225. result := (ZwUnmapViewOfSection(ProcHnd, BaseAddr) = 0);
  226. FreeLibrary(m);
  227. end;
  228. end;

  229. { 創建外殼進程並獲取其基址、大小和當前運行狀態 }
  230. function CreateChild(Cmd: string; var Ctx: TContext; var ProcHnd, ThrdHnd, ProcId, BaseAddr, ImageSize: Cardinal): Boolean;
  231. var
  232. si: TStartUpInfo;
  233. pi: TProcessInformation;
  234. Old: Cardinal;
  235. MemInfo: TMemoryBasicInformation;
  236. p: Pointer;
  237. begin
  238. FillChar(si, Sizeof(si), 0);
  239. FillChar(pi, SizeOf(pi), 0);
  240. si.cb := sizeof(si);
  241. result := CreateProcess(nil, PChar(Cmd), nil, nil, False, Create_SUSPENDED, nil, nil, si, pi); // 以掛起方式運行進程
  242. if result then
  243. begin
  244. ProcHnd := pi.hProcess;
  245. ThrdHnd := pi.hThread;
  246. ProcId := pi.dwProcessId;

  247. { 獲取外殼進程運行狀態,[ctx.Ebx+8]內存處存的是外殼進程的加載基址,ctx.Eax存放有外殼進程的入口地址 }
  248. ctx.ContextFlags := CONTEXT_FULL;
  249. GetThreadContext(ThrdHnd, ctx);
  250. ReadProcessMemory(ProcHnd, Pointer(ctx.Ebx+8), @BaseAddr, SizeOf(Cardinal), Old); // 讀取加載基址
  251. p := Pointer(BaseAddr);

  252. { 計算外殼進程佔有的內存 }
  253. while VirtualQueryEx(ProcHnd, p, MemInfo, Sizeof(MemInfo)) <> 0 do
  254. begin
  255. if MemInfo.State = MEM_FREE then
  256. break;
  257. p := Pointer(Cardinal(p) + MemInfo.RegionSize);
  258. end;
  259. ImageSize := Cardinal(p) - Cardinal(BaseAddr);
  260. end;
  261. end;

  262. { 創建外殼進程並用目標進程替換它然後執行 }
  263. function AttachPE(CmdParam: string; peH: PImageNtHeaders; peSecH: PImageSectionHeaders;
  264. Ptr: Pointer; ImageSize: Cardinal; var ProcId: Cardinal): Cardinal;
  265. var
  266. s: string;
  267. Addr, Size: Cardinal;
  268. ctx: TContext;
  269. Old: Cardinal;
  270. p: Pointer;
  271. Thrd: Cardinal;
  272. begin
  273. result := INVALID_HANDLE_VALUE;
  274. s := PrepareShellExe(CmdParam, peH.OptionalHeader.ImageBase, ImageSize);
  275. if CreateChild(s, ctx, result, Thrd, ProcId, Addr, Size) then
  276. begin
  277. p := nil;
  278. if (peH.OptionalHeader.ImageBase = Addr) and (Size >= ImageSize) then // 外殼進程可以容納目標進程並且加載地址一致
  279. begin
  280. p := Pointer(Addr);
  281. VirtualProtectEx(result, p, Size, PAGE_EXECUTE_READWRITE, Old);
  282. end
  283. else if IsNT then // 98 下失敗
  284. begin
  285. if UnloadShell(result, Addr) then // 卸載外殼進程佔有內存
  286. // 重新按目標進程加載基址和大小分配內存
  287. p := MyVirtualAllocEx(Result, Pointer(peH.OptionalHeader.ImageBase), ImageSize, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  288. if (p = nil) and hasRelocationTable(peH) then // 分配內存失敗並且目標進程支持重定向
  289. begin
  290. // 按任意基址分配內存
  291. p := MyVirtualAllocEx(result, nil, ImageSize, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  292. if p <> nil then
  293. DoRelocation(peH, Ptr, p); // 重定向
  294. end;
  295. end;
  296. if p <> nil then
  297. begin
  298. WriteProcessMemory(Result, Pointer(ctx.Ebx+8), @p, Sizeof(DWORD), Old); // 重置目標進程運行環境中的基址
  299. peH.OptionalHeader.ImageBase := Cardinal(p);
  300. if WriteProcessMemory(Result, p, Ptr, ImageSize, Old) then // 復制PE數據到目標進程
  301. begin
  302. ctx.ContextFlags := CONTEXT_FULL;
  303. if Cardinal(p) = Addr then
  304. ctx.Eax := peH.OptionalHeader.ImageBase + peH.OptionalHeader.AddressOfEntryPoint // 重置運行環境中的入口地址
  305. else
  306. ctx.Eax := Cardinal(p) + peH.OptionalHeader.AddressOfEntryPoint;
  307. SetThreadContext(Thrd, ctx); // 更新運行環境
  308. ResumeThread(Thrd); // 執行
  309. CloseHandle(Thrd);
  310. end
  311. else begin // 加載失敗,殺掉外殼進程
  312. TerminateProcess(Result, 0);
  313. CloseHandle(Thrd);
  314. CloseHandle(Result);
  315. Result := INVALID_HANDLE_VALUE;
  316. end;
  317. end
  318. else begin // 加載失敗,殺掉外殼進程
  319. TerminateProcess(Result, 0);
  320. CloseHandle(Thrd);
  321. CloseHandle(Result);
  322. Result := INVALID_HANDLE_VALUE;
  323. end;
  324. end;
  325. end;

  326. function MemExecute(const ABuffer; Len: Integer; CmdParam: string; var ProcessId: Cardinal): Cardinal;
  327. var
  328. peH: PImageNtHeaders;
  329. peSecH: PImageSectionHeaders;
  330. Ptr: Pointer;
  331. peSz: Cardinal;
  332. begin
  333. result := INVALID_HANDLE_VALUE;
  334. if alignPEToMem(ABuffer, Len, peH, peSecH, Ptr, peSz) then
  335. begin
  336. result := AttachPE(CmdParam, peH, peSecH, Ptr, peSz, ProcessId);
  337. VirtualFree(Ptr, peSz, MEM_DECOMMIT);
  338. //VirtualFree(Ptr, 0, MEM_RELEASE);
  339. end;
  340. end;

  341. initialization
  342. MyVirtualAllocEx := GetProcAddress(GetModuleHandle('Kernel32.dll'), 'VirtualAllocEx');

  343. end.
複製代碼
  1. //Test.dpr
  2. program Test;

  3. //{$APPTYPE CONSOLE}

  4. uses
  5.   SysUtils,
  6.   Classes,
  7.   PEUnit in 'PEUnit.pas';

  8. var
  9. ABuffer: array of byte;
  10. Stream: TFileStream;
  11. ProcessId: Cardinal;
  12. begin
  13. Stream:=TFileStream.Create('Target.exe', fmOpenRead); //加載的程序.exe
  14. try
  15. SetLength(ABuffer, Stream.Size);
  16. Stream.ReadBuffer(ABuffer[0], Stream.Size);
  17. MemExecute(ABuffer[0], Stream.Size, '', ProcessId);
  18. finally
  19. Stream.Free;
  20. end;
  21. end.
複製代碼
Arrive a goal
Taiwan technology University

功用是?秘密執行EXE程序?

TOP