1. Ce que ça veut dire concrètement
Un ELF contient des symboles : noms de fonctions, variables globales, etc. Ils sont stockés dans des sections comme .symtab, .strtab, .dynsym.
-
Binaire non strippé
- table de symboles complète (souvent aussi les infos de debug) ;
- on voit
main,check_password,vulnerable,win,global_counter.
-
Binaire strippé
- on a supprimé tout ou partie de ces symboles (souvent
.symtab, parfois debug) ; - les fonctions deviennent
sub_401020,fcn.00401123, sans noms parlants ; - c’est une technique classique d’anti-analyse / obfuscation.
- on a supprimé tout ou partie de ces symboles (souvent
Important : strip ne change pas le code machine, ni les adresses (à binaire identique). Ça n’affecte que la visibilité.
2. Comment exploiter un binaire non strippé
Ici, on est en « easy mode ». L’objectif : capitaliser à mort sur les noms.
2.1. Reconnaître les points intéressants
Avec nm, objdump -t, gdb :
nm -C ./crackme # liste des symboles + noms
objdump -t ./crackme # table de symboles
gdb ./crackme
(gdb) info functionsOn verra des choses du genre :
mainget_flag,print_flagcheck_serial,validate,authvuln,overflow_me,read_input
Crackme :
- On repère direct une fonction
check_serial()/validate().- On met un breakpoint dessus, suit le contrôle, regarde les comparaisons, conditions.
- On peut même patcher le retour (forcer
ret = 1).
Pwn / exploitation :
-
On identifie très vite :
- la fonction vulnérable :
vuln(),overflow(); - une éventuelle
win()/secret()avec unsystem("/bin/sh")ou flag.
- la fonction vulnérable :
-
Dans gdb/pwndbg :
(gdb) b vuln (gdb) disas win -
Pour un ret2plt/ROP :
nmdonnesystem@plt,puts@plt;- on récupère directement les adresses des gadgets type
pop rdi; reten les cherchant autour de fonctions connues.
En résumé : on passe beaucoup moins de temps à reverse « structurel », on peut se concentrer sur la logique et l’exploitation.
3. Comment travailler sur un binaire strippé
Ici, l’idée est : les infos « humaines » ont disparu, mais le CPU s’en fout. On doit recréer du sens.
3.1. Ce qu’on a encore
Même strippé, on garde :
- le code
.textintact, - les chaînes
.rodata(strings,r2,ghidra), - les symboles dynamiques (
.dynsym) si le binaire est dynamiquement lié →puts,printf,read,systemsont encore nommés dans beaucoup de bins CTF.
Exemple :
readelf -s ./bin | grep FUNC
# on verra souvent les fonctions d'API (libc) même si les fonctions internes sont anonymes3.2. Reconstruire la structure
Avec radare2, IDA, Ghidra, Cutter, on laisse l’outil faire une première passe, puis on renomme tout au fur et à mesure :
fcn.00401234→vuln_like()si l’on voit ungets/readsans taille ;fcn.004013f0→menu()si ça affiche plusieurs choix ;- etc.
Méthodo typique :
-
Entrée du programme
- part de
_startou directementmainsi le loader l’a retrouvé ; - sinon on demande à l’outil de détecter les fonctions (
afldans radare2).
- part de
-
Suivre les chaînes de caractères
strings -tx ./binpour voir les offsets + chaînes ;- dans l’outil, on cherche les xrefs vers
"Enter password","Wrong","Correct","FLAG"; - les fonctions qui les utilisent sont souvent les fonctions de logique.
-
Patrons de code classiques
- prologue de fonction (
push rbp; mov rbp, rsp), - appels à
strcmp,memcmp,strncmp, - gestion de fichiers (
fopen,fgets).
- prologue de fonction (
-
Renommage progressif
- à chaque fonction que l’on comprend un peu, on lui donne un nom logique, même approximatif :
read_input,do_auth,loop_menu; - c’est crucial pour rester orienté dans un crackme long.
- à chaque fonction que l’on comprend un peu, on lui donne un nom logique, même approximatif :
3.3. Côté exploitation
Même strippé, pour un binaire non-PIE :
-
Les gadgets ROP sont toujours là :
ROPgadget --binary ./bin | grep "pop rdi ; ret" -
Les offsets restent stables entre exécutions.
Stratégies :
-
ret2libc / ret2plt : on a souvent
puts@plt,printf@plt,system@pltdans.dynsym→ plus simple que d’essayer d’identifierwin()s’il n’y en a pas. -
win()caché : souvent, les auteurs oublient de strip les chaînes :- on cherche
"sh"/"/bin/sh"/"cat flag"/"flag.txt"dans.rodata; - on remonte à la fonction qui les utilise → possible
win().
- on cherche
-
syscall direct : si vraiment il n’y a rien (statiquement lié, lourdement strip), on peut construire des chaînes ROP « pures » :
- charger les registres pour
execve("/bin/sh", NULL, NULL)et finir par un gadgetsyscall.
- charger les registres pour
4. Exploiter le fait même qu’il soit strippé / non strippé
Pour un exam de reverse / shellcoding, ce que ça change surtout :
Si le prof donne un non-stripped :
On peut :
- poser des breakpoints par nom (
b check_password) ; - naviguer dans le code par fonctions en gdb/IDA sans effort ;
- lire directement l’intention de l’auteur rien qu’avec les noms (
debug_mode,is_admin,encrypt).
Ça veut aussi dire en général que :
- l’objectif pédagogique est probablement plus sur la logique (condition à inverser, algorithme à comprendre) que sur la survie sans symboles.
Si le prof donne un stripped :
Ça teste :
- notre capacité à reconstruire : vues globales, renommage, xrefs sur les strings ;
- notre discipline dans le reverse (ne pas se perdre dans
sub_...sans notes) ; - notre capacité à travailler même avec très peu de confort :
- gadgets ROP sans nom de fonction,
- syscalls direct.
Et côté malware / obfuscation, strip est juste une première couche d’anti-analyse parmi d’autres (overlapping instructions, anti-disasm).
5. Mini cheat-sheet
-
Voir si c’est strippé :
file ./bin # "not stripped" / "stripped" readelf -S ./bin # présence de .symtab, .strtab -
Non strippé → réflexes :
nm -C/objdump -tpour mapper les fonctions ;- breakpoints par nom dans gdb ;
- chercher des fonctions « vulnérables » / « intéressantes » par leur nom, puis par leur code.
-
Strippé → réflexes :
strings -tx,readelf -s(dynsym),checksec;- laisser l’outil reconstruire les fonctions, puis :
- renommer progressivement,
- suivre les xrefs de chaînes et d’appels libc,
- chercher gadgets ROP, syscall.