zloader: VBA, R1C1 References, and Other Tomfoolery

The other day, @reecDeep tweeted about new behavior from zloader documents. Another document from the same campaign crossed my path and I decided to take a crack at it.

SHA256: B29C145D4B78DAED34DEA28A0A11BAB857D5583DC6A00578A877511D0D01D3D2


Row and Column References

One of the first things you may notice is that this document doesn’t have the typical letters designating the columns. Instead, this document is using the R1C1 reference style. This was not done by accident. The Excel 4.0 macros used throughout this document depend upon this format.

Getting around “Enable content”

The first sticking point was getting the ability to control execution of the macro. This proved to be a bit difficult. If the ‘Enable Content’ button is showing up, this means that some macros must exist, right? However, the VBAProject contents showed both Sheet1 and ThisWorkbook as blank.

So there were no macros… yet we were still being prompted to enable them. If you did enable them, the macro would execute with no opportunity to interrupt anything before the document would close. I decided to add a simple macro to the project to see if that would help me control execution.

I noticed that when I saved my changes, the size of the document changed.

Opening the copy while holding down the shift key brought up this security notification. Choosing “Enable Macros” allowed me to control execution and continue the analysis.

Finding the Entry Point – R27455C174

As this is an XLM 4.0 macro document, the macro commands in the cells will execute sequentially until the commands send execution path elsewhere. Possible commands for which to search would be =FORMULA or =GOTO. I started by searching for =FORMULA. Once I found one, I started to step through the macro code to see what would happen. It took a few tries, but the entry point for this document is R27455C174. From here, you can right-click that cell and select Run.

We can also see how this document makes use of the R1C1 notation. From what I understand so far, a positive number means you add that number of rows/cells to the current row/cell, and a negative number means you subtract that number of rows/cells to the current row/cell. In this case, it seems that the row being referenced is 51762 rows down and 81 columns to the left. However, I tried going to that cell but found it to be empty. I might be missing something obvious, but in the grand scheme of things, knowing exactly how this particular cell works is more of an academic exercise.

Either way, you could just right-click on the cell, choose Run, Step Into, and then Evaluate a few times get the code execution rolling. You’ll see that =GOTO(”) ends up moving you to cell R46304C95.

BLOCK 1 – R46304C95

This cell is where characters from other cells are assembled into a string. We can see that the first one is “=CLOSE(FALSE)”. We can continue evaluating all of these until we get to the =GOTO() at the bottom.

BLOCK 2 – R48037C63

That =GOTO() takes us to R48037C63. This cell fills in the cells below with the same string. The commands in the following cells take the strings from Block 1 and write them to a new location. For example, let’s look at R48038C63. It says to take the information in a cell that is 1734 rows up and 32 columns to the right and move it 14892 rows up and 20 columns to the left. This continues on until the =GOTO() at the bottom of this block.

BLOCK 3 – R33147C43 – Evasion checks

The next block starts at R33147C43. It contains everything that was written above. Let’s analyze it in pieces. The first portion contains the familiar sandbox checks. Notice how if any of those checks fail, you GOTO(R33146C43). That cell contains =CLOSE(FALSE) which immediately stops execution.

A .vbs file is then created in the C:\Users\Public folder. The lines in cells 33160-65 are written to that file which is then closed.

The next section executes the .vbs file. This file reads information from the system registry containing VBAWarnings. The output is returned to the .txt file. The .vbs file is then deleted, the .txt file is opened, read, and deleted. If the .txt file contains a 1, go back to =CLOSE(FALSE). If not, check environment. If it has a 32 in the results (which it does), GOTO(R13419C196).

BLOCK 4 – R13419C196

This brings us to yet another series of cells getting assembled into strings. We can step through them as before to the =GOTO(”).

BLOCK 5 – R28840C118

Once again, this block takes the strings from above and copies them elsewhere.

BLOCK 6 – R38562C99

And finally, we can see the final execution commands in this document. There are four URLs from which to download a file to C:\Users\Public\lxlGZ4A.html and execute it using rundll32.exe. Notice that if it fails the size check, it doesn’t go through the rest of the URLs. Instead, it immediately jumps down to the ALERT message. I’m guessing this helps to hide the rest of the URLs from showing up in Wireshark or something.


