Un buffer overflow (BOF) est littéralement un dépassement de
la capacité d'un buffer de données. Rien de bien méchant,
cela arrive fréquement lorsque l'on manipule des pointeurs et que l'on
programme sans trop de précautions.
La conséquence est généralement un 'crash' du programme,
qui tente un accès à des zones en dehors de son espace d'adressage
(donne des segmentation fault ou protection fault).
La
saturation ou le crash deserveur n'est généralement le but ultime
des hackers. L'acquisition de privilèges ou l'ouverture de backdoor est
plus souvent ciblée.
Dans le domaine de la sécurité cette technique du BOF est utilisée pour 'dérouter' la séquence normale des instructions d'un programme et le forcer à éxécuter une routine spécifique, permettant généralement l'obtention de droits 'super utilisateur'. Cette routine ou ce programme exploitant la faille est appelé un "exploit" (en Anglais dans le texte)
'droits
super utilisateur' ne veut pas dire seulement obtention de l'UID 'root' ou 'Administrateur'.
Beaucoup d'applications utilisent des variables ou des tables pour gérér
en interne des niveaux de privilèges indépendemment de l'OS. Des
flags du type 'useradmin=1' ou 'loginok=true' ou 'privileges=ALL' sont monnaie
courante dans les programmes et beaucoup plus facilement exploitables.
Ce problème est important de part sa notoriété et également
de par sa persistance. Les premières mises en évidence de la possibilité
de ce type d'attaque datent de 1995 et sont toujours d'actualité.
Statistiquement si l'on consulte les sites de référérence
que sont le site du Cert et
le site d'infosys security par exemple, on constate qu'une part importante
des pbs traités concerne le BOF. Voir plus particulièrement le
rapport du dernier trimestre
2003 du CERT.
bien
qu'abondamment documenté sur le web, ce type d'attaque nécessite
une grande ténacité et de bonnes compétences en programmation
de bas niveau (assembleur), en désassemblage (les sources sauf cas ideal
de certains logiciels libres n'étant pas à la disposition des
hackers) et en gestion de la mémoire et architecture système (structure
et adressage de la pile, contenu des bibliothèques sytème notamment).
Un certain nombre de conditions sont indispensables à l'occurence d'un BOF :
mais aussi
Soit le petit programme C suivant
main(int argc, char **argv) {
char *somevar;
char *important;
somevar = (char *)malloc(sizeof(char)*4);
important = (char *)malloc(sizeof(char)*14);
strcpy(important, "command"); /*This one is the important variable*/
stcrpy(somevar, argv[1]);
..... Code here ....
}
Il présente qq caractéristiques remarquables :
Modifions ce programme pour lui faire imprimer les adresses et les contenus, et executons le en lui passant le parametre 'TOTO' :
$mon_programme TOTO
0x8049700: T(0x616c62)
0x8049701: O(0x616c)
0x8049702: T(0x61)
0x8049703: O(0x0)
0x8049704: (0x0)
0x8049705: (0x0)
0x8049706: (0x0)
0x8049707: (0x0)
0x8049708: (0x0)
0x8049709: (0x19000000)
0x804970a: (0x190000)
0x804970b: (0x1900)
0x804970c: (0x19)
0x804970d: (0x63000000)
0x804970e: (0x6f630000)
0x804970f: (0x6d6f6300)
0x8049710: c (0x6d6d6f63)
0x8049711: o (0x616d6d6f)
0x8049712: m (0x6e616d6d)
0x8049713: m (0x646e616d)
0x8049714: a (0x646e61)
0x8049715: n (0x646e)
0x8049716: d (0x64)
0x8049717: (0x0)
On connait désormais le décalage d'adresse entre 'somevar' et
'important'.
Le C et strcpy() le permettant, on va se permettre un dépassement de
buffer de la variable 'somevar' pour substituer NOTRE commande à la 'command'
d'origine.
0x8049700: T(0x646e6573)
0x8049701: O(0x2d646e65)
0x8049702: T(0x2d2d646e)
0x8049703: O(0x2d2d2d64)
0x8049704: - (0x2d2d2d2d)
0x8049705: - (0x2d2d2d2d)
0x8049706: - (0x2d2d2d2d)
0x8049707: - (0x2d2d2d2d)
0x8049708: - (0x2d2d2d2d)
0x8049709: - (0x2d2d2d2d)
0x804970a: - (0x2d2d2d2d)
0x804970b: - (0x2d2d2d2d)
0x804970c: - (0x2d2d2d2d)
0x804970d: - (0x6e2d2d2d)
0x804970e: - (0x656e2d2d)
0x804970f: - (0x77656e2d)
0x8049710: n (0x6377656e) <--- c'est la !
0x8049711: e (0x6f637765)
0x8049712: w (0x6d6f6377)
0x8049713: c (0x6d6d6f63)
0x8049714: o (0x616d6d6f)
0x8049715: m (0x6e616d6d)
0x8049716: m (0x646e616d)
0x8049717: a (0x646e61)
0x8049718: n (0x646e)
0x8049719: d (0x64)
0x804971a: (0x0)
C'est la le miracle : on peut modifier le déroulement de l'exécution d'un programme sans en modifier le code (heureusement il faudrait le recompiler et reinstaller l'executable sur la cible). Le paramètre d'entrée est le seul point...d'entrée du programme et on peut faire une subsitution de code, si tant est que certains appels se fassent à travers des variables et qu'elles soient correctement 'rangées' en mémoire.
D'après airWalk, "Introduction to buffer overflows" - for interScape, may 1999
Soit le programme C suivant :
void someFunction(char *str) {
char buffer[16];
strcpy(buffer, str);
}
void main()
{
char bigString[256];
int i;
for( i = 0; i < 255; i++)
bigString[i] = 'A';
someFunction(bigString);
}
A l'exécution il provoque une erreur du type : 'Segmentation violation'
.
Pourquoi ? Parce que c'est au sein, et plus précisément juste
avant le 'return' de la fonction qu'a lieu un dépassement de buffer.
Les 240 'A' supplémentaires vont écraser les zones mémoires
suivant la fin de la variable 'buffer' , et en particulier l'adresse de retour
de la fonction, en y subsituant une valeur (pleine de 'A') invalide...d'ou l'erreur
sus nommée.
On perçoit que dans ce cas, si l'on si prend bien il est possible de
modifier l'adresse de retour d'une fonction et donc modifier le déroulement
de l'exécution d'un programme.
Reste maintenant un détail, écrire une exploit capable de menacer le système. A la différence de l'exemple précédent qui faisait des appels à des commandes système, que l'on remplaçait, celui ci n'en fait pas...c'est son coté moins idéal.
rappelons
que "l'exploit" du programme est souvent conditionnée par le
niveau de privilège d'exécution du programme hacké. Un
uid root (ou son équivallent Windows administrateur) permet + facilement
d'executer des commandes fatales.
Nous savons comment on peut derouter un programme en écrivant l'exacte
quantité de mémoire et en écrasant l'adresse de retour
de la routine par une nouvelle adresse. Rest e à faire exécuter
un nouveau code.
Ou peut se trouver le nouveau code agressif ?
ll est impossible de modifier physiquemnt le programme compilé de la
cible. Le nouveau code ne peut qu'être passé qu'AU SEIN des données
modifiées responsables du buffer overflow !
La taille du code ne correspondant pas forcément exactement à
l'offset entre début de buffer et adresse de retour à écraser,
précédera le code par des NOPs.
Que contient le code agressif ?
L'hypothèse étant que le programme vulnérable a un niveau
de privilège intéressant (root), une des exploits les + classiques
sera de lancer un shell permettant d'executer des commandes 'intéressantes'
: rm -R /*, cat /etc/passwd, mail, etc.
Le code sera d'abord écrit en C, compilé , linké (gcc)
et désassemblé (avec gdb). Les codes hexadécimaux du programme
seront ensuite passés directement dans le buffer.
Si
certains octets du code sont à zero, certaines des fonctions vulnérables
(comme strcpy()) risquent d'interpréter le 0x00 comme une fin de chaîne
;-((
Pour plus de détails sur certains de ces aspects on pourra consulter la note du RSA : Countermeasures Againt BOF attacks
|