Buffer Overflow

Divers Ajouter un commentaire

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 de serveur 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.
De l’importance des attaques par buffer oveflow

Ce problème est important de par 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).
Des conditions d’occurence d’un buffer overflow

Un certain nombre de conditions sont indispensables à l’occurence d’un BOF :

  • code de programmation vulnérable

o langage utilisé permissif (C),
o fonctions utilisées laxistes (strcpy(), strcat(), sprintf(), vsprintf(), gets(), scanf())
o appels à des fonctions privilégiées (system()),
o utilisation de variable donnant des privilèges,
o absence de test du code, etc.

  • programme attaqué s’exécutant avec un niveau de privilège fort (uid root par exemple)
  • lisibilité / accessibilité du code (accès au source ou désassemblage explicite, pas de free(), ni de user_check())
  • ordonnancement des données dans le programme (variable importante ‘écrasable’ , adresse de retour de subroutines ‘écrasable’

mais aussi

  • exposition / intérêt du site
  • ténacité du hacker

exemple de buffer overflow minimal …qui ne sert pas à grand chose !

void f() {
int a[10];
a[20] = 3;
}

sur la plipart des machines, le programme se plante et produit une erreur de segmentation de mémoire, du style : segmentation fault, core dumped…

Exemple simple de Buffer Overflow sur flag de sécurité

int main(int argc, char *argv[]) {
char passwd_ok = 0;
char passwd[8];
strcpy(passwd, argv[1]);
if (strcmp(passwd, “niklas”)==0)
passwd_ok = 1;

if (passwd_ok) {

}

La mémoire ressemble à ca :

bof_variable.jpeg

on comprend bien qu’en envoyant le bon nombre de caractère le Hacker, va écrasé le flag PASSWD_OK et se dispenser de s’authentifier avec un mot de passe correct…

Un exemple idéal (un heap overflow)

D’après Ghost Rider “Introduction to Buffer Overflow”, www.governmentsecurity.org

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 :

  • Il utilise des appels de commande système via la variable ‘important’.
  • Il a (au moins) un paramètre d’entrée (argv[1]) stocké dans la variable ’somevar’.
  • La variable ’someva’r est allouée en mémoire AVANT ‘important’, on peut en déduire que son adresse sera probablement inférieure a celle de ‘important’.
  • Enfin, malgré cette précédence, ’somevar’ prendra sa valeur a l’execution APRES ‘important’

Modifions ce programme pour lui faire imprimer les adresses et les contenus, et executons le en lui passant le parametre ‘TOTO’ :

$mon_programme TOTO

0×8049700: T(0×616c62)
0×8049701: O(0×616c)
0×8049702: T(0×61)
0×8049703: O(0×0)
0×8049704: (0×0)
0×8049705: (0×0)
0×8049706: (0×0)
0×8049707: (0×0)
0×8049708: (0×0)
0×8049709: (0×19000000)
0×804970a: (0×190000)
0×804970b: (0×1900)
0×804970c: (0×19)
0×804970d: (0×63000000)
0×804970e: (0×6f630000)
0×804970f: (0×6d6f6300)
0×8049710: c (0×6d6d6f63)
0×8049711: o (0×616d6d6f)
0×8049712: m (0×6e616d6d)
0×8049713: m (0×646e616d)
0×8049714: a (0×646e61)
0×8049715: n (0×646e)
0×8049716: d (0×64)
0×8049717: (0×0)

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.

0×8049700: T(0×646e6573)
0×8049701: O(0×2d646e65)
0×8049702: T(0×2d2d646e)
0×8049703: O(0×2d2d2d64)
0×8049704: - (0×2d2d2d2d)
0×8049705: - (0×2d2d2d2d)
0×8049706: - (0×2d2d2d2d)
0×8049707: - (0×2d2d2d2d)
0×8049708: - (0×2d2d2d2d)
0×8049709: - (0×2d2d2d2d)
0×804970a: - (0×2d2d2d2d)
0×804970b: - (0×2d2d2d2d)
0×804970c: - (0×2d2d2d2d)
0×804970d: - (0×6e2d2d2d)
0×804970e: - (0×656e2d2d)
0×804970f: - (0×77656e2d)
0×8049710: n (0×6377656e) <— c’est la !
0×8049711: e (0×6f637765)
0×8049712: w (0×6d6f6377)
0×8049713: c (0×6d6d6f63)
0×8049714: o (0×616d6d6f)
0×8049715: m (0×6e616d6d)
0×8049716: m (0×646e616d)
0×8049717: a (0×646e61)
0×8049718: n (0×646e)
0×8049719: d (0×64)
0×804971a: (0×0)

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.

Un exemple un peu moins ideal (un stack overflow)

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.

Les “exploit”s de buffer overflow

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 0×00 comme une fin de chaîne ;-((

Les moyens d’actions

  • Utiliser des langages ’sécurisés’ : Cyclone ou Java Vs C, strncpy() Vs strcpy()
  • tester le code avec des outils spécialisés (Flawfinder, BFBtester, StackGuard)
  • Patcher !

Pour plus de détails sur certains de ces aspects on pourra consulter la note du RSA : Countermeasures Againt BOF attacks ici : http://www.rsa.com/rsalabs/node.asp?id=2011

Faire un commentaire