So, you have decided to click on this article to better understand what exactly DLL Sideloading is. Welcome! This article is meant to include more technical details than a standard blogpost or general synopsis. If you wish to have a more general overview, I included a TL;DR below.
In this article, I will explain to you what is the art that is DLL Sideloading. I will use other techniques in the same family of DLL Hijacking to help you understand the impact and alternative uses for this attack. This specific attack does target Windows systems and for this post, a Windows 11 Professional instance. I included a relatively small background section on some concepts that are important to understand prior to diving into the article. If you already are familiar with these concepts, feel free to move past the background section.
Thank you for reading and enjoy.
The concepts in this section will be important to better understand the concepts in this article. If you do not already have a firm grasp on the concepts, it is recommended to at least read through this section to better understand some of the small intricacies that occur.
Many code bases will not always include all the functionality for the main portion of the code in the “main” file itself. For managed and non-managed code such as .NET and C++ respectively, a developer may off-load some of that functionality to a separate file called a library. When the library is compiled in Linux, the code gets compiled down to a Shared Object (SO) file, but for Windows, it is compiled down to a DLL file.
At a high level, DLLs serve as a separate place to reference when the main program needs to call a specific function. This makes code much more portable when there are multiple uses for the same code; just reference the DLL file and boom, you got your function. DLLs support load-time dynamic linking and run-time dynamic linking. More simply put:
There are various reasons why to do one or the other, but the main point is to understand, the code being executed is in a separate file! It is important to note, Run-Time dynamic linking will not immediately cause your program to fail to start whereas Load-Time linking will cause the program fail to start if the DLL was to fail when loading.
For Run-Time and Load-Time dynamic linking, the executable knows what the path is to the specific DLL but sometimes there are assumptions made. Such assumptions as:
These will be important to us later when understanding why these assumptions are dangerous.
In order to understand what functions a DLL has for a program to use, DLLs have this cool hip table called the Exports table. This contains the name of every function that a program could possibly link against or call externally into the DLL file. External executables are pretty much blind to all other functions within the DLL. Think of it similar to private versus public classing in Object Oriented Programming.
DLLs are normally just “dumb” files that hold functions. They are quite mild in comparison to the chad executable file. In order to make the DLL much cooler, you can also add a DLL Entry Point. This is an optional function which can be put into the DLL in the event the developer wants more control over what happens when a process or thread attaches/detaches themselves to the DLL. This is key because we can fire off a set of events to occur on the loading of a DLL without requiring a thread or process to call a malicious function (or any function in general).
These TL;DR explanations are explained here: https://learn.microsoft.com/en-us/troubleshoot/windows-client/deployment/dynamic-link-library . This reference also has a well-documented section on the general structure of the DLL entry point. I would recommend this to understand the different calling points within a DLL.
This is separate from DLLs only because it is key to most DLL vulnerabilities that are being found today.
DLL Search order simply put, is the order of directories to search for a specific DLL resource when the absolute path for the library is not immediately known. Let’s walk through the loading of a DLL. For this I will assume SafeDLLSearchMode is enabled, though a lot of the time it is not (more on that in a second):
When SafeDLLSearchMode is not enabled, it instead first checks the directory from which the application was loaded from then the current directory of the person calling the executable. This is super important for understanding the whole premise behind DLL Sideloading and DLL Hijacking.
Whoa!?! I thought we were talking about DLL Sideloading?
We are.
DLL Sideloading is one of a few different process-flow hijacking techniques using DLLs. It comes second to the good ol’ DLL Search Order Hijacking. To better understand how DLL sideloading works, I will first explain what DLL Search Order Hijacking is and to get a grasp of where it comes from, then dive into the meat of DLL Sideloading.
Remember that quirky thing I mentioned before called “DLL Search Order”? While protections such as SafeDLLSearchMode and DLL hashing exist, some developers may not know how to properly load their DLLs and sometimes suffer from not properly cleaning up development DLL references. The DLL search order is as follows (assuming the program is not already running):
It goes top down, searching for the DLL. If it does not exist, it fails to load the DLL. If it does exist, it will attempt to link the DLL to the program and upon success, the calling thread or process can use functions from the DLL. This order assumes that SafeDLLSearchMode is not enabled. Reference the former for when it is enabled and notice a similar process.
If the developer who created a specific installer writes files critical to the execution of the program into a directory without proper permissions, an attacker can abuse the way an executable searches for DLLs. A few causes for DLL Search Order Hijacking include:
I would also like to add, it is not always because a developer improperly set permissions for an installation directory. Sometimes after a system has been compromised, an attacker might implant a malicious version of the DLL in one of the paths mentioned in the DLL search order. Meaning if an attacker were to replace a DLL inside the installation folder for a program, that executable will gladly load the DLL since it resides in the same directory as the executable. If an attacker were to put the DLL instead inside C:\Windows\System or C:\Windows\System32, then any executable that references the DLL will eventually search in one of those folders and notice it exists. Once it sees it, it loads it, then boom! Instant malicious DLL executable loaded into memory. This is more dangerous at times due to the amount of processes which use DLLs in the system folders may also run as the NT SYSTEM user. A.K.A, root in the Windows world.
Here are a couple of examples of where DLL SOH has been performed due to issues with permissions or proper loading of DLLs.
In September of 2022, a vulnerability was reported for the popular text editor Notepad++ that you could replace the vulnerable DLL UxTheme.dll with your own DLL and run arbitrary code in the context of Notepad++. The steps to reproduce were as follows:
Note: The executable, notepad++.exe will load the DLL from the directory the executable is ran from. In the search order, this ends up being the second step. This enables us to load a malicious DLL without needing to modify the original installation directory. This is not the original DLL Search Order Hijacking we discussed but more resembles the DLL Sideloading concept we are talking more so in a couple sections.
Read more on the original GitHub issue here: https://github.com/notepad-plus-plus/notepad-plus-plus/issues/12113 .
Back in late 2021, Microsoft Teams and Microsoft OneDrive had a SOH vulnerability. These applications had been installed to a user’s AppData\Local folder. Inside the folder held the Microsoft Teams and OneDrive folders where permissions on these folders were not set correctly to restrict average users from modifying the contents in the folder. This meant any ol’ user could place a malicious DLL in the directory where OneDrive or Teams was run from and those applications would load the DLL with the intent of using it.
Read more about this here: https://besteffortteam.it/onedrive-and-teams-dll-hijacking/ .
Yeah, basically. It’s just a subset of SOH if you want to think of it that way. The Notepad++ CVE mentioned above is a great example of this specific subset of DLL SOH. To more plainly put it all together:
Sideloading vulnerabilities allow an attacker to move the binary to a user writable location where their malicious DLL is also located and execute it. The copied-over executable ends up loading the malicious DLL. This is due to the search order failing us and the developer failing to realize the unprotected search order of DLLs is insecure by default.
Okay, so enough with the theoreticals, let’s discuss how DLL Sideloading is performed. It is not super complicated which is what makes this technique super slick.
With a system as complex as Windows, there are many, and I mean many, executables to choose from. Some of which already exist in your system without having to install extra software to take advantage of DLL sideloading. With that being said, let’s go through the process of identifying if an executable is vulnerable to DLL sideloading. For this demo, I am going to select ARP.exe from the C:\Windows\System32 folder. This is nothing special, just one I had lying around in that folder (and you likely do too). If we end up determining it is not vulnerable, we can always just move to another executable we find. Remember, for this technique, I am assuming we have a writable location in the system where we can place this executable as opposed to testing for any opportunities in the installation directory.
For this section, I am going to make use of ol’ tried and true, Process Monitor.
For those of you who have never heard of Process Monitor, worry not. There are a lot of bells and whistles of this tool but we will only use it for a subset of those capabilities. All the information that is pouring out on the screen before you are various logs that Process Monitor has logged for different events which occur in your system. An event can be defined by the different operations a process may perform such as file reading, file writing, Registry key querying, and file locking to name a few.
We want to make use of process monitor to help us identify when there is potential to misuse the way an executable loads a DLL. Process monitor can help us identify when a DLL is being loaded from the current executing directory (CED) of the process. Remember, we want to find an executable that will attempt loading from the CED at some point since this allows for DLL sideloading when the malicious DLL is in the same folder as the executable itself. If we isolate a target executable into its own folder, we can use Process Monitor to target the process spawned from the executable and identify if it fails to load a DLL from its current executing directory. If we see this event, we know it is possible to perform DLL sideloading.
First, you need to copy over the ARP.exe file from the C:\Windows\System32 folder into a user writable folder. I have made a folder called test_folder and inside I have copied over the ARP.exe file:
Before we execute the binary, let’s start up Process Monitor. The program will show a lot of events:
Don’t panic, this is normal. Process Monitor lists all events from all processes that are performing different operations on your machine. It shows first the time that the event happened, the process which performed that action (and its PID), the operation which was performed, the path to the item the process was attempting to read, write, query, lock, etc., the result of the operation, then the details of the operation performed. The most useful thing on your screen is the filter button (it may look different in older versions of ProcMon):
The filter in Process Monitor is the most helpful for weeding out all these unnecessary events we don’t care about. It is pretty simple to add a filter, first select from the drop-down which field you would like to filter on, then the next field is a Boolean comparison to check what you enter in the next text box. Then at the end, a decision is made to keep or to discard the event. We can already see some events in Process Monitor which are so it will filter itself out and some other system processes and files that are negligible.
In Process Monitor, when a file a process is looking for, whether to read, write, or use as a DLL, it comes up as the result of NAME NOT FOUND. We can also use the fact a DLL always should end in .dll if you are in Windows land. With these two facts, we can make two filter rules to find at least processes who cannot find their DLL files. That looks like this:
Which we then end up seeing something like this:
This gives us a lot of interesting findings. The screenshot only shows powershell.exe but the list goes on in the events found. Are they all DLL sideloadable? Maybe. This may give more insight into what potential executables we could perform DLL hijacking on if those paths are not secure.
There is still one issue
We need to be able to find our specific event when we launch it. So let’s add one more event in that allows us to filter on the process name:
You may see some previous logs from something else that Process Monitor caught, but we can clear the logs it has collected so far to allow us to start off on a fresh new plate of logs. To do this, just perform CTRL + x or click the trashcan icon at the top next to the filter button.
Once we execute the ARP.exe file from the folder, we will notice two things:
Both seem to include the path I had ran the executable from. Doing some quick testing to see if maybe it is based on the current executing directory will lead us to similar results. With that being said, we can assume at some level that these two DLLs are
For compilation, using Visual Studio is cool but is a pain if you don’t already have it setup. For me, I like to use Linux so that is what we are going to do! #ubuntu4lyfe Here is some simple test DLL code I will be using:
In Linux, you should be able to compile this using the Mingw cross compiler
Then to compile, it is pretty simple
Note, the DLL we are compiling needs to be the same Instruction Set Architecture ISA as the DLL we are impersonating as well as the executable. For example, if you have a 32-bit binary, the DLL must also be 32-bit, or in more proper phrasing, x86 instead of x86_64 (that is the 64-bit version of the x86 ISA).
After copying the DLL over to our Windows machine, it is as simple as dropping it into the same folder as ARP.exe , renaming it to one of the DLLs and hitting execute! Let’s see what happens…
This seemed to fail at trying to find a specific function entry point within the snpapi.dll. Remember, DLLs are not just carriers for your payload, they serve a legitimate purpose of offering functions that executables can call. Those functions are known via Exporting (see the section above on DLLs). Our DLL code does not export anything. That means if the application is going to attempt to call a function in the library as opposed to just linking the library, we will need to also export some functions for our executable to metaphorically “munch” on.
We could use this error prompt to help us figure out which functions the application is using and slowly adding them to our DLL, but that would take too long. Others who have gone digging for DLL hijacking opportunities have faced similar issues and have instead made use DLL Export Viewer . This is a super neat tool and is free to use.
While this is a neat tool, I also prefer to use a similar technique I used in my sideloadr tool. The same approach can be found in cocomelonc’s DLL hijacking article. This technique is making use of Python and the pefile library to identify the exports of a DLL. I find this a bit simpler and the fact that it is also more convenient since we can work the results somewhere else in the Python script versus outputting to a file then re-reading them into a file.
Pulling from cocomelonc’s article and from my sideloadr tool, we can create a small script to just show all the exports of a DLL file using python:
Which should give output something like:
Note that each function is now redefined to take no arguments. In reality, if the program were to attempt to call functions from this library, it would either silently fail or just kill the process if it did not return what it was expecting. For now, this is acceptable as we just want to see if this darn executable is DLL sideloadable. Make sure to then copy all these extern directives and paste them into the bottom of your DLL code. It would be something like this:
Since C++ is cool with us doing inline functions, we are good to go. Otherwise we would just need to cleanup the formatting which also would not be too hard. Compiling is pretty simple, do the same as before:
Then after copying over and renaming the DLL, we can run the test again. To reiterate, I am running this via commandline and via just clicking on the binary in the same folder as the DLL we created. Let’s see what it does this time…
Spectacular! The message box was displayed which means we have successfully sideloaded a DLL for ARP.exe on Windows 11 Professional. Not a huge feat but still super cool nonetheless.
Here are some various notes to think about when performing sideloading or some DLL hijacking techniques in general. I have ran into a couple issues along the way.
If it was not obvious, we stayed in the main thread of the executable when it was loading up the DLL. All actions we performed were in the DLLMain function and the message box that popped up was a blocking operation. The window for ARP.exe did not close behind it when I ran the executable. In an ideal world, I may try to make a separate thread to handle the execution of my malicious code. Unfortunately, if the parent process dies, so does the thread that spawned the executable (unless you were able to jump to another process in that time).
Why the above is important is due to an issue I ran into with Meterpreter. The Meterpreter shellcode MUST be in its own thread. This goes for if it is in the DLLMain thread, or the main thread of a process. That being said, in order to get a DLL to fire off a Meterpreter shellcode in memory, I need to run the code in a separate thread. This becomes an issue though if the parent process such as ARP.exe does not stay alive that long. Due to this, I need to select a different target that will stay alive on the machine while my DLL is firing off Meterpreter shells so I have enough time to pivot to another process before the parent thread dies.
Looking at the DLL code I have in sideloadr, it is pretty simple to make the DLL call your shellcode as a thread:
This DLL code will spawn a thread using the CreateThread() to execute the function meme() in the code above. Meme() will then get a handle to the current process using GetCurrentProcess() so it can first allocate a memory region in the parent process which is writable, executable, and readable. The shellcode is then written to this location and promptly executed from this thread we have created, calling the shellcode that now exists within the main process’s memory from within the DLL. This is quite similar if someone was to perform DLL injection however, this is being done from the DLL loaded by a process as opposed to another process attempting to inject into another process’s memory.
Exporting functions of a DLL are quite easy; writing multiple functions to define the exports we want to use in a crafted DLL is a little gross. Thankfully we can make use of a DEF file to define our exports that we wish to hint a DLL is supposed to be exporting. This avoids us needing to define multiple “extern C” directives in the C++ file. sideloadr and cocomelonc’s DLL hijacking article are a couple examples making use of this technique.
What this looks like is a file with all the exports listed out like so:
This then gives us the capability to put all our exports into a def file then just include it when we compile the application later with Mingw:
It is nothing super crazy extra but it keeps the exports nice and clean when you are making your code (if doing it by hand) instead of getting bombarded in your C++ file by 200 exports.
Performing the technique is super cool, but let’s take a small look at some of the ways we can notice the DLL sideloading is being performed.
I am going to make use of Process Hacker which is like a cracked version of Process Explorer. You could use either and get similar results.
First I am going to filter for the process name ARP.exe
Note two things:
Then, viewing the properties of the process and viewing modules we notice the snmpapi.dll DLL is loaded however, when inspecting the DLL, we notice it is unverified:
Comparing this to the legitimate ARP.exe when executed (it’s PID 16524 in the picture below; it is purple because I had to suspend it).
We can see the DLL for the OG loaded ARP.exe for snmpapi.dll is verified by Microsoft Windows while ours was not. The DLL count in the modules between the two is different only because I did not let the real ARP.exe execute enough to resolve the runtime-linked DLLs therefore they are not linked yet.
Furthermore, Process Hacker allows us to also find where the location of this Unverified DLL is
Without manually keeping track of DLL hashes in your system, this technique will be one of the most effective ways for identifying potentially malicious DLLs loaded into memory. Some initial automation of finding the ugly duckling in a sea of DLLs for a process is just using Process Hacker or Process Explorer to self-identify all DLLs which are signature verified or unverified
This already is a big step so we do not need to step through each DLL to see whether or not it is a trusted DLL. If you are trying to move through the sea of different processes then for each process verify each of its DLLs, you may find yourself clicking around a lot in Process Explorer to do that more manually. I made a small Powershell script here to enumerate all processes currently running, then for each process enumerate their DLLs and attempt to verify their authenticode signature. This will allow us to see if a particular processes DLLs are signed.
An unsigned DLL is not uncommon as some developers do not invest in a verification signature for their application as it costs anywhere from $359 USD for an EV Code Signing set to $410 USD for a GlobalSign EV Code Signing. That being said, an unverified or Not trusted verification does not mean DLL sideloading has been performed however, this does help us clue in on applications such as ARP.exe which should have only signed DLLs and neighboring resources from Microsoft when bad, unsigned DLLs start being loaded into the application.
Upon executing the script we already see our “malicious” DLL has been picked up as being loaded into the ARP.exe process and it reveals it via showing us the path to the DLL as well:
Showing of the path will hopefully clue us in on whether or not it makes sense for this DLL to be where it is.
I keep mentioning the path being a very important key within DLL sideloading detection. Since we can forward what processes were executed on a system from Sysmon to a SIEM, we can make use of this logging utility and the power of a SIEM such as Splunk to perform small statistics for what processes were started and how often do they get started. What is important here is we do not just do the statistics based on process name but instead we do it based on the file location using the absolute path for the process.
This technique and a few more using Powershell + Sysmon to combad DLL Sideloading on Windows systems can be found here https://github.com/TactiKoolSec/SideLoadHunter with an accompanying article here going over the different techniques used to detect the events that signify DLL sideloading.
What pairs well with this technique is also having a list or understanding of what applications you have running on your system, where are they normally located, and when that list differs from the running processes and DLLs, you should alert on it. Keeping a sort of documented software bill of materials (SBOM) of your system(s) will help the most when trying to mitigate the attack surface of DLL Sideloading attacks.
Here is a list of awesome tools that in varying capacities will allow you to automate DLL Sideloading and Hijacking vulnerabilities (whether discovery or actual performing the task of sideloading):
All serve their own purpose and have different reasons why you may want to use one solution vs another. A point to get across is the technique of fully evading EDR and other AV solutions is still somewhat on the person performing the DLL sideloading. This technique does not stop an EDR from detecting a malicious DLL though it will get around restrictions on what executables can be ran.
We could put a whole bunch here for tips and tricks, but Microsoft has done a swell job with many recommendations for developers who are looking to mitigate the DLL hijacking and sideloading attacks against their applications.
Some additional things to note:
I would also like to link the remediation seen for the Notepad ++ vulnerability in our case studies above here with the changes implemented here. Notice the only difference in their code was one additional line which said “don’t look in any other directory but System32”. This constrains the DLLs which are loaded by a folder which can only be touched if the user is Administrator or SYSTEM.
The methods for mitigation on the end user side is a little less verbose but with proper logging, a system SBOM or catalog of application installation locations, and a way to aggregate all this information will prove quite powerful. Some easy steps are:
In the world of Windows systems there is so many different vectors for attack. In this article, we have addressed one of those attack vectors to help slowly reduce the attack surface you and your company may face on a day to day basis. DLL Hijacking attacks are no laughing matter and are consistently used by adversaries to help pose as persistence methods or ways to avert the eyes of EDR and antivirus software as well as the eyes of the active analyst.
As we draw to a close, remember, these threats can be detected and it matters that we as analysts are more vigilant than ever on threats which pose as legitimate programs we use every day. This topic itself is not new nor is it anything particularly revolutionary, though this article is a piece of the puzzle for reaching anyone who sees it so they are aware of this issue and can defend against. Here are some additional and amazing articles by fellow cybersecurity researchers out in the field on their takes of the DLL hijacking story and its subsidiaries such as DLL sideloading:
There are probably some more I missed but these resources are all quite helpful. Thank you for reading this article and please leave feedback if you have comments on the article both good or bad.