This is a quick lab to get familiar with a technique that's been on my todo list for some time - DLL proxying. This technique could be used for persistence or to intercept data, but in this lab, I am only concerned with persistence.
In the context of malware, DLL proxying is a DLL hijacking technique, where a legitimate DLL say, legit.dll
is renamed to legit1.dll
and a malicious dll, which exports all the same functions that the legit1.dll
exports, is placed instead of legit.dll
.
Once the dll is hijacked, whenever a program calls a function, say exportedFunction1
from legit.dll
, here is what happens:
legit.dll
gets loaded into the calling process and executes its malicious code, say reaches out to the C2legit.dll
forwards the call toexportedFunction1
inlegit1.dll
legit1.dll
executes theexportedFunction1
This function forwarding from one DLL to another is what gives the technique its name - DLL proxying, since the malicious DLL is sitting in between the application calling the exported function and a legitimate DLL that implements that exported function.
At a high-level, below diagram shows how it all looks before and after the DLL is hijacked:
At a high level, the technique works as follows:
- Decide on which DLL to hijack. Let's say, it's located in c:\temp\legit.dll. Move it to c:\temp\legit1.dll
- Get a list of all the exported functions of c:\temp\legit1.dll
- Create a malicious DLL malicious.dll, that once loaded by the target process, executes your payload
- Inside the malicious.dll, redirect/forward all the exported functions by legit.dll (this is the DLL we are hijacking) to legit1.dll (this is still the same DLL we are hijacking, just with a new name)
- Copy malicious.dll to c:\temp\legit.dll
- At this point, any program that calls an any exported function in legit.dll will now execute your malicious payload and then transfer the execution to the same exported function in c:\temp\legit1.dll.
For demo purposes, we will create our own DLL legitimate DLL to be hijacked, called legit.dll
:
{% tabs %} {% tab title="legit-dll.cpp" %}
#include "pch.h"
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;
}
extern "C" __declspec(dllexport) VOID exportedFunction1(int a)
{
MessageBoxA(NULL, "Hi from legit exportedFunction1", "Hi from legit exportedFunction1", 0);
}
extern "C" __declspec(dllexport) VOID exportedFunction2(int a)
{
MessageBoxA(NULL, "Hi from legit exportedFunction2", "Hi from legit exportedFunction2", 0);
}
extern "C" __declspec(dllexport) VOID exportedFunction3(int a)
{
MessageBoxA(NULL, "Hi from legit exportedFunction3", "Hi from legit exportedFunction3", 0);
}
{% endtab %} {% endtabs %}
Let's say we've now compiled the above as a legit.dll
to c:\temp\legit.dll
. It has 3 exported functions as shown below:
To confirm the DLL works, we can see that calling exportedFunction1
from inside the legit.dll
gives a popup like this:
rundll32 c:\temp\legit.dll,exportedFunction1
We now have the legit.dll
and its target function exportedFunction1
to hijack, let's move on to the malicious DLL that will do the function proxying.
Let's now create the malicious.dll
- we will be using it to hijack programs that call functions from c:\temp\legit.dll
. Compile the below as a malicious.dll
:
{% tabs %} {% tab title="malicious-dll.cpp" %}
#include "pch.h"
#pragma comment(linker, "/export:exportedFunction1=legit1.exportedFunction1")
#pragma comment(linker, "/export:exportedFunction2=legit1.exportedFunction2")
#pragma comment(linker, "/export:exportedFunction3=legit1.exportedFunction3")
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
MessageBoxA(NULL, "Hi from malicious dll", "Hi from malicious dll", 0);
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
{% endtab %} {% endtabs %}
The key piece in the malicious.dll
is the #pragma
comment at the top, that tells the linker to export / forward (technical name is Forward Export
) functions exportedFunction1
, exportedFunction2
, exportedFunction3
to the module legit1.dll
.
Also, note that once the malicious.dll
is loaded, it will display a prompt saying Hi from malicious dll
, but this could be any payload of our choice:
Let's test if the malicious.dll
executes our payload - shows a message prompt:
rundll32 malicious.dll,whatever
We now have all the required pieces to test the dll proxying concept.
Let's move the malicious.dll
to c:\temp
, where legit.dll
resides:
Rename the legit.dll
to legit1.dll
and alicious.dll
to legit.dll
:
mv .\legit.dll .\legit1.dll; mv .\malicious.dll .\legit.dll
Now, let's invoke the exportedFunction1
from legit.dll
- this is our malicious DLL with DLL proxying enabled.
If the hijacking is successful, we will see the prompt Hi from malicious dll
followed by the prompt Hi from legit exportedFunction1
from the legit1.dll
:
Implementing DLL proxying for a DLL that exports many functions may be a bit painful, but luckily there are multiple projects that help you automate this process, one of which is https://github.com/Flangvik/SharpDllProxy, so go check it out.
https://dl.packetstormsecurity.net/papers/win/intercept_apis_dll_redirection.pdf