Process Hollowing Theory
There are a few steps we must perform and components to consider,
but the most important is the the use of the
CREATE_SUSPENDED flag during process creation.
This flag allows us to create a new suspended or halted process.
When a process is created through the CreateProcess API,
the operating system does three things:
First, it creates the virtual memory space for the new process.
Next, it allocates the stack along with the Thread Environment Block
and the Process Environment Block.
Finally, it loads the required DLLs and the EXE into memory.
Once all of these tasks have been completed, the operating system will
create a thread to execute the code,
which will start at the EntryPoint of the executable.
If we supply the CREATE_SUSPENDED flag when calling CreateProcess,
the execution of the thread is halted just before it runs the EXE’s first instruction
At this point, we would locate the EntryPoint of the executable
and overwrite its in-memory content
with our staged shellcode and let it continue to execute.
Locating the EntryPoint is a bit tricky due to ASLR
but once the new suspended process is created,
we can turn to the Win32 ZwQueryInformationProcess API,
to retrieve certain information about the target process, including its PEB address.
From the PEB we can obtain the base address of the process
which we can use to parse the PE headers and locate the EntryPoint.
Specifically, when calling ZwQueryInformationProcess,
we must supply an enum from the ProcessInformationClass class.
If we choose this class,
we can obtain the address of the PEB in the suspended process.
We can find the base address of the executable
at offset 0x10 bytes into the PEB.
Next, we need to read the PEB address.
While ZwQueryInformationProcess yields the address of the PEB,
we must read from it, which we cannot do directly because it’s in a remote process.
To read from a remote process, we’ll use the ReadProcessMemory,
which is a counterpart to WriteProcessMemory.
This allows us to read out the contents of the remote PEB at offset 0x10.
From here, it gets a bit complicated and we need to do a little math,
but we begin with the base address that we already found.
Then we’ll once again use ReadProcessMemory
to read the first 0x200 bytes of memory.
This will allow us to analyze the remote process PE header.
The relevant items are shown in the PE file format header.
All PE files must follow this format,
which enables us to predict where to read from.
First, we read the e_lfanew field at offset 0x3C,
which contains the offset from the beginning of the PE (image base)
to the PE Header.
This offset is given as 0x80 bytes but can vary from file to file.
The PE signature found in the PE file format header
identifies the beginning of the PE header.
Once we have obtained the offset to the PE header,
we can read the EntryPoint Relative Virtual Address (RVA)
located at offset 0x28 from the PE header.
As the name suggests, the RVA is just an offset
and needs to be added to the remote process base address
to obtain the absolute virtual memory address of the EntryPoint.
Finally, we have the desired start address for our shellcode.
The details of this attack may seem daunting
but it provides us a way to hide in any process we can create,
masking our presence.