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.

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")