Skip Navigation
Get a Demo
Resources Blog Incident response

Uncompromised: Unpacking a malicious Excel macro

In the second installment of our “Uncompromised” series, a member of Red Canary’s incident handling team analyzes a malicious Excel macro from a customer red team engagement.

Paul Michaud
Originally published . Last modified .

The setup

A customer involved in a penetration test reached out to us recently about a suspicious email message that one of their employees received. The attachment consisted of a Microsoft Excel workbook (.xlsm) that contained a Visual Basic (VBA) macro—I know… we shuddered as well.

Just like any phishing campaign involving Microsoft Office files, the user had to be enticed into opening it. How did the sender entice the user, you ask? They used a classic phishing lure in the subject line: “Annual Employee Evaluation Report.” I mean, how could you not open it and see how you did last year?! Even we were intrigued, so we popped open the attachment to see how the evaluation went.

The sheet

The contents of the spreadsheet were immediately disappointing. There were no results, no new salary, no bonus information (I was going to buy a boat!), no nothing.

Excel macro red team

Well, almost no nothing. The first things that stood out were:

  • Microsoft’s bright yellow notification: “SECURITY WARNING Macros have been disabled”
  • A bunch of red text in cells exclaiming “SECURE CELL: ENABLE MACROS TO RETRIEVE DATA

Enable macros or not? that was the question… I really wanted that boat. What’s the worst that could happen? It’s not as if the macro was going to enumerate local system information or Active Directory data and ship it back to the red team within this workbook… right?

Excel macros red team

Macros enabled!

With the script running, it was the perfect time to poke around the workbook.

Excel macros red team

Interestingly, the “Secure Cells” now displayed an error message: “ERROR 8415E1337: TIMEOUT OCCURED RETRIEVING DATA” (yes, “occurred” was spelled wrong). Only one way to figure out what went wrong: look at the macro itself.

The code

There’s more than one way to analyze a macro, but my favorite method involves OLE Tools. OLEVBA parses OLE and OpenXML and extracts the VBA macro code in clear text, which was specifically useful in this case because, fun fact: Microsoft Office documents are just specialized XML files.

Let’s see what comes out of the workbook from OLEVBA:

Excel macros red team

At first glance, we could see the initial subroutine: Sub_AutoOpen(). It tells the macro to execute once the file is opened (or once macros are enabled). If an error occurs, it jumps to a function called Oops—otherwise, it declares a bunch of variables.

The most interesting pieces in this first section are the following variables, along with some handy comments:

'Gather User Information
strUname = Environ$("username")
strCname = Environ$("computername")
strDomain = Environ$("userdomain")
strDnsDomain = Environ$("userdnsdomain")
strDomainDC = Environ$("logonServer")
strOS = Environ$("os")
strComputer = "."
'Populate target sheet
Sheets("HostInfo").Cells(2, 2).Value = strUname
Sheets("HostInfo").Cells(3, 2).Value = strCname
Sheets("HostInfo").Cells(4, 2).Value = strDomain
Sheets("HostInfo").Cells(5, 2).Value = strDnsDomain
Sheets("HostInfo").Cells(6, 2).Value = strDomainDC
Sheets("HostInfo").Cells(7, 2).Value = strOS

Six variables are set to values that are returned by the Environ function, which is a VBA function that returns the string associated with a specific operating system environment variable. Those values are then stored within a sheet called “HostInfo,” specifically cells starting in Row 2, Column 2.

Weirdly, none of this macro code seemed to have anything to do with an annual employee evaluation. To that point, when we first opened the workbook, we only saw one sheet called “Evaluation,” so where is this HostInfo sheet even located? There’s no call to create the sheet either, so something funny was going on here. Maybe we shouldn’t have executed this macro after all!

Let’s go back and take a look.

Excel macros red team

Are we sure there aren’t more sheets? We weren’t sure, and, when we unhid the sheets at the bottom, there were in fact 10 additional sheets hidden from view. Much to my surprise, none of them had anything to do with an employee evaluation:

  • ADInfo
  • ADUsers
  • ADGroups
  • ADComputers
  • AV
  • HostInfo
  • LocalUsers
  • LocalAdmins
  • Shares
  • Proxy

Based on the sheet names, it appeared as if local system and Active Directory information was supposed to be stored here. Naturally, we then needed to figure out what specifically was going to be stored in these sheets and how was this macro going to enumerate that data from the host.

We can answer this by continuing our analysis. So let’s get back to it!

A little help from WMI

As we scrolled through the macro, the “Gather Running Process Information” section included some interesting Windows Management Instrumentation (WMI) queries, specifically ones that gathered Running Processes, Local Users, Local Admins, and Shares. But how did this all work? Let’s break one down.

First, for those unfamiliar with it, WMI is the implementation of a native functionality for managing data and other operations that go into running and maintaining Windows. It can be used to manage remote Windows systems across an environment.

Excel macro red team

In order to make useful WMI calls to query the necessary information from the system, a namespace and system must be specified—in this case, the local system and its default namespace. However, If a namespace is not specified, WMI will use the value specified in the Registry key located at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WBEM\Scripting\Default Namespace

