Trickbot: ActiveDocument.Words is the word!

This Trickbot document hid a .dll in an interesting place. If you’d like to play along, you can find the document and dropped .dll here:

Document: https://app.any.run/tasks/96c149ce-b01a-4543-a8d4-2b98bb18b9c7
Document Password: INV15
SHA256: 052C9196DFE764F1FBD3850D706D10601235DC266D1151C93D34454A12206C28

Dropped File: C:\programdata\objStreamUTF8NoBOM.Vbe
Dropped File: C:\UTF8NoBOM\APSLVDFB.dll
Dropped .dll: https://app.any.run/tasks/5bc86667-aab3-4513-a433-3697d6a9d3eb

After supplying the provided password to open the document, I suggest that you remove it, save the document, and then use tools like oledump.py to extract the macro. Notice how it keeps making references to ActiveDocument.Range(Start and End) and ActiveDocument.Words.

The macro is pulling data from the current document, piecing them together, and then writing it out to this file and location:

C:\programdata\objStreamUTF8NoBOM.Vbe

Once that is done, the macro creates a Wscript.exe object and executes that .vbe file.

But where did it get all of that data? Where was it hiding in the document? Well, it wasn’t really ‘hiding’ in the typical places we see obfuscated commands (I’m looking at you, Emotet). In this case, it was hiding behind the the picture we see in the document itself. We can see the text below by deleting that picture and zooming in 400%.

You can fit an entire .dll on one page of a word document if you use 1 point font. Who knew?

The macro in the document takes the above characters, rearranges them, and writes them to objStreamUTF8NoBOM.Vbe. Here’s that .vbe file.

Near the bottom of objStreamUTF8NoBOM.Vbe, we can see the base64 decoding function. It gets copied to the following location:

C:\UTF8NoBOM\APSLVDFB.dll

The last two lines create a wscript.shell object and use regsvr32 to run the .dll.

And there you go! Thanks for reading!

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!

Trickbot Analysis – Part 3: Javascript File

Part 1: An analysis of the extracted macros.

Part 2: An analysis of the locked document.

Part 3: An analysis of the dropped javascript file (plus bonus deobfuscation).


It is now time to analyze the dropped javascript file. It is heavily obfuscated. It also completely sucks. However, much thanks goes out to @ps66uk for patiently showing me a not-so-quick-but-mostly-effective method for getting to what’s important.

As before, we will be working off of the document found here.


The dropped file looks like this. It also needs a trip through beautifier.io. That makes it look slightly more tolerable.

There is a function right at the top called evjPnjn. It ends up being used 6721 times in this script. This makes sense because it is also the function that helps to deobfuscate the script. Here it is in the original script (slightly cleaned up):

function evjPnjn(kfwfthat, wjskgiv){
    try
        {/*indeed22*/;/*know86*/;/*there34*/;/*being24*/;/*that75*/;/*thus94*/;/*never7*/;/*this65*/;/*forces20*/;eihHere_8(kfwfthat, wjskgiv);}
    catch(e) {
        if (wjskgiv!='Char') {
            return true;
        }
        else {
            return String[['from']+wjskgiv+['Code']](kfwfthat);
        }
        return false;
    }
};

The next function we see is on line 81. We can see that it calls evjPnjn and feeds it two values. Most everything else is useless.

