Microsoft Office Macros have been the bane of security analysts' lives since the late 1990s. Their flexibility and functionality make them ideal for malware authors to use as a primary stage payload delivery mechanism, and to date the challenge they pose remains unsolved. Many organisations refrain from blocking them completely due to the impact it would cause to their users, and instead rely on a combination of detection and mitigation technology to compensate for the risk they pose.
I have long thought that it would be ideal if Microsoft were able to implement granular controls via group policy over which activities macros were permitted to perform, for example allowing the blocking of process execution or network activity. In the interim, I have been experimenting with alternative methods to limit which functions a macro can call, either by redefining or patching the high risk API calls to prevent their intended outcome.
Below we can see a very basic example of what a malicious macro might look like. The fact that the subroutine is named "AutoOpen" means that it will be run as soon as the document is opened (and if required, macros are enabled). The command "Shell" at the beginning of the line instructs Word to execute the process as specified in the parameter, so in this case the macro will launch the Windows calculator.
Option 1 - Using global templates to override built-in functions
Shell is the function built into VBA which is used to launch new processes, and as such, can often be found in malicious macros. Malware authors leveraging macros as a primary stage payload typically want to either download or drop a file, and then execute it. Alternatively, they could decide to execute an existing process on the system, such as powershell, with parameters which will take actions against their intended target. Common to both of these methods is the requirement for something to be run, and this is where Shell comes in. If we were able to somehow disable the Shell function, then we might be able to prevent the malicious macro from succeeding in it's goal.
The first experiment involved redefining the Shell function, with the hope that the newly defined function would override the built-in one. We can achieve this by placing the code in the global template which is named normal.dotm or within an add-in (which is effectively a template that is loaded every time Word is opened), either way, our code will become part of the malicious macros execution.
Below we can see the redefined function used as part of this experiment. This was saved within normal.dotm (the global template), with the intention that when Shell was called, rather than calling the system function to launch the process, our redefined function would be called, resulting in a popup warning the user that the activity had been blocked.
Then, using the one line test macro that attempts to execute 'cmd.exe /c calc.exe', we can see that the redefined function worked well and blocked the execution of the process. Unfortunately, it only seemed to work when the malicious code was placed within the document and not when it was within a seperate module. The reason for this is unknown and warrants further research, as if solved, this would be the easiest way to achieve our goal.
Option 2 - Patching associated API in VBE7.dll to alter behaviour
For a more surgical approach we can look at API hooking the dll which is leveraged when macro code is executed. Looking through the dlls, which are loaded into Word at runtime, we can observe that VBE7.dll includes a large number of exports that appear related to the execution of macro code. Below we can see a snapshot from the list of exports with one in particular highlighted, rtcShell, that warranted further investigation.
Digging into this function we can see that it's nothing more than a small wrapper around the CreateProcess Windows API call. The particular code block which executes the process is listed below. Looking at the parameters being passed to the API, we can see that the EBX register is being used to hold a pointer to the name of the process to be executed.
At this point, if we want to alter the way the process is launched we can patch the instructions to alter the process creation flags, which is one of the parameters passed to the CreateProcess API. Alternatively, we could completely patch out the API call to prevent the execution from occurring.
In this case we are going to leverage the CREATE_SUSPENDED process creation flag to allow the process to launch, but immediately be frozen into a suspended state, preventing the code from running.
The patch sets the ECX register to 4, which is the numeric value of CREATE_SUSPENDED, and then this is pushed as the creation flags parameter to the CreateProcess API call, resulting in the process being instructed to launch in a suspended state.
Following this patch, a test macro that was designed to launch powershell was executed, and as we can see from the process explorer window, it was placed into a suspended state, thus mitigating any impact and allowing an opportunity for endpoint protection sensors to scan and identify any threatening behavior.
These are just proof of concepts, and would require more research and work before they were used in a production environment. I still believe that the ideal state would be for Microsoft to implement more granular security controls against macros, allowing organisations to continue using them, but at the same time empowering them with the ability to limit their capabilities depending on individual risk appetites.