Mobile, reverse & malware

Qu'est-ce qu'un buffer overflow - Mécanisme, exploitation, protections

Buffer overflow expliqué : stack vs heap, mécanisme d'écrasement, exploitation RCE, protections modernes (ASLR, DEP, canaries, CFG), détection ASan, prévention.

Naim Aouaichia
15 min de lecture
  • Buffer Overflow
  • Vulnérabilités
  • Exploitation
  • Memory Safety
  • C C++
  • Sécurité Binaire

Un buffer overflow (ou dépassement de tampon) est une vulnérabilité mémoire qui se produit lorsqu'un programme écrit plus de données dans une zone mémoire que sa taille allouée, débordant sur les zones adjacentes. Découverte dans les années 70, popularisée par le ver Morris en 1988 et le papier Smashing The Stack For Fun And Profit d'Aleph One en 1996, c'est l'une des vulnérabilités les plus emblématiques de la sécurité informatique. En 2026, malgré 40 ans de protections (ASLR, DEP, canaries, CFG, langages mémoire-safe), elle reste classée par MITRE parmi les CWE les plus dangereuses (CWE-787 Out-of-bounds Write en tête du Top 25 depuis 2021).

1. Définition technique précise

Un buffer overflow apparaît quand :

  1. Un programme alloue une zone mémoire (buffer) de taille fixe N octets.
  2. Une opération d'écriture y dépose plus de N octets.
  3. Les octets en surplus écrasent des données adjacentes (autres variables, métadonnées, pointeurs, code).

L'opération d'écriture peut être triviale (strcpy, gets, memcpy mal dimensionné), arithmétique (boucle qui dépasse), ou indirecte (fonction tierce ne respectant pas le contrat).

Conséquences possibles, par ordre de gravité :

  • Crash (segfault) - déni de service.
  • Corruption logique - écrasement d'une variable adjacente, modifie un comportement (ex. is_admin = 0is_admin = 1).
  • Détournement du flux d'exécution - écrasement d'une adresse de retour ou d'un pointeur de fonction → exécution arbitraire de code (RCE).

Les CWE associés :

  • CWE-787 : Out-of-bounds Write.
  • CWE-119 : Improper Restriction of Operations within the Bounds of a Memory Buffer (parent).
  • CWE-120 : Buffer Copy without Checking Size of Input (classic buffer overflow).
  • CWE-121 : Stack-based Buffer Overflow.
  • CWE-122 : Heap-based Buffer Overflow.
  • CWE-125 : Out-of-bounds Read (variante en lecture, ex. Heartbleed).

2. Architecture mémoire d'un processus

Pour comprendre un buffer overflow, il faut visualiser la disposition mémoire d'un processus Linux x86-64 typique :

+--------------------+ adresses hautes
|       Stack        |  ← grandit vers le bas
|       ↓↓↓          |
+--------------------+
|         |          |
|         ↓          |
|     (libre)        |
|         ↑          |
|         |          |
+--------------------+
|        Heap        |  ← grandit vers le haut
|       ↑↑↑          |
+--------------------+
|        BSS         |  variables globales non init
+--------------------+
|       .data        |  variables globales init
+--------------------+
|       .text        |  code (read + execute)
+--------------------+ adresses basses
  • Stack : variables locales, paramètres de fonction, adresses de retour. Allocation/libération automatiques (LIFO).
  • Heap : allocations dynamiques (malloc, new). Gérées explicitement.
  • BSS / .data / .text : segments statiques.

Un overflow sur la stack et sur le heap exploite des structures différentes - d'où la distinction stack-based vs heap-based.

3. Stack-based buffer overflow

Le cas d'école, et historiquement le plus exploité.

3.1 Mécanisme

Quand une fonction est appelée, le CPU pousse l'adresse de retour sur la pile, puis la fonction réserve de l'espace pour ses variables locales. Une fois finie, la fonction restaure l'état et ret saute à l'adresse de retour stockée.

Si une variable locale est un buffer mal protégé et qu'on écrit au-delà, on peut écraser l'adresse de retour. À la sortie de la fonction, le CPU saute vers l'adresse maintenant contrôlée par l'attaquant.

