Have you ever written a DLL that had standalone functionality and wasn’t meant to be used as a library? Considering that library is in the name, this idea seems contrary to what a library should be. Well, you can thank Microsoft for providing a way to execute standalone functionality from a DLL. Not only did they do that, they also provide DLLs with functionality that you need this utility to run!!! Say hello to rundll32, pronounced run dull all smashed as one word in some circles.

rundll32, as described on MSDN in 1 of 6 sentences of documentation, “loads and runs 32-bit dynamic-link libraries (DLLs)”.1 Wait, the MSDN documentation doesn’t even get that correct…

rundll32 is a utility that will load a DLL and execute an exported function from the DLL. rundll32 comes in a 64-bit version and a 32-bit version:

:: 64-bit
c:\windows\system32\rundll32.exe

:: 32-bit
c:\windows\syswow64\rundll32.exe

But why is it called rundll32 then? Well, I would say I have no idea, but of course Raymond Chen has the answer. In his blog post from 2014, he tells us that the naming is a left over from the days of 16-bit Windows.4 In that time, all of the system binaries were in the same directory, and thus rundll.exe was 16-bit and rundll32.exe was 32-bit. On modern systems, with WoW64, we have different directories for 32-bit and 64-bit system binaries so we got rid of the name collision. In order to maintain backwards compatibility, they just left the name rundll32.exe for the 64-bit version. This doesn’t just apply to rundll32 and is actaully the reason we have 32 in the name of several of the other system binaries and utilities.

Why should we use rundll32? Well, according to Raymond Chen you shouldn’t. On his blog, Raymond discusses the plethora of issues that come along with using rundll32.3 The gist of the post is that anything executed by rundll32, runs in the environment that it sets up, therefore you get no control over the execution environment. As well, it makes it difficult to track where issues are occurring and rundll32 assumes that the exported function is designed to handle windows messages.

Then why am I writing a post about it? There are some useful things that admins can use rundll32 for, but were not really here for that. There is a group of people that are fairly used to executing code in environments they don’t control that tend to like to use rundll32…

Hackers!

Red teamer’s, APT’s, hacktivist’s and all the other things you can think of use rundll32. There is even a MITRE technique specifically for it.8 Many of the red teaming tools are delivered as DLLs, so what better way load and execute them than using a signed Windows binary.

So how how do we use rundll32? Well there are a few legitimate uses for rundll32. One of the big ones documented on MSDN is automating a lot of printer configuration tasks from printui.dll.10 As well, it can be used to run control panel items (.cpl files).5 You can search around the internet and find many other administrative tasks that use rundll32 to accomplish their task.

There are a plethora of uses, legitimate and illegitimate, that can be found through some simple searching. To save you some time, I will link a few resources that you can use at the bottom.

Now that we have seen some normal uses, how do we write our own DLLs to be run with rundll32. We just write a DLL, export the function and we are good to go…..

Well maybe its not that easy.

In order to really figure out how we write a DLL that we can call with rundll32, we could search the internet…

or we can just RE it and find out first hand. It turns out that there isn’t actually too much going on in the binary. We will focus on the parts of loading and executing an exported function from a DLL. Before we get to this point, rundll32 has parsed the input command line into its different parts and done some setup and access checking.

Windows 10 21H2 rundll32.exe

At this point, rundll32 calls its _InitCommandInfo function, which handles calling LoadLibraryExW on the specified library, finding the export function and converting the command line to it from a wide character string to an MBCS string. After this, we see that the binary calls RunDLL_CreateStubWindow. Once again we go to Raymond Chen who talks about this in his blog.3 It boils down to the fact the rundll32 expects the entrypoint provides a task that will handle window messages and if you don’t handle those messages in a long running task you can clog up broadcasts that leads to unresponsive windows.

Finally we see rundll32.exe call our exported function. We can also see that it will always call the function with 4 arguments. This means that if we want to write a DLL that works with rundll32, the export must handle those 4 arguments and also should handle window messages if its a long running task. If your trying to figure why rundll32 doesn’t crash when you don’t have the proper prototype on modern systems, you can thank Raymond Chen for the fix.2 Finally we have a prototype that we need to export.

VOID __fastcall ExportFunction(HWND hWnd, HINSTANCE hInstance, LPSTR lpCmd, INT nShow)

Now we can go ahead and write a simple example library that we can execute with rundll32. For this we will just write a simple hello world message box application, where the message is whatever we pass as command line arguments.

#include <windows.h>

extern "C" {

	__declspec(dllexport)
	VOID __fastcall 
	MyExport(HWND hWnd, HINSTANCE hInst, LPSTR CommandLine, INT nShowCmd)
	{
		UNREFERENCED_PARAMETER(hWnd);
		UNREFERENCED_PARAMETER(hInst);
		UNREFERENCED_PARAMETER(nShowCmd);
		 
		MessageBoxA(NULL, CommandLine, "RUNDLL-FUN", MB_OK);
		return;
	}

}

The code simply defines and exports a function named MyExport that matches the prototype we found from rundll32.exe. Since we are using the MSVC C++ compiler, we wrap the function in the extern “C” so that we get an undecorated export name. This is all that it takes to write a DLL that we can use with rundll32.exe. Now we just need to test it out.

We can run the exported function either by supplying the name of the export, or by supplying the ordinal of the export pre-pended with a #. The examples below show both methods and will give us the same output.

:: Export Name
rundll32.exe rundll-fun.dll,MyExport This is my message

:: Export Ordinal
rundll32.exe rundll-fun.dll,#1 This is my message

gives us

rundll-fun image

Hopefully this post has brought to light some of the internal workings of rundll32 and has brought to light some of the pitfalls associated with using it.

If this piqued your interest, there are also other windows DLLs that exist that you can use with rundll32 to load a DLL with a different export prototypes that you can find in the resources. As well, rundll32 interestingly supports executing different types of scripting languages. The resources below are a good starting point and can guide you towards a lot more fun with rundll32.

Resources

  1. MSDN rundll32
  2. Throwing garbage on the sidewalk: The sad history of the rundll32 program
  3. What’s the guidance on when to use rundll32? Easy: Don’t use it
  4. Why is Rundll32 called Rundll32 and not just Rundll?
  5. A Deep Dive Into rundll32.exe
  6. What is rundll32.exe
  7. SS64 rundll32.exe
  8. MITRE - System Binary Proxy Execution: Rundll32
  9. LOLBAS rundll32.exe
  10. MSDN rundll32 printui