« Quel serait un flot d’exploitation réaliste, rigoureux et opérationnel avec Ghidra et Cutter/rizin, dans le cadre d’une épreuve de reverse engineering exigeante ? »
Compétences clés
- Savoir disséquer un exécutable totalement inconnu : sans symboles, sans documentation, avec les protections classiques (NX, RELRO, etc.).
- Savoir lire des artefacts machine :
- flot de contrôle ;
- conventions d’appel ;
- organisation mémoire, heap/stack ;
- patterns usuels : structures, pointeurs de fonctions, wrappers, gadgets.
- Savoir raisonner comme un attaquant orienté exploitation : c’est trouver comment le programme se casse, comment il s’ouvre, où se trouve le levier permettant de renverser le contrôle.
Le flot d’analyse Ghidra
Étape 1 — Ouvrir, analyser automatiquement, mais ne rien croire
Dans Ghidra :
File → New Project- import du binaire, puis analyse automatique ;
- laisser Ghidra renommer symboles, signatures, etc.
WARNING
Le décompilateur ment parfois, surtout dans les binaires pédagogiques ou obfusqués. Il faut vérifier tout au niveau ASM.
Étape 2 — Reconstituer le squelette du programme
L’objectif est de savoir où l’on est avant de vouloir savoir quoi faire.
Dans Ghidra :
- aller à entry ;
- remonter dans
_start→__libc_start_main→main; - identifier la topologie :
- sections
.text,.plt,.got,.data,.bss; - fonctions utilisateur (souvent peu nombreuses dans les TP).
- sections
Dans Cutter/rizin :
afl # liste les fonctions
pdf @ main # display function
V # flowgraph
On visualise alors l’ossature logique :
- input utilisateur → stockage → comparaison → bifurcations → appel final vulnérable ;
- gestion mémoire (
malloc/free) ; - pointeurs de fonctions → exploitation potentielle ;
- mélange stack/heap → vecteurs de corruption.
Étape 3 — Identifier les zones vulnérables
Méthode systématique, indépendante du binaire.
3.1. Chercher tous les endroits où des données non contrôlées entrent.
Dans Cutter :
izz # strings
axt <string> # qui référence la chaîne ?
Dans Ghidra :
Window → Defined Strings- double-clic → cross-references (Xref).
Chercher :
gets,fgets,scanf,strcpy,strcat,read,recv,sprintf,snprintf, etc. ;malloc/free→ vulnérabilités heap possibles ;- fonction qui lit un PIN, un mot de passe → comparaison → primitives exploitables.
Étape 4 — Lire l’assembleur « à la main »
Un bon reverse engineer vérifie les points importants en ASM, pas dans le décompilateur.
Les registres :
rdi,rsi,rdx,rcx,r8,r9→ arguments ;rbp/rsp→ frame pointer et stack layout.
On regarde :
- où se trouve le buffer ?
- quelle est sa taille ?
- quelle est la distance jusqu’au saved RIP / saved RBP ?
Exemple typique :
sub rsp, 0x40
lea rdi, [rbp-0x20]→ on sait immédiatement où est le buffer.
Étape 5 — Déterminer la primitive d’exploitation
À partir des observations, il faut répondre :
Qu’est-ce que je contrôle dans le programme ? Et qu’est-ce que cela me permet de réécrire ?
Exemples typiques :
- overflow stack → écriture sur saved RIP ;
- overflow heap → overwrite du header, unlink, fastbin dup, pointeur de fonction ;
- format string → arbitrary read/write ;
- pointeur de fonction dans
.data→ redirection immédiate du flux ; - absence de PIE → gadgets à adresses fixes → ROP facile ;
- PIE activé → besoin d’un leak ;
- RELRO full → impossible d’écraser la GOT.
Cela indique si la stratégie sera :
- ret2text ;
- ret2plt ;
- ret2csu ;
- stack pivot / fake stack ;
- ret2libc classique.
Étape 6 — Construire l’exploitation (ROP ou autre)
6.1. Avec Cutter/rizin.
/R pop rdi
/ri syscall
/ra # ROPgadget equivalent dans rizin
6.2. Avec Ghidra.
Search → Instructions → "ret";- filter :
pop; - identifier les gadgets manuellement ;
- vérifier alignement, effets secondaires, clobbering de registres.
6.3. Construire la ROP.
Il faut être capable de :
- charger
RDI,RSI,RDX; - charger
RAXpour les syscalls ; - chaîner
system,execve, ou unsyscall; - faire un pivot si l’espace de stack est limité.
Étape 7 — Diff entre analyse théorique et exécution réelle (gdb)
Cette étape est souvent négligée. Un professionnel fait :
break main;x/100gx $rsp;vmmap;p &variable→ vérifier ses adresses ;- si ROP →
si/nipour vérifier la progression des gadgets ; - si pivot → surveiller
RSP; - si syscall → vérifier
rax/rdi/rsi/rdxjuste avant.
Il faut mettre en correspondance :
- ce que dit Ghidra ;
- ce que voit GDB ;
- ce qu’exécute réellement le binaire.
C’est cette triangulation qui donne la qualité.
Ce qu’il faut retenir pour l’examen
Faiblesses classiques que je me connais :
- trop se reposer sur l’exécution (gdb) et pas assez sur l’analyse statique profonde ;
- absence d’une vision « macro » systémique ;
- comprendre le ROP par intuition locale, pas globale ;
- paniquer lorsque la stack ne suffit plus (stack pivot) ;
- mal maîtriser Ghidra comme outil de raisonnement (flowgraph, structure recover, xrefs).
Ce que Ghidra apporte pour réussir
- Reconstruire automatiquement les structures. Ghidra repère les structures sur heap/stack ; Cutter/rizin le fait moins bien.
- Identifier les pointeurs de fonction. Crucial dans les exercices de heap overflow.
- Décompilation lisible des comparaisons, boucles, branches. On identifie immédiatement la condition qui mène au « ret2win ».
- Récupération exacte des offsets. Multipliée par dix en fiabilité par rapport à l’examen à la main.
Le workflow complet à suivre en examen (checklist)
Phase 1 — Statique (15 min)
- ouvrir binaire dans Ghidra ;
- aller à
main; - identifier l’input path ;
- lire les allocations heap (
malloc/free) ; - trouver la vulnérabilité ;
- déterminer out-of-bound / overflow ;
- identifier fonction intéressante (login, admin, shell…) ;
- repérer les gadgets ROP dans
.text; - vérifier PIE/NX/RELRO (
checksec).
Phase 2 — Dynamique (10 min)
-
break main; - vérifier adresses du buffer ;
- vérifier adresses
rodata/data/bss; - observer layout stack ;
- tester les crashs ;
- tester les écritures heap.
Phase 3 — Stratégie d’exploitation (10 min)
- chercher une primitive ROP viable ;
- si ret2csu nécessaire → vérifier gadgets ;
- si pivot → choisir région mémoire sûre ;
- préparer payload ;
- tester dans gdb.
Phase 4 — Finalisation (10 min)
- nettoyer payload ;
- tester hors gdb ;
- ajouter
sleep/délais si nécessaire ; - rédiger note d’exploitation.