After my last blog post about being able to execute macros in a desktop session regardless of their settings, I had some feedback on the technique. Some people weren’t able to get it to work while others found that they weren’t able to use it to bypass application whitelisting as invoking a blocked executable (such as PowerShell) via VBA code still resulted in the executable being blocked (which is to be expected).
I realised that in trying to prevent my initial post from getting too long I had made some inferences about how the ability to run macros can be leveraged but had failed to actually provide details. This also prompted me to look further into why the technique may not have worked, which Windows hardening measures may/won’t stop it from being leveraged and how some EDR solutions react to it.
Here in part 2, I have detailed the things I investigated and their results. The tools/techniques/knowledge here have all been available for many years, so nothing in this post is new. I am just collating them in the context of the macro settings bypass technique detailed in Part 1.
Fair warning - this is quite a long post with numerous memes. For the time-poor, the TL;DR: is:
- The technique allows application whitelisting to be completely bypassed and blocked executables to be executed anyway by using Clément Labro’s (@itm4n) awesome VBA-RunPE macro.
- Applying Windows Attack Surface Reduction rules on a host does not prevent this technique from working, nor do they prevent blocked executables being loaded into and run from memory per @itm4n’s VBA-RunPE macro.
- Windows Defender and EDR solutions we’ve tested do not stop this technique from working, nor do they prevent blocked executables from being loaded into and run from memory.
- So, if you have a desktop session and Office, unless macros were not installed with Office or are prevented via additional mitigations detailed in Part 1, YOU CAN RUN ANY EXECUTABLE ON THE SYSTEM, INCLUDING THOSE THAT HAVE BEEN BLOCKED*.
Effectively, organisations with Microsoft Office installed are forced to decide for each user whether to prevent ALL macros from running (including those that are signed and trusted) OR they accept the risk that the user with a desktop session could run any executable they wish.
There does not appear to be any option in between.
*N.B. When trying to load larger executables such as Mimikatz there appears to be a size restriction. I have not yet looked into why this is the case but I can’t imagine it being too difficult to find a workaround to directly load a larger binary. Alternatively, a meterpreter shell or other C2 beacon could just be obtained in the initial instance and the binary then loaded via that.
Credit
-
Based on previous blog posts by Didier Stevens, Clément Labro (@itm4n) did some quite brilliant work leveraging Windows APIs in macros and provided details in his 2-part blog titled “VBA RunPE - Breaking Out of Highly Constrained Desktop Environments” (part 1, part 2). In them, Clément describes how he used the imported APIs to run cmd.exe in a highly constrained environment and then developed a macro that could load an arbitrary executable into memory and then execute it.
Luckily for us, he also released his VBA-RunPE tool and I have unashamedly used it for researching this post. Thanks Clément! I strongly suggest reading his blog posts to understand what he did and how the VBA-RunPE tool works.
- Thanks to Emeric Nasi of Sevagas Information Security for his work on bypassing Windows Defender Attack Surface Reduction rules. That post saved me hours of going down potential rabbit-holes.
Arbitrary Code and Binary Execution
In my previous post I alluded to being able to do this when I said the technique allows a user to tap into the Windows APIs with malicious intent regardless of the macro setting level, but I didn’t provide any more information.
Essentially, once you have the ability to execute macros, you have the ability to import Windows API functions and use them in a macro. Once you have access to these APIs it’s pretty much game-over as far as code execution goes as they can be used to run shellcode, create reverse shells and so on.
Using the macro settings bypass technique and Clément Labro’s RunPE.vba macro without alteration, we can execute a binary of our choosing (as long as it isn’t too large). Here, I have simply loaded and executed PowerShell, for reasons that will become clear later, however it is also possible to directly embed a PE file into the macro and run it using pe2vba.py.
Sweet, that works! Note how the path shown in the resulting prompt isn’t Powershell, but is C:\Program Files\Microsoft Office\root]Office16\EXCEL.EXE which is a child process of the Excel process we execute the macro bypass from. This has implications for other Windows hardening measures that may have been implemented as I’ll get to later in this post.
WDAC, WDAC, wherefore art thou, WDAC??
Many of the environments I pentest prevent standard users from running PowerShell and cmd.exe using Applocker, WDAC or a third-party application control solution. Let’s see whether we can get around this.
First, let’s set up a WDAC policy that will prevent third party applications from executing. Using the Default Windows Mode base template, cmd.exe and PowerShell.exe are added with Deny rules:
After generating the policy, we’ll apply it and ensure it is working:
Now that is set up and working, let’s use RunPE.vba again to see what happens:
Ok, so we have just managed to bypass WDAC which is intended by Microsoft to be a security boundary. Blocking PowerShell via the Local Group Policy editor also failed to stop the bypass.
The problem is that these policies simply block the specific executables from running based upon their hash or their path, whereas RunPE.vba just needs read access to them in order to load into memory and run them from there, thus completely bypassing application blocking.
Maybe also removing read access from these executables for users that should not be running them would fix the problem? Well, not exactly as we could just directly embed a PowerShell PE file into the macro and run anyway it using pe2vba.py.
This is all less than ideal from a security perspective.
Attack Surface Reduction Rules to the Rescue?
Microsoft’s Attack Surface Reduction (ASR) rules are designed to help provide an additional layer of protection to Windows hosts by, well, reducing their attack surface. They are something we advise clients to apply wherever possible. Several of the rules are applied to Office in order to stop certain behaviours that do not occur under normal, legitimate circumstances and which help reduce the likelihood of a phishing attack being successful. They also help prevent malicious users from easily abusing Office for their own nefarious purposes.
Clear explanations of the ASR rules are detailed on the Microsoft website, so I won’t go into full details here, however it is worth noting the following ones that target Office:
- Block all Office applications from creating child processes
- Block executable files from running unless they meet a prevalence, age, or trusted list criteria
- Block execution of potentially obfuscated scripts
- Block JavaScript or VBScript from launching downloaded executable content
- Block Office applications from creating executable content
- Block Office applications from injecting code into other processes
- Block Office communication application from creating child processes
- Block Win32 API calls from Office macros
It’s reasonable to expect that at least one of the above rules will prevent us from using a macro to execute a PE file from memory, so let’s apply them all and see what happens. Here, we use the Local group Policy Editor to apply every ASR rule in addition to WDAC:
Running RunPE.vba gives us…….a PowerShell console again:
Let’s briefly examine each of the above rules to see where things might have gone wrong:
Block executable files from running unless they meet a prevalence, age, or trusted list criterion: We aren’t directly running an executable file therefore this rule won’t prevent our technique.
Block execution of potentially obfuscated scripts: The VBA is not obfuscated in any way, so this rule should not apply.
Block JavaScript or VBScript from launching downloaded executable content: There is no downloaded content that is executed here, so this rule does not apply.
Block Office applications from creating executable content: This rule prevents potentially malicious code from being written to disk by Office applications. As we aren’t writing anything to disk, this rule is bypassed.
Block Office communication application from creating child processes: This rule applies to Outlook so will not apply when we use Excel or Word to execute our macro.
Block Win32 API calls from Office macros: Things become a little more interesting here. This rule is supposed to prevent VBA macros from calling Win32 APIs, but VBA RunPE clearly does this. I would therefore have expected this rule to prevent the macro from running properly and was at a loss as to why it doesn’t.
Doing some digging on the internet I came across a post by Emeric Nasi of Sevagas Information Security (and creator of MacroPack) on bypassing Windows Defender Attack Surface Reduction which potentially shed some light on what is going on here. On p14 of the post, Emeric mentions that “It seems this rule will not trigger for an existing instance of Excel. It will however prevent a document which contains macro calling Win32 API to start”. If this is the case, the rule is probably only designed to prevent phishing attacks as opposed to malicious users.
Assuming this is correct (which dynamic testing suggests is the case), as we have to use an unsaved Office document for the macro settings bypass technique, we have an existing instance of Excel and this ASR rule will not be triggered when the VBA RunPE macro is executed.
Block all Office applications from creating child processes: This rule is supposed to block Office applications from creating child processes. If we create a macro that tries to pop calc using WScript.Shell, the rule is triggered and process creation fails as expected:
However, looking at things with Process Hacker, a new EXCEL.EXE child process has clearly been created when the VBA RunPE macro is executed, therefore I would have expected this ASR rule to prevent our technique from working:
So, what is happening here?
It seems as though the ASR rule is more permissive than its name and Microsoft’s description suggest. By adding a new line after line 636 of the RunPE script and setting the “strCurrentFilePath” variable to an exe of my choice, it was possible to create child processes with WINWORD.EXE, notepad.exe, conhost.exe and even MSBuild.exe:
So, this ASR rule can be easily bypassed as there seem to be numerous executables that Office is allowed to use to create child processes.
Block Office applications from injecting code into other processes: According to Microsoft’s documentation, this rule “blocks code injection attempts from Office apps into other processes”. Perhaps it’s my OCD, but I think this description is a little misleading.
It turns out that the rule still allows Office applications to inject code into child processes. Only by temporarily disabling the “Block all Office applications from creating child processes” ASR rule, we can create a child process using any executable (as opposed to only the ones allowed by the “Block all Office applications from creating child processes” rule) and inject into it.
Therefore, so long as we can create a child process as previously described, it is possible to inject code into it and this ASR rule is bypassed.
So, as things stand, this way of bypassing macro settings and using the RunPE macro means completely bypassing Microsoft’s Attack Surface Reduction rules AND application execution restrictions.
EDR
So far in this post, Windows Defender (fully updated at the time of writing) has been providing endpoint protection. Perhaps other EDR solutions will catch and prevent what we are doing?
I downloaded trial versions of several EDR solutions that I’ll decline to name, installed them and cranked their settings to maximum protection to see whether they would stop this technique. In short, they didn’t.
Mitigation/Detection
I covered several mitigations to this issue in my last blog post, however all of them effectively prevent all macros from being executed, including those that are trusted or signed. For me, that is a less than ideal situation for organisations as it forces them to either prevent all macros or accept the risk that users could execute arbitrary binaries.
If the risk is to be accepted, then it may be possible to detect this technique being used, however it may not be reliable or easy to do so.
Setting up Sysmon and looking at the events generated, we find that there were 2 process create events (Event ID 1) when PowerShell was invoked via the VBA RunPE macro.
An MS Office application creating 2 or more sub-processes, with the final one being conhost.exe may allow alerting of the technique if it is used to spawn PowerShell or cmd.exe. However, I wouldn’t recommend creating alerting rules that rely on the 1st created process being consistent (e.g. EXCEL.EXE) as that could easily be changed as we have already seen.
Now, what if a PE embedded in the macro is injected into the created subprocess using @itm4n’s pe2vba.py script? In this instance, we only see a single sub-process created (and only 1 process create event in Sysmon) and the conhost.exe one is nowhere to be seen. In the below screenshot, I used an embedded Meterpreter reverse shell and we can clearly see that the newly created Excel.exe sub-process was the one that was injected into:
So, perhaps simply alerting on an Office program creating a sub-process would be a more reliable way of detecting this technique, however I have no idea how many false-positives this may create.
An additional point to note is that VBE7.DLL, VBEUI.DLL and VBEUIRES.DLL are loaded into the Excel process the first time it executes a macro (and not before). It may be possible to monitor for the loading of these as well, however this could result in numerous false positives if trusted or signed macros are expected to be executed in the environment.
The final way detection might be possible is if Win32 API calls can be monitored and some form of behaviour-based monitoring is in effect. The VBA RunPE technique uses well known process injection calls such as CreateProcess, VirtualAlloc, WriteProcessMemory and ResumeThread (as well as some others) so perhaps these could be alerted on. Again though, these aren’t the only calls that can be used for process injection so it would be worth ensuring any behaviour-based detection rules are not too brittle.
Conclusion
Although this turned into a fairly long post, I think it was worth going into depth to explore the ramifications of being able to bypass macro settings in a desktop session. Even though the technique cannot be used in a phishing attack (as far as I know), until it is fixed, I still feel it presents a relatively significant danger to organisations due to the number of security controls it can bypass.