In Blog

Introduction

DLL side loading has been used for quite some time now to achieve code execution in a trusted signed process. It has been extensively discussed in other blogs, so I invite you to read them first as I won’t repeat that here. The easy and most popular way to do it is by placing code into DllMain to do remote shellcode injection once the DLL gets loaded into a process.

Remote injection has been less and less easy to do in the recent time against good EDRs, but it is still doable using recent techniques, for example, how “Pool Party” is doing it at this time.

Local execution of the shellcode in the original process by jumping to its beginning remains a more stable and OPSEC approach in most scenarios, but it comes with a limitation when doing DLL side-loading, namely the “Loader Lock”. In essence, being inside the loader lock means being significantly restricted as per which functions that can be called in that state.

Figure 1 source: https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-best-practices

This is because most C2 shellcodes, like Cobalt Strike’s, will attempt to load other DLLs during initialization, which is not permitted nor recommended inside the loader lock. This will result, most of the time, in a blocked process and no shell.

So, what are our options? Some people are creating a separate thread and executing from there with success, but it is not a recommended solution as per Microsoft because, depending on what is done in that thread, it can create a deadlock or a crash. I personally never use DllMain to do side-loading and prefer to reimplement and export the first method that is exported by the legitimate DLL and called by the side-loaded process. This is notably useful in scenarios where you don’t want to have the original program display any visual indicator to the potentially currently logged-in user and where completely hijacking the program functionality is what is desired.

This blog post will present the methodology I use to find good candidates and how to circumvent problems that might arise when compiling.

Finding Side-Loadable Candidates

First, I fire up Process Monitor with the usual parameters to find DLLs which are first searched in the current directory but not found.

Then, I start executing arbitrary signed binaries on my test system until I see a missing DLL that is attempted to be loaded from the testing folder:

 

The ideal candidates are signed binaries that can be launched without requiring dozens of other files in the same folder architecture to make them more portable.

In this example, I found an HP related binary that seemed to be vulnerable to side-loading when placing a custom VERSION.dll in the same folder. OPSEC wise, however, version.dll is a common target for detections and should be avoided for real life scenarios, if possible.

Finding Hijackable Exported Functions

Then, using API Monitor (some people prefer to use Frida here, but in this case, I find it’s easier to just use this tool), it is possible to see what functions are called by the original signed process from the target DLL by filtering on GetProcAddress and LoadLibrary function calls. Here, there were three functions looked up in the target DLL.

In the use cases I described at the beginning of this post, its ideal to hijack the first function that gets called and jump directly to the shellcode to eliminate the need to reimplement anything from the original DLL.

In this case, with Microsoft’s documentation for VerQueryValueW, It is possible to confirm which one is called first: “Retrieves specified version information from the specified version-information resource. To retrieve the appropriate resource, before you call VerQueryValue, you must first call the GetFileVersionInfoSize function, and then the GetFileVersionInfo function.” This also matches what can be seen from API Monitor.

Writing the Payload

At this point I now know what name I should give my DLL, and which exported function name to use. It is time to write the malicious code and compile it.

For that, it is only important to mimic the original function signature. In this case, since it comes from a system DLL, the function signature can be found on MSDN.

Due to personal preferences, the current example will be using the Nim-Lang equivalent. Here is the final code:

In this version, nothing is being done in DllMain at initialization, but the local injection code would be executed shortly after “GetFileVersionInfoSizeW” is called.

After compilation, it is a good idea to make sure the DLL correctly exports the desired function:

Tricking the compiler

Sometimes, and as it is the case in this example, the compiler will prevent compilation since the target function name being exported is already defined in one of its files:

The easy way to circumvent this problem is to temporarily comment that function in the compiler’s file and try again. If cross compiling from Linux, the following file needs to be modified: /usr/share/mingw-w64/include/winver.h

The resulting files:

Conclusion

Using this method, I find it is trivial to successfully implement DLL side-loading in the original process while always avoiding DllMain and its deadlock limitation.

If you have been struggling with this, or just starting out playing with DLL side-loading, I hope this saves you time. Enjoy your shells!

Leave a Comment

Start typing and press Enter to search