Hunting for Windows “Features” with Frida: DLL Sideloading


Offensive security professionals have been using Frida for analyzing iOS and Android mobile applications. However, there has been minimal usage of Frida for desktop operating systems such as Windows. Frida is described by the author as a “Dynamic instrumentation toolkit for developers, reverse-engineers, and security researchers.” From a security research and adversarial simulation perspective, Frida can be used to identify MITRE ATT&CK technique T1574.002 also known as dynamic-link library (DLL) sideloading. Frida is not limited to identifying DLL sideloading. It can also identify MITRE ATT&CK technique T1546.015 also known as Component Object Model (COM) hijacking. This blog post will review DLL sideloading, and how attackers and offensive security professionals can identify potential DLL sideloading opportunities using X-Force Red’s proof-of-concept Frida tool Windows Feature Hunter (WFH).

What Is DLL Sideloading?

MITRE ATT&CK describes DLL sideloading in T1574.002 as follows:

Adversaries may execute their own malicious payloads by side-loading DLLs. Similar to DLL Search Order Hijacking, side-loading involves hijacking which DLL a program loads. But rather than just planting the DLL within the search order of a program then waiting for the victim application to be invoked, adversaries may directly side-load their payloads by planting then invoking a legitimate application that executes their payload(s).

MITRE ATT&CK goes on to say that “side-loading takes advantage of the DLL search order used by the loader by positioning both the victim application and malicious payload(s) alongside each other.”

Microsoft also wrote a blog post where they define what is considered a vulnerability, saying that CWD scenarios would be addressed with a security fix, while PATH directory scenarios would not, “since there can’t be a non-admin directory in the PATH, [it] can’t be exploited.”

Windows DLL Search Order

Microsoft details DLL search order in this post. The post describes DLL search order, as shown in the excerpt below:

A system can contain multiple versions of the same dynamic-link library (DLL). Applications can control the location from which a DLL is loaded by specifying a full path or using another mechanism such as a manifest. If these methods are not used, the system searches for the DLL at load time as described in this topic.

Dynamically compiled Windows executables use functions that are exported by built-in or third-party DLLs. There are two ways to accomplish this. However, we are going to focus on how Windows executables do this at runtime. At runtime, a Windows executable can import or load libraries from the Windows API. The Windows API calls that can import or load libraries according to Microsoft’s documentation are as follows:

Microsoft provides the syntax to call these Windows API calls as shown in the screenshots below.

LoadLibraryA defintion

LoadLibraryExW defintion

Each LoadLibrary Windows API definition returns a handle, which can be used to identify the module calls to GetProcAddress. This retrieves the address of an exported function or variable from a specified DLL.

Developer Usage of LoadLibrary and GetProcAddress

Developers can use LoadLibrary to load a DLL and GetProcAddress to call an exported function within a DLL. We are going to craft a DLL to demonstrate how developers can use the Windows API to import DLLs and call an exported function within a Windows executable. The DLL source code written in C is shown below.


#include


#define DllImport   __declspec( dllimport )
#define DllExport  extern "C" __declspec( dllexport )


DllExport void Demo()
{
MessageBoxA(NULL, "Demo Export Called!", NULL, 0);
}

BOOL WINAPI DllMain(
HINSTANCE hinstDLL,  // handle to DLL module
DWORD fdwReason,     // reason for calling function
LPVOID lpReserved )  // reserved
{
// Perform actions based on the reason for calling.
switch( fdwReason )
{
case DLL_PROCESS_ATTACH:
// Initialize once for each new process.
// Return FALSE to fail DLL load.
break;

case DLL_THREAD_ATTACH:
// Do thread-specific initialization.
break;

case DLL_THREAD_DETACH:
// Do thread-specific cleanup.
break;

case DLL_PROCESS_DETACH:
// Perform any necessary cleanup.
break;
}
return TRUE;  // Successful DLL_PROCESS_ATTACH.
}

The proof-of-concept DLL above contains a DLL export named “Demo” and will pop up a MessageBox when the “Demo” export is executed. We will compile our DLL with mingw and validate that it contains the “Demo” export with Visual Studio’s dumpbin utility as shown in the screenshots below.

Compiling the DLL with mingw

Validating DLL export with dumpbin

We successfully compiled our DLL and validated the “Demo” export function exists. Now we need to write an executable to load our DLL using the LoadLibrary and GetProcAddress Windows API calls.

#include

typedef UINT (CALLBACK* LPFNDLLFUNC1)();

INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, INT nCmdShow) {
LPFNDLLFUNC1 demoFunction;
HINSTANCE dllHandle = LoadLibraryExW(L"LoadLibraryDemo.dll", NULL, NULL);
demoFunction = (LPFNDLLFUNC1)GetProcAddress(dllHandle, "Demo");
demoFunction();

return 0;

}

Compiling and executing LoadLibraryDemo.exe

We have successfully demonstrated how developers would use the LoadLibrary and GetProcAddress Windows API calls to load a DLL and call an exported function.

Manual Identification of DLL Sideloading with APIMonitor

We are going to take a look at mspaint.exe and attempt to identify a DLL sideloading opportunity. Let’s copy C:\Windows\System32\mspaint.exe to a directory we can write to as an Authenticated User.

Now that we have copied mspaint.exe to a directory we control, we will open mspaint.exe from our directory with APIMonitor.

Opening mspaint.exe in APIMonitor

After pressing the “OK” button, we search the mspaint.exe module for LoadLibrary to identify DLLs loaded by mspaint.exe.

