Ceci est une ancienne révision du document !
Je rédige ce papier afin de combler un manque cruel (à mon sens) de documentation à propos de ces failles. L’essentiel des tutos/papiers que l’on trouve sur le net sont en anglais, ce qui ne facilite pas la compréhension du sujet. Pour les quelques rares documents rédigés en français, ils sont bien souvent très mal tournés (je trouve).
Je vais donc tenter d’expliquer le principe des Format String de la façon dont j’aurais aimé qu’on le fasse au moment ou j’ai moi-même appris.
SOMMAIRE
Ces fonctions ont la particularité d'utiliser une technique un peu spéciale pour traiter les données qu'elles manipulent. En effet, elles se basent sur des “formateurs”, pour interpréter leurs arguments et les mettre en forme. Voici un exemple :
#include <stdio.h> int main() { int nombre = 5; printf("L'adresse de la variable nombre est %x ou encore %d, et sa valeur est %d.\n", &nombre, &nombre, nombre); return 0; }
warr@debian:~$ gcc printf.c -o printf warr@debian:~$ ./printf L'adresse de la variable nombre est bffffd60 ou encore -1073742496, et sa valeur est 5.
Pour l'instant, juste quelques mots sur les formateurs afin de bien comprendre leur fonctionnement. Chaque formateur placé dans une chaine remplace une valeur codée sur 4 octets (généralement). Cela signifie qu'au moment de l'exécution, les variables à afficher ont été placées sur la pile et que la fonction printf() parcourt cette dernière 4 octets par 4 octets pour trouver les valeurs qu'elle doit afficher, puisque ces valeurs sont stockées dans l'ordre qui a été spécifié dans le programme. Nous allons nous arrêter ici pour le moment en ce qui concerne les formateurs.
Le programme ci-dessous sera l’exemple utilisé tout au long de l’article.
#include <string.h> #include <stdio.h> int main(int argc, char**argv) { char msg[1024]; if (argc < 2) { printf("Usage : entrez une chaine a afficher\n"); return -1; } strncpy(msg, argv[1], 1023); msg[1023] = 0; printf(msg); printf("\n"); return 0; }
[root@VmZenk:~/tests]$ ./vuln Yop Yop
Bon pas de surprise. La variable « msg » a été remplacée par notre argument et printf() l’a affichée. Voyons autre chose :
[root@VmZenk:~/tests]$ ./vuln %x%x%x%x bffffea23fff078257825
Ou encore :
[root@VmZenk:~/tests]$ ./vuln %s%s%s%s Erreur de segmentation
Prenons le premier cas. On passe en argument à notre programme des formateurs. Le programme se retrouve donc à exécuter la chose suivante en arrivant à la ligne printf(msg); :
printf("%x%x%x%x");
Seulement vous avez remarqué que la fonction printf() n'a pas d'arguments pour remplacer les formateurs dans la chaine. Elle affiche donc ce qu'elle a sous la main, c'est à dire les valeurs présentes sur la pile. C'est une première chose embêtante, par exemple dans le cas d'un programme utilisant un système de login ou de mots de passe, on pourrait se servir de cette technique pour les afficher à l'écran. Mais les possibilités sont bien plus alléchantes que ça.
Dans le deuxième cas, le programme se termine en erreur de segmentation, et cela est dû au fait que le formateur %s fonctionne différemment par rapport au %x du premier cas. Nous allons tout de suite expliquer cette différence de fonctionnement.
Nous avons vu précédemment, et brièvement ce qu'étaient et à quoi servaient les formateurs. A présent nous allons devoir les classer en deux catégories, et comprendre impérativement le pourquoi du comment, et ce qui les différencie. C'est selon moi la partie la plus importante et délicate à comprendre.
Les deux catégories seront donc les formateurs “pointeurs”, et les formateurs “directs”. Les noms des catégories sont maisons je le précise, il y a peu de chance que vous retrouviez ces termes autre part.
Reprenons le premier des deux exemples ci dessus, afin de décrire son fonctionnement :
[root@VmZenk:~/tests]$ ./vuln %x%x%x%x bffffea23fff078257825
Le formateur %x est donc un formateur “direct” pour la raison suivante : il affiche tout simplement la valeur qu'il trouve sur la pile. Voyons ça avec une petite représentation de la pile. Ne vous inquiétez pas, les valeurs de la pile correspondent à l’affichage sauf que dans le terminal les 0 sont tronqués.
PILE ____________ 1er %x ->[ bffffea2 ] <- %x cible la valeur bffffea2 sur la pile, donc affichage : "bffffea2" dans le terminal. 2eme %x ->[ 000003ff ] <- %x cible la valeur 000003ff sur la pile, donc affichage : "000003ff" dans le terminal. 3eme %x ->[ 000000f0 ] <- %x cible la valeur 000000f0 sur la pile, donc affichage : "000000f0" dans le terminal. 4eme %x ->[ 78257825 ] <- %x cible la valeur 78257825 sur la pile, donc affichage : "78257825" dans le terminal. [ XXXXXXXX ] ------------
Vous devez donc retenir que le formateur %x affiche donc simplement la valeur qu'il cible sur la pile, sans se préoccuper de quoi que ce soit d'autre.