dridex maldoc: The unholy union of VBA and XLM

Kirk Sayre (@bigmacjpg) posted some info on a dridex malicious document the other day. It was a real piece of work. To put it succinctly, the VBA script grabs data from cells in the spreadsheet, decodes them, and then runs them as an XLM command. Even better, all of the cells have white font (tricky, tricky).

Here’s the info Kirk dumped from the document:


And here’s the document itself:


First things first

Before we can start stepping through the macro, we see that it is unviewable.

Enter: EvilClippy. Much could be written here about how this tool works, but just read this article by Carrie Roberts (@OrOneEqualsOne) instead. It is fantastic.

We can make the project viewable by just using –uu. It will automatically make a new copy of the document, but the project has become viewable.


lncol() is the main Sub. Much of the other logic/decoding functions branch off this one.

Lines 90/91: pic, oks, and img become the names of the new folders created and the name of the downloaded file. More on them later.
Line 92: This for loop will cycle through rows A208 to A212.

Line 94: Send the information from cell A208 along with a random number from 1 through 4 to function Ami.

Creating the XLM String

Line 45: Take content from A208, send to function verud, and put results in array df.
Line 53: Convert cell content to decimal numbers.
Lines 46-47: Take each number in array, add f (remember, that random number from 1-4), and send to function zen.
Line 39: Convert decimal number back to ASCII.
Line 47: Add ASCII character to variable Ami. Convert the rest of the array in the same manner. Then head back to the main function…

Line 95: This line is important. It checks to see if the string you just decoded ends in a right parentheses “)”. If it does, continue on to line 96. If it doesn’t, run line 94 again until you get the desired output. This is why the random number from 1-4 is important. It will keep looping and decoding until the right output is created.

Here are all five of those decoded cells. But cells A209-A212 are not complete. Certain characters in those XLM strings will need to be replaced before they can be executed.

Replacing Characters in XLM String

Line 97: This line calls up function kio which will execute the XLM string (line 105). BUT before it does that, the string itself needs to be altered. That is the purpose of function vs.
Line 108: This function takes the semicolon, single quotation mark, dollar sign, and question mark and replaces them if they’re found in the string. Those strings become the names of the directories that are created as well as the URL from which the malware is downloaded.

Speaking of URLs…

These are the remaining strings still buried in the spreadsheet. They can be found in cells A593:D626.

Function mores() decodes these strings.

Line 23: Get a random row between 593 and 626.
Line 24-29: Start with column 1 and grab that cell information. If it is empty, try cells 2 through 4 until you get a cell with text in it.
Line 31: Send that found data to function Ami. Note that the value for f is the same as the column.

Once everything is laid out, it isn’t too complicated. But get ready for either more like this or adjustments to this layout.

Thanks for reading!

zLoader XLM Update: Macro code and behavior change

We’ve got ourselves a change to the zloader XLM code and also some document behavior. Here’s today’s sample:



Central Loop Mechanism

The decoding part of the central loop mechanism still exists as it did before. It grabs hex characters from elsewhere in the document, decodes them, and writes those strings to new cells. However in this case, the document only runs through two rounds of this decoding.

Round 1

The first round behaves pretty much the same as it did before. It checks to see if it’s in a sandbox, checks the registry, and if VBAWarnings is turned on, the code will go back to the loop and start round 2.

Round 2

This is where the main difference lies. A series of lines get written to a file called QP0L3.vbs and then executed.


The code in the .vbs file is nothing that special. It’s just an array of URLs going through a For Each loop. The file gets downloaded and then saved as an .html to the Temp folder.

Back to Round 2

At this point, the .html file is executed with what looks to be rundll32.exe.

And that’s pretty much it! Again, not a major change, but I thought it was a noteworthy one.

Thanks for reading!

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:


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:


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

And there you go! Thanks for reading!

2020-08-05: Update on zloader XLM code

On August 5, 2020, @abuse_ch warned about more ZLoader activity:

It had been awhile since an XLM document had crossed my desk and I was wondering if anything had changed since the last one I did in mid-July? It hadn’t. I’ll be honest, these ones are a real pain to get through. But if you know what to look for, they’re not that difficult to unravel. I’ve even got some tips on how to become more efficient at investigating this particular document. Let’s get to it.

XLM Code Location

This version of the zloader document is interesting because the main activity takes place all in one column rather than being scattered all over the place. This column contains all of the loops and functions that will create, write, and execute the commands needed to reach out to a URL and begin downloading. The XLM commands begin in R337C185.

While the XLM code starts there, that is not where the entry point is located. It took some time to figure it out, but execution actually starts in R443C185.

This XLM code contains three basic steps.

  1. Set up variables and locations from which to grab hex characters and where to write new strings.
  2. Grab hex characters, convert them to ASCII, and write them to a new location.
  3. Jump to new location and execute XLM commands.

Step 1: Set up variables

For space-saving purposes, I copied the XLM commands to Notepad++ and removed the spaces between lines. Starting at the entry point (R443C185) and continuing down, some of these variables define the location of certain commands necessary for code execution. The highlighted variables below show where the hex characters can be found and also where to write the assembled strings.

Step 2: Convert hex to ASCII and write to new location

Things get kind of hairy here and I don’t know if there is a good way to explain this. Find “Start here” and follow the numbers in the dashed boxes.

Step 3: Jump to new location and execute

This part is pretty self explanatory. Once all of those strings have been written, jump to R17978C243 and continue XLM command execution. You may remember that these are the commands that are used to evade sandboxes.

Understanding the Pattern

Now that we understand how this flows, we can see the pattern. After round one is completed, rounds two and three each provide new locations from which to grab hex characters, convert them, write to a new location, and then execute them.

