Seu primeiro estouro de buffer

Nov 27 2022
Seja você um estudante, um desenvolvedor ou um gerente de uma empresa, você deve tentar isso. A chave para escrever um código seguro é entender como um código ruim é quebrado.

Seja você um estudante, um desenvolvedor ou um gerente de uma empresa, você deve tentar isso. A chave para escrever um código seguro é entender como um código ruim é quebrado. A única maneira de quebrar o código ruim é sujar as mãos e começar a estourar shells em desafios de exploração binária. Neste artigo, mostrarei como executar seu próprio ataque de estouro de buffer usando um exemplo que fiz no YouTube.

Em um dos meus vídeos do YouTube, expliquei como as strings na linguagem de programação C estão fazendo com que as pessoas sejam hackeadas. A principal razão para isso é que não há nenhum atributo de comprimento integrado associado ao tipo de string C. A única maneira de saber se uma string está concluída é navegar até o final da string.

https://youtu.be/fjMrDDj47E8

Esse conceito levou a inúmeras vulnerabilidades de estouro de buffer em software e, como resultado, levou a milhares de ataques cibernéticos e roubo de dados pessoais.

Nosso servidor vulnerável

Abaixo está algum código para um servidor simples em C.

#include <stdio.h>
#include <secrets.h>

void debug() 
{
    printf("!! ENTERING DEBUG MODE !!\n");
    system("/bin/bash");
}

int checkPassword()
{
    char password[64];

    printf("password: ");
    gets(password);

    return isValidPassword(password);
}

int main(int argc, char **argv)
{
    printf("WELCOME TO THE SECURE SERVER\n");

    if (checkPassword())
    {
        debug();
    } else {
        printf("Wrong password, sorry;\n");
    }
}

No entanto, há uma grande vulnerabilidade aqui. A senha que eventualmente é verificada é lida pela função gets , que é uma função extremamente vulnerável. Se você não acredita em mim, verifique a página do manual para obter.

Never use gets(). Because it is impossible to tell without knowing the data 
in advance how many characters gets() will read, and because gets() will 
continue to store characters past the end of the buffer, it is extremely 
dangerous to use. It has been used to break computer security. Use fgets() 
instead.

A pilha

A parte mais difícil para programadores e profissionais de segurança cibernética entender é como uma sobrecarga de dados fora de um buffer pode levar um hacker a obter o controle de seu código e, por fim, de seu computador. A maneira como isso acontece é por causa de como os computadores organizam a memória quando um programa é executado.

Figura 1: exemplo de controle de fluxo do programa

No exemplo de código acima, o programa começa em main. Depois que main inicia, na linha 10, other_function é chamada. O que é extremamente importante entender é como other_function sabe ir para a linha 11 após retornar.

A estrutura que contém essas informações é chamada de “pilha”. A pilha é a região da RAM que é usada quando seu programa é executado e é usada para armazenar informações de fluxo de controle (como endereços de retorno), mas também dados.

Quando main chama other_function , o endereço que será executado em seguida em main após o retorno de other_function é colocado no topo da pilha. other_function é executado e, em seguida, o programa volta para o endereço armazenado por main .

É aqui que os estouros de buffer começam a importar. Se houver um estouro de buffer nos dados do seu programa, você pode alterar as informações do fluxo de controle e, eventualmente, alterar a forma como o programa é executado.

Tente você mesmo

A mesma coisa se aplica a este trecho de código aqui. A ideia por trás deste programa é: se você souber a senha, receberá um shell. Infelizmente, nós hackers não sabemos a senha, então não pegamos um shell. No entanto, como a função get é usada, podemos aproveitar a vulnerabilidade de estouro de buffer e estourar o endereço de retorno. Em vez de retornar ao main , podemos retornar diretamente à função de depuração sem saber a senha.

Acompanhe aqui:

https://github.com/lowlevellearning/secure-server-stuff

Funcionalidade de teste

Para começar, devemos executar o servidor e testar sua funcionalidade. Além disso, vamos tentar adivinhar a senha.

Tudo bem, sem dados. É aqui que o hacking começa.

travar o servidor

Podemos aproveitar o estouro do buffer, como o nome indica, transbordando o buffer. Para fazer isso, fornecemos mais dados do que o servidor pode suportar.

Na parte inferior, vemos que o programa tinha uma falha de segmentação. O que isso significa é que o programa tentou acessar a memória que não conseguiu acessar. Se você é um desenvolvedor, isso é ruim. Mas, se você está tentando hackear o servidor, isso é bom :)

Vamos detalhar um pouco mais o acidente.

Aqui, podemos ver que o programa falhou ao tentar executar as instruções no endereço 0x41414141. Esse endereço deveria parecer... estranho. Curiosamente, 0x41414141 é a representação hexadecimal da string “AAAA”, o que significa que nossa string de As estourou no endereço de retorno e a CPU tentou executá-la. AGORA ESTAMOS HACKEANDO!

Tomando o controle

Para obter o controle exato do endereço de retorno, precisamos determinar especificamente qual parte de nossa string ASCII causou a falha do programa. Podemos fazer isso modulando nossa string com caracteres diferentes e usando os caracteres para determinar onde começa e termina nosso controle do endereço de retorno.

Em vez de uma longa sequência de As, colocamos 64 As, seguidos por uma série de caracteres ASCII extras. 64 é o comprimento do nosso buffer de entrada, então é um bom lugar para começar a adivinhar o comprimento. Depois disso, inserimos caracteres adicionais. Para verificar onde nosso controle começa, use o mesmo comando de antes.

Agora você pode ver que temos um novo endereço de retorno. 0x45454545 é a representação hexadecimal de “EEEE”! O que isso significa é que determinamos onde nosso estouro assume o controle do ponteiro de retorno. A partir daqui, precisamos colocar um endereço no buffer com o qual realmente nos importamos. Estamos tentando retornar à função de depuração, então devemos colocar esse endereço no lugar da string EEEE.

Para determinar o endereço de depuração, executamos um comando simples…

objdump -d -Mintel ./hacked | less

Parece que o endereço de depuração é 0x08049296. Se colocarmos o endereço de depuração onde estava nossa string EEEE, talvez possamos controlar o programa e voltar a depurar sem precisar da senha.

A façanha final

Podemos fazer isso usando um pouco de python-fu.

import sys

payload = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
payload += b"BBBBCCCCDDDD"
payload += b"\x08\x04\x92\x96"[::-1]

sys.stdout.buffer.write(payload)

user@user:~/vuln$ (python3 exploit.py; cat) | ./hacked
WELCOME TO THE SECURE SERVER

password: !! ENTERING DEBUG MODE !!

cat password
too_kool_4_skoo

Por favor, siga-me no Medium, vá se inscrever no YouTube. Mais para vir!