Identifying LoadLibary Windows API calls

We can see that mspaint.exe is loading the “MSFTEDIT.DLL” via LoadLibrary. Now that we’ve identified a potential DLL sideloading opportunity, we will write a DLL to determine if we can load our own version of “MSFTEDIT.DLL” and execute code within the DllMain entry point because the LoadLibrary call was not followed by GetProcAddress. The source code of the proof-of-concept DLL is shown below.

#include

#define DllImport __declspec( dllimport )

BOOL WINAPI DllMain(
HINSTANCE hinstDLL, // handle to DLL module
DWORD fdwReason, // reason for calling function
LPVOID lpReserved ) // reserved

{
// Perform actions based on the reason for calling.
switch( fdwReason )
{

case DLL_PROCESS_ATTACH:
// Initialize once for each new process.
// Return FALSE to fail DLL load.
MessageBoxA(NULL, "DllMain Sideload Successful!", NULL, 0);
break;

case DLL_THREAD_ATTACH:
// Do thread-specific initialization.
break;

case DLL_THREAD_DETACH:
// Do thread-specific cleanup.
break;

case DLL_PROCESS_DETACH:
// Perform any necessary cleanup.
break;

}
return TRUE; // Successful DLL_PROCESS_ATTACH.
}

After crafting our proof-of-concept we compile it with mingw as msftedit.dll and execute mspaint.exe to validate the potential opportunity.

DLL Sideloading mspaint.exe via DllMain 

We successfully identified a DLL sideloading opportunity in mspaint.exe via the DllMain entry point in msftedit.dll.

Windows Feature Hunter (WFH)

Background

At X-Force Red, we wanted to be able to quickly identify potential DLL sideloading opportunities in an automated fashion that scales a proof-of-concept tool called Windows Feature Hunter (WFH). WFH is a python script that uses Frida to assist in potentially identifying common “features” within Windows executables. WFH currently has the capability to automatically identify potential DLL sideloading and COM hijacking opportunities at scale. Full documentation on the usage of WFH is included on the X-Force Red GitHub repository.

Usage and Examples

DLL Sideloading using DllMain Entry Point

We are going to attempt to identify the same DLL sideloading opportunity in mspaint.exe with WFH that we previously identified with APIMonitor. First, we will copy mspaint.exe to the WFH directory.

Copying mspaint.exe to WFH directory

Now that we have copied mspaint.exe to the same directory as WFH, we are going to run WFH against it and look at the results.

Running WFH against mspaint.exe

We can see that mspaint.exe will load a DLL named “MSFTEDIT.DLL” via the DllMain entry point, which is the same DLL sideloading opportunity we identified previously with APIMonitor. By default, WFH has a ten second timeout value on the target Windows executable. Compared to the manual effort with APIMonitor, WFH is significantly faster at identifying potential DLL sideloading opportunities. WFH will also write a CSV file with the results to “dll_results.csv” as shown in the screenshot below.

WFH results for mspaint.exe

DLL Sideloading using Export Function

Now we will look at another Microsoft signed Windows executable to demonstrate the identification of a DLL export called via the GetProcAddress Windows API call. First, we will copy mcbuilder.exe to the same directory as WFH.

Copying mcbuilder.exe to WFH directory

Now we will run WFH against mcbuilder.exe to identify potential DLL sideloading opportunities and then validate the opportunity.

Running WFH against mcbuilder.exe

We can see the potential DLL sideloading opportunity via a DLL export in the output above. The mcbuilder.exe file will load “mrmcoreR.dll” and call the “MergeSystemPriFiles” export. We will craft a DLL written in C to validate this potential opportunity; the source code is shown below.

#include

#define DllImport __declspec( dllimport )
#define DllExport extern "C" __declspec( dllexport )

DllExport void MergeSystemPriFiles()
{
MessageBoxA(NULL, " MergeSystemPriFiles Export Called!", NULL, 0);
}

BOOL WINAPI DllMain(
HINSTANCE hinstDLL, // handle to DLL module
DWORD fdwReason, // reason for calling function
LPVOID lpReserved ) // reserved

{
// Perform actions based on the reason for calling.
switch( fdwReason )
{

case DLL_PROCESS_ATTACH:
// Initialize once for each new process.
// Return FALSE to fail DLL load.
break;

case DLL_THREAD_ATTACH:
// Do thread-specific initialization.
break;

case DLL_THREAD_DETACH:
// Do thread-specific cleanup.
break;

case DLL_PROCESS_DETACH:
// Perform any necessary cleanup.
break;

}
return TRUE; // Successful DLL_PROCESS_ATTACH.
}

We will compile the DLL with mingw as “mrmcoreR.dll” then run mcbuilder.exe to validate the export is called as shown in the screenshot below.

Validating DLL sideloading in mcbuilder.exe

Defensive Considerations

In order to prevent attackers from using DLL sideloading, defenders can implement application allow-list solution to prevent unsigned DLLs from loading with AppLocker, Windows Defender Application Control (WDAC), and similar solutions. Implementing application allow-listing is not always viable as a defender. In order to detect attackers using DLL sideloading, defenders can monitor for execution of binaries executing outside of the expected path. Red Canary has a great blog post on how to design detection logic for detecting DLL sideloading. Additionally, Microsoft has provided resources on DLL security and triaging a DLL hijacking vulnerability.

The post Hunting for Windows “Features” with Frida: DLL Sideloading appeared first on Security Intelligence.