Many of the sandbox evasion techniques used are the same as before. The added difficulty was the use of (nonexistent) VBA macros, more ways to disguise the commands being run, and ways to hide the other URLs.

As always, thanks for reading.

zloader and XLM 4.0: Making Evasion Great Again


Zloader documents now have extra layers of defense making it more difficult to determine the entry point of the macro code as well as adding extra evasion checks.


Thanks to Excel 4.0 macros, everything old is new again and we’ve all had to quickly familiarize ourselves with these archaic macros. At the beginning of April 2020, these macros were rather neat, tidy, and easy to follow.

Then Trickbot came along. Workbook sheets were hidden, macro execution was tossed all over the place, and analysis became much more difficult.

Which brings us to where we are today. @ffforward’s tweet came across my feed yesterday so I thought I’d take a crack at his zloader document to see what’s up. At first blush, it seemed fairly normal. There were not any hidden sheets or password protected macros, and if you look at Sheet2 and zoom out, you’d see the scattered Excel 4.0 commands.

Finding The Starting Point

The difficult bit is figuring out where the macro execution starts. You could try searching for ‘formula’, right-click on the first cell you find, and attempt to step through the macro commands.

However, you would end up with an error since that formula depends upon information having already been written to a different cell. And that can only happen if you first start in the right location.

This means that we really need to find that starting point. Thankfully, @c0ntrol_z wrote about how the label for auto_open was messed up. This ended up hiding the label of entry point from Excel’s name manager, yet still allowing it to auto execute. (For a more detailed explanation, see this thread between @control_z and @BouncyHat). Using the same command as @c0ntrol_z did, oledump.py and a few options gives us similar output.

> oledump.py -p plugin_biff –pluginoptions “-o LABEL -a” [file]

Using a hex editor, we can overwrite 0x21 with 0x00 and the 0x01 with another character (he chose 0x67). We can now see the entry point at cell V2004.

Evasion Techniques

Now that we know the entry point, we can start stepping through the macro code. This was a very tedious process, but here are the decoded strings, with a brief note of what is being checked.

=FORMULA.FILL("=CLOSE(FALSE)",$AS$58860) => CLOSE active window
=IF(GET.WINDOW(7),GOTO(AS58860),) => Check if window is hidden
=IF(GET.WINDOW(20),,GOTO(AS58860)) => Check if window is maximized
=IF(GET.WINDOW(23)<3,GOTO(AS58860),) => Check size of window
=IF(GET.WORKSPACE(31),GOTO(AS58860),) => Check if macro is in single-step mode
=IF(GET.WORKSPACE(13)<770,GOTO(AS$58860),) => Check workspace width
=IF(GET.WORKSPACE(14)<390,GOTO(AS$58860),) => Check workspace height
=IF(GET.WORKSPACE(19),,GOTO(AS58860)) => Check if mouse is present
=IF(GET.WORKSPACE(42),,GOTO(AS58860)) => Check if sounds can be played
=IF(ISNUMBER(SEARCH("Windows",GET.WORKSPACE(1))),,GOTO(AS58860)) => Get name of environment in which Excel is running followed by version number

If any of those checks fail, notice how execution ends up at cell AS58860 to close the document.

Rest of the code…

But if you make it through the gauntlet of checks, then the rest of the code execution can take place. Many of these commands seem to have been around since late April. This blog post by Micah Lee shows many of the same commands as the document I’m working off of. The document exports information from the registry, examines it to see if macros are disabled. If so, then continues to download malware and then execute it. Here are a variety of the decoded commands.

	=FOPEN("C:\Users\Public\I6yqG.reg")  => exported registry information
	""VBAWarnings""=dword:00000002  => "Disable all macros with notification"

Looking over the past month or so, we can see the same core commands that zloader was using at the beginning of April. The attackers have just added more layers on top to make analysis more difficult.

And the dance goes ever on…

Thanks for reading!