Welcome to a new series I’m calling Threat Spotlight! In this series, we will focus on common vulnerabilities or potential risk factors that developers or security researchers should consider when securing their systems.
In this article, we’ll be focusing on the way Windows handles DLL loading, how threat actors may attack the search chain, and what developers can do to avoid such weakness.
TL;DR
Make sure your environment has the entire orange section secured, and use LoadLibraryEx
with secure flags and use absolute path for the DLL if possible.
DLL Search Order
As always, to understand how Windows handles things, we need to look no further than the official documentation - Dynamic-Link Library Search Order. In the section “Search Order for Desktop Applications,” the documentation notes the following.
Desktop applications can control the location from which a DLL is loaded by specifying a full path, using DLL redirection, or by using a manifest. If none of these methods are used, the system searches for the DLL at load time as described in this section.
Before the system searches for a DLL, it checks the following:
- If a DLL with the same module name is already loaded in memory, the system uses the loaded DLL, no matter which directory it is in. The system does not search for the DLL.
- If the DLL is on the list of known DLLs for the version of Windows on which the application is running, the system uses its copy of the known DLL (and the known DLL’s dependent DLLs, if any). The system does not search for the DLL. For a list of known DLLs on the current system, see the following registry key:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs
.
So, what does that tell us? The paragraph tells us that before searching for a DLL, Windows will first check: a) if the library is already loaded in the memory, if so, use that, to avoid loading the same DLL in memory; b) if the DLL is in the KnownDLLs
list under HKLM\System\CurrentControlSet\Control\Session Manager\KnownDLLs
.
This section is generally safe from threat actor, as these factors cannot be controlled by attackers under normal circumstances.
⚠Warning By default, Windows uses what is known as the “Safe DLL Search Mode” to search for the requested library. This mode is controlled by the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode
registry value. Because SafeDLLSearchMode
is enabled by default, we will only cover the default behavior.
But what about after that? Once again, let’s refer to the official documentation for clues.
- The directory from which the application loaded.
- The system directory. Use the
GetSystemDirectory
function to get the path of this directory.- The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched.
- The Windows directory. Use the
GetWindowsDirectory
function to get the path of this directory.- The current directory.
- The directories that are listed in the PATH environment variable. Note that this does not include the per-application path specified by the App Paths registry key. The App Paths key is not used when computing the DLL search path.
Given the above steps, we can plot out what the search order is going to look like:
The Problem
See the problem? The parts marked in red can easily be manipulated by threat actors, especially if they have arbitrary write permissions. If the attacker could drop files into the application folder or into any of the environment path folders with weak permissions, they could easily cause havoc by replacing the intended DLL with a malicious one (with exceptions, see The Solution).
Consider the following C# code:
[DllImport("unknown.dll")]
public static extern void UnknownMethod();
static void Main(string[] args) => UnknownMethod();
And our unknown.dll
C++ code:
#include <iostream>
extern "C" __declspec(dllexport)
void UnknownMethod()
{
std::cout << "Hello, World!" << std::endl;
}
When we run the application, it will attempt to look for unknown.dll
in various directories based on the search order. In this instance, unknown.dll
is planted at where the current working directory is (where dotnet myapp.dll
is called). We can observe this behavior using Process Monitor.
So let’s say our threat actor has managed to gain access to the Windows or System directory and dropped in their malicious DLL, what would happen when we execute the application again?
Now, this is a very unlikely scenario as the threat actor would first have to elevate their user account with one that has Administrator ACL permissions in order to perform a file write into a protected directory.
Here’s a more likely scenario - what if we were to delete the DLL from the application folder and find a weak ACL controlled directory that is present within our %PATH%
? Fortunately for us (and me), PrivescCheck can help us find PATH directories with weak permissions.
With Invoke-DllHijackingCheck
, we are able to quickly find folders that anyone under NT AUTHORITY\Authenticated Users
can write to. Let’s try moving our malicious DLL there.
DLL hijacking could also be used to target Windows services - though very few of them, if at all, are still vulnerable in one way or another. With PrivescCheck, we can find potentially vulnerable services by invoking Invoke-HijackableDllsCheck
. It will list out the services that has hijackable DLLs, what user account will the process be run as when the hijack is complete, and whether or not a reboot is required for the hijack to work.
PS C:\Users\Still> Invoke-HijackableDllsCheck
Name Description RunAs RebootRequired
---- ----------- ----- --------------
cdpsgshims.dll Loaded by CDPSvc upon service startup NT AUTHORITY\LocalService True
WptsExtensions.dll Loaded by the Task Scheduler upon service startup LocalSystem True
For more details and hands on practices of DLL hijacking, I recommend itm4n’s excellent article regarding DLL hijacking, which can be found in the Additional Resources section.
The Solution
.NET
You may have noticed my peculiar language/runtime choice for testing DLL hijacking. There is a reason that I chose dotnet over C/C++ here. Here’s the problem - there is not a single good solution to avoid DLL hijacking when working with dotnet. At least, not that I can tell. The only method that I can tell is by invoking the LoadLibrary
call directly, as opposed to calling the DLL exports. With LoadLibraryEx
, DLL hijacking can be alleviated to some degree (see below).
If you do know a good method of preventing dotnet DLL hijacking, please do let me know in the comments.
For .NET Core 3.0 and above (inc. .NET 5), the use of System.Runtime.InteropServices.NativeLibrary
class is preferred. So, in our case, we can rewrite the application like this to specify that we only want to search within the assembly directory:
public delegate void UnknownMethod();
static void Main(string[] args)
{
if (NativeLibrary.TryLoad("unknown.dll", typeof(Program).Assembly, DllImportSearchPath.AssemblyDirectory, out var entryPointer))
{
if (NativeLibrary.TryGetExport(entryPointer, "UnknownMethod", out var methodPointer))
{
var MyUnknownMethod = Marshal.GetDelegateForFunctionPointer<UnknownMethod>(methodPointer);
MyUnknownMethod();
}
}
else
{
Console.WriteLine("Failed to locate the DLL within the application directory.");
}
}
No more exceptions when dealing with extern
with missing DLL or fumbling with DLL search order!
Win32 LoadLibraryEx
In Windows 8 or above (or Windows 7 or below with KB2533623 installed), the LoadLibraryEx
function has a number of new flags that are created specifically to address this issue.
The new flags are LOAD_LIBRARY_SEARCH_APPLICATION_DIR
, LOAD_LIBRARY_SEARCH_SYSTEM32
, LOAD_LIBRARY_SEARCH_USER_DIRS
and more. Additionally, there are flags such as LOAD_LIBRARY_REQUIRE_SIGNED_TARGET
, LOAD_LIBRARY_SAFE_CURRENT_DIRS
to better ensure the integrity of the library before it is loaded. I’m not going to delve into what each flag does here, as they are best explained in the LoadLibraryExA function (libloaderapi.h) documentation.
Additional Resources
The following are excellent articles that provide better insights into this type of attack and remedy,
- Preventing DLL hijacking vulnerability does not working in C#
- Windows DLL Hijacking (Hopefully) Clarified by itm4n
- Dynamic-Link Library Search Order - Win32 apps
- Dynamic-Link Library Security - Win32 apps
- Triaging a DLL planting vulnerability - Microsoft Security Response Center
- Windows 10 - Task Scheduler service - Privilege Escalation/Persistence through DLL planting
Leave a comment