Suas aplicações são seguras?

Conheça a Conviso!

Entendendo e explorando o CVE-2012-4576 para o Kernel do FreeBSD

Introdução

Dando continuidade a exploração de vulnerabilidades em kernel-land [1], neste artigo serão analisados os detalhes da vulnerabilidade registrada no CVE-2012-4576 e como podemos utilizá-la para obter execução de código no kernel do FreeBSD.

Esta vulnerabilidade foi reportada por Mateusz Guzik e o advisory [2] oficial do time de segurança do FreeBSD foi publicado no dia 22 de Novembro de 2012.

A falha ocorre devido a falta de validação em uma chamada de sistema. Como resultado, regiões de memória podem ser sobrescritas. A vulnerabilidade existe no módulo que adiciona a compatibilidade de execução de arquivos binários nativos do Linux no FreeBSD, então apenas sistemas que usam este recurso podem estar vulneráveis.

Todos os experimentos neste artigo foram realizados no sistema operacional FreeBSD 7.0, 8.2 e 9.0 arquitetura 32 bits (i386).

A vulnerabilidade

De acordo com o patch [3] disponibilizado pelo time de segurança oficial do FreeBSD, percebe-se que o código vulnerável se encontra no arquivo “sys/compat/linux/linux_ioctl.c” na função “linux_ifconf()”.

 

Arquivo: /usr/src/sys/compat/linux/linux_ioctl.c

2134 /*
2135 * Implement the SIOCGIFCONF ioctl
2136 */
2137
2138 static int
2139 linux_ifconf(struct thread *td, struct ifconf *uifc)
2140 {
2141 #ifdef COMPAT_LINUX32
2142 struct l_ifconf ifc;
2143 #else
2144 struct ifconf ifc;
2145 #endif

2149 struct sbuf *sb;

2152 error = copyin(uifc, &ifc, sizeof(ifc));
2153 if (error != 0)
2154 return (error);

2236 ifc.ifc_len = valid_len;
2237 sbuf_finish(sb);
2238 memcpy(PTRIN(ifc.ifc_buf), sbuf_data(sb), ifc.ifc_len);
2239 error = copyout(&ifc, uifc, sizeof(ifc));
2240 sbuf_delete(sb);
2241 CURVNET_RESTORE();
2242
2243 return (error);
2244 }

Na linha 2139 vemos que a função recebe dois argumentos, sendo o primeiro o endereço para uma estrutura do tipo “thread” e o segundo o endereço para uma estrutura “ifconf”. Por acreditar ser desnecessário, o trecho de código que faz a chamada a função “linux_ifconf()” foi omitido, mas a única informação relevante para o correto entendimento desse artigo é que o segundo argumento passado é o endereço de memória que foi utilizado como argumento na chamada ioctl, portanto, o conteúdo da variável “uifc” é controlado pelo usuário. Exemplo da utilização da ioctl SIOCGIFCONF pode ser vista em [4].

 

Como podemos ver na linha 2152, o conteúdo da variável “uifc” é copiado para a variável “ifc” utilizando a função “copyin()” [5], a partir deste ponto a estrutura “ifc” também será controlada pelo usuário. Para quem não conhece, as funções “copyin()” e “copyout()” [6] são utilizadas para transferir dados do espaço de endereçamento do usuário para o kernel e vice-versa. Após isso, na linha 2238, o valor controlado pelo usuário é utilizado como primeiro argumento na chamada a função “memcpy()” [7]. Um detalhe relevante é que ao realizar a chamada ioctl podemos informar a quantidade de bytes que o endereço que passamos comporta, desta forma a função “linux_ifconf()” poderá calcular se irá ocorrer overflow ou não ao copiar os dados, durante esse cálculo o valor “ifc.ifc_len” é alterado e não é totalmente controlado pelo usuário.

O valor retornado pela chamada a função “sbuf_data(sb)” na linha 2238 são as configurações das interfaces disponíveis na máquina.

Exploração

Como mencionado anteriormente, o módulo que adiciona compatibilidade binária Linux ao FreeBSD, falha em validar um endereço passado como argumento na chamada a ioctl SIOCGIFCONF, permitindo que seja passado qualquer endereço de memória que terá seu conteúdo sobrescrito pelas configurações das interfaces da máquina. Para quem já tem alguma experiência com exploração de vulnerabilidades, inclusive no kernel, sabe que este tipo de falha em determinadas situações pode ser facilmente exploradas, em [8] você encontrará uma vulnerabilidade similar no kernel do linux encontrada pelo pesquisador Dan Rosenberg.

Existem estruturas importantes para o correto funcionamento do kernel que podem ser utilizadas como alvo e ter seus valores sobrescritos, o que permite redirecionar o fluxo de execução do kernel para um desejável por um atacante. Para realizar a exploração dessa vulnerabilidade foi selecionada a entrada número 3 da Interrupt Descriptor Table (IDT) [9] para ser sobrescrita pelos 16 bits mais significativos do nome da interface retornada. Os valores da entrada da IDT que serão sobrescritos são apenas os bits mais significativos do endereço de uma função. Como podem haver vários nomes para interfaces (“em”, “eth”, “lo”, “usbus”, etc), o exploit que foi escrito para essa vulnerabilidade obtêm esse valor em tempo de execução e ajusta o exploit de acordo com o nome da interface do ambiente. Este comportamento que permitiu o exploit funcionar tanto na versão 8 quanto na versão 9 do FreeBSD onde no mais recente a chamada a ioctl retorna “usbus0” e na versão 8 retorna “eth0”.

