Fuzzing with American Fuzzy Lop – Quickstart

Codificação segura é uma prática muito difícil. Normalmente, quando os programadores desenvolvem software, o seu objectivo é fazer o software funcionar em vez de quebrar. Neste processo, podem desenvolver-se vulnerabilidades nos casos em que foi utilizada uma função herdada em vez de uma função mais segura. Consequentemente, software legado é especialmente vulnerável.

C é uma daquelas linguagens que é inerentemente muito versátil e poderosa, mas tem um inconveniente crítico – a segurança de software baseado em C depende do conhecimento do programador. Isto significa que se o programador estiver bem ciente da codificação segura, o seu software também estará seguro. Por outro lado, e isto forma a maior parte, se o programador não for suficientemente sofisticado, haverá lacunas no seu software que acabarão por levar a um exploit.

Para pessoas como eu, que conhecem programação mas são novas na indústria da segurança, é muito importante estudar código vulnerável e compreender as possíveis consequências. Isso ajuda a refinar as habilidades de codificação e desenvolver uma atitude de ataque DURANTE a fase de codificação ao invés de DEPOIS de codificar todo o software.

Com toda honestidade, é bastante complicado estudar o código fonte completo de uma aplicação quando se procura por vulnerabilidades como estouros de buffer. Embora este método tenha seus próprios méritos, ele não é o método mais fácil de encontrar vulnerabilidades simples que podem ser críticas. Tais vulnerabilidades devem ser imediatamente resolvidas e a maneira mais fácil de encontrá-las é através de uma técnica chamada Fuzzing.

Fuzzing é uma técnica para encontrar vulnerabilidades “fáceis” no código, enviando dados gerados “aleatoriamente” para um executável. Em geral, existem três tipos de fuzzers:

  1. Mutation: Um tipo de fuzzing “burro” onde amostras de entrada mal-formadas são geradas e fornecidas ao executável. Esse input pode ou não estar de acordo com o tipo de input esperado pela aplicação, portanto a probabilidade de encontrar bugs reais não é alta.
  2. Generation: Um tipo de fuzzing “inteligente” que requer alguns dados de teste iniciais a partir dos quais o algoritmo fuzzer pode gerar um input malformado a partir do zero. Este tipo de fuzzing é melhor que o fuzzing burro em muitos casos porque o programa recebe a entrada que espera.
  3. Evolucionário: Estes tipos de fuzzers usam feedback de cada “fuzz” para aprender ao longo do tempo o formato do input.

Neste post, vamos ver o fuzzing com o American Fuzzy Lop (AFL). É um tipo de fuzzer evolutivo que é adequado para programas fuzz que recebem input do STDIN ou de um arquivo.

Há uma grande quantidade de fuzzers na natureza, incluindo Peach e syzkaller. Então, porquê AFL?

  1. O caso de uso. Este é o ponto mais importante a considerar. O meu caso de uso foi para fuzz uma aplicação que recebe inputs de um arquivo. É importante notar que o AFL não tem a capacidade de fuzz através de redes.
  2. É simples de instalar.
  3. A interface UI do AFL empacota uma tonelada de informação incluindo estatísticas em tempo real do processo de fuzzing.

Setup AFL

Configurar AFL é fácil e eu tornei isso mais fácil para você escrevendo um script shell simples (mas rudimentar) que irá instalá-lo para você! Execute o script com seus privilégios de usuário e ele irá instalar todas as dependências, AFL e ferramentas relacionadas. O script da shell pode ser encontrado aqui: https://github.com/nikhilh-20/enpm691_project/blob/master/install_afl.sh

Selecione a Aplicação para Fuzz

Neste post, vamos olhar apenas para fuzzing das aplicações para as quais temos o código fonte. Isto é porque o AFL instrumentaliza o código fonte para monitorar a execução, erros e outras coisas relacionadas à performance. Também é possível fuzzar diretamente um executável mas isso é experimental e fora do escopo deste post (dica: ele requer QEMU).

Selecionar qualquer sistema open source do GitHub para fuzzing. Quanto mais conhecida a sua escolha, menor o número de vulnerabilidades que ela provavelmente terá. Outros estão procurando por bugs também! Um método simples que eu uso para encontrar código vulnerável é usar o GitHub Search. Isto é o que eu faço:

  1. Procurar por uma função vulnerável, digamos strcpy.
  2. Os resultados serão em milhões. Vá para a categoria de commits dos resultados. Aqui você encontrará os repositórios onde strcpy foi usado (ou talvez removido). Estes repositórios são um bom ponto de partida para começar o fuzzing.

Instrumentando a Aplicação

Por razões de privacidade, eu não posso revelar o repositório que estou usando.

Clonar o repositório git.

nikhilh@ubuntu:~$ git clone https://github.com/vuln; cd vuln

Configurar uma variável de ambiente, AFL_HARDEN=1. Isto activa certas opções de endurecimento do código no AFL enquanto compila, o que facilita a detecção de bugs de corrupção de memória.

nikhilh@ubuntu:~/vuln$ export AFL_HARDEN=1

