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!

One thought on “Trickbot Analysis – Part 3: Javascript File

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s