var llArzroadI41 = String[(function() {
    var ksjskill6 = [];
    ksjskill6[0] = 1;
    try {ksjskill6[1] = kekcondu_8();}
    catch (twwwothe) {
        if ((twwwothe + '').indexOf(']') > -1 && evjPnjn(101, 101)) {
            ksjskill6[1] = 101;
            return evjPnjn(ksjskill6[1] + ksjskill6[0], 'Char');}}
    return evjPnjn(ksjskill6[1] + ksjskill6[0], 'Char');}

Now ksjskill6[1] + ksjskill6[0] = 102. This means that the call function to evjPnjn is going to be set up like this:  evjPnjn(102, ‘Char’)

This helps us to decode what is going on with evjPnjn. Replacing kfwfthat and wjskigv with more meaningful words can help us to understand what it does.

function evjPnjn(digits, value){
    try
        {/*indeed22*/;/*know86*/;/*there34*/;/*being24*/;/*that75*/;/*thus94*/;/*never7*/;/*this65*/;/*forces20*/;eihHere_8(kfwfthat, wjskgiv);}
    catch(e) {
        if (value!='Char') {
            return true;
        }
        else {
            return String[['from']+value+['Code']](digits);
        }
        return false;
    }
};

The important piece happens on the line starting with ‘else’. Look what happens when we replace value with ‘Char’ and clean it up a little.

else {return String[['from'] + 'Char' + ['Code']](102);}
else {return String['fromCharCode'](102);}

fromCharCode takes a hexadecimal number and changes it into an ASCII letter. The ASCII letter for hex 102 = ‘f’. evjPnjn will be called up 6720 more times and will eventually unravel the script letter by letter and execute this javascript.

There’s got to be a faster way. Well, thanks to @ps66uk, there is. It will require a judicious use of CyberChef.io and a spreadsheet. First to CyberChef!

Take the beautified javascript and paste that sucker into CyberChef. It will be huge. Load up a recipe called “Regular Expression” with the following values:

Built in regexes: User defined
Regex: \w{1,10}\[[0]\] = (\d{1,3});
Regex 2: \w{1,10}\[[1]\] = (\d{1,3});
Output format: List capture groups

The first regex finds a pattern that is 1 to 10 word characters long, followed by a [0], space, equals sign, another space, 1 to 3 digit characters, and finally a semicolon. The second regex does the same thing, but instead looks for a pattern that is 1 to 10 word characters long followed by a [1].

Looking at the example we saw above, regex pattern 1 pulls out ksjskill6[0] = 1; and regex pattern 2 pulls out ksjskill6[1] = 101;. The output format of ‘List capture groups’ dumps out their values respectively.

var llArzroadI41 = String[(function() {
    var ksjskill6 = [];
    ksjskill6[0] = 1;
    try {
        ksjskill6[1] = kekcondu_8();
    } catch (twwwothe) {
        if ((twwwothe + '').indexOf(']') > -1 && evjPnjn(101, 101)) {
            ksjskill6[1] = 101;
            return evjPnjn(ksjskill6[1] + ksjskill6[0], 'Char');
        }
    }
    return evjPnjn(ksjskill6[1] + ksjskill6[0], 'Char');

We saw that the evjPnjn function takes those two values, adds them together, and then converts them from hexadecimal to ASCII. A spreadsheet can do the same thing. Copy the output from the two CyberChef results into a spreadsheet like below. Then, use the formula in column C to add the two values together and then convert to ASCII. The output should start making sense.

javascript05.PNG

But we’re not done yet. Copy column C back into CyberChef and use the recipe called “Remove Whitespace”. You can see the rest of the words pop out. I unchecked ‘Spaces’ because those characters were used by the script.

javascript06.PNG

Here’s the final output. We can also see a URL near the beginning. This is associated with Trickbot.

javascript07.PNG

You can kind of get an idea of what is going on here, but what if someone were to have crawled through the javascript and deobfuscated the whole thing by hand? Yeah it would have taken a long time. But you can find a rough draft of it here. Enjoy!

Trickbot Analysis – Part 1: Macro Analysis

This is the first in a three-part series of analysis on this malicious document.

Part 1: An analysis of the extracted macros.

Part 2: An analysis of the locked document.

Part 3: An analysis of the dropped javascript file.


Macro Analysis

I extracted the macros using OfficeMalScanner. It dumped two files, NewMacros and ThisDocument. We can see that ThisDocument contains Sub AutoClose(). This means that the macro will run when you close the document.

trickbotmacro01

The sub named Tokio is located in NewMacros. It pulls from other variables in that same file.

trickbotmacro02

A little bit of searching, copying, and pasting in that file allows us to piece together line 28.

VBA.CallByName VBA.CreateObject(Shell.Application), ShellExecute, VbMethod, WScript, /e:JScript "glob", "open", 1

The variable glob is the name of the file that is going to be executed. And that’s what is unusual about this document. Not only does it drop a new file to disk, but it also is going to run it as javascript. A sub called Oslo is used to drop a .css file with the same name as the original .docm file.

trickbotmacro03.PNG

Line 40 grabs the name of the current document and replaces .docm with .css. Line 48 creates the new document. Notice how it grabs the data from ActiveDocument.Content.Text. This data is stored in the pages of the .docm file itself. If you were to open the document, you would see this:

trickbotmacro04

The data that gets dumped into the .css file is all of the yellow text above. In fact, from this point you could copy and paste all of that yellow text into a new text file and find all of the same javascript.