Como funciona o
Buffer Overflow?
Ao copiar uma quantidade grande de bytes, esses bytes sobrepõem outros valores na memória, tais como o armazenado no registador EBP e EIP.
Como é realizado um ataque contra esse tipo de falha?
O atacante gera um buffer contendo uma string qualquer (geralmente usam NOP) mais um endereço de retorno que, no caso, aponta para onde a execução do programa deve saltar e, por fim, o shellcode (códigos de máquina. Chama-se shellcode, pois na maioria das vezes, é executado um código que retorna um shell ao atacante).
Quando o valor de EIP é sobreposto, o programa desvia sua execução saltando para o código do shellcode e então o código malicioso é executado.
Nesse artigo, irei mostrar formas de prevenir essa falha e alguns métodos conhecidos como cookie, que também ajudam a evitar o Buffer Overflow.
Prevenindo o buffer overflow (métodos tradicionais)
Como já foi explicado anteriormente, a exploração da falha ocorre ao passar uma string com quantidade maior do que o esperado pelo programa. Então, já temos em mente que para evitar a falha, basta que verifiquemos a quantidade de bytes que será copiado, e caso seja maior, copiaremos apenas a quantidade suportada pela variável.
Exemplo
example1.c:
#include <stdio.h>
#include <string.h>
int main(int argc, char** argv)
{
char buffer[128];
if (argc > 1)
strcpy(buffer, argv[1]);
return 0;
}
Temos acima, um programa vulnerável que não está verificando o tamanho da string que será copiada para a variável buffer. Como a verificação não é feita, este programa pode ser explorado facilmente.
Como podemos evitar que ocorra um estouro de buffer nesse programa?
A resolução do problema é simples! Basta que utilizemos uma outra função que faz a copia por tamanho definido por nós. Veja:
#include <stdio.h>
#include <string.h>
int main(int argc, char** argv)
{
char buffer[128];
if (argc > 1)
memcpy(buffer, argv[1], 127);
return 0;
}
Este é o método mais simples de proteção contra Buffer Overflow e é 100% garantido que só será copiado para a variável buffer uma string de tamanho até 127 bytes.
Agora, vamos ver um método que resolve o problema de forma diferente ao anterior.
Iremos copiar toda a string informada pelo usuário, mesmo que a quantidade de bytes seja grande. Precisaremos utilizar alocação dinâmica de memória para que o tamanho da memória reservada para a variável seja do mesmo tamanho da string. Pra trabalhar com alocação dinâmica, iremos fazer uso de ponteiros, o canivete suíço da linguagem
C.
Exemplo
example2.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char** argv)
{
char *buffer;
long long int len;
if (argc > 1)
{
len = strlen(argv[1]) + 1;
buffer = (char*) malloc(sizeof(char) * len--);
memcpy(buffer, argv[1], len);
buffer[len] = '\0';
printf("%d bytes copiados\n", len);
}
return 0;
}
A diferença desse código para o anterior é praticamente de 99%, 1% fica para a linha aonde fizemos a cópia da string. O que fizemos foi obter o tamanho da string que será copiada para buffer, alocamos memória com a quantidade obtida, copiamos a string e, por fim, finalizamos a string (buffer[len] = '\0';).
Este código é tão seguro quanto o anterior, mas existe um problema com esse último exemplo. Quem está decidindo a quantidade de memória a reservar é o usuário e não o programador. Isso é um grande problema.
Para resolvermos isso, basta definirmos um tamanho máximo de bytes que podem ser copiados. Veja:
Exemplo
example3.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_BUFFER 1025
int main(int argc, char** argv)
{
char *buffer;
long long int len;
if (argc > 1)
{
len = strlen(argv[1]) + 1;
if (len > MAX_BUFFER)
{
fprintf(stderr, "String muito longa\n.");
exit(-1);
}
buffer = (char*) malloc(sizeof(char) * len--);
memcpy(buffer, argv[1], len);
buffer[len] = '\0';
printf("%d bytes copiados\n", len);
}
return 0;
}
Prevenindo o buffer pverflow (cookies)
Cookies como são conhecidos, servem para armazenar informações que serão utilizadas em um futuro pouco distante, seja para validação ou para lembrar algo feito anteriormente pelo usuário.
Vamos aplicar este método em nosso programa para verificarmos se o valor do cookie não foi sobreposto. Se o valor for outro, significa que um Buffer Overflow está acontecendo.
Exemplo
example4.c:
#include <stdio.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>
#define MAX_BUFFER 10
#define MAX_COOKIE sizeof(int)
int main(int argc, char** argv)
{
char buffer[MAX_BUFFER + MAX_COOKIE];
static int cookie;
int bkp_cookie;
if (argc > 1)
{
srand(time(NULL));
//Gera um cookie
cookie = rand() % INT_MAX;
//Copia o cookie para o final da string
memcpy(&buffer[MAX_BUFFER], &cookie, MAX_COOKIE);
//Copia a string (Tamanho 125)
strcpy(buffer, argv[1]);
memcpy(&bkp_cookie, &buffer[MAX_BUFFER], MAX_COOKIE);
if (bkp_cookie != cookie)
{
printf("Buffer overflow!!!\n");
printf("Cookie: %d Backup Cookie: %d\n", cookie, bkp_cookie);
exit(-1);
}
else
{
buffer[MAX_BUFFER] = '\0';
printf("String informada: %s\n", buffer);
}
}
return 0;
}
Obs.: isso é só um exemplo! Não estou dizendo aqui que você deve utilizar isto ou que seja a melhor forma.
A melhor forma de evitar falhas de Buffer Overflow, é seguindo os exemplos da página anterior.