While working on building a couple new simulation modules for ATTACKIFY, we came across an interesting feature within rundll32.exe
. When creating some test DLL files we created exported functions with the following names:
So at this point our DLL has two exported functions named MyFunction
and MyFunctionA
(this was originally just testing). The first function, MyFunction
could contain potentially malicious code, however in this case it only displays a message box to indicate where you are in the execution.
So let’s run the new DLL with rundll32.exe
and execute the exported function by name, MyFunction
, and see what happens.
Well that’s is interesting rundll32.exe
is executing the function MyFunctionA
instead of the specified MyFunction
as seen in the command line. As mentioned before we were testing something else when we came across this problem and couldn not figure out why rundll32.exe
was not running the function we specified as it seemed to run just fine with other tools that are not rundll32.exe
. So inorder to successfully execute the function we want, we can use something that is not rundll32.exe
like a custom tool or PowerShell.
Our snippet for PowerShell can be something as simple as:
Running the above PowerShell script successfully executes our function specified in the DLL as compared to rundll32.exe
:
Not all that much, however, something attackers/malware writers/red teamers could use this technique to potentially hide their malicious functionality from some basic analysis for a moment or two longer. An attacker can create 3 exported functions with the same name but ending two harmless functions with the the W or A suffix.
for example: FuncW()
and FuncA()
could contain non-malicious code and Func()
would contain the malicious functionality
The idea would be that if the suspicious DLL is manually analysed using rundll32.exe
the observed function will run the non-malicious functionality because of the strange behaviour, thus protecting the malicious code from being run and easily analysed. Obviously this not fool proof, but if only basic analysis is performed, this would easily circumvent the malicious code from being observed or detected as one of the harmless functions would run not the malicious one via rundll32.exe
.
There is not a lot of information or research on this particular behaviour that we could find before writing this up. We do however suspect that it has to do with Microsoft Windows wide/Unicode and ANSI character compatibility support. So it seems like default behaviour is for rundll32.exe
to fist check for wide/ANSI character supported functions before defaulting to the command-line specified function.
We found this mini write up on rundll32:
It is possible to force rundll32.exe
to still run the specific function, just not by the exported function name. To do this one can use the ordinal value and tell rundll32.exe
to execute the function at position #1, #2, #3 etc. So we can load the DLL up in a DLL Export View that will list all the available function names and the ordinal value for each function (you could also just brute-force the values starting from 1):
rundll32.exe <dllFile>, #1
As you can see the function in question has an ordinal value of 1, we can now simply instruct rundll32.exe
to execute the correct exported function without getting tricked up by the naming obscurity issue:
I guess the lessons here include:
- Know your tools and what they are doing, what they are not doing and the tools limitations
- Make sure you thouroughly perform analysis and dont rely on basic testing
- Dig a little deeper and use the right tools
We have already implemented a module to simulate the behaviour for you. The module will drop a DLL file to the endpoint, run the exported function that will spawn calc.exe and then when run with rundll32.exe
will prompt the user to try harder! We also leave a copy of the artefact for you to manually play with.