# Format String Bug sur GNU/Linux
Format String Bug sur GNU/Linux
Introduction
Les format string bugs sont des vulnérabilités qui peuvent se produire lorsque le programmeur passe à l'une des fonctions de la famille de printf une chaîne de caractères / une string fournie par l'utilisateur.
L'utilisateur peut alors fournir une chaîne de caractères contenant des spécificateurs de format / des paramètres de formatage.
Par exemple, si des "%x" se trouvent dans cette chaîne de caractères et qu'aucun argument à printf n'est donné, alors la fonction printf prendra comme argument ce qui se trouve empilé sur la stack.
Il est donc possible de lire la stack avec plusieurs "%x".
Rappel: "%x" affiche sous forme hexadécimal.
Même si la fonction printf est dans la plupart des cas utilisées pour lire ou afficher la valeur d'une variable, elle est aussi capable d'y écrire grâce au formateur "%n".
Rappel: "%n" stocke le nombre de caractères déjà écrits ou affichés dans l'argument correspondant.
Exemple de ces deux formateurs:
|
|
Ce qui, une fois compilé et exécuté, nous donnes:
|
|
Le formateur "%x" a fait afficher la valeur du caractère 'a' en hexadécimal, soit 0x61. Le formateur "%n" a bien écrit dans la variable i le nombre 4 qui correspond bien aux quatre caractères affichés ("1234").
Exemple de lecture et d'écriture
Note: Les exemples qui vont suivre ont été réalisés sur une Debian GNU/Linux 3.1 (Sarge) i386.
Nous allons voir maintenant comment modifier la valeur d'une variable d'un programme grâce à un format string bug.
Voici le code source du programme vulnérable (vuln.c):
|
|
La compilation de ce code source:
$ gcc vuln.c -o vuln
Pour commencer, il nous faut connaître la position de la variable buf sur la stack.
On y parvient en dépilant avec le formateur "%x" les valeurs de la stack jusqu'à retrouver notre string passée en argument.
Prenons comme argument "ABCD" et utilisons une boucle for en shell script avec un grep sur 44434241 qui représente ABCD en hexadecimal:
|
|
Nous pouvons voir que le début de notre variable buf se trouve en huitième position, vérifions:
|
|
Nous avons bien notre string "ABCD". C'est OK.
Maintenant, nous allons changer la valeur de la variable var qui est de type int et dont l'adresse s'affiche: 0x8049778.
Il faut donc écrire avec "%n" à cette adresse:
|
|
La valeur de la variable var est maintenant de 4.
Ceci, car nous avons affiché quatre caractères: '\x78' + '\x97' + '\x04' + '\x08'.
Mais nous pouvons très bien lui donner n'importe quelle valeur.
Exemple avec la valeur 500:
500 - 4 ('\x78' + '\x97' + '\x04' + '\x08') = 496
|
|
Nous savons donc donner une valeur à une variable, voyons maintenant comment lui donner une adresse.
Nous allons donner à la variable var l'adresse 0xdeadbeef.
Il y a deux façons d'y parvenir:
- byte par byte en commençant par ceux de poids faible
- word par word en commençant par celui de poids faible
Écriture byte par byte
En x86_32, une adresse est composée de quatre bytes: 0xde, 0xad, 0xbe, 0xef.
Il faut donc écraser ces quatre adresses: 0x8049778, 0x8049778+1, 0x8049778+2 et 0x8049778+3.
Il est nécessaire d'utiliser le spécificateur de format "%hhn" avec "%nc" ou n correspond au nombre de char à écrire.
Nous devons commencer par les bytes de poids faible:
|
|
Donc, en premier 0xad, puis 0xbe, suivi de 0xde, et pour finir 0xef.
Pour le premier byte: 0xad, seize bytes sont déjà placés. Ces bytes correspondent à nos adresses 0x8049778, 0x8049778+1, 0x8049778+2 et 0x8049778+3, soit: "\x78\x97\x04\x08", "\x79\x97\x04\x08", "\x7a\x97\x04\x08" et "\x7b\x97\x04\x08".
|
|
Sur les architectures x86, on est sur du little endian Pour écrire 0xdeadbeef, il faut donc commencer par 0xef, puis 0xbe, suivi de 0xad, et pour finir 0xde.
Nous avons donc:
- adresse de buf + 0 soit à la position %8 = 0xef
- adresse de buf + 1 à la position %9 = 0xbe
- adresse de buf + 2 donc à la position %10 = 0xad
- adresse de buf + 3 à la position %11 = 0xde
Voici notre exploit:
|
|
\o/
Écriture word par word
En x86_32, une adresse est composée de deux words: 0xdead et 0xbeef.
Il faut donc écraser ces deux adresses: 0x8049778, 0x8049778+2.
Il est nécessaire d'utiliser le spécificateur de format "%hn" avec "%nu" ou n correspond au nombre (unsigned int) à écrire.
Comme pour l'écriture byte par byte, nous devons commencer par le word de poids faible:
|
|
Pour le premier word: 0xbeef, huit bytes sont déjà placés. Ces bytes correspondent à nos adresses 0x8049778 et 0x8049778+2 soit: "\x78\x97\x04\x08" et "\x7a\x97\x04\x08".
|
|
Sur les architectures x86, on est sur du little endian Pour écrire 0xdeadbeef, il faut donc commencer par 0xbeef, puis 0xdead.
Nous avons donc:
- adresse de buf + 0 soit à la position %8 = 0xbeef
- adresse de buf + 1 donc à la position %9 = 0xdead
Voici notre exploit:
|
|
Exploitation avec shellcode
Je vais démontrer ici deux types d'exploitations de format string bug avec shellcode.
Je vais utiliser un shellcode provenant de Metasploit installé sur Kali GNU/Linux
|
|
Exploitation avec DTORS - Destructors
.dtors est une section créée par le compilateur GCC qui sert de destructeur. La section utilisée comme constructeur est appelée .ctors.
Cette section .dtors est une table de fonctions. Ces fonctions sont appelées juste après la sortie de main().
Voici à quoi ressemble la section .dtors pour notre programme vulnérable:
|
|
La section .dtors étant accessible en écriture, il est possible d'écraser une adresse contenue dans cette section par une adresse pointant sur notre shellcode. Nous allons donc écraser la dernière adresse de la table des fonctions de la section .dtors. Cette dernière adresse peut être trouvée grâce à l'outil "nm":
|
|
DTOR_LIST représente le début de la section et DTOR_END la fin. Il nous faut donc écraser l'adresse de DTOR_END, soit 0x08049748.
Pour commencer, nous allons détourner le flux d'exécution du programme vuln pour faire pointer le registre EIP sur l'adresse 0xdeadbeef afin d'obtenir un Segmentation fault.
$ ulimit -c unlimited
|
|
|
|
Maintenant que nous arrivons à détourner le flux du programme, il faudrait faire en sorte qu'il jump sur notre shellcode. Dans cet exemple, le shellcode sera placé dans une variable d'environnement:
|
|
Pour trouver l'adresse sur laquelle jumper nous allons utiliser GDB, reprendre l'exemple plus haut avec 0xdeadbeef, puis chercher dans la stack les 0x90, les NOPs.
|
|
Nous allons utiliser l'adresse 0xbffffbec pour sauter dans les NOPs et finir par exécuter le shellcode:
Cherchons d'abord nos valeurs:
|
|
Ce qui nous donnes (word par word):
|
|
W00t!
Exploitation avec GOT - Global Offset Table
La GOT pour Global Offset Table est une table qui contient les adresses réelles des fonctions partagées utilisées par un programme. On obtient ces adresses grâce à l'outil objdump:
|
|
Dans le code source de notre programme vulnérable, nous pouvons voir qu'après l'appel du printf qui engendre le format string bug, c'est encore la fonction printf qui est appelée.
Dans la sortie de l'outil objdump, nous pouvons voir que la fonction printf se trouve à l'adresse 0x08049764. C'est cette adresse qu'il nous faut écraser.
Pour commencer, nous allons détourner le flux d'exécution du programme vuln en écrasant l'adresse de printf, soit l'adresse 0x08049764 pour faire pointer le registre EIP sur l'adresse 0xdeadbeef afin d'obtenir un Segmentation fault.
Après le segfault, nous en profiterons pour choisir l'adresse à utiliser pour jumper sur nos NOPs:
|
|
$ ulimit -c unlimited
|
|
|
|
Nous allons utiliser l'adresse 0xbffffb6c pour sauter dans les NOPs et finir par exécuter le shellcode:
Cherchons d'abord nos valeurs:
|
|
Ce qui nous donnes (word par word):
|
|
W00t! again