The role of Rundll32 is to load DLLs.
On a 32-bit OS it is a very straightforward task, but when you mix architectures interesting things happen. One of a side-effects of having more than one architecture on the same box is that Windows On Windows (WOW) layer gets involved so that we can run 32- and 64- bit code at the same time.
This makes life of rundll32 developer harder. There are two version of rundll32 on a 64-bit Windows system: one inside system32 directory, and another one – in SysWOW64. But rundll32 users don’t want to known about these versions, couldn’t care less about multiple architectures, and when they run a command they simply expect their library to be loaded, and its function called.
When 32-bit rundll32.exe is called to load a 64-bit DLL, 32-bit rundll32.exe will spawn 64-bit version of rundll32.exe and that 64-bit DLL will be loaded there.
The very same happens when a 64-bit rundll32.exe is used to load a 32-bit library.
It’s like that… and that’s the way it is.
This is a well-known stuff and you are probably wondering where I am going with it.
Today most of applications are 64-bit, so one could exploit the rundll32.exe behavior I described in various ways:
Set up a persistence Run key (or use any other common startup method) to point to rundll32.exe <name of a 32-bit library>
This will keep the persistence entry clean (points to a signed 64-bit rundl32.exe, even if full path is not explicitly set in a startup entry to point to c:\windows\system32\rundll32.exe), and will ensure cascading execution of a 32-bit rundll32.exe that will load the given 32-bit DLL (possibly malicious).
Same as before, set up a persistence in any way you like, just pointing it to a rundll32.exe and using a file name of an existing, clean 32-bit DLL, and then place a malicious rundll32.exe under c:\windows\SysWOW64.
This will launch the malicious 32-bit rundll32.exe.