Suite au Wargame #2, on obtient un accès ssh vers le 3ème wargame. Là encore il s'agit d'un programme avec des droits suid.
Lançons-le :
./prog.bin
***************************************************
*** Bienvenue dans la gestion d'éléments ***
*** ***
*** NB : Taille de l'ID : 50 octets ***
*** Vous pouvez mettre la taille ***
*** en paramètre pour la changer ***
***************************************************
Que voulez-vous faire ?
-> 1) nouvel élément
-> 2) affichage
-> 3) détruire un élément
-> 4) changer de nom
-> 5) changer d'id
-> 6) sortie
choix $
On est face à un challenge typique de ceux traitant des problèmes d'allocation mémoire dynamique ptmalloc2. Comme c'est indiqué lorsqu'on lance le programme, nous avons la possibilité de changer la taille de l'ID en passant un entier en paramètre. Voici ce que fait chaque élément du menu :
malloc(0x10)
pour stocker un élément qui contient juste deux pointeurs. On nous demande alors le nom de l'élément. Lemalloc
effectué ensuite dépends alors de la taille de la chaîne rentrée par l'utilisateur. On nous demande ensuite l'id de l'élément. Lemalloc
effectué est alors fixe (en fonction du paramètre passé ou 0x32 par défaut)- Affichage du contenu de chaque élément.
- Appel à
free()
soit sur le nom, soit sur l'id. - Réécriture du nom. le nouveau nom ne peut pas excéder 8 octets. Pas d'appel à
malloc()
. - Nouvel id. le nouvel id ne peut pas dépasser la taille de l'id. Pas d'appel à
malloc()
. - Fin du programme
Le problème est qu'aucune vérification n'est faite lorsque l'on fait un free()
pour savoir si la mémoire allouée à cet endroit n'a pas déjà été libérée et qu'aucune vérification n'est faite lors de l'affichage pour savoir si ce qu'on affiche n'est pas de la mémoire libérée.
On peut donc exploiter ce binaire en réalisant une attaque du type double free.
Là encore j'ai réalisé un script python qui se charge d'exploiter cette vulnérabilité. Voilà ce qu'il fait :
- Lancement du programme avec 15 en paramètre afin que tous les malloc() aient la même taille.
- Création d'un élément (3
malloc()
) - Création d'un second élément (3
malloc()
) - Destruction du nom du premier élément =>
free()
sur la mémoire correspondant au 2ememalloc()
- Destruction de l'id du premier élément =>
free()
sur la mémoire correspondant au 3ememalloc()
- Destruction à nouveau du nom du premier élément =>
free()
à nouveau sur la mémoire correspondant au 2ememalloc()
- Création d'un 3ème élément : 3
malloc()
. Le 1er et le 3ememalloc()
renvoient les mêmes adresses à cause du doublefree()
. Pour cet élément on va passer l'adresse de la GOT defree()
en nom et id. Après le troisièmemalloc()
, l'adresse de la got defree()
est écrite à la place de l'id (normal). Le truc c'est que l'endroit où le programme écrit est aussi celui où sont stockés les pointeurs vers les chaînes nom et id. Ces pointeurs sont alors écrasés par l’adresse de la got. - Affichage des éléments. Lors de l'affichage de l'id du 3ème élément, c'est l'adresse réelle de la fonction
free()
dans la libc qui est affiché. Mon script calcule l'adresse desystem()
à partir de l'adresse leakée defree()
. - Renommage du nom du second élément en "/bin/sh"
- Renommage du nom du troisième élément par l’adresse de
system()
. En fait à cet endroit on réécrit l’adresse defree()
dans la GOT - Destruction du nom du second élément. Le programme va appeler
free()
sur le nom. Or l’adresse defree()
a été réécrite par celle desystem()
et le nom contient "/bin/sh". C'est doncsystem("/bin/sh")
qui est exécuté. On obtient un shell et on peut afficher le contenu du dernier drapeau :)
Voilà c'est terminé, il n'y a plus d'autre épreuve. C'était un ensemble de challenge bien sympathique et au final pas très compliqué. J'espère que mes explications auront été claires.