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 celui-ci, 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, 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.
Voici le code :
- //irc-logger.c : Bot de log IRC
- #include <stdio.h> //Nécessaire au FILE
- #include <netdb.h> //Bloc Fonctions Sockets
- #include <arpa/inet.h>
- #include <netinet/in.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <sys/ioctl.h>
- #define h_addr h_addr_list[0]
- typedef enum { false, true } bool; //Definition du type booléen
- int vrecv(int); //Déclaration des prototypes des fonctions utilisées
- bool debloq_sock(int);
- bool cnect(int,char *,int);
- int recu(int,char *);
- void erreur(void);
- int main() {
- int sock = socket(AF_INET, SOCK_STREAM,0); //Identifiant de la connexion
- int bytesRecv; //Nombre de bytes reçues pendant un envoi du serveur distant
- int port; //Port
- if (cnect(sock,serveur,port) > 0) { //Si la connexion a réussi...
- while (1) //Reception de tous les messages jusqu'à déconnexion (Ping Timeout)
- switch(recu(sock,bufferRecv)) { //A-t-on reçu quelque chose ?
- case -2: //La connexion n'est pas active
- case -1: break; //Non
- default: //Oui, on l'écrit (+ traitement des données)
- }
- }
- else erreur();
- close(sock); //On ferme la connexion
- return 0;
- }
- bool cnect(int sock,char *hote,int port) { //Fonction de connexion à un serveur et un port donné
- if (sock > 0) {
- struct sockaddr_in clientService;
- clientService.sin_family = AF_INET;
- struct hostent *HostInfo;
- if (HostInfo = gethostbyname(hote)) { //Résoud le nom d'hôte
- struct in_addr **a;
- a=(struct in_addr **)HostInfo->h_addr_list;
- if (*a) {
- clientService.sin_addr.s_addr = inet_addr(inet_ntoa(**a));
- clientService.sin_port = htons( port );
- }
- }
- if ( connect( sock,(struct sockaddr*) &clientService, sizeof(clientService) ) == -1) { //Connexion
- close(sock);
- return false;
- }
- else if (debloq_sock(sock)) return true;
- else {
- return false;
- }
- }
- else printf