← Retour à Reverse Engineering

Le Buffer Overflow global ou statique

13 septembre 2026•10 min•Facile
Reverse EngineeringLinux

Débordement de tampon global / statique ou Global / Static Buffer Overflow

Dans les articles précédents on a fait déborder un buffer sur la pile, sur le tas et via une variable d'environnement. Ici le buffer n'est plus alloué à l'exécution, il existe dès le lancement du programme et pour toute sa durée. C'est un scénario un peu différent mais le principe de fond reste le même.

Global / Static Buffer Overflow, c'est quoi ?

Définition du concept

Une variable globale est déclarée en dehors de toute fonction. Elle existe pendant toute la durée de vie du programme et n'a pas besoin d'être allouée ou libérée manuellement contrairement à une variable sur le tas. Quand cette variable est un buffer de taille fixe, une écriture qui déborde de ce buffer touche la mémoire qui a été réservée juste à côté par le compilateur, exactement comme un débordement sur la pile touche ce qui suit un buffer local.

Différence entre variable globale et static

Une variable globale classique est visible depuis n'importe quel fichier du programme qui la déclare. Ajouter static devant change les choses selon le contexte.

Sur une variable globale, static restreint sa visibilité au fichier où elle est déclarée, elle n'est plus accessible ailleurs.

Sur une variable locale à une fonction, static change son comportement plus radicalement. Une variable locale classique vit sur la pile et disparaît à chaque retour de la fonction. Une variable locale déclarée static garde sa valeur d'un appel à l'autre, elle se comporte en réalité comme une variable globale, mais avec une visibilité limitée à la fonction dans laquelle elle est déclarée.

Dans les deux cas, qu'elle soit globale ou statique, une variable de ce type existe dès le lancement du programme et occupe une place fixe en mémoire, ce qui est exactement ce qui rend un débordement sur ce genre de buffer intéressant à étudier.

Comment survient le débordement

Buffer de taille fixe

static char buffer[16];

La taille de buffer est fixée une fois pour toutes à la compilation. Contrairement à un buffer alloué avec malloc, il n'y a pas de possibilité d'agrandir cette zone en cours d'exécution.

Écriture au-delà de ses limites

Le débordement lui-même vient toujours de la même famille de causes, une fonction qui écrit dans le buffer sans jamais vérifier sa taille. strcpy, sprintf ou gets sont les coupables habituels, on les a déjà croisés dans les articles précédents.

Ce qui peut être corrompu

Ce qui se trouve juste après le buffer dépend entièrement de l'ordre dans lequel les variables ont été déclarées et de la façon dont le compilateur a choisi de les organiser. Ça peut être une autre variable globale ou statique, un autre buffer, un flag, ou une donnée qui n'a strictement rien à voir avec celle qu'on a écrasée.

Exemple concret

Deux buffers globaux/statics

#include <stdio.h>
#include <string.h>

static char utilisateur[16];
static char role[16] = "invite";

int main(void) {
    strcpy(utilisateur, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
    printf("role = %s\n", role);
    return 0;
}

utilisateur et role font 16 octets chacun. role vaut "invite" au départ.

Débordement du premier

Le souci vient de cette ligne :

strcpy(utilisateur, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");

La chaîne copiée fait 32 caractères plus le \0 final, largement au-delà des 16 octets réservés pour utilisateur. strcpy ne vérifie jamais la taille de destination, elle continue d'écrire jusqu'à la fin de la chaîne source.

Corruption du second

Comme utilisateur et role sont déclarées l'une juste après l'autre, il y a de bonnes chances qu'elles soient placées côte à côte en mémoire. L'écriture qui déborde de utilisateur vient alors écraser le contenu de role.

$ ./global
role = AAAAAAAAAAAAAAAA

role ne valait plus "invite", elle affiche maintenant une partie des A qu'on a écrits dans utilisateur. Le programme n'a jamais touché role directement, c'est le débordement qui s'en est chargé.

Observer la corruption avec GDB

Vérifier les adresses

gdb ./global
(gdb) break main
(gdb) run
(gdb) print &utilisateur
$1 = (char (*)[16]) 0x404040 <utilisateur>

(gdb) print &role
$2 = (char (*)[16]) 0x404050 <role>

role commence 16 octets après utilisateur, exactement la taille réservée pour utilisateur. Les deux buffers sont donc parfaitement voisins, sans rien entre eux.

Examiner la mémoire

(gdb) x/32bx &utilisateur
0x404040 <utilisateur>:   0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x404048 <utilisateur+8>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

Avant l'écriture, utilisateur est vide et role contient encore "invite". Après avoir laissé le strcpy s'exécuter, on peut refaire le même examen et voir la différence.

Voir les données écrasées

(gdb) next
(gdb) print role
$3 = "AAAAAAAAAAAAAAAA"

role a bien changé de valeur alors qu'aucune ligne du programme ne l'a jamais modifiée directement. C'est la preuve la plus simple qu'on puisse avoir de ce type de débordement, une variable qui change sans raison apparente dans le code, uniquement parce qu'elle était voisine d'un buffer mal écrit.

Comment l'éviter

Contrôler les tailles

Avant toute copie dans un buffer de taille fixe, il faut connaître la taille de ce qu'on s'apprête à copier et la comparer à la taille du buffer de destination.

static char utilisateur[16];

if (strlen(source) < sizeof(utilisateur)) {
    strcpy(utilisateur, source);
}

Éviter les écritures hors limites

Le plus simple reste d'utiliser directement des fonctions qui prennent une taille en paramètre plutôt que de vérifier soi-même avant chaque appel.

strncpy(utilisateur, source, sizeof(utilisateur) - 1);
utilisateur[sizeof(utilisateur) - 1] = '\0';

Un outil comme AddressSanitizer, activable avec -fsanitize=address, peut aussi détecter ce genre de débordement à l'exécution si on veut automatiser la recherche de ce type de bug plutôt que de tout relire à la main.

Conclusion

Un débordement dans une variable globale ou statique suit la même logique que sur la pile ou sur le tas, une écriture qui dépasse une taille prévue et qui touche ce qui se trouve juste à côté. La différence, c'est que la disposition est fixée dès la compilation et reste stable d'une exécution à l'autre, ce qui rend la corruption facile à reproduire une fois les variables voisines identifiées. Le danger ne vient jamais du type de mémoire utilisé, mais de l'absence de vérification au moment d'écrire dedans.

Voir aussi d'autres catégories...

Ces thèmes pourraient aussi t'intéresser.