Brownie
is a platform to rapidly prototype and weaponise DLL hijacks. In particular, we are interested in DLL Search Order Hijacking to sideload our malicious code by a signed and legitimate executable. It is sometimes wrongly(or rightly?) known as DLL Sideloading which has a very specific definition.
We are particularly interested in how this technique is an interesting(and often underrated) alternative to Code Injection that shares the same objectives i.e. to evade AV/EDRs by executing malicious code from the address space of a "trusted" process. We won't be looking at DLL hijacks for LPE or even Persistence as such although it can certainly be adapted for the latter purpose quite easily.
This post will be heavily borrowing from public research and it was a personal note before I decided to release it by packaging it up nicely with a bow.
So without any further ado, let's get started!
As a quick recap here is the highlighted DLL Search Order that we will be targeting considering SafeDllSearchMode
is turned ON.
Furthermore, we will be targeting System32
executables for this demonstration and won't be targeting third-party applications since those are target-specific.
So we will be using a slight variation of this technique known as Relative Path DLL Hijacking i.e. dropping the malicious DLL in a user-writeable folder and copying the legitimate, signed executable from System32
to the folder before executing it finally to load our "evil" DLL.
Our first order of business is finding the right candidate for the job. There are many automated tools to do that and I found a wonderful post that details almost 300 executables in System32
that are vulnerable to DLL Hijacking but what kind of hackers would we be if we didn't try to find our own? ;)
Almost always, you'll hear someone say that DLL hijacks in Windows executables are plentiful. I fully agree considering the evidence. But that doesn't always mean that all of them are perfect candidates to exploit.
For example, some applications contain visual elements which upon execution will immediately notify the target effectively rendering it useless for use in covert operations.
With all that mind, we will initiate our good old-fashioned hunt!
The algorithm for the hunt goes something like:
- Copy the executable from
System32
to our current working directory
- Run procmon with primarily two filters:
Result contains NOT FOUND -> include & Path ends with .dll -> include
- Finally, execute the binary
Aand profit!!!
So if we rename an arbitrary DLL to DUI70.dll
in the application's directory, it should get loaded by our target executable which in turn should execute our malcode right?
Welp not quite :(
Process Monitor logs clearly show that DUI70.dll
is searched by LicensingUI.exe
from the same directory it is executed from following the DLL Search Order in absence of absolute path in LoadLibrary
call. So what happened?
Well if you think about it, it's not that the executable is loading the DUI70
DLL because it just felt like doing so right? After all, it is being loaded for a purpose because the executable needs to perform some function(s) defined in the DLL which our "evil" DLL is obviously missing that in this case also causes the application to not start properly/crash i.e. We do not maintain stability in the target executable.
So how do we solve this problem? By using DLL Proxying or delegating the legitimate functionality to the real DLL by linking the export table of "evil" DLL to the original DLL.
One of the simplest ways to implement this is by export forwarding using linker redirects. In other words, cloning all the exported functions from the real DLL and redirecting them to the real DLL and letting the Windows loader subsystem do the rest of the work for us.
#pragma comment(linker, "/export:{exportedFunctionName}={realDLLName/FullPath}.{exportedFunctionName},@{exportedFunctionalOrdinal}")
Here we can clearly examine the exports of our "evil" DUI70.dll
redirected to the real DUI70.dll
in System32
using PEStudio:
There is a wonderful tool written by Melvin Langvik named SharpDllProxy that aims to automate the mentioned process. While it is a nice alternative, for the purposes of our experiment, I found notably two problems with this approach:
- We would need to re-compile our implant DLL every time we want to target an executable since this technique is dependent on it
- The implant generated by
SharpDllProxy
loads the payload from disk which means it'll need to be dropped to disk as well
Enter Koppeling by Nick Landers a.k.a. @monoxgas!
Quoting from the SBS blogpost:
The process goes like this:
1. We compile/collect our “evil” DLL for hijacking. It doesn’t need to be aware of any hijacking duty (unless you need to add hooking).
2. We clone the exports of a reference DLL into the “evil” DLL. The “real” DLL is parsed and the export table is duplicated into a new PE section. Export forwarding is used to correctly proxy functionality.
3. We use the freshly cloned DLL in any hijack. Stability is guaranteed and we can maintain execution inter-process.
What does this essentially mean for us attackers? This means that now we can weaponize our arbitrary DLL post-build without needing to re-compile for testing multiple targets.
Oh and if you haven't yet read the above post, I'd implore the readers to do so before continuing further.
I have already prepped our target-agnostic DLL which embeds the payload(malcode which we want to be executed) in the Resource
section of PE. Upon loading, it extracts the payload from rsrc
and executes it locally in a separate thread(to avoid loader lock complexities).
One thing I've noticed is that as soon I dropped our "evil" DLL in our testing VM, it'd almost immediately get detected and quarantined by Defender AV. Bummer :(
Upon closer signature inspection with DefenderCheck by Matt Hand a.k.a. matterpreter, I'd soon learn that the Metasploit calc shellcode signature is what got our well-behaved DLL in trouble.
On learning this, I have taken the liberty the encrypt the payload with AES-256 in CBC mode with a random IV and adding code to our DLL which will decrypt the payload after resource extraction ergo you can say goodbye to static detections.
I have also taken the liberty to build the NetClone
project from Koppeling and ILMerge
d all the dependency DLLs together into a portable package so that you don't have to(No need to thank me :) )
So continuing from Step 3 of our hunt where we left off previously:
-
Compile our target-agnostic DLL. But before that let's do one more thing. Copy the malcode which we want to execute to
Bin
directory and rename it aspayload_x64.bin
so that we can locate it while building our DLL. Finally, compile by executingcompile64.bat
from an x64 Developer Command Prompt. This will also encrypt the payload using aPython
script before embedding it as anRCDATA
resource in our DLL. Optionally, feel free to change the passphrase used to derive the symmetric key(you really should!) here -
Once our DLL is built successfully(you can find it in
Bin
asbrownie_x64.dll
), we will move on to weaponizing it. From a Command Prompt, executeprepdll.bat <Name of original DLL to clone>
which in our case would beprepdll.bat DUI70
. On success message, we can find our weaponized evil-twin ofDUI70.dll
inBin
folder. We can further verify thatNetClone
worked as intended by inspecting PE sections too:
- Now all that remains is to execute our target binary
LicensingUI.exe
and wait for it to load our "evil" DLL(which in turn would also load the real DLL) and execute our malcode. Aand Bingo!
Can you spot which is the real and which is our "evil" DLL?
One thing I want to point out is that I have deliberately chosen a target that stays alive until termination in order to avoid additional complexities.
There are still tons of potential candidates waiting to be discovered and exploited and I can safely say this because I was able to find 6 previously unknown(or call it lesser-known if you will?) System32
executables that are vulnerable to DLL Hijacking and was able to weaponize it in less than an hour.
So we used a calc payload in our demonstration but almost certainly we won't be using that in a real engagement unless we want to annoy a friend right? :)
I'd NOT recommend using a C2 agent/Egress implant PIC blob as payload for this purpose simply because almost any half-decent AV/EDR would pick it up especially if that executable does not load Wininet.dll
or Winhttp.dll
or is expected to not have any network activity.
So what do we use for payload? I would recommend using a loader PIC blob as payload that injects the Stage-1/Beaconing payload blob to another process from where network activity is NOT considered unusual.
What this technique essentially helps us achieve is cloaking/shielding the malicious activity of Code Injection which could give us up especially when dealing with an EDR that doesn't use User-mode hooking to gain visibility into potentially suspicious actions(can be bypassed rather easily using well-placed direct syscalls) but rather relies on Kernel-mode ETW Threat Intelligence functions like MDATP. It will still generate telemetry but will probably allow the activity since it is originating from an MS signed, trusted and legitimate binary :)
Here is a mandatory CAPA scan result of our Brownie
DLL:
And here is a Sysmon sample log with SwiftOnSecurity Sysmon configuration:
And here is a Sysmon sample log with Image Loaded event enabled - Sysmon Event ID 7:
When it comes to Detection/Mitigation, Samir Bousseaden is one of the best authorities to go to.
I want to highlight here one of his tweets.
In essence, a lot of false-positives could be weeded out with a rule like this if it can be made:
1. System32/SysWoW64 DLL loaded from anywhere other than their original location AND
2. An MS-signed binary loading a non-MS signed image
With that being said, I agree that this requires quite a bit of baselining in the target environment to produce high-quality telemetry.
This section will consist of some of my favourite posts on DLL Hijacking and the authors from which I have heavily borrowed stuff from:
- https://silentbreaksecurity.com/adaptive-dll-hijacking/ - My favourite post on DLL Hijacking
- https://www.wietzebeukema.nl/blog/hijacking-dlls-in-windows - Another awesome in-depth post detailing quite a number of binaries
- https://itm4n.github.io/windows-dll-hijacking-clarified/ - Another nice read to clarify some stuff
- https://posts.specterops.io/automating-dll-hijack-discovery-81c4295904b0 - Tbh has SpecterOps team ever disappointed?
- https://www.contextis.com/en/blog/dll-search-order-hijacking - Another nice read
- https://blog.nviso.eu/2020/10/06/mitre-attack-turned-purple-part-1-hijack-execution-flow/ - One of the newer posts
- https://redteaming.co.uk/2020/07/12/dll-proxy-loading-your-favorite-c-implant/ - Here's to our favourite Flangvik whose work inspired me to look into DLL Hijacks
- @SBousseaden for the detection methodologies
- @reenz0h and RTO: MalDev course for the templates that I keep using to this date
Upayan (@slaeryan) [slaeryan.github.io]
All the code included in this project(excluding NetClone) is licensed under the terms of the GNU GPLv2 license.