← Retour à Linux

Le Buffer Overflow dans .bss

14 septembre 2026•8 min•Facile
Reverse EngineeringLinux

Le Buffer Overflow dans .bss

Dans l'article précédent on a fait déborder un buffer global et écrasé sa voisine. Ici on zoome sur l'endroit précis où vivent la plupart de ces buffers quand ils ne sont pas initialisés à la déclaration, la section .bss.

La section .bss

.bss est la section qui accueille les variables globales et statiques qui n'ont pas de valeur initiale explicite, ou qui sont initialisées à zéro. Comme leur valeur de départ est connue d'avance (zéro), le binaire n'a pas besoin de stocker de vraies données pour elles, juste leur taille. C'est le chargeur du système qui réserve l'espace et le remplit de zéros au lancement du programme.

C'est là que finit un buffer comme celui-ci.

static char buffer[64];

Aucune valeur n'est donnée à la déclaration, donc buffer atterrit dans .bss, avec 64 octets de zéros en attendant d'être remplis à l'exécution.

Comment survient le Buffer Overflow

Le mécanisme ne change pas par rapport à un buffer global classique. Une fonction comme strcpy ou sprintf écrit dans le buffer sans vérifier sa taille, et si les données dépassent l'espace réservé dans .bss, l'écriture continue dans la mémoire qui suit, où que se trouve la prochaine variable placée par le compilateur.

Exemple concret

Deux buffers dans .bss

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

static char buffer[16];
static char secret[16];

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

buffer et secret n'ont pas de valeur à la déclaration, les deux vivent donc dans .bss. secret reçoit sa valeur à l'exécution avec strcpy(secret, "supersecret").

Débordement du premier

Le souci vient de cette ligne :

strcpy(buffer, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");

La chaîne fait 32 caractères plus le \0, largement au-delà des 16 octets réservés pour buffer.

Corruption du second

$ ./bss_overflow
secret = AAAAAAAAAAAAAAAA

secret valait "supersecret" avant l'appel à strcpy(buffer, ...), et se retrouve écrasé alors que le code ne l'a jamais touché directement.

Observer la corruption avec GDB

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

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

secret commence 16 octets après buffer, exactement la taille réservée pour buffer. Les deux sont donc voisines, sans rien entre elles.

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

Après les deux strcpy, secret ne contient plus "supersecret" mais une partie du contenu écrit dans buffer.

Identifier un buffer .bss dans un binaire

Sans avoir le code source sous les yeux, nm permet de lister les symboles d'un binaire et de voir lesquels vivent dans .bss.

nm ./bss_overflow | grep -iE "buffer|secret"
0000000000404040 b buffer
0000000000404050 b secret

La lettre après l'adresse indique le type de symbole. Un b (ou un B pour un symbole visible depuis d'autres fichiers) signale une variable dans .bss. Un d ou un D indiquerait à l'inverse une variable dans .data, donc initialisée à une valeur non nulle dès la compilation.

objdump donne une vue complémentaire, avec la taille de la section elle-même.

objdump -h ./bss_overflow | grep bss
 23 .bss          00000030  0000000000404040  0000000000404040

De quoi confirmer où commence la section et combien d'octets elle occupe au total, avant même de chercher un buffer précis dedans.

Comment l'éviter

Le principe reste identique à ce qu'on a vu jusqu'ici, contrôler la taille de ce qu'on écrit avant de l'écrire.

static char buffer[16];

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

Le fait que le buffer vive dans .bss plutôt que sur la pile ou le tas ne change rien à la règle, il n'y a jamais de raison de faire confiance à la taille d'une donnée d'entrée sans la vérifier.

Conclusion

Un débordement dans .bss suit exactement la même logique qu'un débordement dans n'importe quelle autre variable globale ou statique, la seule différence est que le buffer en question n'avait pas de valeur initiale. Ça n'a aucune influence sur la façon dont le bug se produit, mais ça donne un moyen concret de repérer ce type de buffer dans un binaire, avec nm ou objdump.

Voir aussi d'autres catégories...

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