Je me rends compte du fonctionnement d'un débordement de tampon, mais j'ai du mal à comprendre la direction dans laquelle le débordement est dirigé. Donc, si la pile croît vers le bas , cela signifie que l'adresse de retour est au-dessus l'espace réservé à la variable. Lorsque cette variable est maintenant débordée, ne devrait-elle pas écraser la mémoire en dessous au lieu d'en haut?
tl; dr: si la pile croît vers le bas, comment un débordement de tampon peut-il écraser le contenu au-dessus de la variable?
Si la pile croît vers le bas, les fonctions appelées plus tard obtiennent des trames de pile à des adresses mémoire inférieures. De plus, l'adresse de retour est poussée vers la pile avant que l'espace pour les variables locales ne soit réservé, donc l'adresse de retour obtient une adresse supérieure par rapport aux variables locales. Mais les tableaux et les tampons sont toujours indexés vers le haut dans la mémoire, donc l'écriture après la fin du tableau atteindra bien l'adresse de retour suivante sur la pile.
Exemple, avec obligatoire ASCII art:
Considérez la fonction triviale qui prend l'entrée d'une source non fiable et la copie dans un tampon local:
void foo(char *s)
{
char buf[8];
strcpy(buf, s);
return;
}
La pile ressemble un peu à ceci:
<---- stack grows to the left
memory addresses increase to the right -->
0x8000 0x8010
+--------+----------+---------++------------
+ buf[8] | ret addr | char *s || .......
+--------+----------+---------++--------------
<--------- foo() -----------> <---- caller --
La pile est remplie de droite à gauche, en commençant par les arguments de la fonction, puis l'adresse de retour, puis les sections locales de la fonction. Il est facile de voir qu'un simple débordement de buf
vers des adresses croissantes frappera bien l'adresse de retour.
Alors, que se passe-t-il si la pile est inversée et qu'elle augmente vers le haut? Ensuite, le débordement d'un tampon s'exécutera de la même manière que la pile se développe, vers la partie vide de la pile.
Ça a l'air sympa, mais cela n'aide pas si foo()
appelle une autre fonction pour faire la copie. Ce qui n'est pas inhabituel, je viens de le faire avec le strcpy
. Maintenant, la pile ressemble à ceci:
stack grows to the right -->
memory addresses increase to the right -->
0x8000 0x8010
------------++---------+----------+---------++-----------+-------------+
.... || char *s | ret addr | buf[8] || ret addr | locals ... |
------------++---------+----------+---------++-----------+-------------+
caller ---> <-------- foo() -------------> <---- strcpy() ---------->
Maintenant, le dépassement (à droite) du tampon dans le cadre de pile de foo()
écrasera bien l'adresse de retour de strcpy()
, pas foo()
. Peu importe, cependant, nous sautons toujours à un emplacement défini par les données débordantes contrôlées par les attaquants.
La pile ne croît que lorsque quelque chose y est alloué. D'un autre côté, lire la fin d'un tableau signifie lire vers le haut en mémoire.
Disons que le pointeur de pile indique 0x1002. À cette adresse se trouve un pointeur de retour qui vous tient à cœur. Si vous allouez, disons, un tableau de 2 octets, la pile se développe vers le bas: le pointeur de pile est changé en 0x1000, et l'adresse du tableau est définie sur cette nouvelle valeur. L'écriture du premier octet dans le tableau utilise l'adresse 0x1000 et l'écriture du second utilise 0x1001. La suppression de la fin utilise 0x1002 et remplace la chose qui compte.
Vous pouvez le voir dans les images que vous avez publiées, car la boîte grise représentant le tampon B grandit à mesure qu'elle est écrite au-delà de ses limites.
Il existe une différence entre le débordement de tampon et le débordement de pile. Les tampons alloués ne peuvent pas utiliser la pile, mais le tas. Cela dépend de la façon dont ils sont alloués et de ce que le compilateur serait meilleur/plus rapide/etc.
Un débordement de pile remplace en effet la mémoire ci-dessous, qui peut avoir été affectée à un autre appel (antérieur) ou finalement au tas. Le tas croît vers le haut et à un moment donné, ils peuvent entrer en collision. C'est le moment idéal pour le système d'exploitation de lancer une erreur de segmentation, d'arrêter le programme ou d'envoyer des signaux. Le problème avec les débordements de pile est qu'ils peuvent entrer en contact avec d'autres threads du même processus. Maintenant, imaginez un thread qui a un grand tampon pour l'envoi/recv du réseau, le contenu d'une autre pile de threads pourrait remplacer cette zone de mémoire provoquant des fuites de mémoire sur le réseau.
Les débordements de tampons sont plus courants et dépassent les limites de leur propre mémoire. Tant que la mémoire accessible se trouve toujours dans la région du segment de mémoire, il n'y a pas de problème (du point de vue du système d'exploitation). Cela devient plus dangereux lorsque le tas est trop petit et que des données supplémentaires doivent être allouées. À ce stade, rien ne dit ce qui se passerait lorsque les limites seraient atteintes. Lors de la liaison pour accéder à un octet au-delà de la frontière du tas, le système d'exploitation devrait tuer le processus avec un SIGSEGV.
Une pile se développe vers le bas par l'instruction Push, mais écrit et lit vers le haut. par exemple, si votre pile occupe l'adresse 10 à 5 qui est une longueur de 6 adresses utilisables, lorsque vous avez une instruction Push, la pile descendra et ajoutera une mémoire supplémentaire, ce qui entraînera votre pile à occuper l'adresse 10-4 qui est 7 adresses utilisables. mais lorsque vous écrivez dans la pile et que votre pointeur d'instructions est à 4, il écrit de 4 vers le haut à 10 et lorsque vous passez l'adresse 10, vous avez un débordement de tampon