3.2 Code vulnérable classique

#include <stdio.h>
#include <string.h>
 
void vulnerable(char *input) {
    char buffer[64];
    strcpy(buffer, input);  // pas de borne, faille
    printf("Reçu: %s\n", buffer);
}
 
int main(int argc, char **argv) {
    if (argc > 1) vulnerable(argv[1]);
    return 0;
}

Si argv[1] fait 200 octets, strcpy écrira 200 octets dans un buffer de 64 - les 136 octets en trop écrasent ce qui suit sur la pile : autres locales, sauvegarde de RBP, puis l'adresse de retour.

3.3 État de la pile après débordement

Pile avant strcpy :          Pile après strcpy d'un payload long :
+-------------------+        +----------------------------------+
|  ret address      |        |  AAAAAAAA (écrasé)              |
+-------------------+        +----------------------------------+
|  saved RBP        |        |  AAAAAAAA (écrasé)              |
+-------------------+        +----------------------------------+
|  buffer[63]       |        |  A                              |
|     ...           |        |     ...                         |
|  buffer[0]        |        |  A                              |
+-------------------+        +----------------------------------+

L'attaquant choisit son payload pour que les octets qui tombent sur l'adresse de retour pointent vers du code qu'il maîtrise (shellcode injecté dans le buffer ou - aujourd'hui - une chaîne ROP).

3.4 Fonctions C dangereuses

Les coupables historiques. À éviter ou à utiliser avec extrême prudence :

Fonction dangereuseAlternative recommandée
gets()fgets(buf, sizeof(buf), stdin) (ou supprimée en C11)
strcpy()strncpy() ou mieux strlcpy() (BSD), snprintf()
strcat()strncat(), strlcat()
sprintf()snprintf(buf, sizeof(buf), ...)
scanf("%s", ...)scanf("%63s", ...) avec largeur explicite
memcpy() avec longueur calculéeVérifier longueur ≤ taille destination

4. Heap-based buffer overflow

Mécaniquement différent, exploite les métadonnées de l'allocateur (glibc malloc, dlmalloc, jemalloc, ptmalloc2).

4.1 Principe

malloc retourne un bloc avec en amont des métadonnées (taille, pointeurs vers chunk précédent/suivant en cas de free). Un overflow sur un chunk déborde sur les métadonnées du chunk adjacent. Lors d'un free ultérieur, l'allocateur suit les pointeurs corrompus → écriture arbitraire en mémoire.

4.2 Techniques d'exploitation historiques

  • Unlink attack (Solar Designer, 2000s) : exploiter unlink() lors de la coalescence de chunks libres. Mitigée depuis glibc 2.4 par check d'intégrité.
  • House of Force, House of Spirit, Fastbin Attack, Tcache Poisoning - techniques modernes ciblant les structures de l'allocateur. Activement étudiées en CTF.

Le heap exploitation est réputé plus difficile que le stack overflow : non-déterminisme, contraintes d'agencement, fenêtres de timing. Mais reste exploitable - cf. nombreux CVE récents sur navigateurs et noyaux.

5. Variantes proches

5.1 Off-by-one

Écriture d'un seul octet en trop. Souvent due à confusion < vs <= dans une boucle, ou oubli du \0 final d'une chaîne. Suffit parfois à écraser le LSB d'un pointeur sauvegardé et déclencher une RCE.

5.2 Integer overflow vers buffer overflow

size_t total = nb_items * item_size;  // overflow possible
char *buf = malloc(total);            // alloue trop peu
memcpy(buf, src, nb_items * item_size); // déborde

Si nb_items * item_size déborde l'entier, malloc reçoit une petite valeur, mais le memcpy copie la valeur réelle. CVE-2002-0083 (OpenSSH), nombreux exemples ImageMagick.

5.3 Format string

printf(user_input);  // si user_input contient %s, %x, %n

Permet lecture (%x, %s) et écriture mémoire arbitraire (%n). Considéré comme une famille proche du buffer overflow car il viole l'isolation mémoire.

5.4 Out-of-bounds read

Symétrique : on lit au-delà du buffer. Conséquence typique : exfiltration de données. Cas célèbre : Heartbleed (CVE-2014-0160, OpenSSL) - un OOB read jusqu'à 64 ko de mémoire serveur exposée par requête.

6. Exemples historiques marquants

AnnéeIncidentTypeImpact
1988Morris WormStack BO sur fingerd~6000 machines (10 % de l'Internet de l'époque)
2001Code RedStack BO sur IIS .ida359 000 machines en 14h, ~2 G$ de dommages
2003SQL SlammerStack BO sur SQL Server UDP75 000 machines en 10 min
2003BlasterStack BO RPC DCOM WindowsMillions de machines
2008ConfickerStack BO RPC NetAPI (MS08-067)9-15 millions de machines
2014HeartbleedOOB read OpenSSLQuasi-tout l'Internet TLS
2017EternalBluePool overflow SMBv1 (MS17-010)Bases pour WannaCry, NotPetya
2021Log4Shell(ce n'est pas un BO, lookup JNDI - mais souvent confondu)Quasi-tout l'écosystème Java
2022-2025Nombreux CVE Chrome / WebKit / Linux kernelHeap BO et UAFExploits sur navigateurs et téléphones (Pegasus chains)

Les BO « basiques » sur services réseau exposés à Internet ont fortement diminué grâce aux mitigations - mais la classe reste vivante dans les navigateurs, noyaux, hyperviseurs, et logiciels embarqués.

7. Exploitation : du crash à la RCE

Trois étapes typiques pour transformer un BO en exécution de code :

7.1 Trouver l'offset

Calibrer le payload pour identifier l'offset exact entre le début du buffer et l'adresse de retour. Outils : cyclic / pattern_create (pwntools, Metasploit), debug avec gdb.

7.2 Choisir la cible du saut

  • Avant DEP/NX : injecter du shellcode dans le buffer, faire pointer ret vers le buffer.
  • Avec DEP/NX : impossible d'exécuter sur la stack. On utilise ROP (Return-Oriented Programming) - chaînes de petits gadgets (séquences se terminant par ret) déjà présents dans le binaire ou les bibliothèques chargées.
  • Avec ASLR : adresses randomisées, il faut d'abord une fuite d'information pour défaire l'aléa.

7.3 Atteindre l'objectif

Spawn shell, lire fichier sensible, élever les privilèges, désactiver une protection. En réseau : binder un shell sur un port, ou faire revenir une connexion (reverse shell).

L'exploitation moderne (2020-2026) est rarement triviale : il faut chaîner BO + info leak + ROP + bypass de canary, parfois avec contraintes de caractères (pas de \0, pas d'espaces). C'est la spécialité pwn des CTF.

8. Protections modernes

Quatre décennies de mitigations empilées. Aucune n'est suffisante seule, leur combinaison rend l'exploitation difficile.

8.1 Stack Canaries (StackGuard, ProPolice, /GS)

Une valeur aléatoire (le canary) est insérée entre les variables locales et l'adresse de retour. Avant ret, le code vérifie que le canary n'a pas changé. Un BO classique l'écrase et déclenche un abort.

// Compilation avec canary :
gcc -fstack-protector-strong sample.c

Bypass possible : fuite de la valeur du canary, ou écriture qui ne traverse pas le canary (off-by-one ciblé).

8.2 DEP / NX bit (Data Execution Prevention)

Marque les pages de données (stack, heap) comme non exécutables. Un saut vers du shellcode injecté dans la stack provoque une exception. Disponible depuis 2004 (Windows XP SP2, processeurs avec NX bit).

Bypass : ROP / JOP / SROP. C'est précisément ce qui a poussé l'invention du ROP par Hovav Shacham (2007).

8.3 ASLR (Address Space Layout Randomization)

Les adresses de la stack, du heap, des bibliothèques (et du binaire si compilé PIE) sont randomisées à chaque exécution. L'attaquant ne sait plus où sauter sans information préalable.

Limites : entropie réduite sur 32 bits (bruteforce possible en quelques heures), ne protège pas si le binaire n'est pas PIE, contournable par fuite d'info.

