Format Strings

17:10 Unknown 2 Comentarios






            Este post aborda a vulnerabilidade conhecida por “Format Strings”, que pode ser utilizada na exploração de binários em sistemas Unix.
O objetivo não é ensinar ao leitor como explorar a vulnerabilidade, mas sim alertar que implementações desenvolvidas de maneira incorreta podem se tornar um crítico vetor de exploração, comprometendo drasticamente a segurança da aplicação.
O conteúdo aqui apresentado não é “entry level” e, embora a postagem tenha sido elaborada buscando a utilização de uma linguagem facilitada, o assunto depende da assimilação prévia de conceitos implícitos ao tema.  
           

O que é String?

É a identificação de uma sequência de caracteres, geralmente usada para representações de texto. Em linguagem C, não existe um tipo de dado que declare nativamente uma string, na verdade, trata-se de um vetor do tipo “char”, obrigatoriamente terminado em um caractere nulo “\0”, que identifica o final da string.

            char nome[80];

A declaração da string acima cria um vetor de caracteres com 81 posições, considerando a necessidade da indicação do final da string ser referenciada por um caractere nulo. Dessa maneira, o armazenamento do nome deve ocupar as 80 posições e ser finalizado por “\0” na posição 81.


Entendendo a Vulnerabilidade

            Como em grande parte das vulnerabilidades, a exploração de Format Strings é diretamente relacionada a um erro de implementação, onde o programador não trata corretamente o “tipo” de saída da string. Por meio desta técnica é possível ganhar controle do fluxo de execução de um programa vulnerável.
Essa vulnerabilidade pode ser encontrada em funções da família printf (printf, vsprintf, fprintf, vsnpr, sprintf, vfprintf, snprintf,  vprintf) e explorada por meio do abuso dos formatadores (%d, %s, %u, %n):
Para exemplificar de maneira prática vamos ao seguinte trecho de código:

//dados de entrada
  AAAA.%x.%x.%x.%x

// recebendo valor para variável texto
  strcpy(texto, argv[1]);

// método de exibição utilizado de maneira correta
  printf("Saída: ");
  printf("%s", texto);
  Saída: AAAA.%x.%x.%x.%x 

// método de exibição utilizado de maneira incorreta
  printf("Saída: ");
  printf(texto);
  Saída: AAAA.bff6e518.0.4e91f3.41414141

Perceba que, quando a implementação é feita de maneira incorreta (vulnerável), são retornados endereços de memória em formato hexadecimal. Por meio da técnica de exploração de Format Strings, essa saída, devidamente manipulada, pode ser utilizada por um atacante para ganhar controle do fluxo de execução do programa.
Os argumentos passados para a função são inseridos na pilha em ordem reversa, no modo e operação LIFO (last in first out), logo, o trecho de código abaixo justifica essa afirmação.

printf("A eh %d e esta em %08x. B eh %x.\n", A, &A, B);
A eh 5 e esta em 71dba9d8. B eh 7.

Endereço da string formatada
Valor de A
Endereço de A
Valor de B
Fundo da Pilha
* Representação da pilha de memória

Ainda analisando a execução, se o último parâmetro for removido, a saída apresentada deve ser algo do tipo:

printf("A eh %d e esta em %08x. B eh %x.\n", A, &A);
A eh 5 e esta em bfe541d8. B e 4771d0.

            Nota-se que o valor 0x4771d0, foi despejado pois não havia um valor armazenado na pilha, logo, a resposta apresentou os dados de onde o último argumento seria armazenado.


Leitura e Escrita com Format Strings

            Por meio da manipulação dos parâmetros de formatação, essa vulnerabilidade permite que seja possível realizar procedimentos de leitura e gravação de endereços de memória arbitrários.
Utilizando o formatador %s é possível efetuar a leitura de endereços, bem como, utilizando a mesma técnica por meio do formatador %n é possível a gravação.
Por exemplo:

Leitura: Para ler a variável de ambiente “LOGNAME” (lembrando que é necessário adicionar 4 bytes devido a diferença de 2 bytes entre os nomes “poc_fms1” e “getenvaddr”):

root@kali:~ # env|grep LOGNAME
LOGNAME=root

root@kali:~# ./getenvaddr LOGNAME ./poc_fms1
LOGNAME will be at 0xbffffeac

root@kali:~# ./poc_fms1 $(printf "\xAC\xfE\xff\xbf")%08x.%08x.%08x.%s
O metodo correto:
%08x.%08x.%08x.%s
O metodo incorreto:
bfffeea0.b7fd0110.080491dd.root
[*] valor_teste -> 0x0804c02c = -72 0xffffffb8

Escrita: para exemplificar o processo de escrita, aos mesmos moldes das operações realizadas para a leitura, porém formatando a saída com %n, foi possível sobrescrever a variável valor_teste.

root@kali:~ # ./poc_fms1 $(printf "\x2c\xc0\x04\x08")%08x.%08x.%08x.%n
O metodo correto:
,%08x.%08x.%08x.%n
O metodo incorreto:
,bfffeea0.b7fd0110.080491dd.
[*] valor_teste -> 0x0804c02c = 31 0x0000001f

Obs.: é possível realizar a manipulação também alterando o tamanho do campo a ser apresentado

root@kali:~ # ./poc_fms1 $(printf "\x2c\xc0\x04\x08")%x.%x.%120x.%n
O metodo correto:
,%x.%x.%120x.%n
O metodo incorreto:
,bfffeeb0.b7fd0110.                                                                                                                 80491dd.
[*] valor_teste -> 0x0804c02c = 143 0x0000008f
*perceba a criação de linhas em branco

O formatador %n grava os dados e não apresenta nenhuma saída. Ao encontrar o formatador %n, a função grava o número de bytes que foram escritos até então no endereço do argumento da função correspondente e o operador do endereço unitário é usado para gravar os dados das variáveis.


Operações de Acesso Direto

Para “reduzir” esforços durante o desenvolvimento dos exploits que utilizam “Format String”, é possível que os parâmetros sejam acessados diretamente utilizando o sinal de “$” %n$d, conforme exemplificado abaixo:

printf("Quinto elemento: %5$d, Quarto elemento: %4$05d\n", 1, 2, 3, 4, 5, 6, 7, 8);
Quinto elemento: 5, Quarto elemento: 00004


Escritas Curtas

Um dado do tipo “short”, é uma palavra (WORD) de dois bytes e, recorrendo ao formatador %hn, esta técnica pode ser utilizada para também facilitar a exploração. A execução consiste na utilização dos formatadores para escrever um valor de quatro bytes com o uso de apenas dois parâmetros (%hn).


Conclusão

            Para que seja possível efetivar a exploração por meio da técnica de Format Strings, permitindo a possibilidade e interação do atacante com o alvo por meio de um interpretador de comandos, o uso de alguns outros recursos se faz necessário, como por exemplo a utilização de construtores e destrutores (.ctors, .dtors), ou mesmo sobrescrevendo a “Global Offset Table”. Detalhes foram suprimidos intencionalmente, considerando que este post não tem a finalidade de instruir o processo de exploração.


Referências
            http://phrack.org/issues/59/7.html
Book: Hacking, The Art Of Exploitation
Book: The Shellcoders Handbook



2 comentários: