Windows的PE加载器在启动程序的时候,会将磁盘上的文件加载到内存,然后做很多操作,如函数导入
重定位,变量预处理之类的 Windows的PE加载器在启动程序的时候,会将磁盘上的文件加载到内存,然后做很多操作,如函数导入表重定位,变量预处理之类的。这位仁兄等于是自己写了一个PE加载器。直接将内存中的程序启动。记得以前的“红色代码”病毒也有相同的特性。 直接启动内存中的程序相当于加了一个壳,可以把程序加密保存,运行时解密到内存,然后启动,不过对于增加破解难度还要稍微复杂点。否则人家把内存中的进程DUMP出来然后修复导入表就被拖出来了。 #include "stdafx.h" typedef IMAGE_SECTION_HEADER (*PIMAGE_SECTION_HEADERS)[1]; // 计算对齐后的大小 unsigned long GetAlignedSize(unsigned long Origin, unsigned long Alignment) { return (Origin + Alignment - 1) / Alignment * Alignment; } // 计算加载pe并对齐需要占用多少内存 // 未直接使用OptionalHeader.SizeOfImage作为结果是因为据说有的编译器生成的exe这个值会填0 unsigned long CalcTotalImageSize(PIMAGE_DOS_HEADER MzH , unsigned long FileLen , PIMAGE_NT_HEADERS peH , PIMAGE_SECTION_HEADERS peSecH) { unsigned long res; // 计算pe头的大小 res = GetAlignedSize( peH->OptionalHeader.SizeOfHeaders , peH->OptionalHeader.SectionAlignment ); // 计算所有节的大小 for( int i = 0; i < peH->FileHeader.NumberOfSections; ++i) { // 超出文件范围 if(peSecH[i]->PointerToRawData + peSecH[i]->SizeOfRawData > FileLen) return 0; else if(peSecH[i]->VirtualAddress)//计算对齐后某节的大小 { if(peSecH[i]->Misc.VirtualSize) { res = GetAlignedSize( peSecH[i]->VirtualAddress + peSecH[i]->Misc.VirtualSize , peH->OptionalHeader.SectionAlignment ); } else { res = GetAlignedSize( peSecH[i]->VirtualAddress + peSecH[i]->SizeOfRawData , peH->OptionalHeader.SectionAlignment ); } } else if( peSecH[i]->Misc.VirtualSize < peSecH[i]->SizeOfRawData ) { res += GetAlignedSize( peSecH[i]->SizeOfRawData , peH->OptionalHeader.SectionAlignment ); } else { res += GetAlignedSize( peSecH[i]->Misc.VirtualSize , peH->OptionalHeader.SectionAlignment ); }// if_else }// for return res; } // 加载pe到内存并对齐所有节 BOOL AlignPEToMem( void *Buf , long Len , PIMAGE_NT_HEADERS &peH , PIMAGE_SECTION_HEADERS &peSecH , void *&Mem , unsigned long &ImageSize) { PIMAGE_DOS_HEADER SrcMz;// DOS头 PIMAGE_NT_HEADERS SrcPeH;// PE头 PIMAGE_SECTION_HEADERS SrcPeSecH;// 节表 SrcMz = (PIMAGE_DOS_HEADER)Buf; if( Len < sizeof(IMAGE_DOS_HEADER) ) return FALSE; if( SrcMz->e_magic != IMAGE_DOS_SIGNATURE ) return FALSE; if( Len < SrcMz->e_lfanew + (long)sizeof(IMAGE_NT_HEADERS) ) return FALSE; SrcPeH = (PIMAGE_NT_HEADERS)((int)SrcMz + SrcMz->e_lfanew); if( SrcPeH->Signature != IMAGE_NT_SIGNATURE ) return FALSE; if( (SrcPeH->FileHeader.Characteristics & IMAGE_FILE_DLL) || (SrcPeH->FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE == 0) || (SrcPeH->FileHeader.SizeOfOptionalHeader != sizeof(IMAGE_OPTIONAL_HEADER)) ) { return FALSE; } SrcPeSecH = (PIMAGE_SECTION_HEADERS)((int)SrcPeH + sizeof(IMAGE_NT_HEADERS)); ImageSize = CalcTotalImageSize( SrcMz, Len, SrcPeH, SrcPeSecH); if( ImageSize == 0 ) return FALSE; Mem = VirtualAlloc( NULL, ImageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); // 分配内存 if( Mem != NULL ) { // 计算需要复制的PE头字节数 unsigned long l = SrcPeH->OptionalHeader.SizeOfHeaders; for( int i = 0; i < SrcPeH->FileHeader.NumberOfSections; ++i) { if( (SrcPeSecH[i]->PointerToRawData) && (SrcPeSecH[i]->PointerToRawData < l) ) { l = SrcPeSecH[i]->PointerToRawData; } } memmove( Mem, SrcMz, l); peH = (PIMAGE_NT_HEADERS)((int)Mem + ((PIMAGE_DOS_HEADER)Mem)->e_lfanew); peSecH = (PIMAGE_SECTION_HEADERS)((int)peH + sizeof(IMAGE_NT_HEADERS)); void *Pt = (void *)((unsigned long)Mem + GetAlignedSize( peH->OptionalHeader.SizeOfHeaders , peH->OptionalHeader.SectionAlignment) ); for( i = 0; i < peH->FileHeader.NumberOfSections; ++i) { // 定位该节在内存中的位置 if(peSecH[i]->VirtualAddress) Pt = (void *)((unsigned long)Mem + peSecH[i]->VirtualAddress); if(peSecH[i]->SizeOfRawData) { // 复制数据到内存 memmove(Pt, (const void *)((unsigned long)(SrcMz) + peSecH[i]->PointerToRawData), peSecH[i]->SizeOfRawData); if(peSecH[i]->Misc.VirtualSize < peSecH[i]->SizeOfRawData) Pt = (void *)((unsigned long)Pt + GetAlignedSize(peSecH[i]->SizeOfRawData, peH->OptionalHeader.SectionAlignment)); else // pt 定位到下一节开始位置 Pt = (void *)((unsigned long)Pt + GetAlignedSize(peSecH[i]->Misc.VirtualSize, peH->OptionalHeader.SectionAlignment)); } else { Pt = (void *)((unsigned long)Pt + GetAlignedSize(peSecH[i]->Misc.VirtualSize, peH->OptionalHeader.SectionAlignment)); } } } return TRUE; } typedef void *(__stdcall *pfVirtualAllocEx)(unsigned long, void *, unsigned long, unsigned long, unsigned long); pfVirtualAllocEx MyVirtualAllocEx = NULL; BOOL IsNT() { return MyVirtualAllocEx!=NULL; } // 生成外壳程序命令行 char *PrepareShellExe(char *CmdParam, unsigned long BaseAddr, unsigned long ImageSize) { if(IsNT()) { char *Buf = new char[256]; memset(Buf, 0, 256); GetModuleFileName(0, Buf, 256); strcat(Buf, CmdParam); return Buf; // 请记得释放内存;-) } else { // Win98下的处理请参考原文;-) // http://community.csdn.net/Expert/topic/4416/4416252.xml?temp=8.709133E-03 return NULL; } } // 是否包含可重定向列表 BOOL HasRelocationTable(PIMAGE_NT_HEADERS peH) { return (peH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress) && (peH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size); } #pragma pack(push, 1) typedef struct{ unsigned long VirtualAddress; unsigned long SizeOfBlock; } *PImageBaseRelocation; #pragma pack(pop) // 重定向PE用到的地址 void DoRelocation(PIMAGE_NT_HEADERS peH, void *OldBase, void *NewBase) { unsigned long Delta = (unsigned long)NewBase - peH->OptionalHeader.ImageBase; PImageBaseRelocation p = (PImageBaseRelocation)((unsigned long)OldBase + peH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); while(p->VirtualAddress + p->SizeOfBlock) { unsigned short *pw = (unsigned short *)((int)p + sizeof(*p)); for(unsigned int i=1; i <= (p->SizeOfBlock - sizeof(*p)) / 2; ++i) { if((*pw) & 0xF000 == 0x3000){ unsigned long *t = (unsigned long *)((unsigned long)(OldBase) + p->VirtualAddress + ((*pw) & 0x0FFF)); *t += Delta; } ++pw; } p = (PImageBaseRelocation)pw; } } // 卸载原外壳占用内存 BOOL UnloadShell(HANDLE ProcHnd, unsigned long BaseAddr) { typedef unsigned long (__stdcall *pfZwUnmapViewOfSection)(unsigned long, unsigned long); pfZwUnmapViewOfSection ZwUnmapViewOfSection = NULL; BOOL res = FALSE; HMODULE m = LoadLibrary("ntdll.dll"); if(m){ ZwUnmapViewOfSection = (pfZwUnmapViewOfSection)GetProcAddress(m, "ZwUnmapViewOfSection"); if(ZwUnmapViewOfSection) res = (ZwUnmapViewOfSection((unsigned long)ProcHnd, BaseAddr) == 0); FreeLibrary(m); } return res; } // 创建外壳进程并获取其基址、大小和当前运行状态 BOOL CreateChild(char *Cmd, CONTEXT &Ctx, HANDLE &ProcHnd, HANDLE &ThrdHnd, unsigned long &ProcId, unsigned long &BaseAddr, unsigned long &ImageSize) { STARTUPINFOA si; PROCESS_INFORMATION pi; unsigned long old; MEMORY_BASIC_INFORMATION MemInfo; memset(&si, 0, sizeof(si)); memset(&pi, 0, sizeof(pi)); si.cb = sizeof(si); BOOL res = CreateProcess(NULL, Cmd, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi); // 以挂起方式运行进程; if(res){ ProcHnd = pi.hProcess; ThrdHnd = pi.hThread; ProcId = pi.dwProcessId; // 获取外壳进程运行状态,[ctx.Ebx+8]内存处存的是外壳进程的加载基址,ctx.Eax存放有外壳进程的入口地址 Ctx.ContextFlags = CONTEXT_FULL; GetThreadContext(ThrdHnd, &Ctx); ReadProcessMemory(ProcHnd, (void *)(Ctx.Ebx+8), &BaseAddr, sizeof(unsigned long), &old); // 读取加载基址 void *p = (void *)BaseAddr; // 计算外壳进程占有的内存 while(VirtualQueryEx(ProcHnd, p, &MemInfo, sizeof(MemInfo))) { if(MemInfo.State = MEM_FREE) break; p = (void *)((unsigned long)p + MemInfo.RegionSize); } ImageSize = (unsigned long)p - (unsigned long)BaseAddr; } return res; } // 创建外壳进程并用目标进程替换它然后执行 HANDLE AttachPE(char *CmdParam, PIMAGE_NT_HEADERS peH, PIMAGE_SECTION_HEADERS peSecH, void *Ptr, unsigned long ImageSize, unsigned long &ProcId) { HANDLE res = INVALID_HANDLE_VALUE; CONTEXT Ctx; HANDLE Thrd; unsigned long Addr, Size; char *s = PrepareShellExe(CmdParam, peH->OptionalHeader.ImageBase, ImageSize); if(s==NULL) return res; if(CreateChild(s, Ctx, res, Thrd, ProcId, Addr, Size)){ void *p = NULL; unsigned long old; if((peH->OptionalHeader.ImageBase == Addr) && (Size >= ImageSize)){// 外壳进程可以容纳目标进程并且加载地址一致 p = (void *)Addr; VirtualProtectEx(res, p, Size, PAGE_EXECUTE_READWRITE, &old); } else if(IsNT()){ if(UnloadShell(res, Addr)){// 卸载外壳进程占有内存 p = MyVirtualAllocEx((unsigned long)res, (void *)peH->OptionalHeader.ImageBase, ImageSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); } if((p == NULL) && HasRelocationTable(peH)){// 分配内存失败并且目标进程支持重定向 p = MyVirtualAllocEx((unsigned long)res, NULL, ImageSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); if(p) DoRelocation(peH, Ptr, p); // 重定向 } } if(p){ WriteProcessMemory(res, (void *)(Ctx.Ebx+8), &p, sizeof(DWORD), &old); // 重置目标进程运行环境中的基址 peH->OptionalHeader.ImageBase = (unsigned long)p; if(WriteProcessMemory(res, p, Ptr, ImageSize, &old)){// 复制PE数据到目标进程 Ctx.ContextFlags = CONTEXT_FULL; if((unsigned long)p == Addr) Ctx.Eax = peH->OptionalHeader.ImageBase + peH->OptionalHeader.AddressOfEntryPoint; // 重置运行环境中的入口地址 else Ctx.Eax = (unsigned long)p + peH->OptionalHeader.AddressOfEntryPoint; SetThreadContext(Thrd, &Ctx);// 更新运行环境 ResumeThread(Thrd);// 执行 CloseHandle(Thrd); } else{// 加载失败,杀掉外壳进程 TerminateProcess(res, 0); CloseHandle(Thrd); CloseHandle(res); res = INVALID_HANDLE_VALUE; } } else{// 加载失败,杀掉外壳进程 TerminateProcess(res, 0); CloseHandle(Thrd); CloseHandle(res); res = INVALID_HANDLE_VALUE; } } delete[] s; return res; } /**//******************************************************* { ******************************************************* } { * 从内存中加载并运行exe * } { ******************************************************* } { * 参数: } { * Buffer: 内存中的exe地址 } { * Len: 内存中exe占用长度 } { * CmdParam: 命令行参数(不包含exe文件名的剩余命令行参数)} { * ProcessId: 返回的进程Id } { * 返回值: 如果成功则返回进程的Handle(ProcessHandle), } { 如果失败则返回INVALID_HANDLE_VALUE } { ******************************************************* } *******************************************************/ HANDLE MemExecute(void *ABuffer, long Len, char *CmdParam, unsigned long *ProcessId) { HANDLE res = INVALID_HANDLE_VALUE; PIMAGE_NT_HEADERS peH; PIMAGE_SECTION_HEADERS peSecH; void *Ptr; unsigned long peSz; if(AlignPEToMem(ABuffer, Len, peH, peSecH, Ptr, peSz)) { res = AttachPE(CmdParam, peH, peSecH, Ptr, peSz, *ProcessId); VirtualFree(Ptr, peSz, MEM_DECOMMIT); } return res; } // 初始化 class CInit { public: CInit() { MyVirtualAllocEx = (pfVirtualAllocEx)GetProcAddress(GetModuleHandle("Kernel32.dll"), "VirtualAllocEx"); } }Init; int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { HANDLE hFile = NULL; hFile = ::CreateFile( "f:\SourceFromCsdn2.exe" , FILE_ALL_ACCESS , 0 , NULL , OPEN_EXISTING , FILE_ATTRIBUTE_NORMAL , NULL ); if( hFile == INVALID_HANDLE_VALUE ) return -1; ::SetFilePointer( hFile, 0, NULL, FILE_BEGIN); DWORD dwFileSize = ::GetFileSize( hFile, NULL); LPBYTE pBuf = new BYTE[dwFileSize]; memset( pBuf, 0, dwFileSize); DWORD dwNumberOfBytesRead = 0; ::ReadFile( hFile , pBuf , dwFileSize , &dwNumberOfBytesRead , NULL ); ::CloseHandle(hFile); unsigned long ulProcessId = 0; MemExecute( pBuf, dwFileSize, "", &ulProcessId); delete[] pBuf; return 0; } ================DELPHI版本======================================================== windows似乎只提供了一种启动进程的方法:即必须从一个可执行文件中加载并启动。 而下面这段代码就是提供一种可以直接从内存中启动一个exe的变通
。 用途嘛, 也许可以用来保护你的exe,你可以对要保护的 exe 进行任意切分、加密、存储,只要运行时能将exe的内容正确拼接到一块内存中,就可以直接从内存中启动,而不必不安全地去生成一个临时文件再从临时文件启动进程。另外这段代码也提供了一种自己写exe外壳的简单途径,如果能配合其它各种外壳技术就更好地保护你的exe文件。 原理很简单:就是“借尸还魂”,启动一个僵尸进程(NT下可以是自身程序启动的另一个进程),然后在它运行前将其整个替换成内存中的exe内容,待正式运行后执行的就是你的目标代码了。 不过代码中还有一些不尽人意的地方,比如在98下运行会留一个僵尸程序的壳在硬盘上(其实那个僵尸程序本身就是一个完整的可执行程序,直接运行的话只显示一条错误信息然后就退出了)。另外由于客观条件限制,代码没有经过充分测试,只在XP下进行了一些初步测试:普通exe都能正常运行,upx压缩过的exe绝大多数情况下都能运行,只有在不能卸载僵尸外壳时才有问题(upx压缩过的exe没有重定向表,无法加载到其它地址运行)。 如果有bug望告之,如果有更好的方法特别是能解决98下的遗留尾巴的话希望不吝赐教。 作者:Idle_ (阿呆) { ******************************************************* } { * 从内存中加载并运行exe * } { ******************************************************* } { * 参数: } { * Buffer: 内存中的exe地址 } { * Len: 内存中exe占用长度 } { * CmdParam: 命令行参数(不包含exe文件名的剩余命令行参数)} { * ProcessId: 返回的进程Id } { * 返回值: 如果成功则返回进程的Handle(ProcessHandle), } { 如果失败则返回INVALID_HANDLE_VALUE } { ******************************************************* } unit PEUnit; interface uses windows; function MemExecute(const ABuffer; Len: Integer; CmdParam: string; varProcessId: Cardinal): Cardinal; implementation {$R ExeShell.res} // 外壳程序
(98下使用) type TImageSectionHeaders = array [0..0] of TImageSectionHeader; PImageSectionHeaders = ^TImageSectionHeaders; { 计算对齐后的大小 } function GetAlignedSize(Origin, Alignment: Cardinal): Cardinal; begin result := (Origin + Alignment - 1) div Alignment * Alignment; end; { 计算加载pe并对齐需要占用多少内存,未直接使用OptionalHeader.SizeOfImage作为结果是因为据说有的编译器生成的exe这个值会填0 } function CalcTotalImageSize(MzH: PImageDosHeader; FileLen: Cardinal; peH:PImageNtHeaders; peSecH: PImageSectionHeaders): Cardinal; var i: Integer; begin {计算pe头的大小} result := GetAlignedSize(PeH.OptionalHeader.SizeOfHeaders,PeH.OptionalHeader.SectionAlignment); {计算所有节的大小} for i := 0 to peH.FileHeader.NumberOfSections - 1 do if peSecH[i].PointerToRawData + peSecH[i].SizeOfRawData > FileLen then // 超出文件范围 begin result := 0; exit; end else if peSecH[i].VirtualAddress <> 0 then //计算对齐后某节的大小 if peSecH[i].Misc.VirtualSize <> 0 then result := GetAlignedSize(peSecH[i].VirtualAddress +peSecH[i].Misc.VirtualSize, PeH.OptionalHeader.SectionAlignment) else result := GetAlignedSize(peSecH[i].VirtualAddress + peSecH[i].SizeOfRawData,PeH.OptionalHeader.SectionAlignment) else if peSecH[i].Misc.VirtualSize < peSecH[i].SizeOfRawData then result := result + GetAlignedSize(peSecH[i].SizeOfRawData,peH.OptionalHeader.SectionAlignment) else result := result + GetAlignedSize(peSecH[i].Misc.VirtualSize,PeH.OptionalHeader.SectionAlignment); end; { 加载pe到内存并对齐所有节 } function AlignPEToMem(const Buf; Len: Integer; var PeH: PImageNtHeaders; var PeSecH: PImageSectionHeaders; var Mem: Pointer; var ImageSize:Cardinal): Boolean; var SrcMz: PImageDosHeader; // DOS头 SrcPeH: PImageNtHeaders; // PE头 SrcPeSecH: PImageSectionHeaders; // 节表 i: Integer; l: Cardinal; Pt: Pointer; begin result := false; SrcMz := @Buf; if Len < sizeof(TImageDosHeader) then exit; if SrcMz.e_magic <> IMAGE_DOS_SIGNATURE then exit; if Len < SrcMz._lfanew+Sizeof(TImageNtHeaders) then exit; SrcPeH := pointer(Integer(SrcMz)+SrcMz._lfanew); if (SrcPeH.Signature <> IMAGE_NT_SIGNATURE) then exit; if (SrcPeH.FileHeader.Characteristics and IMAGE_FILE_DLL <> 0) or (SrcPeH.FileHeader.Characteristics and IMAGE_FILE_EXECUTABLE_IMAGE = 0) or (SrcPeH.FileHeader.SizeOfOptionalHeader <>SizeOf(TImageOptionalHeader)) then exit; SrcPeSecH := Pointer(Integer(SrcPeH)+SizeOf(TImageNtHeaders)); ImageSize := CalcTotalImageSize(SrcMz, Len, SrcPeH, SrcPeSecH); if ImageSize = 0 then exit; Mem := VirtualAlloc(nil, ImageSize, MEM_COMMIT,PAGE_EXECUTE_READWRITE); // 分配内存 if Mem <> nil then begin // 计算需要复制的PE头字节数 l := SrcPeH.OptionalHeader.SizeOfHeaders; for i := 0 to SrcPeH.FileHeader.NumberOfSections - 1 do if (SrcPeSecH[i].PointerToRawData <> 0) and (SrcPeSecH[i].PointerToRawData< l) then l := SrcPeSecH[i].PointerToRawData; Move(SrcMz^, Mem^, l); PeH := Pointer(Integer(Mem) + PImageDosHeader(Mem)._lfanew); PeSecH := Pointer(Integer(PeH) + sizeof(TImageNtHeaders)); Pt := Pointer(Cardinal(Mem) +GetAlignedSize(PeH.OptionalHeader.SizeOfHeaders,PeH.OptionalHeader.SectionAlignment)); for i := 0 to PeH.FileHeader.NumberOfSections - 1 do begin // 定位该节在内存中的位置 if PeSecH[i].VirtualAddress <> 0 then Pt := Pointer(Cardinal(Mem) + PeSecH[i].VirtualAddress); if PeSecH[i].SizeOfRawData <> 0 then begin // 复制数据到内存 Move(Pointer(Cardinal(SrcMz) + PeSecH[i].PointerToRawData)^, pt^,PeSecH[i].SizeOfRawData); if peSecH[i].Misc.VirtualSize < peSecH[i].SizeOfRawData then pt := pointer(Cardinal(pt) + GetAlignedSize(PeSecH[i].SizeOfRawData,PeH.OptionalHeader.SectionAlignment)) else pt := pointer(Cardinal(pt) + GetAlignedSize(peSecH[i].Misc.VirtualSize,peH.OptionalHeader.SectionAlignment)); // pt 定位到下一节开始位置 end else pt := pointer(Cardinal(pt) + GetAlignedSize(PeSecH[i].Misc.VirtualSize,PeH.OptionalHeader.SectionAlignment)); end; result := True; end; end; type TVirtualAllocEx = function (hProcess: THandle; lpAddress: Pointer; dwSize, flAllocationType: DWORD; flProtect: DWORD): Pointer; stdcall; var MyVirtualAllocEx: TVirtualAllocEx = nil; function IsNT: Boolean; begin result := Assigned(MyVirtualAllocEx); end; { 生成外壳程序命令行 } function PrepareShellExe(CmdParam: string; BaseAddr, ImageSize: Cardinal):string; var r, h, sz: Cardinal; p: Pointer; fid, l: Integer; buf: Pointer; peH: PImageNtHeaders; peSecH: PImageSectionHeaders; begin if IsNT then { NT 系统下直接使用自身程序作为外壳进程 } result := ParamStr(0)+CmdParam else begin // 由于98系统下无法重新分配外壳进程占用内存,所以必须保证运行的外壳程序能容纳目标进程并且加载地址一致 // 此处使用的方法是从资源中释放出一个事先建立好的外壳程序,然后通过修改其PE头使其运行时能加载到指定地址并至少能容纳目标进程 r := FindResource(HInstance, 'SHELL_EXE', RT_RCDATA); h := LoadResource(HInstance, r); p := LockResource(h); l := SizeOfResource(HInstance, r); GetMem(Buf, l); Move(p^, Buf^, l); // 读到内存 FreeResource(h); peH := Pointer(Integer(Buf) + PImageDosHeader(Buf)._lfanew); peSecH := Pointer(Integer(peH) + sizeof(TImageNtHeaders)); peH.OptionalHeader.ImageBase := BaseAddr; // 修改PE头重的加载基址 if peH.OptionalHeader.SizeOfImage < ImageSize then // 目标比外壳大,修改外壳程序运行时占用的内存 begin sz := Imagesize - peH.OptionalHeader.SizeOfImage; Inc(peH.OptionalHeader.SizeOfImage, sz); // 调整总占用内存数 Inc(peSecH[peH.FileHeader.NumberOfSections-1].Misc.VirtualSize, sz); // 调整最后一节占用内存数 end; // 生成外壳程序文件名, 为本程序改后缀名得到的 // 由于不想 uses SysUtils (一旦 use 了程序将增大80K左右), 而且偷懒,所以只支持最多运行11个进程,后缀名为.dat,