Load and start an exe from memory

Source: Internet
Author: User
Windows seems to provide only one way to start a process: that is, it must be loaded and started from an executable file.
The following code provides a method to enable an exe directly from the memory.
Purpose: It may be used to protect your exe. You can split, encrypt, and store the exe to be protected. When running, the content of the exe can be correctly spliced into a piece of memory, you can directly start the process from the memory without generating a temporary file unsafe. In addition, this code also provides a simple way to write the exe shell by yourself. If you can work with other shell technologies, you can better protect your exe files.

The principle is very simple: it is "Let the dead go" to start a zombie process (another process started by your program in NT), and then replace it with the exe content in the memory before it runs, after the code is officially run, your target code is executed.

However, there are still some unsatisfactory aspects in the Code. For example, when running under 98, a zombie program shell will be stored on the hard disk (in fact, the zombie program itself is a complete executable program, if you run the command directly, only one error message is displayed and then you exit ). In addition, due to objective constraints, the Code has not been fully tested. Only some preliminary tests are conducted under XP: normal exe can run normally, and upx compressed exe can run in most cases, this problem occurs only when the zombie shell cannot be uninstalled (The upx compressed exe does not have a redirection table and cannot be loaded to other addresses for running ).

If there is a bug, we hope to give you some advice if there is a better solution, especially if it can solve the legacy tail under 98.

{*************************************** ****************}
{* Load and run exe from memory *}
{*************************************** ****************}
{* Parameter :}
{* Buffer: exe address in memory}
{* Len: length of exe in memory}
{* CmdParam: command line parameter (excluding the remaining command line parameters of the exe file name )}
{* ProcessId: returned process Id}
{* Return value: if it succeeds, return the Handle (ProcessHandle) of the process ),}
{If it fails, INVALID_HANDLE_VALUE} is returned}
{*************************************** ****************}

Unit PEUnit;

Interface

Uses windows;

Function MemExecute (const ABuffer; Len: Integer; partition Param: string; var ProcessId: Cardinal): Cardinal;

Implementation

{$ R ExeShell. res} // shell template (used in 98)

Type
TImageSectionHeaders = array [0 .. 0] of TImageSectionHeader;
PImageSectionHeaders = ^ TImageSectionHeaders;

{Calculate the size after alignment}
Function GetAlignedSize (Origin, Alignment: Cardinal): Cardinal;
Begin
Result: = (Origin + Alignment-1) div Alignment * Alignment;
End;

{Calculate how much memory is needed to load the pe and align it. OptionalHeader. SizeOfImage is not directly used as the result because it is said that the value of exe generated by some compilers will be 0}
Function CalcTotalImageSize (MzH: PImageDosHeader; FileLen: Cardinal; peH: PImageNtHeaders;
PeSecH: PImageSectionHeaders): Cardinal;
Var
I: Integer;
Begin
{Calculate the size of the pe Header}
Result: = GetAlignedSize (PeH. OptionalHeader. SizeOfHeaders, PeH. OptionalHeader. SectionAlignment );

{Calculate the size of all segments}
For I: = 0 to peH. FileHeader. NumberOfSections-1 do
If peSecH [I]. PointerToRawData + peSecH [I]. SizeOfRawData> FileLen then // exceeds the file range
Begin
Result: = 0;
Exit;
End
Else if peSecH [I]. VirtualAddress <> 0 then // calculate the size of a section after alignment.
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;

{Load pe to memory and align all nodes}
Function AlignPEToMem (const Buf; Len: Integer; var PeH: PImageNtHeaders;
Var PeSecH: PImageSectionHeaders; var Mem: Pointer; var ImageSize: Cardinal): Boolean;
Var
SrcMz: PImageDosHeader; // DOS Header
SrcPeH: PImageNtHeaders; // PE Header
SrcPeSecH: PImageSectionHeaders; // Section Table
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); // allocate memory
If Mem <> nil then
Begin
// Calculate the number of PE Header bytes to be copied
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
// Locate the position of the node in memory
If PeSecH [I]. VirtualAddress <> 0 then
Pt: = Pointer (Cardinal (Mem) + PeSecH [I]. VirtualAddress );

If PeSecH [I]. SizeOfRawData <> 0 then
Begin
// Copy data to memory
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 ));
// Locate pt to the start position of the next section
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;

{Shell Generation Command Line}
Function PrepareShellExe (partition Param: 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
{Directly use your program as the shell process in the NT System}
Result: = ParamStr (0) + paramparam
Else begin
// Because the Shell Process in the 98 system cannot be re-allocated to occupy the memory, ensure that the running shell program can accommodate the target process and the load address is consistent.
// The method used here is to release a pre-established shell program from the resource, and then modify its PE Header so that it can load to the specified address during running and at least accommodate the target process
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); // READ memory
FreeResource (h );
PeH: = Pointer (Integer (Buf) + PImageDosHeader (Buf). _ lfanew );
PeSecH: = Pointer (Integer (peH) + sizeof (TImageNtHeaders ));
PeH. OptionalHeader. ImageBase: = BaseAddr; // modify the loading base address of the PE Header.
If peH. OptionalHeader. SizeOfImage <ImageSize then // The target is larger than the shell. Modify the memory occupied by the shell program.
Begin
Sz: = Imagesize-peH. OptionalHeader. SizeOfImage;
Inc (peH. OptionalHeader. SizeOfImage, sz); // adjust the total memory usage
Inc (peSecH [peH. FileHeader. NumberOfSections-1]. Misc. VirtualSize, sz); // adjust the memory usage for the last section
End;