Configurar certas flags do compilador, de modo a que a aplicação seja compilada de modo a facilitar a procura (e exploração) de vulnerabilidades. O ideal seria usar variáveis de ambiente para definir o que precisamos, mas há um pouco de customização, então vamos editar diretamente o Makefile.

Certo que o compilador usado é afl-gcc ou afl-clang ao invés de gcc e clang respectivamente. Isto é o que permite ao AFL instrumentar o código fonte.

Adicionar flags do compilador:

-fno-stack-protector desliga o protetor de pilha que nos permitirá explorar o buffer overflows.

-m32 só é necessário se você estiver usando uma máquina de 32 bits, caso contrário no.

Após estas alterações terminarem, é hora de compilar a aplicação. Execute make. Quando o fizer, você DEVE ver instruções como estas no log:

Localizações de 123 bits (32-bit, modo endurecido, ratio 100%).

Se você não vir tais instruções, significa que o AFL não ativou o código da aplicação para fuzzing. Em outras palavras, ele não instrumentou o código fonte com sucesso.

Test Samples

AFL é um tipo evolutivo de fuzzer. Isso significa que, assim como fuzzers baseados em geração, também requer dados de teste iniciais para entender que tipo de dados a aplicação alvo espera. Quando o alvo é sistemas de código aberto, isto é fácil de encontrar. Basta procurar no diretório de testes deles e você encontrará todos os dados de teste que precisa.

nikhilh@ubuntu:~/vuln$ mkdir afl_in afl_out

nikhilh@ubuntu:~/vuln$ cp test/* afl_in/

Fuzzing Begins

Agora que temos nossas amostras de teste, estamos prontos para o fuzz!

Oh, espera… também precisamos de mudar para onde vão as notificações de crash da aplicação. By default, when an application crashes, the core dump (basically, the contents of RAM are stored in a file to help in debugging) notification is sent to the system’s core handler. We don’t want this. Why? By the time this notification reaches AFL, it’ll be classified as a timeout rather than a crash.

nikhilh@ubuntu:~/vuln$ sudo su

password for nikhilh:

root@ubuntu:/home/nikhilh/vuln# echo core > /proc/sys/kernel/core_pattern

root@ubuntu:/home/nikhilh/vuln# exit

NOW, we are ready to fuzz!

nikhilh@ubuntu:~/vuln$ afl-fuzz -i afl_in -o afl_out -S slaveX — ./vuln @@

Command line flags used:

  1. -i — This marks the test input directory. Este é o diretório onde armazenamos os dados iniciais do teste.
  2. -o- Este é o diretório onde o AFL escreve informações úteis sobre travamentos, pendências, etc.
  3. -S – Este é o modo Escravo. Basicamente, o AFL irá aleatoriamente ajustar o input causando fuzzing não-determinístico.
  4. A opção -M é o modo Master que é fuzzing determinístico, o que basicamente significa que cada bit do input está sendo modificado de alguma forma. (Isto é lento! … Obviamente..)
  5. @@ – Esta é a posição onde o arquivo de teste de entrada será. AFL substitui isto por você automaticamente. Se o seu executável recebe input do STDIN, então isto não é necessário.

Fuzzing Results

Isto vai levar algum tempo para ser mostrado. Muitas vezes, as pessoas fuzzing por mais de 24 horas (e pode acabar sem nada). No meu caso, eu acho que a aplicação estava um pouco vulnerável demais, então eu tive 516 crashes únicos dentro de uma hora. No entanto, isso não significa que existam 516 vulnerabilidades!

Você pode sair da sessão de fuzzing com um Ctrl-C.

Analysis Phase

Now that we have results, we need to analyze them to see which ones are exploitable. To this end, we will use one of AFL’s utilities called afl-collect. This will have been installed through the installation script as well.

nikhilh@ubuntu:~/afl-utils$ afl-collect -d crashes.db -e gdb_script -r -rr ~/vuln/afl_out/slaveX ./output_dir — ~/vuln/vuln

To understand what each command line flag does, refer to its help section.

nikhilh@ubuntu:~/afl-utils$ afl-collect — help

If you see lines such as these in the output, celebrate! You’ve found something interesting to try and exploit.

*** GDB+EXPLOITABLE SCRIPT OUTPUT ***

slaveX:id:000000,sig:11,src:000000,op:havoc,rep:2……………:EXPLOITABLE

slaveX:id:000046,sig:11,src:000004,op:havoc,rep:4……………:EXPLOITABLE

AFL irá mostrar-lhe qual a entrada que causou o crash da aplicação. Neste caso, o arquivo: id:000046,sig:11,src:000004,op:havoc,rep:4 causou um StackBufferOverflow na aplicação. Tais arquivos podem ser encontrados em ../afl_out-slaveX/crashes/

Done!

P>Isso é tudo para um início rápido no fuzzing! O processo é realmente simples e muito conveniente, já que é tudo automatizado. O próximo passo seria analisar porque a entrada causou um estouro de buffer e procurar uma maneira de explorá-lo. Lembre-se que nem todas as vulnerabilidades podem levar a um exploit.

Deixe uma resposta

O seu endereço de email não será publicado.