Evasive VBA — Advanced Maldoc Techniques

Authors: Kirk Sayre (@bigmacjpg), Harold Ogden (@haroldogden) and Carrie Roberts (@OrOneEqualsOne)


Different methods of creating processes in VBA come with benefits and drawbacks. Shell$, Shell, or a variation of CreateObject(“WScript.Shell”).Run will result in the office application being listed as the parent of the created process. This may be unusual in the target environment and could raise suspicion.

WMI process creation has the benefit of the process being created by wmiprvse.exe (the WMI service process), disassociating the process creation from the maldoc unless WMI activity is being monitored.


Business processes commonly use Shell$, Shell, or a variation of CreateObject(“WScript.Shell”).Run to run reg.exe, ping.exe, cmd.exe, and other utility applications to read information from stdout or make changes to enable the processes to run. This may create ambiguity for defenders, since they cannot automatically treat process creation from office products as malicious or block it out-right.

Regardless of your choice from the above process creation methods, an office process creating a powershell.exe process is a key indicator that defenders are looking for. Disassociating by even one layer can reduce the chances of the behavior being flagged. One method is to call cmd.exe /c powershell.exe instead of calling powershell.exe directly.


Where Shell$ leans on the uncertainty of legitimacy, WMI has no such benefit. WMI process creation is not something commonly seen in business processes, giving defenders a strong signal to investigate further. WMI process creation carries with it the disadvantage of three immutable VBA elements that can be used in signatures on the embedded OLE file: GetObject(), Get() and .Create.

Empire as a Baseline

Below is the VBA used for WMI process creation out of the default PowerShell Empire VBA builder. Empire is a post-exploitation framework that includes a malicious VBA macro builder, amongst various other functionality. Variable names and strings in the Empire code will all be present in the P-Code (compiled pseudo-code) section of the document’s embedded OLE (Object Linking and Embedding) file, and again in the compressed VBA source code. The default embedded OLE file used to store VBA macros in the Office 2007+ file format is vbaProject.bin. Many of these are static from build to build, reducing the effort of signature creation and detection.

Detection rate of document with default Empire builder VBA.


While the verbosity of the Empire WMI process creation has its drawbacks, the advantage to this approach is that the WMI process is created hidden from the target (achieved with objConfig.ShowWindow = 0). Without this property set, there is a chance that the target may notice the brief appearance of the created process appearing on their task bar.

Opting for less code in the VBA, while taking the risk that the target may notice the process briefly appearing on their task bar, WMI process creation can be done on one line as demonstrated below.

In this example, two processes are created in the same way. On the second process creation, every possible part of the VBA has been stubbed out and obfuscated. StrReverse() was used to demonstrate that the strings do not need to be stored in the form required by GetObject() anywhere in the VBA. Note that GetObject().Get() and .Create are still present, and remain a possible target for signatures.

Undefined variables can serve as Null in this case, reducing the number of hints provided as to what kind of method is being run.

The result from even this simple obfuscation is a reduction of 4 AV detections, and then down to 7 when we reverse the strings:



Payload Obfuscation and Reassembly

StrReverse() serves well as an example, but it is a well-known obfuscation technique that can be keyed off of for detection. Some obfuscation techniques raise more suspicion, and others less. The degree you will need to obfuscate and reassemble your payload is greatly dependent on its size.

The goal is to have as little code introduced as possible and to prevent any representation of the payload from appearing in a format that can be keyed off of in a signature.

Payload Size Matters

Smaller payloads will not require as much, if any, reassembly. VBA’s maximum string length in a variable definition is 1024, so if your obfuscated payload is longer than that, you will likely be reassembling with string concatenation.

String concatenation can increase AV detection rates, as it is a common technique seen in major maldoc campaigns like Emotet.

Emotet reassembling the payload prior to process creation

The benefit of supporting larger payloads with a builder is the capability of bringing along a more robust second stage.

Regardless of the payload size, reducing the likelihood of static detection and AV detection rate can be achieved by obfuscating the payload to a non-standard encoding or encryption, and using the least possible number of string manipulation functions.

Functions to avoid using in high quantity are Left(), Right(), Mid(), Chr(), ChrW(), and the like — anything that has to do with string manipulation or character decoding. Signatures can easily be written for simple de-obfuscation like ‘Chr(115) + Chr(116) + Chr(117)’ due to the use of this technique in various maldoc campaigns.

Here, we have used a payload that is small enough to fit on one line and not require reassembly.

Decoding each ASCII code is done by a set of functions we’ve named after the months of the year, and days of the week, making them difficult to fingerprint.

To go through the stored payload and decode it, we have function June that makes use of the above functions.

All strings we have are encoded, hiding our intent as much as possible, with June used to decode them. Uninitialized variables are used for the Null and 0 values that would normally be passed to create the process, so that signatures looking for WMI process creation arguments will be less effective.

The result remains 7 detections on initial upload, but is now more resilient –an arbitrary amount of payload can be added at this point with very little risk of increasing the detection rate.

In this example, the strings are obfuscated using Rot9. This requires only a nominal addition to the VBA. Our strings are now arbitrary and without emulation or sandboxing, specific payload strings can no longer be looked for with signatures. The structure of the VBA can still be keyed on for signatures, but most of what we’re using exists in real-world business process VBA, making this build more resilient.

To Rot9 the ASCII codes to enter in the VBA, using a short PowerShell script in this case. ASCII codes are always padded to 3 digits, to make decoding easier, and allowing us to not store the payload in an array that could trigger a signature looking for large arrays in the VBA.

Anti-Sandbox (offline) and Anti-FakeNet

Detecting and avoiding execution when in a sandbox can be done in the VBA without significantly adding to your chances of being detected by AV.

https://github.com/joesecurity/pafishmacro is a great source of inspiration, but I don’t suggest implementing any of these as-is. Since we are using WMI to avoid behaviors being tied to our WINWORD.EXE process, here is an example sandbox and fakenet bypass using the WMI method Win32_PingStatus. This will result in wmiprvse.exe being the process associated with the DNS and ICMP traffic, instead of potentially raising suspicion if WINWORD.EXE were to generate the traffic.

The first check is whether location.microsoft.com can be resolved. If it can be resolved, or we can ping the host, we know we’re in a fake net — location.microsoft.com is a non-existent domain.

The second check is whether the userdomain and computername environment variables are the same. If they are, the system is not domain joined.

Only if both return True will we proceed with running our payload. This bypass has been shown to bypass automated sandboxing systems, and to disassociate the ICMP and DNS traffic from the sample when using a sandbox solution to provide a behavioral report.

Using the methods from our previous section to obfuscate strings, the detection rate drops to 3.


In summary, if you want your VBA to be detected less:

· Avoid using code directly from attack simulation frameworks (security companies love to write signatures for these)

· Use non-standard encryption or encoding for the payload that will be visible during static analysis

· Stub out functions that you need but don’t want to look suspicious (Chr(), etc.)

Crafting quality VBA is important, but there is more that can be done. In the next section titled VBA Stomping, we cover additional obfuscation techniques and the Office internals that enable them, leading to a minimized detection rate across AntiVirus products.