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.

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 functions

On verra des choses du genre :

  • main
  • get_flag, print_flag
  • check_serial, validate, auth
  • vuln, 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 un system("/bin/sh") ou flag.
  • Dans gdb/pwndbg :

    (gdb) b vuln
    (gdb) disas win
  • Pour un ret2plt/ROP :

    • nm donne system@plt, puts@plt ;
    • on récupère directement les adresses des gadgets type pop rdi; ret en 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 .text intact,
  • les chaînes .rodata (strings, r2, ghidra),
  • les symboles dynamiques (.dynsym) si le binaire est dynamiquement lié → puts, printf, read, system sont 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 anonymes

3.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.00401234vuln_like() si l’on voit un gets / read sans taille ;
  • fcn.004013f0menu() si ça affiche plusieurs choix ;
  • etc.

Méthodo typique :

  1. Entrée du programme

    • part de _start ou directement main si le loader l’a retrouvé ;
    • sinon on demande à l’outil de détecter les fonctions (afl dans radare2).
  2. Suivre les chaînes de caractères

    • strings -tx ./bin pour 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.
  3. Patrons de code classiques

    • prologue de fonction (push rbp; mov rbp, rsp),
    • appels à strcmp, memcmp, strncmp,
    • gestion de fichiers (fopen, fgets).
  4. 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.

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@plt dans .dynsym → plus simple que d’essayer d’identifier win() 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().
  • 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 gadget syscall.

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 -t pour 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.