// Generate the shell program file name, which is obtained by modifying the suffix of this program
// Because you do not want to use uses SysUtils (once the program is used, it will increase by about 80 K) and are lazy, you can only run up to 11 processes with the suffix. dat,. da0 ~. Da9
Result: = ParamStr (0 );
Result: = copy (result, 1, length (result)-4) + '. dat ';
R: = 0;
While r <10 do
Begin
Fid: = CreateFile (pchar (result), GENERIC_READ or GENERIC_WRITE, 0, nil, Create_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 );
If fid <0 then
Begin
Result: = copy (result, 1, length (result)-3) + 'da' + Char (r + Byte ('0 '));
Inc (r );
End
Else begin
// SetFilePointer (fid, Imagesize, nil, 0 );
// SetEndOfFile (fid );
// SetFilePointer (fid, 0, nil, 0 );
WriteFile (fid, Buf ^, l, h, nil); // write the file
CloseHandle (fid );
Break;
End;
End;
Result: = result + prepare Param; // generate a command line
FreeMem (Buf );
End;
End;

{Whether to include the redirected list}
Function HasRelocationTable (peH: PImageNtHeaders): Boolean;
Begin
Result: = (peH. OptionalHeader. DataDirectory [IMAGE_DIRECTORY_ENTRY_BASERELOC]. VirtualAddress <> 0)
And (peH. OptionalHeader. DataDirectory [IMAGE_DIRECTORY_ENTRY_BASERELOC]. Size <> 0 );
End;

Type
PImageBaseRelocation = ^ TImageBaseRelocation;
TImageBaseRelocation = packed record
VirtualAddress: cardinal;
SizeOfBlock: cardinal;
End;