Rounds two and three produce this output:

Making Analysis More Efficent: =PAUSE()

The XLM function =PAUSE() allows for the possibility of debugging. We can make the macro do most of the work for us if we use this strategically.

Looking at the original XLM commands to set up the variables, placing =PAUSE() right after the call to =ebnSmgBKoRc() will allow all of the hex to be decoded and written to R17977C243. Once it hits =PAUSE(), execution will… pause allowing us to inspect it at our leisure. Make sure that you start the code execution at the entry point (R443C185). You need all of the variables populated so that the decoder function can run properly. Right-click on that cell and select run. =PAUSE() can also be placed right after the other two decoding functions to grab that data.

Thanks for reading!

Emotet (2020-07-21): Still Making Use of Userforms

DISCLAIMER: Today’s sample is not overly complicated at all. Making use of Userforms is nothing new. Also, anyone can toss an Emotet document into Any.run in order to grab the base64 encoded Powershell string being executed.

Yet, malicious documents are hiding commands that must be run on the system in some way. Finding those locations and understanding how they work can help us better understand the techniques, tactics, and procedures (TTPs) of attackers.

Sample: https://app.any.run/tasks/475e4427-efd3-40c6-a19f-8703552d0194
MD5: 6f6987737db0575b978f60be457cd374
SHA256: 12A9D51F23B64A1C6DC2146C8325AD73C6810CCDA73586EEF181C4CDDB309A99

Where We’ve Been

If you are familiar with the typical behavior of an Emotet document at all, you expect WINWORD.exe or WmiPrvSE.exe to spawn powershell.exe and pass it a big string of base64 encoded text. That base64 string decodes to a variety of commands that attempt to download an .exe from one of five URLs.

Yet, where does that string live in the malicious document? Sometimes it is scattered all over the the macro before it gets concatenated. Here’s an emotet sample from February 2019. It is quite easy to see how the powershell command and the base64 string get assembled.

At other times, the macro references and grabs strings from a Userform. In this example from May 2019, we see two Userforms named F82063 and N9_9818.

Can someone ask Microsoft to give Snip & Sketch the ability to draw straight lines?

Notice how the macro references components within that Userform. While we can see only one empty box within the Userform above, there are really a stack of them on top of each other. They each have their own name and contain some sort of text. Again, it is not too difficult to see how they will get rearranged into the familiar “powershell -e JAB…”

Where We Are Today

Emotet documents are still making use of Userforms, but with a minor twist. In this sample, the long string is brought back into the macro, split apart, and then joined to create the “powershell -e JAB…” string.

These are all of the components in the Userform named woicroib. It contains a variety of ComboBoxes, Frames, and even a MultiPage.

This macro line below references the Userform containing the the encoded text. woicroib is the name of the Userform. raopfeukchaup is a MultiPage component within the Userform. raopfeukchaup contains two pages. The ControlTipText is then grabbed from the second page. However, it looks like that box is empty.

But this is not the case. If we put a cursor in there, select all of it (ctrl + A) and then copy (ctrl + C) and paste it in notepad, we get this giant string. How is it going to be used in the macro?

Returning back to the macro, the above string is tossed into variable io (line 66). This becomes the parameter used in function chiexbeachjeuhkiam (lines 69 and 49). The entire string above is split (line 53) on another string of characters and then joined back together in line 59.

We can emulate this behavior quite easily. Within our notepad document, we can do a simple search for the string in line 53 above and when we replace all of them with nothing…

… we get the powershell string!

And as my calculus teacher in college used to say, we’ve reduced this to a previously solved problem.

Thanks for reading!

AgentTesla: .rtf and Equation Editor

While extracting Equation Editor shellcode is nothing new on this blog, it never hurts to practice the skills necessary to do this. To that end, we will be working on this document right here: https://app.any.run/tasks/0a1096aa-339e-4602-a3e0-2496a07efea4

.rtf document?

Using rtfdump.py against the document, we see that item 8 contains objdata. This is a good place to start.

We can select item 8 (-s) and decode it as hexadecimal data (-H) in order to take an initial look at that object. We can see that this object contains a call to Equation Editor (EQNEDT32.exe).

To extract this as a file, we will decode it as hexadecimal (-H), dump it (-d), and then send the output to another file which we will call output.bin.

We can use XORSearch.exe to search that binary file for various signatures of 32-bit shellcode. We see that GetEIP was found in two locations. This indicates that shellcode might start at 0xF2. This information will be useful in the next step.


scDbg.exe is a shellcode emulator. If we load up our .bin file and start with the offset of 0xF2, decoded shellcode may appear.

Based on the output, it looks like we had a good offset address. We can tell because we see some decoded lines… but not too many decoded lines. However, we’ve seen ExpandEnvironmentStringsW before and we know how to deal with that. Notice also where it says “Change found at 706…” and that it dumped to a new file called output.unpack.

The change was found at position 706. This means that there are a bunch of extraneous characters before our useful shellcode. While there are a variety of ways to get rid of them, cut-bytes.py will also do the trick.

We can see a variety of useful strings by opening output-cut.unpack in a hex editor.

One of the reasons we didn’t get this output before was that the shellcode used ExpandEnvironmentStringsW. scDbg.exe doesn’t hook into that function. Instead, it will hook into ExpandEnvironmentStringsA. If we overwrite the W in our file with an A, we ought to be able to get some much cleaner output.

Save your changes and toss it back into scDbg.exe. Note, there is no need to include an offset address or create a dump.

We now have the decoded shellcode!

Thanks for reading!

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!

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.


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


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.


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.


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.


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.


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.


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.