THM9/Exploiter les format bugs dans le tas (2)
Un article de HackaWiki.
Un format bug plus vicieux
La technique de l'article précédent était encore simple comparé à ce qu'il faut faire lorsque les contraintes sont plus fortes : pile et tas non exécutables, environnement et arguments inutilisables, etc... Avis aux amateurs !
Elite
By ezekiel
Jouons avec la pile
Pour ce deuxième exemple, nous allons étudier le programme vuln2 du HRChallenge de Sauron (qui n'est plus en ligne, malheureusement). Voyons, sans plus attendre, le code du programme vulnérable :
------------ heapfmt2.c ------------
/*
** Hrchallenge vuln n° 2
** It is a blind format string, heap based.
** Must work on stack/heap non-exec !
*/
#include <stdio.h>
#include <string.h>
extern char ** environ;
char buf[1024];
void vuln(char * str){
snprintf(buf, 1024, str);
}
void truc(char * str){
vuln(str);
}
int main(int ac, char **av){
char *buf;
int i, j;
if (ac != 2)
return (0);
buf = strdup(av[1]);
for (i = 0; i < ac; i++)
for (j = 0; av[i][j]; av[i][j++] = 0);
for (i = 0; environ[i]; i++)
for (j = 0; environ[i][j]; environ[i][j++] = 0);
truc(buf);
return (0);
}
---------------------------------------------
La première différence par rapport au programme vulnérable de l'article précédent, c'est que l'on ne peut plus utiliser l'environnement, ni les arguments pour passer le shellcode à l'exploit. En fait, le seul élément réellement maîtrisable est la chaîne de format elle-même.
Cependant si l'on y met le shellcode, la chaîne de format étant dans le tas, il va nous falloir récrire plus de 2 octets sur la pile. En effet, dans l'exemple précédent, on n'écrivait que les octets de poids faible car les 2 octets de poids fort avaient déjà la valeur dont nous avions besoin (0xbfff). Les adresses se situant dans le tas sont généralement de la forme 0x0804XXXX sous Linux.
Jusqu'ici, on utilisait les pointeurs de frame, car ils avaient la propriété de pointer les uns sur les autres. Dans cet exemple, on n'a pas assez de pointeurs de frame pour générer les 2 pointeurs dont on a besoin. On va donc devoir utiliser autre chose. Il nous faut trouver quelque chose qui a la même propriété que les pointeurs de frame, dans le sens où on doit avoir quelque chose se trouvant dans la pile et un pointeur sur ce quelque chose situé aussi dans la pile, tout cela accessible par la chaine de format évidement. On va utiliser un pointeur sur argv[0] qui se trouve dans le bas de la pile. Nous aurons donc besoin ici des adresse des frame A et B, de argv[0], d'un pointeur sur argv[0], ainsi que celle de la chaîne de format.
Illustration (même page que le paragraphe suivant, ou même double, à la rigueur) Légende : Figure 1 Image heapfmt2_figure1.jpg Légende : Figure 2 Image heapfmt2_figure2.jpg Légende : Figure Image heapfmt2_figure3.jpg Légende : Figure Image heapfmt2_figure4.jpg
Voyons comment va se dérouler l'exploitation pas à pas. Comme tout à l'heure, la figure 1 représente l'état de la pile avant la conversion de la chaine de format. On va poper les valeurs de la pile jusqu'a arriver à la frame A que l'on va utiliser pour faire pointer la frame B sur la frame A (Fig.2). Ensuite, on va poper les valeurs de la pile jusqu'à arriver à la frame B,qui pointe sur les bits de poids faible de la frame A, pour écrire les 2 octets de poids faible de notre payload (Fig.3). Il nous reste à utiliser argv[0] et son pointeur pour écrire les 2 octets de poids fort. Pour cela, on pope les valeurs de la pile jusqu'à arriver à l'adresse d'argv[0] et on fait pointer argv[0] sur le MSB de la frame A (ses 2 octets de poids fort, Fig.4). Et enfin on pope jusqu'à argv[0] et on l'utilise pour écrire les 2 octets de poids fort de notre payload à l'endroit où l'on avait les octets de poids fort de la frame A.
On aurait pu directement écraser un saved eip et jumper sur un shellcode qu'on aurait mis au début de la chaine de format mais tout cela doit fonctionner sur un système avec pile et tas non exécutables (souvenez vous du "Must work on stack/heap non-exec !"). On va donc exploiter le programme vulnérable en return-into-libc en utilisant la technique bien connue de l'esp-lifting (si cette technique vous est inconnue, référez vous à l'article de gangstuck dans "the hackademy manuel #5"). Nous devrons donc récupérer en plus de nombreuses autres adresses spécifiques au programme vulnérable et au système cible, dont notamment des adresses de fonctions.
Comme dans l'exemple précédent, on va utiliser un "faux exploit". Pour récupérer l'adresse du premier argument (i.e. argv[0]), on va mettre un breakpoint sur une instruction qui suit la création de la frame de la fonction main.
Breakpoint 1, 0x080484c2 in main (ac=2, av=0xbffffb54) at heap_fmt2.c:23
l'adresse de argv[0] est donc 0xbffffb54 =). On place ensuite un breakpoint après l'appel à snprintf. Un fois le programme stoppé à nouveau, on récupère les adresses de frames :
(gdb) info frame Stack level 0, frame at 0xbffffa9c: eip = 0x804849c in vuln (heapfmt2.c:14); saved eip 0x80484b6 called by frame at 0xbffffabc source language c. Arglist at 0xbffffa9c, args: str=0x8049c08 'A' <repeats 200 times>... [...]
La frame A est donc à l'adresse 0xbffffa9c. La frame B sur laquelle elle pointe est en 0xbffffabc. Et on note au passage l'adresse de la chaîne de format :0x8049c08.
Il nous reste à trouver un pointeur sur argv[0], que l'on trouve facilement en farfouillant dans la pile.
Maintenant que l'on a toutes les informations nécéssaires, à vous d'écrire le code de l'exploit ;). La solution sera donnée dans un prochain article dans lequel on décrira une technique d'exploitation similaire à celle étudiée dans cet article mais dans laquelle on fait boucler le programme vulnérable afin de pouvoir utiliser plusieurs fois le même bug de chaîne de format.
