First, we need to understand how legitimate executables run in windows. Legitimate executables often used system dll to serve as a part of their code and need underlying hardware resources. User mode executables cannot directly access hardware; they have first to access the kernel level code (which in turn talks to hardware). From a security perspective, it will be good not to allow user mode processes directly talk with kernel level code, so it turns out that there are various API calls that happen from user mode to kernel mode. For example, I have a user mode process ‘U’ which will talk with an intermediary DLL like ntdll, etc. to talk with a kernel which in turn will talk to the underlying hardware. (Well this is a view from way up as in real there are lot of other components fits at each layer to interact with each other). Now there are various ways in which this process can be abused so that the malicious code can run. Well, that is the whole purpose; the end goal is to make sure that the malicious code should run instead of legitimate one. That is called code injection, and there are various types of it. In this article, we will talk about Remote DLL injection and Hollow Process Injection. In part 2 of this article series we will cover Remote Code Injection and Reflective DLL injection.
Remote DLL injection
Every process needs system DLLs to run. Remote DLL injection is the way in which a remote malicious DLL gets injected into a legitimate process. Below are some of the calls that you can see in an executable for this type of injection
Since my malicious process ‘M’ needs to write to legitimate process ‘L,’ it should have appropriate rights to do so. So first process M enables debug privilege (SE_DEBUG_PRIVILEGE). Process M then opens a handle to process L using OpenProcess. After this process, M allocate some memory in process L using VirtualAllocEx Now in Remote DLL injection a DLL is injected which means that injected DLL has to be present somewhere on the disk. So for DLL to be injected, process M has to make aware process L where that DLL is. After the process M allocate memory in process L then, process M write in process L memory using WriteProcessMemory the complete path where DLL is located. Now to load this DLL, LoadLibrary must be called. LoadLibrary is located in Kernel32.dll and kernel32.dll is located at the same address on the same machine. To call loadlibrary we must know its address which means we must know kernel32.dll address which is done using GetModuleHandle() function call. Once Kernel32.dll is located in memory then, GetProcAddress() is used with parameter LoadLibrary to know the address of LoadLibrary in Kernel32.dll. After all this, process M will call CreateRemoteThread() in the process L with the parameter as the address of LoadLibrary.
Here is a look at the API calls of an injected process.
GetProcAddress:
LoadLibrary:
CreateRemoteThread:
VirtualAllocEx:
WriteProcessMemory:
With Windows 7, DLL injection is controlled to a certain limit by introducing the concept of User Interface Privilege Isolation (UIPI) which focus on integrity levels and a process of lower integrity level cannot write to the memory of process at a higher level. As you can see this is not a full proof mechanism as processes can still inject into processes at the same level, or it will not work where the program privileges are already escalated.
Hollow Process Injection
It is a technique by which malware will replace a legitimate process with a duplicate process but with malicious code. This helps the malware to hide among other legitimate processes. The new malicious process looks so similar to the original legitimate process that even the image name, path and command lines remain unchanged. Below are the steps usually followed for process hollowing:
First, target process is created in a suspended state using CreateProcess with CREATE_SUSPENDED option. After the process is created, its memory space can be altered with the handle provided. This handle will be referenced in all the subsequent function calls. Note that at this point malicious process is loaded but not yet executed because it was created in a suspended state. Next step is to find the Process Environment Block (PEB section) for this malicious process which can be done using ReadRemotePEB helper function. After this is acquired, image base address is read to locate the NT headers. Next step is to hollow the legitimate code from memory in the hosted process. For this NtUnmapViewOfSection is used. Because it is a kernel function, malware usually resolves the function at runtime using GetProcAddress. Next step is to allocate a new block of memory to host malicious code. Malware usually makes the entire block PAGE_EXECUE_READWRITE for simplicity otherwise permissions can be set for each section as well. Since space has been allocated for a new image, WriteProcessMemory is used to write the new image code in place of original image code. In the optional header structure, the base address will be changed to that of the new memory image. If however, the image base of the new image does not match up with image base of original image then the image base of the new image need to be rebased. SetThreadContext function is used to set the context of this process.
The last step is just to resume the process using ResumeThread. A complete analysis of Hollow Process Injection is covered in my earlier article here.
In this article, we have seen how Remote DLL injection and Hollow Process Injection works. In part 2 of this article series we will cover Remote Code Injection and Reflective DLL injection.