A escolha do pattern importa ?

Posted on Sat, Feb 6, 2021 binary-exploitation bof

Neste artigo trataremos de um Buffer Overflow do tipo Vanilla em um binário compilado sem nenhuma proteção, com o ASLR do sistema operacional desabilitado. O ambiente é um Kali Linux e usaremos o gdb com o plugin gef para debug.

No geral, em um processo de exploração de Buffer Overflow, o primeiro passo é fazer fuzzing na aplicação para encontrar entradas vulneráveis a técnica. Testa-se exaustivamente todas as possíveis entradas com combinações de caracteres dos mais variados tipos e tamanhos, até achar uma combinação que leve a falha.

Após a confirmação de que a aplicação está vulnerável elabora-se um POC (proof-of-concept), enviando a string, que levou ao erro no programa, seguida de uma sequência de caracteres que permita o atacante confirmar, através de um debugger, que consegue manipular o registrador EIP e consequentemente controlar o fluxo de execução do programa.

$ python -c "print'A'*100+'BBBB'" 

Bem, este é o caso de uma aplicação onde podemos enxergar a importância na escolha do Pattern para atingir nossos objetivos.

A escolha da string, no processo inicial de exploração, já é importante uma vez que o erro pode ser causado não apenas pelo tamanho da string mas pelo tamanho e/ou formação da string. Já ouvi falar por ai que o fuzzing por si só é uma ciência. Observe, o que seria um exemplo de fuzzing manual com duas strings de tamanhos iguais mas usando caracteres diferentes:

$ python -c "print 'A'*100" | ./jdark-overflowme-if-you-can
Enter your name: Filtered Name: AAAAAAAAAAAA
 
[1]    17427 done                python -c "print 'A'*100" | 
       17428 segmentation fault  ./jdark-overflowme-if-you-can

$ python -c "print 'a'*100" | ./jdark-overflowme-if-you-can
Enter your name: Filtered Name: aaaaaaaaaaaa
 
$

BoF - same size , different chars, different results.

Example how a string with same size but different chars could leave an buffer overflow or not. Complete Article: https://www.offensivethink.com

Observe, do exemplo acima, que a mesma quantidade de caracteres podem ou não levar a um buffer overflow, dependendo da combinação de caracteres escolhidos.

O próximo passo então, diante de um segmentation fault, seria, utilizando um debuger, validar que conseguiremos, por exemplo, controlar o valor que irá ser atribuído ao registrador EIP. Vamos confirmar para o caso onde conseguimos gerar o segmentation fault:

Executando o POC dentro do debugger para validar a sobrescrita do EIP

Observe que conseguimos escrever em várias regiões de memória e alguns registradores, mas apenas parcialmente no EIP que ficou apontando para o endereço 0x414141F6,transformando em caracteres ASCII, "AAAö".

Para o caso deste binário, este comportamento se repetirá inserindo 100,200,300,400,1000 e até mais caracteres, sempre com o endereço terminando em 0xF6.

BoF - Always Same EIP

We can only partial control the address that will be put on EIP.

Podemos então observar que poderemos apenas controlar 3 bytes do endereço que será colocado no EIP, montando a nossa exploração para tentar abusar destes fato.

Vamos avançar e observar que a correta escolha do conjunto de caracteres pode nos levar não somente a controlar todo o endereço do EIP mas também influenciará na descoberta do offset que nos levará ao controle deste registrador.

Os padrões aqui escolhidos não foram ao acaso. Existe um motivo que tentarei explicar em um segundo artigo mostrando o porque de tudo isso estar ocorrendo!

Como primeiro exemplo, admitindo que conseguiremos explorar direcionando o fluxo do programa para uma região de memória que termina em 0xF6, vamos então achar o offset que nos levaria a sobrescrita do endereço parcial do EIP, usando o comando msf-pattern_create do Metasploit Framework e em seguida enviando o pattern na entrada do nosso binário.

Mesma quantidade (100) de caracteres enviados, resultado normal!

Observe que enviamos a mesma quantidade de caracteres de antes (100) só que não conseguimos gerar o erro! Já começamos a ver que a escolha do pattern terá influência em como conseguiremos explorar o binário.

Vamos tentar usando o pattern da pwntools!

Mesmo resultado do anterior!

Mais uma vez, não conseguimos gerar a falha!

Vamos então criar patterns personalizados, usando o pwntools, e ver o que acontece.

Obs.: É possível usar o msf-pattern_create também para criar patterns personalizados.

Usando um pattern personalizado para tentar controlar o EIP!

Com a opção -a , do comando pwn cyclic, conseguimos personalizar qual a sequência de caracteres que desejamos que exista em nosso pattern.

Observe que conseguimos forçar a falha e o EIP, desta vez, aponta para um suposto endereço que na verdade é um pedaço de nosso pattern, diferentemente do que aconteceu anteriormente, onde só conseguimos controlar uma parte do endereço. Ou seja, com esse padrão conseguimos não só forçar a falha mas também controlar completamente o EIP.

Vamos confirmar se o pattern funcionou, procurando o offset, e tentando forçar o EIP a receber o valor 0x42424242 ("BBBB").

Observe que dos comandos acima, conseguimos definir qual o offset. Para fins de facilitar, geramos o mesmo padrão anterior só que com o tamanho exato que precisamos (32 bytes neste caso). Em seguida enviamos nosso POC composto agora do padrão do tamanho exato e a sequência de BBBB's. Desta forma, conseguimos validar que conseguimos controlar o EIP.

Veja o que acontece com simplesmente adicionando um caracter na sequência que gerará o novo padrão, neste caso, mudando de 0123 para 01234.

Usando um novo pattern e obtendo um novo offset!

continuando com a exploração usando o novo offset.

Observe, das imagens acima, que a mudança do pattern também nos permitiu gerar a interrupção que desejamos, mas observe que o offset mudou de 32 para 29. Mesmo assim, conseguimos controlar o EIP uma vez que ele aponta para o nosso BBBB.

Agora é continuar a explorar.

Com isso, conseguimos observar que até mesmo a escolha do nosso pattern pode influenciar em vários detalhes na nossa exploração que pode nos levar ao êxito ou a falha!

Já era esperado que mudanças de caracteres enviados para a entrada do programa gerassem falhas ou não, uma vez que esse é o processo de fuzzing. Mas é interessante notar que mesmo depois de atingida a falha, a escolha do pattern pode dificultar achar o offset (caso 1 onde usamos o msf-pattern_create e o programa não gerou um erro sequer!) e talvez o offset seja apenas um dos possíveis.

Qual o motivo da mudança do offset ?

Bem , de acordo como o programa é estruturado, podem existir várias funções aninhadas ou não e consequentemente vários retornos (instrução ret) que podem ser sobrescritos em pontos diferentes da execução.

Em uma próxima mostro o código do binário e a importância de aprofundar os conhecimentos em assembly, engenharia reversa, dentre outros assuntos correlatos. Estes conhecimentos podem se tornar cruciais para a exploração ou não de uma falha.

Até a próxima!