A DLL Injection Detector for Windows.
The DLL Injection Detector currently supports only x86. However, it can be easily adapted for x64 by replacing the hooking engine with one that is compatible with 64-bit environments, such as Microsoft Detours. My primary goal was to focus on the core functionality of injection detection without introducing unnecessary overhead from large external libraries. Since API hooking is significantly simpler in x86, I chose to implement a minimalistic, custom hooking engine that is sufficient for the intended purpose.
DLLInjectionDetector.exe -m
: Start in monitoring mode (monitoring only)
DLLInjectionDetector.exe -g
: Start in guard mode (blocking dll injections)
Afterwards the DLLInjectionDetector is ready and waiting for DLL injection attempts. For this purpose, you can use the TestDLL.dll provided with the solution, for example.
The easiest way to inject a DLL into a running process is to use the Windows APIs OpenProcess, VirtualAllocEx, WriteProcessMemory, GetProcAddress and CreateRemoteThread:
-
OpenProcess : Open the target process using its process ID
-
VirtualAllocEx : Allocate memory inside the target process to store the DLL path.
-
WriteProcessMemory : Write the DLL path into that memory.
-
GetProcAddress : Get the address of LoadLibraryA to load the DLL.
-
CreateRemoteThread : Create a remote thread in the target process that calls LoadLibraryA with the DLL path as parameter, causing the process to load (inject) the DLL.
In addition to this straightforward method, there are alternative techniques for injecting a DLL into a process, such as thread hijacking, where an existing thread is used to load the DLL.
To detect when another process attempts to inject a DLL into our process, certain well-known APIs can be hooked. Since DLL injector applications often rely on low-level system APIs from ntdll.dll rather than higher-level user-mode APIs like those in kernel32.dll, it is preferable to hook the system APIs directly whenever possible.
- LdrLoadDll (Module: ntdll.dll) : Handles the loading of DLLs.
- BaseThreadInitThunk (Module: kernel32.dll) : Called during thread creation. Useful for detecting remote thread injection.
- RtlGetFullPathName_U (Module: ntdll.dll) : Resolves full paths of DLLs being loaded.
While LdrLoadDll and BaseThreadInitThunk are obvious candidates for hooking, RtlGetFullPathName_U is less so. This function is internally used by LdrLoadDll, but only seems to be involved in the code path used for loading non-system (i.e., non-Windows) DLLs.
I have tested this using the tool API Monitor 2.0 (http://www.rohitab.com/):
Loading shell32.dll : LdrLoadDll does not call RtlGetFullPathName_U
Loading custom TestDLL.dll : LdrLoadDll calls RtlGetFullPathName_U
The solution consists of the projects DLLInjectionDetector
which serves as the main project and TestDLL
, a simple DLL that can be used for injection into DLLInjectionDetector.exe
.
- Installs the specified hooks, including trampolines, within the
InjectionDetector::Initialize
method.- Hooks
- LdrLoadDll (Module: ntdll.dll)
- BaseThreadInitThunk (Module: kernel32.dll)
- RtlGetFullPathName_U (Module: ntdll.dll)
- Hooks
- Forwards hook calls to implementations of the
IInjectionHandler
interface
- Implements the
IInjectionHandler
interface - Solely monitors hook events and outputs information to the console.
- Implements the
IInjectionHandler
interface - Monitors hook events, logs information to the console, and actively blocks DLL injection attempts
- Blocking unwanted thread creation
- In the
InjectionGuard::HandleBaseThreadInitThunk
method, unwanted thread creation is directly blocked using:
InjectionDetector::Instance()->CallBaseThreadInitThunkStub(LdrReserved, (LPTHREAD_START_ROUTINE)Sleep, 0);
where i redirect the original thead start routine to the Sleep() function with 0 milliseconds as parameter.
- In the
- Blocking unwanted DLL loading
- In the
InjectionGuard::HandleRtlGetFullPathName_U
method, unwanted DLL loading is blocked by using:
InjectionDetector::Instance()->CallRtlGetFullPathName_UStub(NULL, BufferLength, Buffer, FilePart);
where i pass NULL as FileName parameter and also zero-initialize Buffer to be on the safe side.
- In the
It is also worth mentioning that within the method ::HandleBaseThreadInitThunk
, the method InjectionDetector::IsModuleAddress
is called to determine whether the thread’s start address belongs to a loaded module. If this is not the case, it is highly likely that an external process has injected code at that address.
Tool | Method | Result |
---|---|---|
Extreme Injector v3.7.3 | Standard Injection | blocked |
Extreme Injector v3.7.3 | Thread Hijacking | blocked |
Extreme Injector v3.7.3 | LdrLoadDllStub | blocked |
Extreme Injector v3.7.3 | LdrpLoadDllStub | blocked |
Extreme Injector v3.7.3 | Manual Map | blocked |
Process Hacker 2.39.124 | Standard Injection | blocked |
ScyllaHide | Normal Injection | blocked |
ScyllaHide | Stealth Injection | blocked |