# Vérifier ASLR sur Linux :
cat /proc/sys/kernel/randomize_va_space  # 2 = full ASLR

8.4 RELRO et FORTIFY_SOURCE

  • RELRO (Relocation Read-Only) : marque la GOT en lecture seule après résolution dynamique.
  • FORTIFY_SOURCE : remplace certaines fonctions par des versions vérifiant la taille à la compilation (-D_FORTIFY_SOURCE=2 -O2).

8.5 Control Flow Integrity (CFI) et CET

  • CFG (Control Flow Guard, Windows) : valide les cibles de sauts indirects.
  • Intel CET (Control-flow Enforcement Technology) : Shadow Stack matérielle (sauvegarde séparée de l'adresse de retour) + IBT (Indirect Branch Tracking).
  • ARM PAC (Pointer Authentication Codes) : signe les pointeurs avec une clé secrète. Apple Silicon, Android moderne.
  • MTE (Memory Tagging Extension) : marque chaque allocation avec un tag, vérifié à l'accès. Disponible sur ARMv8.5+ (Pixel 8+).

8.6 Tableau récapitulatif

ProtectionAnnéeDéfaut surBypass classique
Stack canaries1998gcc, MSVCFuite d'info, brute force
DEP / NX2004Windows XP SP2+, Linux 2.6+ROP / JOP
ASLR2003-2007Linux, Windows Vista+Info leak, partial overwrite
RELRO Full~2010gcc/ldSi lazy binding actif
CFG2014Windows 8.1+Bypass complexe, recherches actives
Intel CET2020Windows 11, Linux récentTrès récent, peu de bypass publics
ARM PAC2018Apple A12+, AndroidPACMAN attack (académique 2022)
ARM MTE2024Pixel 8+, prochains SoCEn cours d'évaluation

9. Détection et test

9.1 Sanitizers

  • AddressSanitizer (ASan) : détection runtime de OOB read/write, UAF, double free. Coût ~2x mémoire et CPU. À activer en dev/test.
gcc -fsanitize=address -g -O1 sample.c -o sample
./sample
  • MemorySanitizer (MSan) : détecte les lectures de mémoire non initialisée.
  • UndefinedBehaviorSanitizer (UBSan) : détecte les comportements indéfinis (overflow d'entier signé, etc.).
  • Valgrind / Memcheck : alternative historique, plus lent qu'ASan.

9.2 Fuzzing

  • AFL++ : fork de AFL, le standard open source.
  • libFuzzer : intégré à Clang, in-process.
  • honggfuzz : Google, multi-architecture.
  • Jazzer, cargo-fuzz, go-fuzz - fuzzers par langage.

Combiner fuzzer + ASan est la méthode la plus efficace pour découvrir des BO en 2026.

9.3 Analyse statique

  • clang-tidy, cppcheck, Coverity, CodeQL : détectent certains patterns (utilisation de gets, oubli de borne, etc.). Faux positifs et faux négatifs présents.

9.4 Audit manuel

Pour les fonctions critiques, rien ne remplace une revue manuelle ciblée sur les opérations mémoire. Lecture systématique des appels à memcpy, strncpy, read, recv, et de toute arithmétique de taille.

10. Prévention en 2026

10.1 Choisir un langage mémoire-safe

La meilleure prévention reste de ne pas utiliser C/C++ pour le code nouveau quand c'est évitable :

  • Rust : ownership et borrow checker éliminent les BO sans cost runtime. Adopté par Linux kernel (modules), Windows kernel (composants), Android (Bluetooth, ULTRA-HDR).
  • Go, Java, C#, Kotlin, Swift, Python : runtime géré, BO impossibles dans le code utilisateur (mais possibles dans le runtime ou les bindings natifs).

Le NSA, la CISA, l'ANSSI, la Maison Blanche (rapport ONCD 2024) recommandent explicitement la migration des composants critiques vers les langages mémoire-safe.

10.2 Pour le code C/C++ existant

  • Compiler avec toutes les protections : -fstack-protector-strong -D_FORTIFY_SOURCE=2 -O2 -fPIE -pie -Wl,-z,relro,-z,now.
  • Utiliser les API safe : strncpy, snprintf, memcpy_s (C11 Annex K), strlcpy/strlcat (BSD).
  • Activer warnings stricts : -Wall -Wextra -Wformat-security -Wconversion.
  • Audit régulier avec ASan + fuzzing en CI.
  • Refactor des fonctions critiques en Rust quand possible (FFI / interop).

10.3 Au runtime

Activer toutes les mitigations OS : ASLR full, DEP, CFG, CET si disponible. Ne jamais les désactiver « pour debug » en production.

11. FAQ

11.1 Quelle différence entre stack overflow et heap overflow ?

Le stack overflow déborde sur la pile (variables locales, adresse de retour). Le heap overflow déborde sur le tas (chunks malloc, métadonnées allocateur). Le premier est historiquement plus simple à exploiter (adresse de retour immédiatement après le buffer), le second exploite les structures de l'allocateur, plus complexe mais toujours exploitable.

11.2 Pourquoi les buffer overflows existent encore en 2026 ?

Trois raisons : (1) le code C/C++ legacy représente des milliards de lignes en production (noyaux, bibliothèques système, navigateurs, embarqué) ; (2) certains domaines (firmware, OS, jeux haute performance) restent dépendants de C/C++ ; (3) même les nouveaux développements C/C++ continuent d'introduire des BO faute de discipline ou de revue.

11.3 ASLR et DEP suffisent-ils à empêcher l'exploitation ?

Non. ASLR + DEP rendent l'exploitation plus coûteuse mais une fuite d'information (info leak) suffit à défaire ASLR, et ROP contourne DEP. Les exploits modernes (Pwn2Own, NSO Pegasus) chaînent typiquement 3-7 vulnérabilités pour atteindre RCE + SBX + LPE.

11.4 Le langage Rust empêche-t-il vraiment tous les buffer overflows ?

Dans le code safe Rust (par défaut), oui : le compilateur refuse les accès hors borne, et les indexations runtime panique au lieu de déborder. Dans les blocs unsafe (FFI, structures de données performantes), les BO redeviennent possibles - d'où l'importance d'auditer ces sections. Mais leur surface est très réduite par rapport au tout-C/C++.

11.5 Comment savoir si un binaire a les protections activées ?

Outil checksec (recommandé) :

checksec --file=./binary
# Affiche : RELRO, Canary, NX, PIE, Fortify

Sur Windows, PESec ou WinChecksec. Pour vérifier ASLR au runtime côté OS Linux : cat /proc/sys/kernel/randomize_va_space (2 = full).

11.6 Puis-je exploiter un buffer overflow pour apprendre ?

Oui, c'est la voie classique - dans un cadre légal. Les CTF (picoCTF, FCSC, HackTheBox, pwn.college) proposent des challenges pwn dédiés. Le tutoriel canonique reste Smashing The Stack For Fun And Profit d'Aleph One (Phrack #49, 1996) - lecture obligatoire pour tout aspirant exploit developer. Pour l'exploitation moderne, pwn.college et LiveOverflow (chaîne YouTube) sont les meilleures ressources gratuites.


Le buffer overflow est la vulnérabilité fondatrice de la sécurité offensive moderne. Comprendre son mécanisme - et celui des protections qui ont émergé en réponse - reste un passage obligé pour qui veut faire du pentest binaire, du reverse engineering, ou de l'AppSec native sérieuse. Sa disparition annoncée à chaque génération technologique ne s'est jamais réalisée : tant que du C/C++ tournera dans les noyaux, navigateurs et firmwares, les BO continueront d'alimenter l'industrie de l'exploitation et de la défense.

Écrit par

Naim Aouaichia

Expert cybersécurité et fondateur de Zeroday Cyber Academy

Expert cybersécurité avec un master spécialisé et un parcours hybride : développement, DevOps, DevSecOps, SOC, GRC. Fondateur de Hash24Security et Zeroday Cyber Academy. Formateur et créateur de contenu technique sur la cybersécurité appliquée, la sécurité des LLM et le DevSecOps.