O endereço da função que irá gerenciar determinada interrupção fica dividido pela metade na IDT, os bits menos significativos armazenados no começo da entrada e os mais significativos no final da entrada, sendo que cada entrada tem o tamanho de 8 bytes. Assim, como falado anteriormente, somente os bits mais significativos serão sobrescritos, desta forma o novo endereço da função terá os bits mais significativos (os dois primeiros bytes) retornados pelo nome da interface e os bits menos significativos continuarão com os valores da função original. Antes de sobrescrever, será necessário mapear o novo endereço gerado e inserir o código que deseja ser executado quando uma nova interrupção da entrada sobrescrita for gerada, no caso do exploit escrito, essa interrupção é gerada usando a instrução chamada int3.

Partindo da premissa que os nomes das interfaces sejam caracteres ascii, não precisamos checar se o endereço gerado após a sobrescrita está dentro dos limites do espaço de endereçamento do usuário. Considerando que o nome da interface seja composto por letra e números, o maior valor em ascii é o caracter ‘z’ que tem o valor ‘7f’ em hexadecimal. Então considerando que a interface retornada seja ‘zz0’ e o endereço original da função na IDT seja 0xc0d33d08, após a sobrescrita o endereço será ‘0x7f7f3d08’, limite dentro do espaço de endereçamento do usuário.

Neste ponto já é possível obter execução de código, o próximo passo agora é reparar o estrago que foi feito na IDT com as outras informações da interface. Como as informações de cada interface são armazenadas em uma estrutura chamada “ifreq” e seu tamanho é 32 bytes, esse é o tamanho mínimo que conseguimos sobrescrever da IDT. Quanto menos valores conseguirmos sobrescrever, menor o estrago a ser reparado. No exploit liberado anteriormente não utilizava esse recurso e sobrescrevia muito mais que 32 bytes da IDT no caso do FreeBSD 8. Depois de consertar a IDT única coisa que resta é elevar os privilégios e retornar para userland, no exploit utilizamos a técnica “iret” [10] para esse propósito.

Através do método explicado acima foi possível escrever um exploit que funcionasse tanto na versão 7, 8 e 9 do FreeBSD sem precisar usar endereços fixos no código para cada versão. Não foi realizado nenhum teste em outras versões, mas é possível que o exploit também funcione normalmente desde que seja versão i386 do sistema operacional.

Outro fator importante é que, teoricamente, sobrescrever a IDT não é uma boa escolha devido ao motivo que o processo pode ser interrompido pelo scheduler logo após a sobrescrita e o novo processo acionar a interrupção da entrada sobrescrita ao invés do processo do exploit. Como o endereço que foi sobrescrito não é mapeado no novo processo, possivelmente um kernel panic irá ocorrer. Apesar disto, o exploit disponibilizado aqui foi executado e em nenhum momento aconteceu algo inesperado.

Conclusão

A correção feita pela equipe de segurança foi substituir a função “memcpy()” pela função “copyout()”. Esta função automaticamente valida se os endereços passados como argumento pertencem ao seus devidos espaço de memória, evitando que seja feito a cópia de dados kernel kernel e/ou user-land user-land.

O exploit está disponível em [11], para utilizá-lo é necessário que seja compilado em um ambiente Linux e com a biblioteca structs.h [12] no mesmo diretório onde encontra-se o arquivo CVE-2012-4576-linux.c, após isso o binário gerado pode ser executado normalmente em uma versão vulnerável do FreeBSD. Não é necessário especificar nenhum parâmetro adicional ao compilador mas se o ambiente Linux estiver faltando alguma biblioteca necessária, recomenda-se utilizar a flag “-static” para o gcc. O ambiente Linux utilizado foi Ubuntu versão 12.04 instalado em uma máquina virtual VMWARE.

O vídeo abaixo demonstra a exploração da falha nos sistemas FreeBSD 7, 8.2 e 9.

Referências

 

[1] – http://blog.conviso.com.br/uma-analise-do-cve-2012-0217
[2] – http://www.freebsd.org/security/advisories/FreeBSD-SA-12:08.linux.asc
[3] – http://security.FreeBSD.org/patches/SA-12:08/linux.patch
[4] – http://www.techpulp.com/blog/2008/10/get-list-of-interfaces-using-siocgifconf-ioctl/
[5] – http://www.unix.com/man-page/FreeBSD/9/copyin/
[6] – http://www.unix.com/man-page/FreeBSD/9/copyout/
[7] – http://linux.die.net/man/3/memcpy
[8] – http://www.vsecurity.com/download/tools/linux-rds-exploit.c
[9] – https://en.wikipedia.org/wiki/Interrupt_descriptor_table
[10] – https://www.blackhat.com/presentations/bh-usa-03/bh-us-03-cesare.pdf
[11] – https://github.com/andersonc0d3/exploits/blob/master/CVE-2012-4576-linux/CVE-2012-4576-linux.c
[12] – https://github.com/andersonc0d3/exploits/blob/master/exploits/CVE-2012-4576-linux/structs.h

Originalmente postado no Blog da Conviso Application Security – Siga-nos no Twitter @conviso Google+

Tags

Um comentário

Deixe um comentário

topo
%d blogueiros gostam disto: