segunda-feira, 24 de dezembro de 2012

Uma análise do CVE-2012-0217


Introdução


Em junho desse ano o time de segurança do sistema operacional FreeBSD publicou um alerta de
segurança sobre uma vulnerabilidade descoberta por Rafal Wojtczuk que afeta todas as versões 64 bits.
Essa  vulnerabilidade  não  só  afeta  o  sistema  operacional  FreeBSD  mas  também  vários  sistemas
operacionais  e sistemas de virtualização[1] disponíveis, com exceção do OpenBSD 5.0 e o Linux que
já tinha corrigido a vulnerabilidade desde 2006[3].

Nesse post será explicado a natureza da vulnerabilidade e como ela pode ser utilizada para obter
execução de códigos no kernel. Apesar de já existir exploits públicos para Windows [4] e para FreeBSD[5] [6], um exploit para FreeBSD também será apresentado nesse artigo.

Assume-se que o leitor saiba  como funciona exploração  de vulnerabilidades  em  kernel, sistemas
operacionais e suas estruturas, principalmente IDT[7] e como realizar debugging do kernel.

Os  experimentos  realizados  nesse  artigo  foram  realizados  utilizando  a  versão  9.0  RELEASE  do
FreeBSD.

A vulnerabilidade


Esse artigo tem como foco o padrão 64 bits desenvolvido pela AMD, chamada de AMD64[8] ou x86-
64 que também é utilizado pela Intel.

A CPU tem vários modos de operação, sendo os dois principais: o modo mais privilegiado que é o
modo em que o kernel é executado e chamado de ring0 e o menos privilegiado, chamado de ring3, o
modo em que os programas comum do dia a dia são executados como browsers e editores de texto.
Quando uma aplicação que está sendo executada em ring3 precisa realizar uma operação privilegiada,
um conjunto de operações precisam ser realizadas para mudar o modo de operação para o modo mais
privilegiado.

O padrão AMD64 adicionou um novo conjunto de instruções para realizar a troca de contexto, chamada de fastsyscall[10] e é basicamente composto por duas instruções: 'SYSCALL' e 'SYSRET'.
A instrução 'SYSCALL' é utilizada para passar do modo menos privilegiado para o mais privilegiado (ring3→ ring0), invocar o kernel, e a instrução 'SYSRET' o processo inverso, do mais privilegiado para o menos privilegiado, retornando para modo usuário.

A vulnerabilidade existe devido a um erro na implementação da instrução 'SYSRET' nos processadores
AMD64 da Intel que é utilizada na troca de contexto (ring0 → ring3) em sistemas 64 bits, por isso essa
vulnerabilidade só afeta sistemas 64 bits que estejam utilizando processadores da Intel.

A AMD também adicionou um novo tipo de endereçamento chamado de endereço canônico[11]. Esse
tipo de endereçamento exige que os bit 47 ao 63 sejam iguais para serem utilizados como endereço de
memória, não satisfazendo essa igualdade, o endereço é considerado inválido. É utilizado a faixa de
endereço  0x0000000000000000 até 0x00007fffffffffff e 0xffff800000000000  até 0xffffffffffffffff, qualquer endereço de memória fora desses dois limites é um endereço não canônico.

Quando algum tipo de acesso a um endereço de memória não canônico for realizado, uma exceção é
gerada  e  tratada  pelo  sistema  operacional.  A  diferença  no  processador  da  Intel  que  gerou  a
vulnerabilidade é que, em determinada situação, o sistema operacional irá tratar essa exceção no modo
mais privilegiado da CPU diferente do processador da AMD que irá tratar no modo menos privilegiado.

A criticidade dessa sútil diferença entres os processadores é que no momento que o gerenciador de
exceção for executado, o sistema operacional já restaurou os valores dos registradores para valores
controláveis pelo usuário, entre esses registradores o mais interessante do ponto de vista de um atacante
é a pilha, registrador %rsp na arquitetura AMD64.

Portanto, quando a instrução 'SYSRET' for encontrada e executada a CPU deverá realizar basicamente
as seguintes ações:

1 → Mudar o modo de operação da CPU do mais privilegiado para o menos privilegiado;
2 →Redirecionar o fluxo de execução de instrução para o endereço armazenado no registrador
%rcx;

O que ocorre com o processador da Intel é o processo inverso, o passo 2 é executado antes do passo 1.

A forma utilizada para acionar essa vulnerabilidade foi alterar o valor do registrador  %rcx para um
endereço não canônico e após isso executar a instrução 'SYSRET'. É permitido armazenar valores não canônico no registrador %rcx por ele ser um registrador de propósito geral.

Apesar de não ser o único, o método utilizado por alguns expoits públicos para FreeBSD consiste em
mapear a última página de memória canônica disponível ao usuário, e colocar a instrução 'SYSCALL'
nos últimos bytes alocados. Assim, após a execução da instrução e a passagem do controle para o
kernel, ao executar a instrução 'SYSRET', o registrador %rcx terá o valor não canônico. Como pode ser
visto na imagem abaixo. Clique na imagem para uma melhor visualização.

Figura 1: Acionando a vulnerabilidade

Em discussão com Joilson Rabelo[12] e depois confirmado através do blog-post feito pela VUPEN[13], no Windows 7 esse método não funciona, pois o  kernel do Windows não permite mapear a última página de memória disponível para o usuário.

Houve  modificações  no  manual  da  Intel  endereçando  o  problema.  Abaixo  trecho  retirado  do
manual[14] que especifica que o sistema operacional precisa realizar validações para evitar que esse
problema  ocorra,  modificação  realizada  em  Agosto  de  2012,  dois  meses  após  a  publicação  da
vulnerabilidade.


The SYSRET instruction does not modify the stack pointer (ESP or RSP). For that reason, it is necessary for software to switch to the user stack. The OS may load the user stack pointer (if it was saved after SYSCALL) before executing SYSRET; alternatively, user code may load the stack pointer (if it was saved before SYSCALL) after receiving control from SYSRET.
If the OS loads the stack pointer before executing SYSRET, it must ensure that the handler of any interrupt or exception delivered between restoring the stack pointer and successful execution of SYSRET is not invoked with the user stack. It can do so using approaches such as the following:
• External interrupts. The OS can prevent an external interrupt from being delivered by clearing EFLAGS.IF before loading the user stack pointer.
• Nonmaskable interrupts (NMIs). The OS can ensure that the NMI handler is invoked with the correct stack by using the interrupt stack table (IST) mechanism for gate 2 (NMI) in the IDT (see Section 6.14.5,“Interrupt Stack Table,” in Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A).
• General-protection exceptions (#GP). The SYSRET instruction generates #GP(0) if the value of RCX is not canonical.
The OS can address this possibility using one or more of the following approaches:
— Confirming that the value of RCX is canonical before executing SYSRET.
— Using paging to ensure that the SYSCALL instruction will never save a non-canonical value into RCX.
— Using the IST mechanism for gate 13 (#GP) in the IDT.


Executando códigos arbitrários no kernel


Como, apartir das informações que temos até agora, é possível executar códigos arbritários? Ao acionar
a vulnerabilidade o gerenciador de exceção nos processadores Intel 64 bits será executado em ring0.
Precisamos fazer com que a exceção seja executada, fazendo com que o processador tente acessar
alguma região de memória numa área não canônica como descrito anteriormente. Durante a execução
do gerenciador de exceção valores  são escritos  no endereço que está no registrador  %rsp que é
controlado pelo usuário (e daí que a vulnerabilidade pode ser explorada). Podemos alterar o registrador
%rsp para apontar para alguma estrutura do kernel que, durante a execução do gerenciador de exceção
será sobrescrita com valores controlados pelo usuário.  Como pode ser visto na imagem a baixo. Clique na imagem para uma melhor visualização.

Figura 2: Sobrescrevendo entrada na IDT

Então o que foi feito para obter execução de códigos é colocar o endereço de determinada entrada da
IDT no registrador %rsp e alterar o valor do registrador %rdx para um valor que, ao sobrescrever os
valores originais, modificará os campos gd_hioffset e gd_looffset da entrada 14 da IDT que é a entrada
referente ao Page Fault Exception(#PF) para o endereço de uma função que será responsável por
escalar os privilégios do usuário atual, reparar todo o estrago feito da IDT e retornar para modo usuário
com sucesso. Como os campos responsáveis por armazenar o endereço da função original que será
executada foi sobrescrito pelo endereço da função  kernel_code (0x400b50), nosso código pode ser
executado a qualquer momento que uma exceção do tipo #PF for acionada. Devido a forma que foi
utilizada para acionar a vulnerabilidade, um exceção #PF é gerada automaticamente durante a execução
do gerenciador de exceção original devido a alguns registradores estar com seus valores incoerentes.

Como  mencionado  acima,  após  ganharmos  execução  de  código  precisamos  restaurar  os  valores
originais da IDT que foram corrompidos para evitar um  crash e reiniciar o sistema e depois disso
elevar o privilégio do usuário atual para root. Para podermos fazer isso precisamos saber o endereço da
estrutura que armazena as informações do usuário, há várias formas de conseguir isso, a que foi utilizada no  exploit é mencionada nesse artigo em [15]. A vantagem de utilizar esse método é que
podemos obter o endereço da estrutura antes mesmo de acionar a vulnerabilidade, diferente do método
utilizado nos exploits públicos que obtem esse endereço após ganhar execução de código no kernel. O
último passo que o exploit deve fazer é retornar para modo usuário após de ter feito tudo que queríamos
no kernel, isso é feito colocando no registrador %rcx o endereço de uma função do exploit que checará
se realmente os privilégios  foram elevados e finalizar o processo, no  exploit é a função  done  a responsável por isso.

Conclusão


Particularmente, essa foi umas das vulnerabilidades mais interessantes do ano, inclusive foi nomeada
para o prêmio Pwnie Awards[16] na categoria de melhor vulnerabilidade que permite escalação de
privilégios, infelizmente não ganhou. O motivo que mais chamou a atenção nessa vulnerabilidade, além
da sua complexidade, é o fato dela existir há muito tempo, como mencionado na introdução que no
Linux foi corrigida desde 2006. Aqui[17] há uma discussão em húngaro bastante interessante com detalhes da vulnerabilidade e outra forma de como a exploração pode ser feita.

Código disponível aqui.

Referências


terça-feira, 18 de dezembro de 2012

Construindo um fuzzer com Ruckus e DFuzz

Introdução

A comunidade de segurança da informação já está bastante acostumada com a palavra fuzzing. Para aqueles que ainda não conhecem, fuzzing é uma técnica de teste tendo como objetivo a descoberta de vulnerabilidades. Existem diversas ferramentas (fuzzers) disponíveis que implementam esta técnica. Neste post é apresentado alternativas Ruby-like para a realização de fuzzing, são elas: Ruckus[1] e DFuzz[2]. Primeiramente é apresentado o Ruckus, os principais tipos de dados que o mesmo é capaz de representar e como é possível através dele modelar um formato de dados. Em seguida é mostrado como o DFuzz pode ser utilizado para a realização de fuzzing. Por fim, é feita a combinação do Ruckus com o DFuzz para demonstrar que a dupla pode ser usada na construção de um smart fuzzer.

Ruckus

Ruckus é um smart fuzzer escrito em Ruby. Considera-se smart fuzzer as ferramentas que fazem fuzzing levando em consideração o tipo dos dados que estão sendo testados [3]. Com o Ruckus pode-se fazer a modelagem de formatos de dados (formato de arquivo ou protocolo) utilizando a própria linguagem Ruby para isto. Para começar a modelar uma estrutura basta criar uma nova classe herdando de Ruckus::Structure, conforme visto na Figura 1.


Figura 1: Modelagem de estrutura no Ruckus.


Tipos

Uma vez feito isto, é hora de representar os campos que o formato a ser testado contém. No Ruckus os principais tipos de dados disponíveis são: Number e Str.

Number, como o nome já diz, é usado para representar diversos tipos de inteiros. Para campos deste tipo é possível definir algumas características, conforme visto na tabela 1.

Tabela 1: Propriedades do Ruckus::Number



:width
Quantidade de bits do campo.
:endian
Representação em :big ou :little endian.
:ascii
Representação textual do campo (:true ou :false).
:radix
Definir base do número caso o campo seja textual.

Do tipo Number originaram-se outros tipos, são eles: byte, be16, be32, le16, le32, le64, be64, decimal, entre outros. Na Figura 2 pode-se ver um exemplo de definição de campos inteiros (be32 e be16).



Figura 2: Exemplo de campos inteiros.


Para um campo do tipo Str, também é possível definir características. Estas características podem ser vistas na tabela 2.

Tabela 2: Propriedades do Ruckus::Str

:size
Tamanho (absoluto) do campo (forçará padding se preciso).
:min
Tamanho mínimo do campo.
:max
Tamanho máximo do campo.
:padding
Caractere usado em caso de padding (padrão: '\0')
:pad_to
Alinhamento do padding.
:unicode
Define se o campo deve ser unicode.
:nul_terminated
Define se o campo deve ser terminado com nulo.


Na Figura 3 pode-se observar um exemplo de uso do Str, neste exemplo é definido um campo chamado filename e setado seu valor padrão para “default.txt”.

  Figura 3: Definição do campo filename do tipo str.


Há outros campos interessantes que podem ser usados no Ruckus como: ipv4, bit e nibble. Com os tipos disponíveis é possível modelar diversos formatos de arquivos e protocolos.

Relação

O Ruckus é capaz de manter relações entre campos. Na definição do formato PseudoFileFormat existe o campo len que é um inteiro de 16 bits, big endian e o campo filename que contém uma string. Pode-se relacionar o campo len com o tamanho da string em filename. Toda vez que essa string for alterada este campo será automaticamente atualizado. Na Figura 4, pode-se ver a criação do relacionamento utilizando o método relate_value.


Figura 4: Relacionamento entre o campo len e o campo filename.


Resultado do PseudoFileFormat

Para visualização em hexdump o formato definido, foi utilizanda a gem Ruby BlackBag[4]. Pode-se observar na Figura 5 que o campo len é setado automaticamente para o tamanho da string filename. Na Figura 6 vemos que após a alteração de filename o campo len é atualizado para refletir a modificação.

format = PseudoFileFormat.new
puts format.to_s.hexdump


Figura 5: Visualização em hexadecimal do formato PseudoFileFormat. O campo len está destacado.

format.filename = "post.txt"
puts format.to_s.hexdump



Figura 6: Visualização em hexadecimal do formato PseudoFileFormat após modificação do campo filename. O campo len está destacado.


DFuzz

Embora exista a capacidade de fazer fuzzing utilizando somente o código do próprio Ruckus, para este post foi escolhido o DFuzz porque o mesmo possui uma interface mais amigável para condução dos testes. A boa notícia é que o DFuzz já vem embutido no Ruckus. Mas afinal, o que é o DFuzz? DFuzz é um código em Ruby escrito pelo pesquisador Dino Dai Zovi que provê uma série de inputs que podem ser utilizados para se testar sistemas. Os inputs são gerados de acordo com o tipo de dado a ser testado. Os principais tipos suportados pelo DFuzz são: Integer, Byte, Short, Long e String. Na Figura 7 é possível ver um exemplo de uso do DFuzz. Utilizando DFuzz::String é possível encontrar falhas como buffer overflows, format strings, path traversal, entre outros.


Figura 7: Exemplo de uso do DFuzz.

PseudoFuzzer

Para este post foi escrito um código a fim de exemplificar como o Ruckus e o DFuzz podem ser utilizados em conjunto para criação de uma ferramenta de fuzzing. Na Figura 8 segue o que seria um fuzzer para o PseudoFileFormat. Neste exemplo, o teste focou-se apenas no campo filename, mas o código poderia ser estendido para testar os outros campos. Nem o Ruckus, nem DFuzz possuem um mecanismo para monitorar as exceções que podem ocorrer durante os testes. Para isto foi feita uma abstração na linha 27.


Figura 8: Exemplo do que seria um fuzzer para o PseudoFileFormat

Conclusão

Utilizando o Ruckus é possível modelar um formato de dados e com o DFuzz pode-se obter inputs úteis para realização de fuzzing. Com a combinação destas duas ferramentas é possível a criação de smart fuzzers utilizando funcionalidades e facilidades que estão presentes na linguagem Ruby.

Referências

[1] https://github.com/tqbf/ruckus/
[2] https://github.com/tqbf/ruckus/blob/master/ruckus/dfuzz.rb
[3] http://peachfuzzer.com/SmartFuzzer
[4] https://github.com/emonti/rbkb
[5] http://www.matasano.com/research/ruby_for_pentesters/Ruby-For-Pentesters.pdf

Autor do Post

Ricardo Silva é estudante de Computação e atualmente trabalha como Analista de Segurança da Informação na Conviso. Como exploiter já contribuiu para o metasploit framework. Sua área de interesse inclui fuzzing, análise e exploração de vulnerabilidades, engenharia reversa, arquitetura de computadores e sistemas operacionais. Acredita que 0x41414141 é um número mágico!

segunda-feira, 10 de dezembro de 2012

Desenvolvi minha aplicação em Ruby on Rails, estou seguro?

O framework para desenvolvimento de aplicações web Ruby on Rails já vem por padrão com proteções contra alguns tipos de ataques e oferece vários  métodos que auxliam o desenvolvedor a deixar sua aplicação segura.
Agora, se você me perguntar "estou seguro?" a princípio minha resposta seria "depende!". Neste texto vou considerar que o framework Ruby on Rails, em si, não tem nenhum furo de segurança. "Ah! quer dizer que o framework Rails tem furos de segurança". Não exatamente, mas, vez por outra, aparece uma nova versão que diz que foi consertado ítens de segurança, assim como em qualquer outro framework Java, .Net ou em sistemas operacionais como o Windows e Linux ou em banco de dados e aplicações em geral.
De agora em diante vamos nos preocupar, apenas, na codificação de sua aplicação.
Vamos começar com o exemplo mais básico, mas que muito desenvolvedor costuma errar, veja o código abaixo:
Product.where("name LIKE '%" + params[:name] + "%'")
Duas lições que devem ficar na sua memória sempre e em qualquer linha de código que você criar.
1. Nunca confie em, absolutamente nada, que o usuário vai digitar.
2. Novamente, nunca confie em, absolutamente nada, que o usuário vai digitar.
A partir dessas duas lições, vamos notar que o usuário pode burlar a consulta e inserir comandos SQLs indesejados (SQL Injection). Logo, ao invés de usar concatenação de strings, use a interpolação disponível no framework que, por si só, vai tratar o Injeção de SQL:
name_like = "%#{params[:name]}%"
Product.where("name LIKE ?", name_like)
Mas concatenando strings não é única maneira de fazer Injeção de SQL. Imagine só uma view com esse campo em um formulário:
<select name="report">
    <option value="weekly">Vendas Por Semana</option>
    <option value="monthly">Vendas Por Mês</option>
    <option value="yearly">Vendas Por Ano</option>
</select>
Chamando o método send em uma classe de Model você pode estar dando espaço ao atacante para roubar dados ou até mesmo destruir informações, veja o exemplo sendo passado por parametro a opção escolhida:
Sell.send(params[:report])
Agora imagine se o atacante mudar um "option" por isso:
...
<option value="destroy_all">Vendas Por Semana</option>
...
Ou seja a classe de Model "Sell" executará o método destroy_all que apagará todos os dados da tabela. Para resolver este problema você poderia simplesmente fazer:
allowed_methods = ['weekly', 'monthly', 'yearly']
if allowed_methods.include?(params[:report])
    Sell.send(params[:report]) 
end
Ou seja, apenas os métodos permitidos para exibir relatórios podem ser executados pela classe de Model "Sell".
Programação é algo bastante dinâmico e não podemos nos limitar a estes dois exemplos apresentados aqui, é preciso sempre estar atento a novas oportunidades que um atacante pode explorar. É como já disse antes, e vou repetir:
Nunca confie em, absolutamente nada, que o usuário vai digitar.
Vamos dar uma olhada agora em outro tipo de ataque. Um tipo de ataque que é feito na View de sua aplicação: a Injeção de JavaScript (Cross-Site Script ou, apenas, XSS).
No Rails, qualquer variável com caracteres <, >, ', " e & serão substituídos por &lt;, &gt;, &#x27;, &quot; e &amp;, respectivamente. Desta maneira, sua aplicação fica livre de ataques por Injeção de Javascript.
Primeiramente vou indicar que evite o máximo o uso do método html_safe para variáveis, pois este método autoriza que os caracteres mencionados acima não sejam substituídos, podendo desta forma deixar sua aplicação vulnerável.
Vamos dar um exemplo bem simples e, aparentemente, ingênuo de um formulário que ao ser submetido troca o cor de fundo de uma DIV em uma página web. Exemplo:
<form action="/tests/" method="get">
    <select name="color">
        <option value="#0000FF">Azul</option>
        <option value="#FF0000">Vermelho</option>
        <option value="#00FF00">Verde</option>
    </select>
    <input type="submit">
</form>

<div style='background-color: <%= (params[:color] || "#0000FF").html_safe %>'>
    Um texto qualquer
</div>
Se o atacante mudar uma option para:
...
<option value=""><script>alert('ok');</script>">Azul</option>
...
Com isso a atacante pode mudar qualquer informação da página web ou, pior, redirecioná-lo para um outro site, contendo uma página igual a que você queria mas onde o atacante tem plenos domínios sobre o que você digita em formulários, podendo, inclusive, pegar o número do seu cartão de crédito.
Se você realmente precisar usar o método html_safe use junto com o método content_tag do framework Rails para evitar o surgimento dessas vulnerabilidades, Por exemplo:
...
<%= content_tag 'div', 'Um texto qualquer', :style => "background-color:#{(params[:color] || "#0000FF").html_safe}"  %>
...

Conclusão

Como já foi dito, programação é muito dinâmica, é preciso ficar atento a tudo que pode se tornar vulnerabilidades em sua aplicação. Todos os dados de entrada que o usuário fizer devem ser tratados com atenção especial e ter restrições. Além disso, todos os dados que serão exibidos, também, devem ter atenção especial e devidamente tratados logo antes de sua exibição.
Se você tomar todos esses cuidados, muito provavelmente sua aplicação estará segura contra esses ataques mencionados, seja qual for a linguagem e framework escolhidos.


Autor do Post

Desenvolvedor antenado em segurança de aplicações, louco por tecnologia e sempre disposto a resolver algum problema. Gosta de Rock n' Roll das antigas e de um bom jogo de futebol

quinta-feira, 6 de dezembro de 2012

CVE-2012-5905 De Negação de Serviço à Execução de Código Remoto

Antes de qualquer coisa vale salientar que esse artigo é inteiramente didático e, apesar do aplicativo em questão apresentar vulnerabilidades que possam comprometer a segurança de sistemas, acredita-se que o mesmo não seja utilizado como serviço pelo fato de possuir outras vulnerabilidades públicas não corrigidas e o projeto não possuir continuidade. De qualquer forma o autor ou a Conviso® não se responsabilizam pela má utilização do material aqui contido.




Introdução



Encontrar vulnerabilidades e subverter sistemas é sem via de dúvidas algo extremamente divertido e que exige um conhecimento especializado tanto do ambiente quanto do alvo a ser explorado. Porém, o que para uns é pura diversão, para outros é meta de produtividade no serviço. A exploração completa de um software passa por uma fase complexa de análise com o intuito de transformar falhas corriqueiras (bugs) em potenciais vulnerabilidades. Porém, devido a pressão exercida por órgãos superiores (chefes, clientes etc) que desejam simplesmente ver quaisquer resultados, diversos especialistas divulgam suas descobertas como simples crash na aplicação e coloca como único princípio de risco a quebra da disponibilidade através de ataques de Negação de Serviço (DoS – Denial of Service), quando na verdade não foi possível fazer uma análise detalhada em busca de transformar um simples crash em uma vulnerabilidade. Então é registrado uma falha em um software, é definido um CVE-ID para ela[0] e os superiores ficam felizes, pois afinal de contas foi apresentado um resultado no prazo. Como exemplo do citado, temos um tópico anterior[1] onde o analista e pesquisador Ricardo Silva transforma um bug público registrado como Negação de Serviço em uma vulnerabilidade de execução remota de código potencialmente perigosa.  


Este artigo visa analisar o CVE-2012-5905[2] que indica um simples ataque de Negação de Serviço, e provar que a vulnerabilidade consistia em execução remota de código (RCE).



Análise do CVE-2012-5905


De acordo com o CVE-2012-5905, foi encontrado uma vulnerabilidade de buffer overflow no software KnFTPd[3] que permite um usuário autenticado indisponibilizar o serviço através de um ataque de negação de serviço através do comando FEAT

Para iniciar a análise foi feito uma simples avaliação no processo a partir da chamada à API WS2_32.recv(), responsável por ler informações em um socket. De acordo com a Figura 1, existem duas chamadas a essa API. Para não prolongar muito o artigo foi feito a análise somente da chamada que já se sabe ser a responsável pela recepção dos comandos.


 Figura 1: Lista de chamada

Na Figura 2 é possível ver a chamada à API WS2_32.recv() com um buffer de tamanho 0x1000 e com os dados recebidos armazenados no local apontado pelo registrador EBX. Em seguida o registrador EBX juntamente com o registrador ESI são utilizado como parâmetros da função localizada em knftpd.00401CA8, que será analisada a seguir.


Figura 2: Recebimento no socket

Na função localizada em knftpd.00401CA8, após o prólogo, é possível ver uma chamada para a função sscanf() com o parâmetro de formatação “%s” (vide Figura 3). Essa chamada é utilizada para obter o primeiro token do buffer recebido no socket. É importante lembrar que a formatação “%s” define como token qualquer sequência de caractere até uma quebra de linha, um espaço, uma tabulação ou o caractere nulo.

Figura 3: Parsing do comando FTP

Ainda na mesma rotina é possível notar a utilização da função strchr() com o objetivo de localizar o caractere de espaço (0x20 em hexadecinal) no buffer (Figura 4).

Figura 4: Obtenção do espaço separador

Tendo obtido o endereço do espaço no buffer recebido pelo socket, a função analisada utiliza o restante do buffer como parâmetro da função strcpy(), que é potencialmente insegura por não fazer qualquer verificação de limite na variável de destino. Como o usuário consegue manipular os dados de origem, é provável que este seja um exemplo de buffer overflow. código que apresenta a chamada à função strcpy() pode ser visto na Figura 5.


Figura 5: Chamada a função insegura strcpy()

Com isso já é possível escrever um código de prova de conceito (p0c) para que possamos analisar o comportamento da aplicação quando passarmos como parâmetro do comando uma cadeia de caracteres razoavelmente grande. O código foi escrito em Python e pode ser visto na Figura 6. O código em questão simplesmente autentica no sistema e passa um parâmetro grande para o comando FEAT.
Figura 6:  Código para prova de conceito#1.

É importante notar que a análise feita foi desde a leitura do socket através da API WS2_32.recv() até a chamada à função strcpy() e que em momento algum foi feito a checagem de qual comando foi passado. Com isso cogita-se que a vulnerabilidade esteja presente não no comando FEAT, mas sim em qualquer buffer que seja transmitido como duas palavras separadas por um espaço. Para comprovar isso experimente trocar o comando FEAT no código por qualquer outro comando FTP ou até mesmo por comandos que não existam no protocolo. 

Após a execução do código o resultado foi o visto na Figura 7. Como esperado, houve um transbordo do buffer e foi possível sobrescrever áreas da memória que posteriormente influenciarão em registradores como o EIP, responsável por armazenar o endereço da próxima instrução a ser executada.

Figura 7:  Controle de EIP (0x41414141)

No momento do estouro de memória, o registrado EBP ficou apontado para regiões do buffer que podemos manipular (Figura 8). Além disto, é possível notar que perdemos parte do começo de nosso buffer e que ainda podemos sobrescrever alguns bytes antes de tomarmos uma exceção de violação de acesso à memória (não estamos explorando por sobrescrita de SEH).

Figura 8: Visualização de ESP no dump

O próximo passo será descobrir em qual offset é sobrescrito o EIP em qual offset o registrado ESP aponta dentro da região que conseguimos manipular. Para isso podemos utilizar o script pattern_create.rb, presente no Metasploit Framework, que gera um cadeia de caracteres de forma que não exista uma sequência de padrão único, para substituir nossa sequência de A's no exploit inicial. O resultado do comando e a alteração do código podem ser vistos na Figura 9 e na Figura 10.

Figura 9: Utilização do pattern_create do Metasploit Framework

Como foi analisado que ainda possuíamos um espaço antes de atingir uma região da memória que geraria uma exceção, o buffer que inicialmente possuía 300 bytes passou a possuir 350 bytes.

Figura 10: Código para prova de conceito #2

Após a execução desse código, o programa parou a execução com os registradores no estado visto na Figura 11.

Figura 11: Registradores

Tendo o valor presente no registrador EIP e os bytes iniciais de onde o registrador ESP aponta é possível utilizar o script pattern_offset.rb para saber a posição exata desses valores dentro do buffer utilizado para a exploração da aplicação. O resultado, de acordo com a Figura 12 é o registrador EIP sendo sobrescrito a partir do offset 284 e o registrador EBP estando apontando para o buffer a partir do offset 292.

Figura 12: Utilização do pattern_offset.rb do Metasploit Framework

Nossa melhor referência na análise é o registrador ESP, que aponta para uma região que podemos manipular. Porém, como o buffer só possui 350 bytes, ao referenciar este registrador só possuímos 58 bytes para nosso payload, o que não seria suficiente para uma exploração real; enquanto que temos quase 300 bytes no começo do buffer, o que seria suficiente para um payload completo ou pelo menos o primeiro estágio de um payload maior (multi-staged).

Para contornar essa limitação, utilizamos os 58 bytes para armazenar um EggHunter[4][5] que irá localizar o payload inserido no começo do buffer, e nosso endereço de retorno irá apontar para alguma rotina que altere o fluxo de execução com um endereço relativo ao registrador ESP. Dessa forma, o payload total deve possuir o formato da Figura 13 abaixo.

Figura 13: Formato do payload

Para uma simples demonstração antes de mostrar o payload final, o código do exploit foi alterado respeitando o formato descrito na Figura 13. O resultado do código pode ser visto na Figura 14.


Figura 14: Código para prova de conceito #3

O resultado da análise pode ser comprovada na Figura 15, onde é possível notar que, como esperado o registrador EIP é sobrescrito com o valor 0x42424242 equivalente a cadeia BBBB e o registrador ESP aponta para o começo da sequência dos caracteres D (0x44 em hexadecimal).

Figura 15: Análise do crash (registradores e memória)

Como dito anteriormente, o endereço de retorno deve ser sobrescrito para uma instrução que salta para onde o registrador ESP está apontando, por essa ser uma região manipulável. As instruções mais comuns para isso são PUSH ESP + RET ou um simples JMP ESP. O ideal seria achar essas instruções (ou equivalentes) dentro do próprio módulo do programa, pois assim o exploit final funcionaria independente de plataforma utilizada. Porém, a fim de reduzir o tempo de análise, a instrução obtida foi tirada do módulo USER32.DLL, o que torna o exploit funcional somente na plataforma utilizada (Microsoft Windows XP SP3 – English) e é necessário a adaptação em algumas situações. A Figura 16, apresenta a instrução que será utilizada durante a exploração.

Figura 16: JMP ESP em USER32

O payload final com nop-sled, shellcode, endereço de retorno e egghunter pode ser visto na Figura 17, e o exploit completo pode ser obtido em [6].


Figura 17:  Payload final


Conclusão

Este artigo demonstrou que, por razões diversas, muitas vezes a publicação de falhas em softwares não representam o real problema existente, ficando os fabricantes a mercê de ataques por não saber a situação real da segurança de seus produtos.

Referências








Maycon Vitali é alto, moreno, tem os olhos verdes, não é baiano e possui como um de seus principais objetivos a dominação mundial. Formado em Ciência da Computação e Mestre em Informática, Maycon atua como Pesquisador e Analista de Segurança da Informação na Conviso. Além disto, Maycon possui aproximadamente 10 anos de experiência em computação, o que não quer dizer muita coisa; palestrou em diversos eventos de segurança, o que não também quer dizer muita coisa; e possui certificação OSCE, o que também não .... blah! Vocês já sabem.