The first command set the objServices to a WMI object, returned by the GetObject function. winmgmt is the WMI service within the SVCHOST process running under the “LocalSystem” account. strComputer is the name of the host, and the majority of the WMI classes for management are in the root\cimv2 namespace. Next, using the specified WMI object, a WMI query is executed, which gathers the name and process ID of each process on the operating system, storing the results in the variable objProcessSet.

Now it’s time to populate the sheet with the relevant data. The red team used a For loop to iterate through the list of processes and store the ProcessID and ProcessName in the “HostInfo” sheet. The variable IntProcStartRow was set to 11, which specified the starting row number where the results will be stored. In this case, we started with Row 11, Column 1. From there, the row number was incremented by one until it had stored all the results from the query.

Excel macros red team

So far the macro had collected local system information and stored that data in the appropriate sheet. Not too complicated, since you can point to the local system. However, if I am a threat actor and I don’t know where my script is going to land, I need to figure out a way to enumerate Active Directory information with no child processes or suspect network connections. I don’t want to tip off any defenders, after all. You’re probably wondering the same thing I was: how do you query a domain without knowing anything about it?

The answer? RootDSE.

The initial variable was set by calling the GetObject function against LDAP://RootDSE. RootDSE is part of the Active Directory Service Interface (ADSI), which is a set of COM interfaces used to access the features of directory services from different network providers. The script was using RootDSE, which is a unique entry in a directory server that provides data about that server, including Lightweight Directory Access Protocol (LDAP) version and naming context, to identify the context in which it’s running. In other words, by retrieving the DefaultNamingContext from RootDSE, you can bind to the current domain without explicitly knowing it!

After setting RootDSE, strFilterUsers and strAttribUsers were set. These were strings that would be used in the query to return specific domain user objects and specific attributes of those objects.

Excel macros red team


Next, an ADODB connection is created. ActiveX Data Objects (ADO) allow connections to OLE DB data sources, in our case ADSI. The next part is to create the command using information previously defined.

Excel macros red team

Now it was time to execute the command and return the results. Once it was executed, the results were returned, iterated through, and stored in the “ADUsers” sheet within the workbook. Notice the objRs.Fields attributes: they are the same attributes that are being filtered in the strAttribUsers variable.

Excel macros red team

This continued to loop through and store results until it had reached the end, then it closed the connection. The same process and setup was repeated to gather information on:

AD InfoAD GroupsAD Computers
AD Info:

Lockout Duration
Lockout Observation
Min Password Length
Min Password Age
Max Password Age

AD Groups:

SAM Account Name

AD Computers :

SAM Account Name
Operating System
Password Last Set

After all the AD information was enumerated and stored within the appropriate sheets, the macro then updated the “Secure Cells” from earlier setting a string variable and then calling the sheet name and specific cell to be updated:

Sheets("Evaluation").Cells(8, 5).Value = timeoutResp

Now, you might be wondering “How does the attacker get all this data back?” Great question! Simple answer? The adversary saved it and sent it back via an HTTP POST request.

Excel macro red team

So while this may have looked crazy, it really wasn’t. First, a few variables were set: the full path including the file to the workbook, the file as byte data (which was obtained by calling the GetFileData function), and then the mimetype of the file. Next, these values were passed into the PostFile function and used to POST the data back to a previously declared URL. That’s it!

Don’t be an enabler

At this point, the biggest question our faithful readers probably have is: “How do I detect this?” In general, you might expect these WMI queries to spawn WMI as a childproc from Excel, but that is not the case here: the WMI calls are actually processed by wmiprvse.exe, which allows WMI to interface with the system. That said, Excel does create a potentially suspicious modload, so it makes sense to look out for Microsoft Office applications loading wbemdisp.dll—one of the related WMI COM components. While evidence of this modload is not inherently malicious, it can help an analyst to prioritize investigation of certain macro-enabled Office files that also exhibit WMI-related activity.

Now what about the HTTP POST back to the attackers? You could monitor network connections from Word and Excel, but that can get extremely noisy. Baselining and then filtering out normal connections is also possible, but what if the attacker uses a technique like Domain Fronting? In this case, that’s exactly what the red team did, and this technique could easily allow an adversary to walk this data right out of your network.

So what do you do now? We have a few recommendations:

  • Block macros from running in Office files from the internet with GPO. Microsoft published a great blog post on how to implement this.
  • Validate your email security gateway configuration. Do you normally deal with macro-embedded files such as .docm or .xlsm? If not, you may want to think about adding those and other macro-embedded files to your blocked attachment policy. Microsoft has a list of Office file formats, and you can use it to help determine what to add to your block policy.
  • Educate everyone. While you should never expect your non-security co-workers or employees to be security experts, they can serve as a valuable detection signal when trained to identify and report suspicious behavior.

Accelerating identity threat detection and response with GenAI


What Home Alone teaches us about proactive defense


Adversaries exploit Confluence vulnerability to deploy ransomware


Is your IR plan DOA?

Subscribe to our blog

Back to Top