{Address used by redirection PE}
Procedure DoRelocation (peH: PImageNtHeaders; OldBase, NewBase: Pointer );
Var
Delta: Cardinal;
P: PImageBaseRelocation;
Pw: PWord;
I: Integer;
Begin
Delta: = Cardinal (NewBase)-peH. OptionalHeader. ImageBase;
P: = pointer (cardinal (OldBase) + peH. OptionalHeader. DataDirectory [IMAGE_DIRECTORY_ENTRY_BASERELOC]. VirtualAddress );
While (p. VirtualAddress + p. SizeOfBlock <> 0) do
Begin
Pw: = pointer (Integer (p) + Sizeof (p ^ ));
For I: = 1 to (p. SizeOfBlock-Sizeof (p ^) div 2 do
Begin
If pw ^ and $ F000 = $3000 then
Inc (PCardinal (Cardinal (OldBase) + p. VirtualAddress + (pw ^ and $ 0FFF) ^, Delta );
Inc (pw );
End;
P: = Pointer (pw );
End;
End;

Type
TZwUnmapViewOfSection = function (Handle, BaseAdr: Cardinal): Cardinal; stdcall;

{Detaching the original shell memory usage}
Function UnloadShell (ProcHnd, BaseAddr: Cardinal): Boolean;
Var
M: HModule;
ZwUnmapViewOfSection: TZwUnmapViewOfSection;
Begin
Result: = False;
M: = LoadLibrary ('ntdll. dll ');
If m <> 0 then
Begin
ZwUnmapViewOfSection: = GetProcAddress (m, 'zwunmapviewofsection ');
If assigned (ZwUnmapViewOfSection) then
Result: = (ZwUnmapViewOfSection (ProcHnd, BaseAddr) = 0 );
FreeLibrary (m );
End;
End;

{Create a shell process and obtain its base address, size, and current running status}
Function CreateChild (Cmd: string; var Ctx: TContext; var ProcHnd, ThrdHnd, ProcId, BaseAddr, ImageSize: Cardinal): Boolean;
Var
Si: TStartUpInfo;
Pi: TProcessInformation;
Old: Cardinal;
MemInfo: TMemoryBasicInformation;
P: Pointer;
Begin
FillChar (si, Sizeof (si), 0 );
FillChar (pi, SizeOf (pi), 0 );
Si. cb: = sizeof (si );
Result: = CreateProcess (nil, PChar (Cmd), nil, nil, False, create_suincluded, nil, nil, si, pi); // run the process in suspended Mode
If result then
Begin
ProcHnd: = pi. hProcess;
ThrdHnd: = pi. hThread;
ProcId: = pi. dwProcessId;

{Obtain the running status of the shell process. [ctx. Ebx + 8] The base address for loading the shell process is stored in the memory, and ctx. Eax stores the entry address of the shell process}
Ctx. ContextFlags: = CONTEXT_FULL;
GetThreadContext (ThrdHnd, ctx );
ReadProcessMemory (ProcHnd, Pointer (ctx. Ebx + 8), @ BaseAddr, SizeOf (Cardinal), Old); // read the loading base address
P: = Pointer (BaseAddr );

{Memory occupied by the computing shell process}
While VirtualQueryEx (ProcHnd, p, MemInfo, Sizeof (MemInfo) <> 0 do
Begin
If MemInfo. State = MEM_FREE then
Break;
P: = Pointer (Cardinal (p) + MemInfo. RegionSize );
End;
ImageSize: = Cardinal (p)-Cardinal (BaseAddr );
End;
End;

{Create a shell process and replace it with the target process and execute it}
Function AttachPE (partition Param: string; peH: PImageNtHeaders; peSecH: PImageSectionHeaders;
Ptr: Pointer; ImageSize: Cardinal; var ProcId: Cardinal): Cardinal;
Var
S: string;
Addr, Size: Cardinal;
Ctx: TContext;
Old: Cardinal;
P: Pointer;
Thrd: Cardinal;
Begin
Result: = INVALID_HANDLE_VALUE;
S: = PrepareShellExe (partition Param, peH. OptionalHeader. ImageBase, ImageSize );
If CreateChild (s, ctx, result, Thrd, ProcId, Addr, Size) then
Begin
P: = nil;
If (peH. OptionalHeader. ImageBase = Addr) and (Size> = ImageSize) then // The shell process can accommodate the target process and load the same address
Begin
P: = Pointer (Addr );
VirtualProtectEx (result, p, Size, PAGE_EXECUTE_READWRITE, Old );
End
Else if IsNT then // 98 failed
Begin
If UnloadShell (result, Addr) then // uninstall Shell Process occupies memory
// Re-load the base address and size allocation memory by the target process
P: = MyVirtualAllocEx (Result, Pointer (peH. OptionalHeader. ImageBase), ImageSize, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE );
If (p = nil) and hasRelocationTable (peH) then // The Memory Allocation fails and the target process supports redirection.
Begin
// Allocate memory based on any base address
P: = MyVirtualAllocEx (result, nil, ImageSize, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE );
If p <> nil then
DoRelocation (peH, Ptr, p); // redirection
End;
End;
If p <> nil then
Begin
WriteProcessMemory (Result, Pointer (ctx. Ebx + 8), @ p, Sizeof (DWORD), Old); // reset the base address in the running environment of the target process
PeH. OptionalHeader. ImageBase: = Cardinal (p );
If WriteProcessMemory (Result, p, Ptr, ImageSize, Old) then // copy PE data to the target process
Begin
Ctx. ContextFlags: = CONTEXT_FULL;
If Cardinal (p) = Addr then
Ctx. Eax: = peH. OptionalHeader. ImageBase + peH. OptionalHeader. AddressOfEntryPoint // reset the endpoint address in the running environment
Else
Ctx. Eax: = Cardinal (p) + peH. OptionalHeader. AddressOfEntryPoint;
SetThreadContext (Thrd, ctx); // update the runtime environment
ResumeThread (Thrd); // Execute
CloseHandle (Thrd );
End
Else begin // loading failed, killing Shell Process
TerminateProcess (Result, 0 );
CloseHandle (Thrd );
CloseHandle (Result );
Result: = INVALID_HANDLE_VALUE;
End;
End
Else begin // loading failed, killing Shell Process
TerminateProcess (Result, 0 );
CloseHandle (Thrd );
CloseHandle (Result );
Result: = INVALID_HANDLE_VALUE;
End;
End;
End;

Function MemExecute (const ABuffer; Len: Integer; partition Param: string; var ProcessId: Cardinal): Cardinal;
Var
PeH: PImageNtHeaders;
PeSecH: PImageSectionHeaders;
Ptr: Pointer;
PeSz: Cardinal;
Begin
Result: = INVALID_HANDLE_VALUE;
If alignPEToMem (ABuffer, Len, peH, peSecH, Ptr, peSz) then
Begin
Result: = AttachPE (partition Param, peH, peSecH, Ptr, peSz, ProcessId );
VirtualFree (Ptr, peSz, MEM_DECOMMIT );
// VirtualFree (Ptr, 0, MEM_RELEASE );
End;
End;

Initialization
MyVirtualAllocEx: = GetProcAddress (GetModuleHandle ('kernel32. dll '), 'virtualallocex ');

End.

I wrote a simple program and passed the test :)

Program Test;

// {$ Apptype console}

Uses
SysUtils,
Classes,
PEUnit in 'peunit. pa ';

Var
ABuffer: array of byte;
Stream: TFileStream;
ProcessId: Cardinal;
Begin
Stream: effectfilestream.create('target.exe ', fmOpenRead );
Try
SetLength (ABuffer, Stream. Size );
Stream. ReadBuffer (ABuffer [0], Stream. Size );
MemExecute (ABuffer [0], Stream. Size, '', ProcessId );
Finally
Stream. Free;
End;
End.
 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.