SANS ISC InfoSec Forums

Malicious RTF files can not contain VBA code, in stead, malware authors have to use exploits to achieve code execution. This one here has become a classic: an overflow in the font record of an equation editor expression (CVE-2017-11882).

A cleartext command is easy to spot. But there are also CVE-2017-11882 exploits with shellcode and encoded commands, and there it's harder to find the shellcode and its entry point. My tool format-bytes.py can help with the analysis (format-bytes.py takes binary data as input and parses it according to a format string used by the Python struct module).

The structure of an OLE stream with Equation Editor data has been explained in detail in several articles, like this one, explaining that such an OLE stream starts with an header for Equation Editor data in an OLE stream (EQNOLEFILEHDR), followed by an MTEF header and several MTEF records.

format-bytes.py can parse these headers and records, when it is provided with the appropriate Python struct format string. For a sample like this, the format string to use is: <HIHIIIIIBBBBBBBBBB40sIIBB140s

Entry 16 is the Font record, and entry 19 is the fontname. The fontname is 40 characters long, and can be overflowed on the stack, leading to EIP control. In simple exploits, where the payload command is at most 40 characters long, the fontname will contain the command and the return address will be overwritten to call WinExec.

Selecting entry 19 extracts the command. When the command is more than 40 characters long, malware authors use an exploit that is a bit more complex. The fontname then contains shellcode (without 0x00 bytes) that calls WinExec with a command that is appended to the fontame record + overflow. The sample presented here contains such shellcode, that can also be extracted by selecting entry 19:

Here is the disassembled shellcode:

The command can then be found in entry 24:

The struct format string I'm using here with format-bytes.py is specially crafted for this exploit: I saw that the command was 140 characters long (including terminating 0x00) bytes, and I encoded that in the format string: 140s.

A more generic format string looks like this: "<HIHIIIIIBBBBBBBBBB40sII*:XXXXXXXXXXXXXXXXXXsXX"

I terminated this struct format string with * (remainder), right after the font record format (BBB40sII). By using * at the end of a struct format string, I direct format-bytes.py to dump up to 256 bytes after parsing the input (e.g. the remainder).

This last format string can be used to dissect various CVE-2017-11882 exploits.