Bypassing AMSI for VBA

This blog is a writeup of the various AMSI weaknesses presented at the Troopers talk ‘MS Office File Format Sorcery‘ and the Blackhat Asia presentation ‘Office in Wonderland’.

We will explore the boundaries and design weaknesses of AMSI for VBA that would allow attackers to bypass and evade this defensive mechanism. Note that attacks on the engine itself (such as in-memory patching) are out of scope for this post.

Just a warning before reading ahead: I am particularly proud on the ugliness of my code samples. 🙂 This really illustrates that they should not be used in real offensive operations but merely serve as proof of concepts.

Intro on AMSI

In Windows 10, Microsoft has introduced the Antimalware scanning interface. This feature acts as an interface between script interpreters and anti-virus engines.  It currently supports the PowerShell engine, the Windows Script Host (wscript.exe and cscript.exe) and recently support for Visual Basic for Applications (VBA) has been introduced.

In a nutshell, when executing a command in these interpreters, the runtime commands are send towards the AMSI interface. If an antivirus (AV) product hooks into this interface, the AV engine receives the executed runtime commands via AMSI and can decide upon blocking or allowing a command. The fact that runtime commands are sent allows AV products to gain visibility into unobfuscated and in-memory execution of malicious code, in addition to blocking malicious behavior of this code in real-time.

AMSI & VBA

In September 2018, Microsoft announced an AMSI implementation for VBA and this feature has been implemented in the MS Office 365 semi-annual release channel as of January 2019. Any Office 365 user should be running an AMSI-enabled version of Office by now.

Some highlights of this implementation (note the difference between ‘logs’ and ‘triggers’):

  • Any COM method and Win32 API call should end up in the ‘Behaviour log’.
  • Specific calls have been marked as ‘triggers’. These are high risk executions. Once they are observed, macro execution is halted, and the contents of the circular log are passed to AMSI for AV to make a decision.
  • Based on testing it appears that p-code based attacks where the VBA code is stomped will still be picked up by the AMSI engine (e.g. files manipulated by our tool EvilClippy).
  • In the default configuration, the AMSI engine is not enabled on all macro enabled documents. The ‘macro runtime scope’ is by default set to ‘enabled for low trust documents’. This means that trusted documents, documents from a trusted location or signed by a trusted publisher will not be provided to the AMSI engine under the default setting.

Bypass 1: Attack without VBA – Excel 4.0 macros

In Excel there is a second Macro engine specific for Excel 4.0 macros. This engine is implemented in Excel.exe itself and not in the VBA engine (VBE7.dll). As the AMSI engine only hooks into VBA, it is blind to Excel 4.0 based attacks.

Bypass 2: Macro runtime scope & abusing trust

Microsoft made several exceptions on the scope of AMSI (under the default macro runtime scope setting), especially the exceptions of ‘trusted documents’ and ‘trusted locations’ are of interest.

In my lab environments all ‘trusted documents’ were still flowing through AMSI. Not sure if this is due to error in Microsofts documentation or I made a mistake in my implementation.

Documents from Trusted locations were excluded from AMSI under the default setting. As such I wrote a PoC macro to abuse this. Idea is simple:
On document open, we ‘saveas’ our current document as a template towards the trusted location. Then open a new file based on that template which will trigger a document_new event from a trusted location.

Bypass 3: Innocent COM functions

Microsoft makes a distinction between ‘innocent’ COM and Win32 API functions that just need logging and more suspicious ‘trigger events’. Some examples of ‘innocent’ COM functions that allow for abuse but which do not trigger AMSI are:

  • Instantiating Excel and calling executeExcel4macro or DDEInitialize.
  • Using WMI and ‘spawninstance’ as documented as an ASR bypass by DarkOperator.

There are probably a lot of additional ones. During my testing I noticed that it was especially function names which contained words such as “shell” or “execute” resulted in an AMSI trigger.

Bypass 4: Non-Win32API/COM functions that have ‘side effects’

Besides the Win32 API and COM functions, there are various functions built into Word and Excel that can result in either code execution or disabling AMSI.

  • It is possible to use a macro to edit the contents of a Word file and save it as text files named ‘disableamsi.reg’ and ‘disableamsi.bat’. If we then save the bat in the startup and have the .bat call regedit with the .reg, we can disable AMSI through the HKCU macroruntimescope setting (most companies will not configure the macroruntimescope GPO – as such we can quite safely overwrite the HKCU setting without worying about it being set back through a GPO). In the same Bat file one could start Word without splashscreen and load a malicious VBA file.
  • The ugliest approach by far: Using Excel’s ‘sendkeys’ you can send the key combo CTRL+Esc. This will open up the start menu on the victims system, and by timing correctly it is possible to run an arbitrary command (start calc or a PowerShell script).

On our Github I published proof of concept code for these two attacks.

Closing thoughts

Microsoft designed the AMSI for VBA engine significantly different (or should we say weaker?) from the scripting and PowerShell AMSI engines. By merely scratching the surface of AMSI for VBA, we already demonstrated bypass techniques that are inherent to its design and as such can’t be easily mitigated.

It is possible to strengthen AMSI a bit by applying the following measures:

  • Consider setting AMSIRuntimeScope to scan all documents instead of non-trusted documents only. Note that this setting can be controlled via GPO.
  • Manage the ‘trusted locations’ via a GPO: preferably have no trusted locations where a regular user can write.

If you are a defender, our message remains: disable macros for your end users wherever you can. For users that really need macros, use signed macros. Do not think of AMSI for VBA as a foolproof gatekeeper that prevents all malicious macros from running.